Files
govoplan-cases/tests/test_search_source.py
T
zemion 17415d8831
Module Package Release / publish-packages (push) Successful in 12s
feat(cases): enforce purpose-bound case governance
2026-08-24 20:05:31 +02:00

189 lines
5.8 KiB
Python

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.events import EventObjectRef, EventTenantRef, PlatformEvent
from govoplan_core.core.institutional import (
GovernedContextEnvelope,
InstitutionalReference,
TemporalRevision,
)
from govoplan_core.core.search import (
SearchAuthorizationRequest,
SearchBackfillRequest,
SearchResourceReference,
)
from govoplan_cases.backend.db.models import (
CaseAccessGrant,
CaseIdentity,
CaseRecordRevision,
CaseStatusDefinition,
CaseTimelineEntry,
CaseTypeDefinition,
)
from govoplan_cases.backend.domain import CaseGrant, CaseRecord
from govoplan_cases.backend.search_source import (
SEARCH_ACCESS_PURPOSE,
CasesSearchSource,
PROVIDER_ID,
)
from govoplan_cases.backend.service import (
create_case,
upsert_case_status,
upsert_case_type,
)
NOW = datetime(2026, 8, 6, 10, 0, tzinfo=UTC)
class CasesSearchSourceTests(unittest.TestCase):
def setUp(self) -> None:
self.engine = create_engine("sqlite+pysqlite:///:memory:")
for table in (
CaseStatusDefinition.__table__,
CaseTypeDefinition.__table__,
CaseIdentity.__table__,
CaseRecordRevision.__table__,
CaseAccessGrant.__table__,
CaseTimelineEntry.__table__,
):
table.create(self.engine)
self.session = Session(self.engine)
principal = _principal({"cases:case:read", "cases:case:create"})
upsert_case_status(
self.session,
principal,
status_key="review",
label="Review",
)
upsert_case_type(
self.session,
principal,
type_key="permit",
label="Permit",
initial_status_key="review",
)
case_ref = _ref("case", "case-1", "cases")
create_case(
self.session,
principal,
record=CaseRecord(
reference=case_ref,
case_number="PERMIT-1",
case_type_key="permit",
status_key="review",
title="Permit application",
access_mode="restricted",
access_grants=(
CaseGrant(
"account",
"account-1",
("read",),
(SEARCH_ACCESS_PURPOSE,),
),
),
context=GovernedContextEnvelope(
tenant_id="tenant-1",
temporal=TemporalRevision(
revision="1",
valid_from=NOW,
recorded_at=NOW,
change_reason="Created.",
),
case_ref=case_ref,
),
opened_at=NOW,
recorded_at=NOW,
change_reason="Created.",
),
idempotency_key="case-create",
)
self.session.commit()
self.source = CasesSearchSource()
def tearDown(self) -> None:
self.session.close()
self.engine.dispose()
def test_backfill_authorization_and_event_change_are_bounded(self) -> None:
page = self.source.backfill(
self.session,
request=SearchBackfillRequest(
tenant_id="tenant-1",
provider_id=PROVIDER_ID,
resource_type="case",
rebuild_id="rebuild-1",
),
)
self.assertEqual(("case-1",), tuple(item.resource_id for item in page.documents))
self.assertEqual("PERMIT-1", page.documents[0].metadata["case_number"])
self.assertIn("purpose=cases.search", page.documents[0].url)
reference = SearchResourceReference(
tenant_id="tenant-1",
module_id="cases",
resource_type="case",
resource_id="case-1",
)
request = SearchAuthorizationRequest(reference=reference, source_revision="1")
self.assertTrue(
self.source.authorize(
self.session,
_principal({"cases:case:read"}),
requests=(request,),
)[reference.key]
)
self.assertFalse(
self.source.authorize(
self.session,
_principal(set()),
requests=(request,),
)[reference.key]
)
changes = self.source.index_changes_for_event(
self.session,
event=PlatformEvent(
type="cases.case.updated",
module_id="cases",
tenant=EventTenantRef(id="tenant-1"),
resource=EventObjectRef(type="case", id="case-1"),
),
delivery_key="delivery-1",
)
self.assertEqual("upsert", changes[0].kind)
def _ref(kind: str, object_id: str, owner: str) -> InstitutionalReference:
return InstitutionalReference(
kind=kind, # type: ignore[arg-type]
owner_module=owner,
object_id=object_id,
tenant_id="tenant-1",
version="1",
valid_at=NOW,
)
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="membership-1"),
)
if __name__ == "__main__":
unittest.main()