feat(records): implement native eAkte vertical
This commit is contained in:
+34
-12
@@ -2,22 +2,44 @@ from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
|
||||
from govoplan_records.backend.manifest import ADMIN_SCOPE, READ_SCOPE, WRITE_SCOPE, get_manifest
|
||||
from govoplan_core.core.records import CAPABILITY_RECORDS_FILING
|
||||
from govoplan_records.backend.manifest import (
|
||||
ADMIN_SCOPE,
|
||||
READ_SCOPE,
|
||||
WRITE_SCOPE,
|
||||
get_manifest,
|
||||
)
|
||||
|
||||
|
||||
class ManifestSeedTests(unittest.TestCase):
|
||||
def test_manifest_registers_seed_contract(self) -> None:
|
||||
class ManifestTests(unittest.TestCase):
|
||||
def test_manifest_registers_records_vertical(self) -> None:
|
||||
manifest = get_manifest()
|
||||
|
||||
self.assertEqual(manifest.id, "records")
|
||||
self.assertEqual(manifest.name, "Records")
|
||||
self.assertEqual(manifest.dependencies, ("access",))
|
||||
self.assertEqual({permission.scope for permission in manifest.permissions}, {READ_SCOPE, WRITE_SCOPE, ADMIN_SCOPE})
|
||||
self.assertEqual({role.slug for role in manifest.role_templates}, {"records_manager", "records_viewer"})
|
||||
self.assertTrue(manifest.documentation)
|
||||
self.assertIsNone(manifest.route_factory)
|
||||
self.assertIsNone(manifest.migration_spec)
|
||||
self.assertIsNone(manifest.frontend)
|
||||
self.assertEqual("records", manifest.id)
|
||||
self.assertEqual("Records", manifest.name)
|
||||
self.assertEqual(("access",), manifest.dependencies)
|
||||
self.assertEqual(
|
||||
{READ_SCOPE, WRITE_SCOPE, ADMIN_SCOPE},
|
||||
{permission.scope for permission in manifest.permissions},
|
||||
)
|
||||
self.assertEqual(
|
||||
{"records_manager", "records_viewer", "records_administrator"},
|
||||
{role.slug for role in manifest.role_templates},
|
||||
)
|
||||
self.assertIsNotNone(manifest.route_factory)
|
||||
self.assertIsNotNone(manifest.migration_spec)
|
||||
self.assertIsNotNone(manifest.frontend)
|
||||
self.assertIn(CAPABILITY_RECORDS_FILING, manifest.capability_factories)
|
||||
self.assertEqual(
|
||||
"vertical_slice",
|
||||
manifest.architecture.maturity if manifest.architecture else None,
|
||||
)
|
||||
self.assertEqual(
|
||||
"enforced", manifest.information_governance.temporal_browsing.adoption
|
||||
)
|
||||
self.assertEqual(
|
||||
"partial", manifest.information_governance.purpose_aware_access.adoption
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from alembic.runtime.migration import MigrationContext
|
||||
from sqlalchemy import create_engine, inspect
|
||||
|
||||
from govoplan_core.db.migrations import migrate_database
|
||||
from govoplan_records.backend.manifest import get_manifest
|
||||
|
||||
|
||||
class RecordsMigrationTests(unittest.TestCase):
|
||||
def test_fresh_migration_creates_records_kernel_and_head(self) -> None:
|
||||
with tempfile.TemporaryDirectory(
|
||||
prefix="govoplan-records-migration-"
|
||||
) as directory:
|
||||
url = f"sqlite:///{Path(directory) / 'records.db'}"
|
||||
migrate_database(
|
||||
database_url=url,
|
||||
enabled_modules=("records",),
|
||||
manifest_factories=(get_manifest,),
|
||||
)
|
||||
engine = create_engine(url)
|
||||
try:
|
||||
inspector = inspect(engine)
|
||||
self.assertTrue(
|
||||
{
|
||||
"record_chronology_entries",
|
||||
"record_class_revisions",
|
||||
"record_file_plan_revisions",
|
||||
"record_identities",
|
||||
"record_items",
|
||||
"record_revisions",
|
||||
"record_volume_revisions",
|
||||
}.issubset(inspector.get_table_names())
|
||||
)
|
||||
self.assertIn(
|
||||
"source_revision",
|
||||
{item["name"] for item in inspector.get_columns("record_items")},
|
||||
)
|
||||
with engine.connect() as connection:
|
||||
self.assertIn(
|
||||
"6e4a2c8f1d9b",
|
||||
set(MigrationContext.configure(connection).get_current_heads()),
|
||||
)
|
||||
finally:
|
||||
engine.dispose()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,405 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime, timedelta
|
||||
import unittest
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from govoplan_core.core.records import (
|
||||
RecordFilingRequest,
|
||||
RecordSourceLocator,
|
||||
RecordSourceReference,
|
||||
)
|
||||
from govoplan_core.core.temporal import (
|
||||
TemporalDataContext,
|
||||
bind_temporal_data_context,
|
||||
reset_temporal_data_context,
|
||||
)
|
||||
from govoplan_records.backend.db.models import (
|
||||
RecordChronologyEntry,
|
||||
RecordClassRevision,
|
||||
RecordFilePlanRevision,
|
||||
RecordIdentity,
|
||||
RecordItem,
|
||||
RecordRevision,
|
||||
RecordVolumeRevision,
|
||||
)
|
||||
from govoplan_records.backend.service import RecordConflictError, SqlRecordRegistry
|
||||
|
||||
|
||||
NOW = datetime(2026, 1, 6, 9, 0, tzinfo=UTC)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Principal:
|
||||
tenant_id: str = "tenant-1"
|
||||
account_id: str = "account-1"
|
||||
acting_assignment_id: str = "assignment-1"
|
||||
acting_delegation_id: str | None = None
|
||||
|
||||
|
||||
class SourceProvider:
|
||||
provider_id = "files"
|
||||
|
||||
def resource_types(self):
|
||||
return ("file_version",)
|
||||
|
||||
def resolve(self, session, principal, *, locator, purpose):
|
||||
del session, purpose
|
||||
if principal.tenant_id != locator.tenant_id:
|
||||
raise ValueError("Source access denied.")
|
||||
return RecordSourceReference(
|
||||
locator=locator,
|
||||
label="Decision.pdf",
|
||||
authority_mode="external_authoritative",
|
||||
content_sha256="a" * 64,
|
||||
content_type="application/pdf",
|
||||
size_bytes=2048,
|
||||
valid_from=NOW,
|
||||
recorded_at=NOW,
|
||||
launch_url="/files?fileId=file-1",
|
||||
)
|
||||
|
||||
|
||||
class Registry:
|
||||
def __init__(self) -> None:
|
||||
self.provider = SourceProvider()
|
||||
|
||||
def capability_names(self):
|
||||
return ("records.source.files",)
|
||||
|
||||
def tenant_capability(self, name, session, *, tenant_id):
|
||||
del session
|
||||
return (
|
||||
self.provider
|
||||
if name == "records.source.files" and tenant_id == "tenant-1"
|
||||
else None
|
||||
)
|
||||
|
||||
|
||||
class RecordsTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.engine = create_engine("sqlite+pysqlite:///:memory:")
|
||||
for table in (
|
||||
RecordFilePlanRevision.__table__,
|
||||
RecordClassRevision.__table__,
|
||||
RecordIdentity.__table__,
|
||||
RecordRevision.__table__,
|
||||
RecordVolumeRevision.__table__,
|
||||
RecordItem.__table__,
|
||||
RecordChronologyEntry.__table__,
|
||||
):
|
||||
table.create(self.engine)
|
||||
self.session = Session(self.engine)
|
||||
self.principal = Principal()
|
||||
self.records = SqlRecordRegistry(Registry())
|
||||
self._seed_catalog()
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.session.close()
|
||||
self.engine.dispose()
|
||||
|
||||
def _seed_catalog(self) -> None:
|
||||
self.records.write_file_plan_node(
|
||||
self.session,
|
||||
self.principal,
|
||||
payload={
|
||||
"node_id": "plan-permits",
|
||||
"code": "10.20",
|
||||
"label": "Permits",
|
||||
"recorded_at": NOW,
|
||||
"valid_from": NOW,
|
||||
"idempotency_key": "catalog-node-1",
|
||||
"institutional_context": {"organization_unit_id": "unit-1"},
|
||||
},
|
||||
)
|
||||
self.records.write_record_class(
|
||||
self.session,
|
||||
self.principal,
|
||||
payload={
|
||||
"class_id": "class-permit",
|
||||
"file_plan_node_id": "plan-permits",
|
||||
"key": "permit.application",
|
||||
"label": "Permit application",
|
||||
"allowed_source_types": ["files:file_version"],
|
||||
"retention_period_days": 3650,
|
||||
"access_mode": "tenant",
|
||||
"recorded_at": NOW,
|
||||
"valid_from": NOW,
|
||||
"idempotency_key": "catalog-class-1",
|
||||
"institutional_context": {"organization_unit_id": "unit-1"},
|
||||
},
|
||||
)
|
||||
self.session.commit()
|
||||
|
||||
def _create_record(self) -> dict[str, object]:
|
||||
result = self.records.create_record(
|
||||
self.session,
|
||||
self.principal,
|
||||
payload={
|
||||
"record_id": "record-1",
|
||||
"record_number": "2026/0001",
|
||||
"class_id": "class-permit",
|
||||
"file_plan_node_id": "plan-permits",
|
||||
"title": "Permit application Ada Example",
|
||||
"description": "Assisted service journey",
|
||||
"state": "open",
|
||||
"source_authority_mode": "native_authoritative",
|
||||
"access_mode": "tenant",
|
||||
"purpose": "process permit application",
|
||||
"institutional_context": {
|
||||
"organization_unit_id": "unit-1",
|
||||
"case_id": "case-1",
|
||||
},
|
||||
"recorded_at": NOW + timedelta(minutes=1),
|
||||
"valid_from": NOW,
|
||||
"change_reason": "Application received.",
|
||||
"idempotency_key": "record-create-1",
|
||||
},
|
||||
)
|
||||
self.session.commit()
|
||||
return result
|
||||
|
||||
def test_record_creation_revision_and_temporal_reconstruction(self) -> None:
|
||||
first = self._create_record()
|
||||
replay = self._create_record()
|
||||
self.assertEqual(first["record_id"], replay["record_id"])
|
||||
|
||||
revised = self.records.update_record(
|
||||
self.session,
|
||||
self.principal,
|
||||
record_id="record-1",
|
||||
payload={
|
||||
"expected_revision": 1,
|
||||
"title": "Permit application Ada Example - verified",
|
||||
"purpose": "verify application",
|
||||
"recorded_at": NOW + timedelta(minutes=2),
|
||||
"change_reason": "Identity verified.",
|
||||
"idempotency_key": "record-update-1",
|
||||
},
|
||||
)
|
||||
self.session.commit()
|
||||
self.assertEqual(2, revised["revision"])
|
||||
self.assertEqual("process permit application", revised["purpose"])
|
||||
|
||||
token = bind_temporal_data_context(
|
||||
TemporalDataContext(
|
||||
validity_mode="at",
|
||||
valid_at=NOW + timedelta(hours=1),
|
||||
recorded_at=NOW + timedelta(minutes=1, seconds=30),
|
||||
)
|
||||
)
|
||||
try:
|
||||
historical = self.records.get_record(
|
||||
self.session, self.principal, record_id="record-1"
|
||||
)
|
||||
self.assertEqual(1, historical["record"]["revision"])
|
||||
self.assertEqual(
|
||||
"Permit application Ada Example", historical["record"]["title"]
|
||||
)
|
||||
finally:
|
||||
reset_temporal_data_context(token)
|
||||
|
||||
with self.assertRaisesRegex(RecordConflictError, "current revision is 2"):
|
||||
self.records.update_record(
|
||||
self.session,
|
||||
self.principal,
|
||||
record_id="record-1",
|
||||
payload={
|
||||
"expected_revision": 1,
|
||||
"title": "Stale title",
|
||||
"purpose": "update record",
|
||||
"recorded_at": NOW + timedelta(minutes=3),
|
||||
"change_reason": "Stale edit.",
|
||||
"idempotency_key": "record-update-stale",
|
||||
},
|
||||
)
|
||||
|
||||
def test_update_replay_returns_the_original_revision(self) -> None:
|
||||
self._create_record()
|
||||
first_payload = {
|
||||
"expected_revision": 1,
|
||||
"title": "First revised title",
|
||||
"purpose": "correct title",
|
||||
"recorded_at": NOW + timedelta(minutes=2),
|
||||
"change_reason": "First correction.",
|
||||
"idempotency_key": "record-update-replay",
|
||||
}
|
||||
first = self.records.update_record(
|
||||
self.session,
|
||||
self.principal,
|
||||
record_id="record-1",
|
||||
payload=first_payload,
|
||||
)
|
||||
self.records.update_record(
|
||||
self.session,
|
||||
self.principal,
|
||||
record_id="record-1",
|
||||
payload={
|
||||
"expected_revision": 2,
|
||||
"title": "Second revised title",
|
||||
"purpose": "correct title again",
|
||||
"recorded_at": NOW + timedelta(minutes=3),
|
||||
"change_reason": "Second correction.",
|
||||
"idempotency_key": "record-update-second",
|
||||
},
|
||||
)
|
||||
self.session.commit()
|
||||
|
||||
replay = self.records.update_record(
|
||||
self.session,
|
||||
self.principal,
|
||||
record_id="record-1",
|
||||
payload=first_payload,
|
||||
)
|
||||
|
||||
self.assertEqual(2, first["revision"])
|
||||
self.assertEqual(2, replay["revision"])
|
||||
self.assertEqual("First revised title", replay["title"])
|
||||
|
||||
def test_file_plan_revisions_reject_indirect_parent_cycles(self) -> None:
|
||||
self.records.write_file_plan_node(
|
||||
self.session,
|
||||
self.principal,
|
||||
payload={
|
||||
"node_id": "plan-child",
|
||||
"parent_node_id": "plan-permits",
|
||||
"code": "10.20.1",
|
||||
"label": "Child",
|
||||
"recorded_at": NOW + timedelta(minutes=1),
|
||||
"idempotency_key": "catalog-child",
|
||||
},
|
||||
)
|
||||
self.records.write_file_plan_node(
|
||||
self.session,
|
||||
self.principal,
|
||||
payload={
|
||||
"node_id": "plan-grandchild",
|
||||
"parent_node_id": "plan-child",
|
||||
"code": "10.20.1.1",
|
||||
"label": "Grandchild",
|
||||
"recorded_at": NOW + timedelta(minutes=2),
|
||||
"idempotency_key": "catalog-grandchild",
|
||||
},
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(ValueError, "create a cycle"):
|
||||
self.records.write_file_plan_node(
|
||||
self.session,
|
||||
self.principal,
|
||||
payload={
|
||||
"node_id": "plan-permits",
|
||||
"parent_node_id": "plan-grandchild",
|
||||
"code": "10.20",
|
||||
"label": "Permits",
|
||||
"recorded_at": NOW + timedelta(minutes=3),
|
||||
"expected_revision": 1,
|
||||
"idempotency_key": "catalog-cycle",
|
||||
},
|
||||
)
|
||||
|
||||
def test_exact_source_filing_is_authorized_idempotent_and_reconstructable(
|
||||
self,
|
||||
) -> None:
|
||||
self._create_record()
|
||||
request = RecordFilingRequest(
|
||||
tenant_id="tenant-1",
|
||||
record_id="record-1",
|
||||
source=RecordSourceLocator(
|
||||
tenant_id="tenant-1",
|
||||
source_module="files",
|
||||
resource_type="file_version",
|
||||
resource_id="file-1",
|
||||
source_revision="version-7",
|
||||
),
|
||||
purpose="document decision basis",
|
||||
filing_reason="Submitted evidence belongs to the permit record.",
|
||||
idempotency_key="filing-1",
|
||||
institutional_context={"case_id": "case-1"},
|
||||
)
|
||||
first = self.records.file(self.session, self.principal, request=request)
|
||||
self.session.commit()
|
||||
second = self.records.file(self.session, self.principal, request=request)
|
||||
self.assertEqual(first.item_id, second.item_id)
|
||||
self.assertTrue(second.replayed)
|
||||
|
||||
detail = self.records.get_record(
|
||||
self.session, self.principal, record_id="record-1"
|
||||
)
|
||||
self.assertEqual("version-7", detail["items"][0]["source"]["source_revision"])
|
||||
self.assertEqual("a" * 64, detail["items"][0]["content_sha256"])
|
||||
self.assertEqual(
|
||||
["record.item_filed", "record.created"],
|
||||
[entry["event_type"] for entry in detail["chronology"]],
|
||||
)
|
||||
|
||||
def test_filing_rejects_a_volume_from_another_record(self) -> None:
|
||||
self._create_record()
|
||||
second = self.records.create_record(
|
||||
self.session,
|
||||
self.principal,
|
||||
payload={
|
||||
"record_id": "record-2",
|
||||
"record_number": "2026/0002",
|
||||
"class_id": "class-permit",
|
||||
"file_plan_node_id": "plan-permits",
|
||||
"title": "Second permit record",
|
||||
"state": "open",
|
||||
"purpose": "process second permit",
|
||||
"recorded_at": NOW + timedelta(minutes=2),
|
||||
"change_reason": "Second application received.",
|
||||
"idempotency_key": "record-create-2",
|
||||
},
|
||||
)
|
||||
volume = self.records.create_volume(
|
||||
self.session,
|
||||
self.principal,
|
||||
record_id=str(second["record_id"]),
|
||||
payload={
|
||||
"volume_id": "volume-2",
|
||||
"label": "Second record volume",
|
||||
"recorded_at": NOW + timedelta(minutes=3),
|
||||
"purpose": "structure record",
|
||||
"idempotency_key": "volume-create-2",
|
||||
},
|
||||
)
|
||||
request = RecordFilingRequest(
|
||||
tenant_id="tenant-1",
|
||||
record_id="record-1",
|
||||
volume_id=str(volume["volume_id"]),
|
||||
source=RecordSourceLocator(
|
||||
tenant_id="tenant-1",
|
||||
source_module="files",
|
||||
resource_type="file_version",
|
||||
resource_id="file-1",
|
||||
source_revision="version-7",
|
||||
),
|
||||
purpose="document decision basis",
|
||||
filing_reason="Attempted cross-record volume filing.",
|
||||
idempotency_key="filing-cross-volume",
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(ValueError, "does not belong to this record"):
|
||||
self.records.file(self.session, self.principal, request=request)
|
||||
|
||||
def test_tenant_isolation_and_source_discovery(self) -> None:
|
||||
self._create_record()
|
||||
records, total = self.records.list_records(self.session, Principal("tenant-2"))
|
||||
self.assertEqual([], records)
|
||||
self.assertEqual(0, total)
|
||||
self.assertEqual(
|
||||
[
|
||||
{
|
||||
"id": "files",
|
||||
"source_module": "files",
|
||||
"resource_types": ["file_version"],
|
||||
}
|
||||
],
|
||||
self.records.source_providers(self.session, self.principal),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,178 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
from types import SimpleNamespace
|
||||
import unittest
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from govoplan_core.auth import ApiPrincipal
|
||||
from govoplan_core.core.access import PrincipalRef
|
||||
from govoplan_core.core.search import (
|
||||
SearchAuthorizationRequest,
|
||||
SearchBackfillRequest,
|
||||
SearchResourceReference,
|
||||
)
|
||||
from govoplan_records.backend.db.models import RecordIdentity, RecordRevision
|
||||
from govoplan_records.backend.search_source import (
|
||||
ADMIN_SCOPE,
|
||||
PROVIDER_ID,
|
||||
READ_SCOPE,
|
||||
RESOURCE_TYPE,
|
||||
RecordsSearchSource,
|
||||
)
|
||||
|
||||
|
||||
NOW = datetime(2026, 1, 6, 9, 0, tzinfo=UTC)
|
||||
|
||||
|
||||
class RecordsSearchSourceTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.engine = create_engine("sqlite+pysqlite:///:memory:")
|
||||
RecordIdentity.__table__.create(self.engine)
|
||||
RecordRevision.__table__.create(self.engine)
|
||||
self.session = Session(self.engine)
|
||||
identity = RecordIdentity(
|
||||
id="identity-1",
|
||||
tenant_id="tenant-1",
|
||||
record_id="record-1",
|
||||
record_number="EA-2026-0001",
|
||||
)
|
||||
self.session.add_all(
|
||||
(
|
||||
identity,
|
||||
RecordRevision(
|
||||
tenant_id="tenant-1",
|
||||
record_id="record-1",
|
||||
identity_id=identity.id,
|
||||
revision=1,
|
||||
class_id="class-1",
|
||||
file_plan_node_id="plan-1",
|
||||
title="Permit decision",
|
||||
description="Decision record",
|
||||
state="open",
|
||||
source_authority_mode="native_authoritative",
|
||||
access_mode="tenant",
|
||||
purpose="case-work",
|
||||
institutional_context={"organization_unit_id": "unit-1"},
|
||||
external_reference={},
|
||||
search_text="ea-2026-0001 permit decision",
|
||||
valid_from=NOW,
|
||||
recorded_at=NOW,
|
||||
snapshot={},
|
||||
),
|
||||
RecordIdentity(
|
||||
id="identity-2",
|
||||
tenant_id="tenant-2",
|
||||
record_id="record-2",
|
||||
record_number="EA-OTHER",
|
||||
),
|
||||
)
|
||||
)
|
||||
self.session.flush()
|
||||
self.session.add(
|
||||
RecordRevision(
|
||||
tenant_id="tenant-2",
|
||||
record_id="record-2",
|
||||
identity_id="identity-2",
|
||||
revision=1,
|
||||
class_id="class-1",
|
||||
file_plan_node_id="plan-1",
|
||||
title="Other tenant",
|
||||
state="open",
|
||||
source_authority_mode="native_authoritative",
|
||||
access_mode="tenant",
|
||||
purpose="case-work",
|
||||
institutional_context={},
|
||||
external_reference={},
|
||||
search_text="other tenant",
|
||||
valid_from=NOW,
|
||||
recorded_at=NOW,
|
||||
snapshot={},
|
||||
)
|
||||
)
|
||||
self.session.commit()
|
||||
self.source = RecordsSearchSource()
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.session.close()
|
||||
self.engine.dispose()
|
||||
|
||||
def test_backfill_and_live_authorization_are_tenant_scoped(self) -> None:
|
||||
page = self.source.backfill(
|
||||
self.session,
|
||||
request=SearchBackfillRequest(
|
||||
tenant_id="tenant-1",
|
||||
provider_id=PROVIDER_ID,
|
||||
resource_type=RESOURCE_TYPE,
|
||||
rebuild_id="records-rebuild-1",
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
("record-1",), tuple(document.resource_id for document in page.documents)
|
||||
)
|
||||
reference = SearchResourceReference(
|
||||
tenant_id="tenant-1",
|
||||
module_id="records",
|
||||
resource_type=RESOURCE_TYPE,
|
||||
resource_id="record-1",
|
||||
)
|
||||
request = SearchAuthorizationRequest(reference=reference, source_revision="1")
|
||||
|
||||
self.assertTrue(
|
||||
self.source.authorize(
|
||||
self.session,
|
||||
_principal({READ_SCOPE}),
|
||||
requests=(request,),
|
||||
)[reference.key]
|
||||
)
|
||||
self.assertTrue(
|
||||
self.source.authorize(
|
||||
self.session,
|
||||
_principal({ADMIN_SCOPE}),
|
||||
requests=(request,),
|
||||
)[reference.key]
|
||||
)
|
||||
self.assertFalse(
|
||||
self.source.authorize(
|
||||
self.session,
|
||||
_principal(set()),
|
||||
requests=(request,),
|
||||
)[reference.key]
|
||||
)
|
||||
other_tenant_reference = SearchResourceReference(
|
||||
tenant_id="tenant-2",
|
||||
module_id="records",
|
||||
resource_type=RESOURCE_TYPE,
|
||||
resource_id="record-2",
|
||||
)
|
||||
self.assertFalse(
|
||||
self.source.authorize(
|
||||
self.session,
|
||||
_principal({READ_SCOPE}),
|
||||
requests=(
|
||||
SearchAuthorizationRequest(
|
||||
reference=other_tenant_reference,
|
||||
source_revision="1",
|
||||
),
|
||||
),
|
||||
)[other_tenant_reference.key]
|
||||
)
|
||||
|
||||
|
||||
def _principal(scopes: set[str]) -> ApiPrincipal:
|
||||
return ApiPrincipal(
|
||||
principal=PrincipalRef(
|
||||
account_id="account-1",
|
||||
membership_id="membership-1",
|
||||
tenant_id="tenant-1",
|
||||
scopes=frozenset(scopes),
|
||||
),
|
||||
account=SimpleNamespace(id="account-1"),
|
||||
user=SimpleNamespace(id="user-1"),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user