from __future__ import annotations import unittest from types import SimpleNamespace from unittest.mock import patch from fastapi import HTTPException from govoplan_core.auth import ApiPrincipal from govoplan_core.core.access import PrincipalRef from govoplan_core.core.modules import ModuleManifest from govoplan_core.core.views import ViewSurface from govoplan_views.backend.router import ( _catalogue, _require_definition_read, _require_definition_write, ) def principal( *, account_id: str = "account-1", scopes: tuple[str, ...], group_ids: tuple[str, ...] = (), ) -> ApiPrincipal: return ApiPrincipal( principal=PrincipalRef( account_id=account_id, membership_id="membership-1", tenant_id="tenant-1", scopes=frozenset(scopes), group_ids=frozenset(group_ids), ), account=object(), user=object(), ) class ViewDefinitionAuthorizationTests(unittest.TestCase): def test_personal_writer_cannot_edit_another_accounts_view(self) -> None: actor = principal(scopes=("views:personal_definition:write",)) _require_definition_write(actor, "user", "account-1") with self.assertRaises(HTTPException) as caught: _require_definition_write(actor, "user", "account-2") self.assertEqual(403, caught.exception.status_code) def test_group_reader_is_limited_to_current_memberships(self) -> None: actor = principal( scopes=("views:group_definition:read",), group_ids=("group-1",), ) _require_definition_read(actor, "group", "group-1") with self.assertRaises(HTTPException) as caught: _require_definition_read(actor, "group", "group-2") self.assertEqual(403, caught.exception.status_code) def test_tenant_manager_can_manage_owned_views(self) -> None: actor = principal(scopes=("views:definition:write",)) _require_definition_write(actor, "group", "group-2") _require_definition_write(actor, "user", "account-2") def test_catalogue_excludes_surfaces_of_tenant_unavailable_modules(self) -> None: actor = principal(scopes=("views:definition:read",)) registry = SimpleNamespace( manifests=lambda: ( ModuleManifest(id="access", name="Access", version="1"), ModuleManifest(id="views", name="Views", version="1"), ModuleManifest(id="files", name="Files", version="1"), ), view_surfaces=lambda: ( ViewSurface( id="views.module", module_id="views", kind="module", label="Views", ), ViewSurface( id="files.module", module_id="files", kind="module", label="Files", ), ), ) tenant = SimpleNamespace( settings={ "module_entitlements": { "schema_version": 1, "revision": 1, "system_policy": { "available_modules": ["access", "views"], "forced_modules": ["access"], }, "tenant_selection": { "enabled_modules": ["views"], }, } } ) session = SimpleNamespace(get=lambda model, tenant_id: tenant) with patch( "govoplan_views.backend.router.get_registry", return_value=registry, ): catalogue = _catalogue(session, actor) self.assertEqual(["views.module"], [surface.id for surface in catalogue]) if __name__ == "__main__": unittest.main()