from __future__ import annotations from dataclasses import dataclass from types import SimpleNamespace import pytest from govoplan_campaign.backend.documentation import documentation_topics from govoplan_campaign.backend.manifest import get_manifest from govoplan_core.core.modules import DocumentationContext @dataclass(frozen=True) class _Principal: scopes: frozenset[str] def has(self, scope: str) -> bool: namespace, resource, _action = scope.split(":", 2) return scope in self.scopes or f"{namespace}:{resource}:*" in self.scopes or f"{namespace}:*" in self.scopes or "*:*" in self.scopes class _Registry: def __init__(self, integrations: set[str], *, interface_only: set[str] | None = None, capability_only: set[str] | None = None) -> None: interfaces = integrations | (interface_only or set()) self._capabilities = integrations | (capability_only or set()) self._manifests = ( SimpleNamespace( provides_interfaces=tuple(SimpleNamespace(name=name) for name in sorted(interfaces)), ), ) def manifests(self): return self._manifests def has_capability(self, name: str) -> bool: return name in self._capabilities def _topics(scopes: set[str], integrations: set[str] | None = None, *, documentation_type: str = "user"): return documentation_topics( DocumentationContext( registry=_Registry(integrations or set()), principal=_Principal(frozenset(scopes)), documentation_type=documentation_type, # type: ignore[arg-type] ) ) def test_campaign_runtime_documentation_provider_is_registered() -> None: assert documentation_topics in get_manifest().documentation_providers def test_runtime_documentation_is_user_only_and_requires_a_campaign_task() -> None: assert _topics({"docs:documentation:read"}) == () assert _topics({"campaigns:campaign:read"}, documentation_type="admin") == () def test_runtime_documentation_reflects_actor_authority_without_exposing_scopes() -> None: topic = _topics( { "campaigns:campaign:read", "campaigns:campaign:create", "campaigns:campaign:update", "campaigns:campaign:validate", "campaigns:campaign:build", "campaigns:recipient:read", "campaigns:recipient:write", "campaigns:recipient:import", } )[0] configuration = topic.metadata["current_configuration"] assert "Create new campaigns." in configuration assert "Build exact recipient messages for review." in configuration assert not any("queue" in item.lower() for item in configuration) assert not any("campaigns:" in item or "files:" in item or "mail:" in item for item in configuration) def test_runtime_documentation_requires_both_interface_and_capability() -> None: integration = "mail.campaign_delivery" scopes = {"campaigns:campaign:read", "mail:profile:use"} for registry in ( _Registry(set(), interface_only={integration}), _Registry(set(), capability_only={integration}), ): topic = documentation_topics( DocumentationContext(registry=registry, principal=_Principal(frozenset(scopes)), documentation_type="user") )[0] assert not any("selection is available" in item for item in topic.metadata["current_configuration"]) topic = _topics(scopes, {integration})[0] assert any("Mail profile selection is available" in item for item in topic.metadata["current_configuration"]) @pytest.mark.parametrize( ("integrations", "scopes", "expected"), [ ( {"files.campaign_attachments"}, {"campaigns:campaign:read", "campaigns:recipient:read", "files:file:read"}, "Managed campaign attachments can be previewed", ), ( {"addresses.lookup"}, {"campaigns:campaign:read", "campaigns:recipient:read"}, "Address records can be looked up", ), ( {"addresses.recipient_source"}, {"campaigns:campaign:read", "campaigns:recipient:import"}, "traceable recipient snapshot", ), ( {"notifications.dispatch"}, {"campaigns:campaign:read"}, "status changes", ), ], ) def test_runtime_documentation_reflects_optional_composition_permutations( integrations: set[str], scopes: set[str], expected: str, ) -> None: topic = _topics(scopes, integrations)[0] assert any(expected in item for item in topic.metadata["current_configuration"]) def test_runtime_documentation_full_composition_uses_only_user_facing_names() -> None: integrations = { "mail.campaign_delivery", "files.campaign_attachments", "addresses.lookup", "addresses.recipient_source", "notifications.dispatch", } topic = _topics({"*:*"}, integrations)[0] rendered = "\n".join( ( topic.title, topic.summary, topic.body, *topic.metadata["current_configuration"], *topic.metadata["limitations"], ) ) assert "Mail profile selection" in rendered assert "Managed file versions" in rendered assert "Address records" in rendered assert "In-app notifications" in rendered for technical_name in integrations: assert technical_name not in rendered assert "0.1." not in rendered assert "0.2." not in rendered assert "hostname" not in rendered.lower() assert "secret" not in rendered.lower()