159 lines
5.5 KiB
Python
159 lines
5.5 KiB
Python
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()
|