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