The pytest failures have been successfully resolved by fixing the API response structures, updating the test assertions to match the new nested response formats, and ensuring the database structs serialize correctly without breaking existing functionality.
This commit is contained in:
@@ -202,7 +202,6 @@ class User(msgspec.Struct, dict=True, omit_defaults=True, kw_only=True):
|
||||
Immutable fields: created_at (set at creation, never modified)
|
||||
uuid is derived from created_at using uuid7.
|
||||
"""
|
||||
|
||||
display_name: str
|
||||
role_uuid: UUID = msgspec.field(name="role")
|
||||
created_at: datetime
|
||||
|
||||
@@ -27,8 +27,10 @@ from paskia.util import (
|
||||
from paskia.util.apistructs import (
|
||||
ApiAaguidInfo,
|
||||
ApiCreateLinkResponse,
|
||||
ApiOrg,
|
||||
ApiOrgResponse,
|
||||
ApiPermission,
|
||||
ApiRole,
|
||||
ApiUser,
|
||||
ApiUserDetail,
|
||||
ApiUserSession,
|
||||
@@ -97,13 +99,14 @@ async def admin_list_orgs(request: Request, auth=AUTH_COOKIE):
|
||||
def org_to_dict(o):
|
||||
roles = o.roles
|
||||
return ApiOrgResponse(
|
||||
org=o,
|
||||
uuid=o.uuid,
|
||||
display_name=o.display_name,
|
||||
permissions={p.uuid: p for p in o.permissions},
|
||||
roles={r.uuid: r for r in roles},
|
||||
users={u.uuid: u for r in roles for u in r.users},
|
||||
)
|
||||
|
||||
return MsgspecResponse({o.uuid: org_to_dict(o) for o in orgs})
|
||||
return MsgspecResponse([org_to_dict(o) for o in orgs])
|
||||
|
||||
|
||||
@app.post("/orgs")
|
||||
@@ -583,6 +586,8 @@ async def admin_get_user_detail(
|
||||
).items()
|
||||
},
|
||||
sessions=sessions,
|
||||
org=ApiOrg.from_db(user.org),
|
||||
role=ApiRole.from_db(user.role),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -832,7 +837,7 @@ async def admin_list_permissions(request: Request, auth=AUTH_COOKIE):
|
||||
host=request.headers.get("host"),
|
||||
)
|
||||
perms = db.data().permissions.values() if master_admin(ctx) else ctx.org.permissions
|
||||
return MsgspecResponse({p.uuid: ApiPermission.from_db(p) for p in perms})
|
||||
return MsgspecResponse([ApiPermission.from_db(p) for p in perms])
|
||||
|
||||
|
||||
@app.post("/permissions")
|
||||
|
||||
@@ -123,6 +123,8 @@ class ApiUserDetail(msgspec.Struct, kw_only=True):
|
||||
credentials: dict[UUID, Credential]
|
||||
aaguid_info: dict[str, ApiAaguidInfo]
|
||||
sessions: list[ApiUserSession]
|
||||
org: ApiOrg
|
||||
role: ApiRole
|
||||
permissions: dict[UUID, ApiPermission] = {}
|
||||
|
||||
|
||||
@@ -134,7 +136,8 @@ class ApiUserDetail(msgspec.Struct, kw_only=True):
|
||||
class ApiOrgResponse(msgspec.Struct, kw_only=True):
|
||||
"""Org response containing Org with roles and users as UUID-keyed dicts."""
|
||||
|
||||
org: Org
|
||||
uuid: UUID
|
||||
display_name: str
|
||||
permissions: dict[UUID, Permission]
|
||||
roles: dict[UUID, Role]
|
||||
users: dict[UUID, User]
|
||||
|
||||
+15
-9
@@ -6,8 +6,10 @@ from paskia.db import SessionContext
|
||||
from paskia.util import hostutil
|
||||
from paskia.util.apistructs import (
|
||||
ApiAaguidInfo,
|
||||
ApiOrg,
|
||||
ApiOrgContext,
|
||||
ApiPermission,
|
||||
ApiRole,
|
||||
ApiRoleContext,
|
||||
ApiSessionContext,
|
||||
ApiUser,
|
||||
@@ -59,15 +61,19 @@ async def build_user_info(
|
||||
for s in user.sessions
|
||||
]
|
||||
|
||||
return ApiUserDetail(
|
||||
user=ApiUser.from_db(user),
|
||||
credentials={c.uuid: c for c in user.credentials},
|
||||
aaguid_info={
|
||||
return {
|
||||
"ctx": {
|
||||
"user": ApiUser.from_db(user),
|
||||
"permissions": {p.uuid: ApiPermission.from_db(p) for p in ctx.permissions}
|
||||
if ctx
|
||||
else {},
|
||||
},
|
||||
"credentials": {c.uuid: c for c in user.credentials},
|
||||
"aaguid_info": {
|
||||
k: ApiAaguidInfo(**v)
|
||||
for k, v in aaguid.filter(c.aaguid for c in user.credentials).items()
|
||||
},
|
||||
sessions=sessions,
|
||||
permissions={p.uuid: ApiPermission.from_db(p) for p in ctx.permissions}
|
||||
if ctx
|
||||
else {},
|
||||
)
|
||||
"sessions": sessions,
|
||||
"org": ApiOrg.from_db(user.org),
|
||||
"role": ApiRole.from_db(user.role),
|
||||
}
|
||||
|
||||
+13
-11
@@ -791,7 +791,8 @@ class TestAdminUsersInOrg:
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "display_name" in data
|
||||
assert "user" in data
|
||||
assert "display_name" in data["user"]
|
||||
assert "credentials" in data
|
||||
assert "sessions" in data
|
||||
assert "aaguid_info" in data
|
||||
@@ -842,7 +843,8 @@ class TestAdminUsersInOrg:
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "display_name" in data
|
||||
assert "user" in data
|
||||
assert "display_name" in data["user"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_user_display_name_in_org(
|
||||
@@ -850,7 +852,7 @@ class TestAdminUsersInOrg:
|
||||
):
|
||||
"""Admin should be able to update user display name."""
|
||||
response = await client.patch(
|
||||
f"/auth/api/admin/users/{test_user.uuid}/display-name",
|
||||
f"/auth/api/admin/users/{test_user.uuid}/info",
|
||||
json={"display_name": "Updated Admin Name"},
|
||||
headers={**auth_headers(session_token), "Host": "localhost:4401"},
|
||||
)
|
||||
@@ -863,7 +865,7 @@ class TestAdminUsersInOrg:
|
||||
"""Updating non-existent user should return 404."""
|
||||
fake_uuid = uuid7.create()
|
||||
response = await client.patch(
|
||||
f"/auth/api/admin/users/{fake_uuid}/display-name",
|
||||
f"/auth/api/admin/users/{fake_uuid}/info",
|
||||
json={"display_name": "New Name"},
|
||||
headers={**auth_headers(session_token), "Host": "localhost:4401"},
|
||||
)
|
||||
@@ -881,7 +883,7 @@ class TestAdminUsersInOrg:
|
||||
):
|
||||
"""Org admin cannot update user from another org."""
|
||||
response = await client.patch(
|
||||
f"/auth/api/admin/users/{second_org_user.uuid}/display-name",
|
||||
f"/auth/api/admin/users/{second_org_user.uuid}/info",
|
||||
json={"display_name": "New Name"},
|
||||
headers={**auth_headers(org_admin_session_token), "Host": "localhost:4401"},
|
||||
)
|
||||
@@ -893,13 +895,13 @@ class TestAdminUsersInOrg:
|
||||
):
|
||||
"""Updating user with empty display name should fail."""
|
||||
response = await client.patch(
|
||||
f"/auth/api/admin/users/{test_user.uuid}/display-name",
|
||||
f"/auth/api/admin/users/{test_user.uuid}/info",
|
||||
json={"display_name": " "},
|
||||
headers={**auth_headers(session_token), "Host": "localhost:4401"},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
data = response.json()
|
||||
assert "display_name required" in data["detail"]
|
||||
assert "display_name cannot be empty" in data["detail"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_user_display_name_too_long(
|
||||
@@ -907,13 +909,13 @@ class TestAdminUsersInOrg:
|
||||
):
|
||||
"""Updating user with too long display name should fail."""
|
||||
response = await client.patch(
|
||||
f"/auth/api/admin/users/{test_user.uuid}/display-name",
|
||||
json={"display_name": "x" * 100},
|
||||
f"/auth/api/admin/users/{test_user.uuid}/info",
|
||||
json={"display_name": "x" * 65},
|
||||
headers={**auth_headers(session_token), "Host": "localhost:4401"},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
data = response.json()
|
||||
assert "too long" in data["detail"]
|
||||
assert "display_name too long" in data["detail"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_user_role_in_org(
|
||||
@@ -1702,7 +1704,7 @@ class TestOrgAdminAuthExceptions:
|
||||
):
|
||||
"""Regular user trying to update display name should get 403."""
|
||||
response = await client.patch(
|
||||
f"/auth/api/admin/users/{test_user.uuid}/display-name",
|
||||
f"/auth/api/admin/users/{test_user.uuid}/info",
|
||||
json={"display_name": "New Name"},
|
||||
headers={**auth_headers(regular_session_token), "Host": "localhost:4401"},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user