Add scoped postbox template previews
This commit is contained in:
+20
-7
@@ -30,6 +30,10 @@ class PostboxMigrationTests(unittest.TestCase):
|
||||
"govoplan_postbox.backend.migrations.versions."
|
||||
"d8e3f6a9b2c5_postbox_content_protection"
|
||||
)
|
||||
scope_migration = importlib.import_module(
|
||||
"govoplan_postbox.backend.migrations.versions."
|
||||
"e9f4a7b2c5d8_v014_template_scope_preview"
|
||||
)
|
||||
engine = create_engine("sqlite:///:memory:")
|
||||
try:
|
||||
with engine.begin() as connection:
|
||||
@@ -39,17 +43,20 @@ class PostboxMigrationTests(unittest.TestCase):
|
||||
occ_original = occ_migration.op
|
||||
envelope_original = envelope_migration.op
|
||||
protection_original = protection_migration.op
|
||||
scope_original = scope_migration.op
|
||||
migration.op = operations
|
||||
route_migration.op = operations
|
||||
occ_migration.op = operations
|
||||
envelope_migration.op = operations
|
||||
protection_migration.op = operations
|
||||
scope_migration.op = operations
|
||||
try:
|
||||
migration.upgrade()
|
||||
route_migration.upgrade()
|
||||
occ_migration.upgrade()
|
||||
envelope_migration.upgrade()
|
||||
protection_migration.upgrade()
|
||||
scope_migration.upgrade()
|
||||
tables = set(inspect(connection).get_table_names())
|
||||
self.assertIn("postboxes", tables)
|
||||
self.assertIn("postbox_messages", tables)
|
||||
@@ -75,14 +82,18 @@ class PostboxMigrationTests(unittest.TestCase):
|
||||
"encryption_resource_id",
|
||||
}.issubset(message_columns)
|
||||
)
|
||||
self.assertIn(
|
||||
"encryption_vault_id",
|
||||
template_revision_columns = {
|
||||
column["name"]
|
||||
for column in inspect(connection).get_columns(
|
||||
"postbox_template_revisions"
|
||||
)
|
||||
}
|
||||
self.assertTrue(
|
||||
{
|
||||
column["name"]
|
||||
for column in inspect(connection).get_columns(
|
||||
"postbox_template_revisions"
|
||||
)
|
||||
},
|
||||
"encryption_vault_id",
|
||||
"scope_structure_id",
|
||||
"scope_relation_type_ids",
|
||||
}.issubset(template_revision_columns)
|
||||
)
|
||||
self.assertIn("authoring_key", message_columns)
|
||||
for table_name in (
|
||||
@@ -110,6 +121,7 @@ class PostboxMigrationTests(unittest.TestCase):
|
||||
route_columns
|
||||
)
|
||||
)
|
||||
scope_migration.downgrade()
|
||||
protection_migration.downgrade()
|
||||
envelope_migration.downgrade()
|
||||
occ_migration.downgrade()
|
||||
@@ -128,6 +140,7 @@ class PostboxMigrationTests(unittest.TestCase):
|
||||
occ_migration.op = occ_original
|
||||
envelope_migration.op = envelope_original
|
||||
protection_migration.op = protection_original
|
||||
scope_migration.op = scope_original
|
||||
finally:
|
||||
engine.dispose()
|
||||
|
||||
|
||||
@@ -250,6 +250,63 @@ class PostboxRouterTests(unittest.TestCase):
|
||||
self.assertEqual(response.status_code, 403)
|
||||
self.assertIn("not active for this principal", response.text)
|
||||
|
||||
def test_template_impact_preview_is_available_without_writes(self) -> None:
|
||||
with Session(self.engine) as session:
|
||||
before = session.query(Postbox).count()
|
||||
response = self.client.post(
|
||||
"/api/v1/postbox/admin/templates/preview",
|
||||
json={
|
||||
"slug": "case-intake",
|
||||
"name": "Case intake",
|
||||
"scope_kind": "tenant",
|
||||
"name_pattern": "{unit_name} / {function_name}",
|
||||
"address_pattern": "{template_slug}.{unit_slug}.{function_slug}",
|
||||
"classification": "internal",
|
||||
},
|
||||
)
|
||||
self.assertEqual(200, response.status_code, response.text)
|
||||
self.assertEqual(1, response.json()["total"])
|
||||
self.assertEqual(1, response.json()["ready_count"])
|
||||
with Session(self.engine) as session:
|
||||
self.assertEqual(before, session.query(Postbox).count())
|
||||
|
||||
def test_legacy_subtree_template_remains_readable_but_cannot_be_created_by_api(
|
||||
self,
|
||||
) -> None:
|
||||
with Session(self.engine) as session:
|
||||
self.service.create_template(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
slug="legacy-subtree",
|
||||
name="Legacy subtree",
|
||||
description=None,
|
||||
function_type_id="clerk-type",
|
||||
scope_kind="subtree",
|
||||
scope_id="unit-1",
|
||||
name_pattern="{unit_name} / {function_name}",
|
||||
address_pattern="{template_slug}.{unit_slug}.{function_slug}",
|
||||
classification="internal",
|
||||
allow_vacant_delivery=True,
|
||||
actor_id="account-1",
|
||||
)
|
||||
session.commit()
|
||||
|
||||
listing = self.client.get("/api/v1/postbox/admin/templates")
|
||||
self.assertEqual(200, listing.status_code, listing.text)
|
||||
revision = listing.json()["templates"][0]["revisions"][0]
|
||||
self.assertIsNone(revision["scope_structure_id"])
|
||||
|
||||
rejected = self.client.post(
|
||||
"/api/v1/postbox/admin/templates",
|
||||
json={
|
||||
"slug": "new-subtree",
|
||||
"name": "New subtree",
|
||||
"scope_kind": "subtree",
|
||||
"scope_id": "unit-1",
|
||||
},
|
||||
)
|
||||
self.assertEqual(422, rejected.status_code, rejected.text)
|
||||
|
||||
def test_directory_delivery_message_and_receipt_round_trip(self) -> None:
|
||||
directory = self.client.get("/api/v1/postbox/directory")
|
||||
self.assertEqual(200, directory.status_code, directory.text)
|
||||
@@ -289,6 +346,20 @@ class PostboxRouterTests(unittest.TestCase):
|
||||
self.assertEqual(200, filtered.status_code, filtered.text)
|
||||
self.assertEqual(1, filtered.json()["total"])
|
||||
|
||||
grouping = self.client.post(
|
||||
"/api/v1/postbox/groupings",
|
||||
json={
|
||||
"name": "Assigned work",
|
||||
"is_default": True,
|
||||
"postbox_ids": [self.postbox_id],
|
||||
},
|
||||
)
|
||||
self.assertEqual(201, grouping.status_code, grouping.text)
|
||||
grouped_before_read = self.client.get("/api/v1/postbox/groupings")
|
||||
self.assertEqual(200, grouped_before_read.status_code)
|
||||
self.assertEqual(1, grouped_before_read.json()["groupings"][0]["total_count"])
|
||||
self.assertEqual(1, grouped_before_read.json()["groupings"][0]["unread_count"])
|
||||
|
||||
acknowledged = self.client.patch(
|
||||
f"/api/v1/postbox/messages/{message_id}/state",
|
||||
json={"state": "acknowledged"},
|
||||
@@ -306,6 +377,8 @@ class PostboxRouterTests(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(200, unread.status_code, unread.text)
|
||||
self.assertEqual(0, unread.json()["total"])
|
||||
grouped_after_read = self.client.get("/api/v1/postbox/groupings")
|
||||
self.assertEqual(0, grouped_after_read.json()["groupings"][0]["unread_count"])
|
||||
|
||||
def test_routing_dry_run_explains_default_disabled_state(self) -> None:
|
||||
response = self.client.post(
|
||||
|
||||
@@ -899,6 +899,133 @@ class PostboxServiceTests(unittest.TestCase):
|
||||
revised.revisions[1].name_pattern,
|
||||
)
|
||||
|
||||
def test_template_preview_is_read_only_and_explains_existing_vacant_and_colliding_targets(
|
||||
self,
|
||||
) -> None:
|
||||
self.idm.assignments = [self.assignment]
|
||||
with Session(self.engine) as session:
|
||||
template = self.service.create_template(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
slug="case-intake",
|
||||
name="Case intake",
|
||||
description=None,
|
||||
function_type_id="case-clerk-type",
|
||||
scope_kind="tenant",
|
||||
scope_id=None,
|
||||
name_pattern="{unit_name} / {function_name}",
|
||||
address_pattern="{template_slug}.{unit_slug}.{function_slug}",
|
||||
classification="internal",
|
||||
allow_vacant_delivery=True,
|
||||
actor_id="admin-1",
|
||||
)
|
||||
self.service.publish_template(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
template_id=template.id,
|
||||
revision_number=None,
|
||||
actor_id="admin-1",
|
||||
expected_revision=template.resource_revision,
|
||||
)
|
||||
self.service.materialize_template(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
template_id=template.id,
|
||||
organization_unit_id="unit-1",
|
||||
function_id="function-1",
|
||||
context_key=None,
|
||||
actor_id="admin-1",
|
||||
)
|
||||
before = session.query(Postbox).count()
|
||||
|
||||
preview = self.service.preview_template_targets(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
template_id=template.id,
|
||||
slug=template.slug,
|
||||
name=template.name,
|
||||
description=None,
|
||||
function_type_id="case-clerk-type",
|
||||
scope_kind="tenant",
|
||||
scope_id=None,
|
||||
scope_structure_id=None,
|
||||
scope_relation_type_ids=(),
|
||||
name_pattern="{unit_name} / {function_name}",
|
||||
address_pattern="{template_slug}.{unit_slug}.{function_slug}",
|
||||
classification="internal",
|
||||
allow_vacant_delivery=True,
|
||||
routing_policy={},
|
||||
encryption_profile="plaintext_v1",
|
||||
encryption_vault_id=None,
|
||||
context_key=None,
|
||||
limit=200,
|
||||
)
|
||||
|
||||
self.assertEqual(3, preview["total"])
|
||||
self.assertEqual(1, preview["existing_count"])
|
||||
self.assertEqual(2, preview["vacant_count"])
|
||||
self.assertEqual(before, session.query(Postbox).count())
|
||||
|
||||
collision_preview = self.service.preview_template_targets(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
template_id=None,
|
||||
slug="collision",
|
||||
name="Collision",
|
||||
description=None,
|
||||
function_type_id="case-clerk-type",
|
||||
scope_kind="tenant",
|
||||
scope_id=None,
|
||||
scope_structure_id=None,
|
||||
scope_relation_type_ids=(),
|
||||
name_pattern="{unit_name} / {function_name}",
|
||||
address_pattern="same-address",
|
||||
classification="internal",
|
||||
allow_vacant_delivery=True,
|
||||
routing_policy={},
|
||||
encryption_profile="plaintext_v1",
|
||||
encryption_vault_id=None,
|
||||
context_key=None,
|
||||
limit=200,
|
||||
)
|
||||
self.assertEqual(3, collision_preview["blocked_count"])
|
||||
self.assertIn(
|
||||
"duplicate_generated_address",
|
||||
collision_preview["diagnostics"],
|
||||
)
|
||||
|
||||
self.organizations.duplicate_parent_match = True
|
||||
hierarchy_preview = self.service.preview_template_targets(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
template_id=None,
|
||||
slug="hierarchy",
|
||||
name="Hierarchy",
|
||||
description=None,
|
||||
function_type_id="case-clerk-type",
|
||||
scope_kind="subtree",
|
||||
scope_id="unit-child",
|
||||
scope_structure_id="structure-1",
|
||||
scope_relation_type_ids=("relation-type-1",),
|
||||
name_pattern="{unit_name} / {function_name}",
|
||||
address_pattern="{template_slug}.{unit_slug}.{function_slug}",
|
||||
classification="internal",
|
||||
allow_vacant_delivery=True,
|
||||
routing_policy={},
|
||||
encryption_profile="plaintext_v1",
|
||||
encryption_vault_id=None,
|
||||
context_key=None,
|
||||
limit=200,
|
||||
)
|
||||
self.assertIn(
|
||||
"ambiguous_scope_paths",
|
||||
hierarchy_preview["diagnostics"],
|
||||
)
|
||||
self.assertEqual(
|
||||
"descendants",
|
||||
self.organizations.last_hierarchy_request["direction"],
|
||||
)
|
||||
|
||||
def test_delivery_catalog_exposes_exact_and_derived_target_choices(
|
||||
self,
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user