perf(cases): paginate authorized records in SQL
Module Package Release / publish-packages (push) Successful in 13s

Release v0.1.25. Coordinated integrity review: GovOPlaN/govoplan-core#298.
This commit is contained in:
2026-09-08 12:19:37 +02:00
parent afda3a5ac5
commit 0337e0cf0b
6 changed files with 149 additions and 53 deletions
+94
View File
@@ -0,0 +1,94 @@
from __future__ import annotations
from dataclasses import replace
import unittest
from sqlalchemy import event, select
from sqlalchemy.dialects import postgresql
import test_case_lifecycle as fixture
from govoplan_cases.backend import service
from govoplan_cases.backend.db.models import CaseAccessGrant, CaseRecordRevision
class CaseListQueryTests(unittest.TestCase):
def setUp(self):
self.fixture = fixture.CaseLifecycleTests()
self.fixture.setUp()
def tearDown(self):
self.fixture.tearDown()
def seed(self, count=50, *, restricted=False):
for index in range(count):
original = fixture.record()
reference = replace(original.reference, object_id=f"case-{index:03}")
record = replace(
original, reference=reference, case_number=f"CASE-{index:03}",
context=replace(original.context, case_ref=reference),
access_mode="restricted" if restricted else "tenant",
)
service.create_case(self.fixture.session, self.fixture.principal, record=record, idempotency_key=f"fixture-{index}")
self.fixture.session.add(CaseAccessGrant(
tenant_id="tenant-1", case_id=reference.object_id, subject_kind="account", subject_id="other-account",
permissions=["read"], allowed_purposes=["cases.casework"], source="manual", active=True, source_revision=1,
))
self.fixture.session.commit()
self.fixture.session.expunge_all()
def test_filtered_empty_page_does_not_materialize_grants_or_case_ids(self):
self.seed()
loaded = []
queries = []
def row_loaded(target, context):
loaded.append(target.id)
def executed(conn, cursor, statement, parameters, context, executemany):
if statement.lstrip().upper().startswith("SELECT"):
queries.append((statement, len(parameters)))
event.listen(CaseAccessGrant, "load", row_loaded)
event.listen(self.fixture.engine, "before_cursor_execute", executed)
try:
result = service.list_cases(
self.fixture.session, self.fixture.principal,
query="no-fixture-matches-this", purpose="cases.casework", limit=1,
)
self.assertEqual(((), 0), result)
self.assertEqual([], loaded)
self.assertEqual(2, len(queries))
self.assertTrue(all(count < 30 for _, count in queries))
self.assertIn("LIMIT", queries[-1][0])
finally:
event.remove(CaseAccessGrant, "load", row_loaded)
event.remove(self.fixture.engine, "before_cursor_execute", executed)
def test_current_grants_exact_purpose_and_permissions_govern_total(self):
self.seed(4, restricted=True)
reader = fixture.Principal(account_id="other-account")
session = self.fixture.session
self.assertEqual(((), 0), service.list_cases(session, reader, limit=1))
self.assertEqual(((), 0), service.list_cases(session, reader, purpose="cases.case", limit=1))
page, total = service.list_cases(session, reader, purpose="cases.casework", offset=1, limit=1)
self.assertEqual(4, total)
self.assertEqual("case-001", page[0].reference.object_id)
grants = session.query(CaseAccessGrant).filter(CaseAccessGrant.subject_id == "other-account").order_by(CaseAccessGrant.case_id).all()
grants[0].active = False
grants[1].permissions = ["reader"]
grants[2].allowed_purposes = ["cases.casework.extra"]
grants[3].permissions = ["update"]
session.commit()
self.assertEqual(1, service.list_cases(session, reader, purpose="cases.casework", limit=1)[1])
grants[3].allowed_purposes = {"not_an_array": "cases.casework"}
session.commit()
self.assertEqual(((), 0), service.list_cases(session, reader, purpose="cases.casework", limit=1))
self.assertEqual(((), 0), service.list_cases(session, fixture.Principal(tenant_id="tenant-2", account_id="other-account"), purpose="cases.casework", limit=1))
def test_access_predicate_compiles_for_postgresql_without_case_id_lists(self):
compiled = select(CaseRecordRevision.case_id).where(
service._case_access_predicate(self.fixture.principal, permission="read", purpose="cases.casework"),
).compile(dialect=postgresql.dialect())
self.assertIn("EXISTS", str(compiled))
self.assertIn("json_array_elements", str(compiled))
self.assertNotIn("cases.casework", str(compiled))