feat(connectors): govern RSS and Atom publication
Module Package Release / publish-packages (push) Successful in 11s
Module Package Release / publish-packages (push) Successful in 11s
This commit is contained in:
+78
-1
@@ -4,8 +4,18 @@ import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from defusedxml import ElementTree as SafeET
|
||||
from fastapi import HTTPException
|
||||
|
||||
from govoplan_connectors.backend.feeds import ConnectorFeedProvider, feed_rows
|
||||
from govoplan_connectors.backend.feeds import (
|
||||
FEED_PRIVATE_PUBLISH_SCOPE,
|
||||
FEED_PUBLISH_SCOPE,
|
||||
ConnectorFeedProvider,
|
||||
feed_rows,
|
||||
)
|
||||
from govoplan_connectors.backend.router import api_render_feed
|
||||
from govoplan_connectors.backend.schemas import FeedRenderPayload
|
||||
from govoplan_core.auth import ApiPrincipal
|
||||
from govoplan_core.core.access import PrincipalRef
|
||||
from govoplan_core.core.feeds import (
|
||||
FeedCapabilityError,
|
||||
FeedEntry,
|
||||
@@ -98,6 +108,73 @@ class ConnectorFeedProviderTests(unittest.TestCase):
|
||||
with self.assertRaisesRegex(FeedCapabilityError, "not safe or valid"):
|
||||
self.provider.parse(payload, source_url="https://example.test/rss")
|
||||
|
||||
def test_render_api_derives_visibility_from_audience_and_permissions(self) -> None:
|
||||
entries = [
|
||||
{
|
||||
"id": visibility,
|
||||
"title": visibility.title(),
|
||||
"visibility": visibility,
|
||||
"source_kind": source_kind,
|
||||
"source_module": source_module,
|
||||
"source_ref": f"{source_kind}:{visibility}",
|
||||
"source_revision": "7",
|
||||
}
|
||||
for visibility, source_kind, source_module in (
|
||||
("public", "publication", "docs"),
|
||||
("tenant", "case", "cases"),
|
||||
("private", "report", "reporting"),
|
||||
)
|
||||
]
|
||||
public_payload = FeedRenderPayload(
|
||||
format="rss",
|
||||
title="Selected GovOPlaN updates",
|
||||
feed_url="https://example.test/feed.xml",
|
||||
home_url="https://example.test/",
|
||||
audience="public",
|
||||
entries=entries,
|
||||
)
|
||||
public = api_render_feed(
|
||||
public_payload,
|
||||
principal=_principal(FEED_PUBLISH_SCOPE),
|
||||
)
|
||||
|
||||
self.assertEqual("public", public.headers["x-govoplan-feed-audience"])
|
||||
self.assertEqual("1", public.headers["x-govoplan-feed-included"])
|
||||
self.assertEqual("2", public.headers["x-govoplan-feed-excluded"])
|
||||
self.assertIn(b"Public", public.body)
|
||||
self.assertNotIn(b"Tenant", public.body)
|
||||
|
||||
restricted_payload = public_payload.model_copy(update={"audience": "private"})
|
||||
with self.assertRaises(HTTPException) as denied:
|
||||
api_render_feed(
|
||||
restricted_payload,
|
||||
principal=_principal(FEED_PUBLISH_SCOPE),
|
||||
)
|
||||
self.assertEqual(403, denied.exception.status_code)
|
||||
|
||||
restricted = api_render_feed(
|
||||
restricted_payload,
|
||||
principal=_principal(
|
||||
FEED_PUBLISH_SCOPE,
|
||||
FEED_PRIVATE_PUBLISH_SCOPE,
|
||||
),
|
||||
)
|
||||
self.assertEqual("3", restricted.headers["x-govoplan-feed-included"])
|
||||
self.assertIn(b"Private", restricted.body)
|
||||
|
||||
|
||||
def _principal(*scopes: str) -> ApiPrincipal:
|
||||
return ApiPrincipal(
|
||||
principal=PrincipalRef(
|
||||
account_id="account-1",
|
||||
membership_id="membership-1",
|
||||
tenant_id="tenant-1",
|
||||
scopes=frozenset(scopes),
|
||||
),
|
||||
account=object(),
|
||||
user=object(),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user