Files
govoplan-forms/tests/test_forms.py
T

299 lines
9.3 KiB
Python

from __future__ import annotations
from dataclasses import dataclass, replace
from datetime import UTC, datetime, timedelta
import unittest
from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from govoplan_core.core.institutional import (
FormDefinition,
FormConditionExpression,
FormFieldDefinition,
FormLocalization,
FormPageDefinition,
FormSectionDefinition,
InstitutionalReference,
TemporalRevision,
)
from govoplan_forms.backend.db.models import FormDefinitionRevision
from govoplan_forms.backend.service import (
FormDefinitionStoreError,
SqlFormDefinitionProvider,
assess_form_definition_fragment,
export_form_definition_fragment,
form_definition_diagnostics,
form_definition_history,
import_form_definition_fragment,
record_form_definition,
)
NOW = datetime(2026, 8, 1, 10, 0, tzinfo=UTC)
@dataclass
class Principal:
tenant_id: str = "tenant-1"
account_id: str = "account-1"
def definition(
*,
revision: str = "1",
state: str = "published",
tenant_id: str = "tenant-1",
) -> FormDefinition:
recorded_at = NOW + timedelta(minutes=int(revision) - 1)
return FormDefinition(
reference=InstitutionalReference(
kind="form",
owner_module="forms",
object_id="permit-application",
tenant_id=tenant_id,
version=revision,
),
key="permit-application",
temporal=TemporalRevision(
revision=revision,
recorded_at=recorded_at,
change_reason="Initial schema." if revision == "1" else "Revise schema.",
),
title="Permit application",
fields=(
FormFieldDefinition(
key="name",
label="Name",
required=True,
constraints={"min_length": 2, "max_length": 200},
),
FormFieldDefinition(
key="delivery",
label="Delivery",
value_type="choice",
options=("portal", "mail"),
),
),
publication_state=state, # type: ignore[arg-type]
allow_drafts=True,
max_attachments=2,
handoff_kinds=("case",),
)
class FormsTests(unittest.TestCase):
def setUp(self) -> None:
self.engine = create_engine("sqlite+pysqlite:///:memory:")
FormDefinitionRevision.__table__.create(self.engine)
self.session = Session(self.engine)
self.principal = Principal()
def tearDown(self) -> None:
self.session.close()
self.engine.dispose()
def test_provider_returns_exact_published_revision_and_history(self) -> None:
first = record_form_definition(
self.session,
self.principal,
definition=definition(),
)
provider = SqlFormDefinitionProvider()
self.assertEqual(
(first,),
tuple(
provider.list_form_definitions(
self.session,
self.principal,
tenant_id="tenant-1",
)
),
)
exact = provider.get_form_definition(
self.session,
self.principal,
reference=first.reference,
effective_at=NOW,
)
self.assertEqual("1", exact.temporal.revision if exact else None)
record_form_definition(
self.session,
self.principal,
definition=definition(revision="2"),
expected_revision="1",
)
self.assertEqual(
["2", "1"],
[
item.temporal.revision
for item in form_definition_history(
self.session,
self.principal,
form_id="permit-application",
)
],
)
def test_replay_occ_and_tenant_boundaries_fail_closed(self) -> None:
first = record_form_definition(
self.session,
self.principal,
definition=definition(),
)
replay = record_form_definition(
self.session,
self.principal,
definition=definition(),
)
self.assertEqual(first, replay)
with self.assertRaisesRegex(FormDefinitionStoreError, "stale"):
record_form_definition(
self.session,
self.principal,
definition=definition(revision="2"),
expected_revision="0",
)
with self.assertRaisesRegex(FormDefinitionStoreError, "cross tenants"):
record_form_definition(
self.session,
self.principal,
definition=definition(tenant_id="tenant-2"),
)
with self.assertRaisesRegex(Exception, "cross tenants"):
SqlFormDefinitionProvider().list_form_definitions(
self.session,
Principal("tenant-2"),
tenant_id="tenant-1",
)
def test_pages_conditions_and_localization_are_validated(self) -> None:
item = definition()
composed = replace(
item,
fields=(
item.fields[0],
replace(
item.fields[1],
visibility_condition=FormConditionExpression(
kind="predicate",
field_key="name",
operator="is_not_empty",
),
),
),
pages=(
FormPageDefinition(
key="application",
title="Application",
sections=(
FormSectionDefinition(
key="details",
title="Details",
field_keys=("name", "delivery"),
),
),
),
),
fallback_locale="de",
localizations=(
FormLocalization(
locale="de",
title="Antrag",
field_labels={"name": "Name", "delivery": "Zustellung"},
option_labels={"delivery": {"portal": "Portal", "mail": "Post"}},
page_titles={"application": "Antrag"},
section_titles={"details": "Angaben"},
),
),
)
stored = record_form_definition(
self.session,
self.principal,
definition=composed,
)
self.assertEqual("application", stored.pages[0].key)
self.assertEqual((), form_definition_diagnostics(stored))
cyclic = replace(
definition(revision="2"),
fields=(
replace(
item.fields[0],
visibility_condition=FormConditionExpression(
kind="predicate",
field_key="delivery",
operator="eq",
value="portal",
),
),
replace(
item.fields[1],
visibility_condition=FormConditionExpression(
kind="predicate",
field_key="name",
operator="is_not_empty",
),
),
),
)
with self.assertRaisesRegex(FormDefinitionStoreError, "dependency cycle"):
record_form_definition(
self.session,
self.principal,
definition=cyclic,
expected_revision="1",
)
def test_package_fragment_is_verified_assessed_and_imported_as_draft(self) -> None:
source = definition(tenant_id="tenant-source")
fragment = export_form_definition_fragment(
source,
exported_at=NOW,
exported_by="source-account",
)
assessment = assess_form_definition_fragment(
self.session,
self.principal,
fragment=fragment,
)
self.assertEqual("create", assessment["outcome"])
self.assertTrue(assessment["requires_remap"])
imported = import_form_definition_fragment(
self.session,
self.principal,
fragment=fragment,
target_form_id="local-permit",
target_key="local-permit",
expected_revision=None,
change_reason="Import reviewed package.",
recorded_at=NOW,
)
self.assertEqual("tenant-1", imported.reference.tenant_id)
self.assertEqual("draft", imported.publication_state)
self.assertEqual(
"tenant-source",
imported.metadata["package_import"]["source_tenant_id"],
)
tampered = dict(fragment)
tampered["definition"] = {**fragment["definition"], "title": "Tampered"}
with self.assertRaisesRegex(FormDefinitionStoreError, "digest"):
assess_form_definition_fragment(
self.session,
self.principal,
fragment=tampered,
)
if __name__ == "__main__":
unittest.main()
(assess_form_definition_fragment,)
(export_form_definition_fragment,)
(form_definition_diagnostics,)
(import_form_definition_fragment,)