feat(campaigns): add governed collaboration thread
Module Package Release / publish-packages (push) Successful in 13s

This commit is contained in:
2026-08-22 00:09:34 +02:00
parent 5a21067e44
commit c2f083e5f6
25 changed files with 2315 additions and 24 deletions
+57
View File
@@ -21,6 +21,7 @@ from govoplan_campaign.backend.db.models import (
AttachmentBlob,
AttachmentInstance,
Campaign,
CampaignCollaborationEntry,
CampaignIssue,
CampaignJob,
CampaignMessageAction,
@@ -46,6 +47,61 @@ GROUP_ID = "group-1"
class CampaignAccessProviderTests(unittest.TestCase):
def test_collaboration_access_is_explained_independently_from_campaign_edit(self) -> None:
session = _session()
self.addCleanup(_close_session, session)
_seed_access_subjects(session)
campaign = Campaign(
id="campaign-collaboration",
tenant_id=TENANT_ID,
owner_user_id=OTHER_USER_ID,
external_id="collaboration",
name="Collaboration campaign",
)
entry = CampaignCollaborationEntry(
id="discussion-1",
tenant_id=TENANT_ID,
campaign_id=campaign.id,
actor_user_id=OTHER_USER_ID,
actor_label_snapshot="Other user",
visibility="collaborators",
content="Not disclosed by provenance",
content_sha256="a" * 64,
)
session.add_all(
[
campaign,
entry,
CampaignShare(
id="share-collaboration",
tenant_id=TENANT_ID,
campaign_id=campaign.id,
target_type="group",
target_id=GROUP_ID,
permission="read",
),
]
)
session.commit()
items = CampaignAccessService().explain_resource_provenance(
session,
_principal(
scopes={"campaigns:campaign:read", "campaigns:discussion:post"},
group_ids={GROUP_ID},
),
resource_type="campaign_collaboration_entry",
resource_id=entry.id,
action="campaigns:discussion:post",
)
discussion = next(item for item in items if item.source == "campaigns.collaboration_entry")
self.assertEqual(["campaigns:discussion:post"], discussion.details["permission_actions"])
self.assertFalse(discussion.details["content_disclosed"])
self.assertTrue(any(item.id == "share-collaboration" for item in items))
self.assertNotIn("campaigns:campaign:update", repr(discussion.details))
self.assertNotIn("Not disclosed", repr(items))
def test_campaign_access_provider_explains_owner_share_admin_and_missing_resources(self) -> None:
session = _session()
self.addCleanup(_close_session, session)
@@ -918,6 +974,7 @@ def _session():
Group.__table__,
Campaign.__table__,
CampaignShare.__table__,
CampaignCollaborationEntry.__table__,
CampaignVersion.__table__,
CampaignJob.__table__,
CampaignIssue.__table__,