Files
Cloud-Tour-to-Libo/tests/test_user_management.py

126 lines
4.3 KiB
Python

import unittest
import inspect
from unittest.mock import AsyncMock, patch
from fastapi import HTTPException
from pydantic import ValidationError
from app.api import rbac
from app.api import data_platform
from app.contracts import UserCreate, UserUpdate
class UserContractTests(unittest.TestCase):
def test_only_two_data_permissions_are_accepted(self) -> None:
for permission in ("data_manage", "data_view"):
body = UserCreate(
username="user@example.com",
password="Secure-Pass-2026",
permission=permission,
)
self.assertEqual(body.permission, permission)
with self.assertRaises(ValidationError):
UserCreate(
username="user@example.com",
password="Secure-Pass-2026",
permission="collector",
)
def test_data_view_is_read_only_at_the_api_boundary(self) -> None:
for function_name in ("records", "export_records"):
parameter = inspect.signature(
getattr(data_platform, function_name)
).parameters["_user"]
self.assertEqual(parameter.annotation, "DataViewerUser")
for function_name in (
"preview_records_import",
"import_records",
"add_record",
"edit_record",
"remove_record",
):
parameter = inspect.signature(
getattr(data_platform, function_name)
).parameters["user"]
self.assertEqual(parameter.annotation, "AdminUser")
class UserApiTests(unittest.IsolatedAsyncioTestCase):
async def test_create_maps_public_permission_to_one_internal_role(self) -> None:
create_mock = AsyncMock(
return_value={
"id": 8,
"username": "viewer@example.com",
"full_name": "Viewer",
"status": "active",
"roles": ["operator"],
}
)
body = UserCreate(
username=" viewer@example.com ",
password="Secure-Pass-2026",
full_name="Viewer",
permission="data_view",
)
with (
patch.object(rbac, "create_user", create_mock),
patch.object(rbac, "hash_password", return_value="hashed"),
):
result = await rbac._create_user(
body,
{"username": "admin@example.com", "roles": ["admin"]},
)
create_mock.assert_awaited_once()
data, roles = create_mock.await_args.args
self.assertEqual(data["username"], "viewer@example.com")
self.assertEqual(roles, ["operator"])
self.assertEqual(result["permission"], "data_view")
self.assertNotIn("roles", result)
async def test_list_hides_internal_roles_behind_two_permissions(self) -> None:
rows = [
{"id": 1, "username": "manager", "status": "active", "roles": ["admin"]},
{"id": 2, "username": "legacy", "status": "active", "roles": ["collector"]},
]
with patch.object(rbac, "list_users", AsyncMock(return_value=rows)):
result = await rbac._list_users(
{"username": "manager", "roles": ["admin"]}
)
self.assertEqual(
[item["permission"] for item in result],
["data_manage", "data_view"],
)
self.assertTrue(all("roles" not in item for item in result))
async def test_current_manager_cannot_disable_or_demote_itself(self) -> None:
target = {
"id": 1,
"username": "manager@example.com",
"status": "active",
"roles": ["admin"],
}
current = {"username": "manager@example.com", "roles": ["admin"]}
update_mock = AsyncMock()
for body in (
UserUpdate(status="disabled"),
UserUpdate(permission="data_view"),
):
with (
patch.object(rbac, "get_user_by_id", AsyncMock(return_value=target)),
patch.object(rbac, "update_user", update_mock),
):
with self.assertRaises(HTTPException) as raised:
await rbac._update_user(1, body, current)
self.assertEqual(raised.exception.status_code, 400)
update_mock.assert_not_awaited()
if __name__ == "__main__":
unittest.main()