feat: integrate files and portal surfaces

This commit is contained in:
2026-08-07 14:53:46 +02:00
parent 60f50f7906
commit 11f175cbb3
13 changed files with 512 additions and 13 deletions
+2
View File
@@ -8,6 +8,7 @@ from govoplan_core.core.postbox import (
CAPABILITY_POSTBOX_DIRECTORY,
CAPABILITY_POSTBOX_EVIDENCE,
CAPABILITY_POSTBOX_MESSAGES,
CAPABILITY_POSTBOX_PORTAL,
CAPABILITY_POSTBOX_ROUTING,
)
from govoplan_core.core.encryption import CAPABILITY_ENCRYPTION_CONTENT_CIPHER
@@ -31,6 +32,7 @@ class PostboxManifestTests(unittest.TestCase):
CAPABILITY_POSTBOX_DELIVERY,
CAPABILITY_POSTBOX_EVIDENCE,
CAPABILITY_POSTBOX_ROUTING,
CAPABILITY_POSTBOX_PORTAL,
},
set(manifest.capability_factories),
)
+10
View File
@@ -34,6 +34,10 @@ class PostboxMigrationTests(unittest.TestCase):
"govoplan_postbox.backend.migrations.versions."
"e9f4a7b2c5d8_v014_template_scope_preview"
)
portal_migration = importlib.import_module(
"govoplan_postbox.backend.migrations.versions."
"f2a5c8e1b4d7_v015_portal_visibility"
)
engine = create_engine("sqlite:///:memory:")
try:
with engine.begin() as connection:
@@ -44,12 +48,14 @@ class PostboxMigrationTests(unittest.TestCase):
envelope_original = envelope_migration.op
protection_original = protection_migration.op
scope_original = scope_migration.op
portal_original = portal_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
portal_migration.op = operations
try:
migration.upgrade()
route_migration.upgrade()
@@ -57,6 +63,7 @@ class PostboxMigrationTests(unittest.TestCase):
envelope_migration.upgrade()
protection_migration.upgrade()
scope_migration.upgrade()
portal_migration.upgrade()
tables = set(inspect(connection).get_table_names())
self.assertIn("postboxes", tables)
self.assertIn("postbox_messages", tables)
@@ -93,6 +100,7 @@ class PostboxMigrationTests(unittest.TestCase):
"encryption_vault_id",
"scope_structure_id",
"scope_relation_type_ids",
"portal_visible",
}.issubset(template_revision_columns)
)
self.assertIn("authoring_key", message_columns)
@@ -121,6 +129,7 @@ class PostboxMigrationTests(unittest.TestCase):
route_columns
)
)
portal_migration.downgrade()
scope_migration.downgrade()
protection_migration.downgrade()
envelope_migration.downgrade()
@@ -141,6 +150,7 @@ class PostboxMigrationTests(unittest.TestCase):
envelope_migration.op = envelope_original
protection_migration.op = protection_original
scope_migration.op = scope_original
portal_migration.op = portal_original
finally:
engine.dispose()
+31
View File
@@ -40,6 +40,7 @@ from govoplan_postbox.backend.db.models import (
PostboxTemplateRevision,
)
from govoplan_postbox.backend.router import router
from govoplan_postbox.backend.portal_projection import PortalProjection
from govoplan_postbox.backend.service import PostboxService
@@ -220,6 +221,7 @@ class PostboxRouterTests(unittest.TestCase):
account=SimpleNamespace(id="account-1"),
user=SimpleNamespace(id="membership-1"),
)
self.principal = principal
app = FastAPI()
app.include_router(router, prefix="/api/v1")
@@ -250,6 +252,35 @@ class PostboxRouterTests(unittest.TestCase):
self.assertEqual(response.status_code, 403)
self.assertIn("not active for this principal", response.text)
def test_portal_projection_requires_explicit_visibility_and_keeps_postbox_access(self) -> None:
projection = PortalProjection()
with Session(self.engine) as session, patch(
"govoplan_postbox.backend.portal_projection.get_service",
return_value=self.service,
):
self.assertEqual(
(),
projection.list_portal_entries(
session,
self.principal,
tenant_id="tenant-1",
),
)
postbox = session.get(Postbox, self.postbox_id)
assert postbox is not None
postbox.settings = {**postbox.settings, "portal_visible": True}
session.flush()
entries = projection.list_portal_entries(
session,
self.principal,
tenant_id="tenant-1",
)
self.assertEqual(1, len(entries))
self.assertEqual(self.postbox_id, entries[0].postbox.id)
self.assertEqual(f"/postbox?postbox={self.postbox_id}", entries[0].route_path)
def test_template_impact_preview_is_available_without_writes(self) -> None:
with Session(self.engine) as session:
before = session.query(Postbox).count()