172 lines
8.2 KiB
Python
172 lines
8.2 KiB
Python
from __future__ import annotations
|
|
|
|
import unittest
|
|
from types import SimpleNamespace
|
|
from unittest.mock import patch
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
from govoplan_core.core.modules import DocumentationContext
|
|
from govoplan_mail.backend.documentation import documentation_topics
|
|
from govoplan_mail.backend.mail_profiles import EffectiveMailProfilePolicy, MailProfileError
|
|
|
|
|
|
class _Principal:
|
|
tenant_id = "tenant-1"
|
|
user = SimpleNamespace(id="user-1")
|
|
|
|
def __init__(self, scopes: set[str]) -> None:
|
|
self.scopes = frozenset(scopes)
|
|
|
|
def has(self, scope: str) -> bool:
|
|
return scope in self.scopes
|
|
|
|
|
|
class MailRuntimeDocumentationTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.session = Session()
|
|
|
|
def tearDown(self) -> None:
|
|
self.session.close()
|
|
|
|
def context(self, scopes: set[str], *, documentation_type: str = "user") -> DocumentationContext:
|
|
return DocumentationContext(
|
|
registry=object(),
|
|
principal=_Principal(scopes),
|
|
settings=None,
|
|
session=self.session,
|
|
documentation_type=documentation_type, # type: ignore[arg-type]
|
|
)
|
|
|
|
def topics(self, context: DocumentationContext, user_policy: EffectiveMailProfilePolicy) -> dict[str, object]:
|
|
tenant_policy = EffectiveMailProfilePolicy()
|
|
|
|
def effective_policy(_session, *, scope_type, **_kwargs):
|
|
return user_policy if scope_type == "user" else tenant_policy
|
|
|
|
with patch(
|
|
"govoplan_mail.backend.documentation.effective_mail_profile_policy_for_scope",
|
|
side_effect=effective_policy,
|
|
):
|
|
return {topic.id: topic for topic in documentation_topics(context)}
|
|
|
|
def test_custom_profile_task_requires_write_authority_and_enabled_user_profiles(self) -> None:
|
|
enabled = EffectiveMailProfilePolicy(allow_user_profiles=True)
|
|
without_write = self.topics(self.context({"mail:profile:read"}), enabled)
|
|
self.assertNotIn("mail.workflow.create-custom-profile", without_write)
|
|
|
|
without_read = self.topics(self.context({"mail:profile:write"}), enabled)
|
|
self.assertNotIn("mail.workflow.create-custom-profile", without_read)
|
|
|
|
disabled = EffectiveMailProfilePolicy(allow_user_profiles=False)
|
|
blocked = self.topics(self.context({"mail:profile:read", "mail:profile:write"}), disabled)
|
|
self.assertNotIn("mail.workflow.create-custom-profile", blocked)
|
|
|
|
available = self.topics(self.context({"mail:profile:read", "mail:profile:write"}), enabled)
|
|
self.assertIn("mail.workflow.create-custom-profile", available)
|
|
task = available["mail.workflow.create-custom-profile"]
|
|
self.assertEqual(task.title, "Create a custom Mail profile")
|
|
self.assertEqual(task.metadata["route"], "/settings?section=mail-profiles")
|
|
self.assertEqual(task.metadata["help_contexts"], ["mail.profiles", "app.settings"])
|
|
self.assertIn("current account's user scope", task.metadata["prerequisites"][0])
|
|
|
|
def test_custom_profile_task_distinguishes_secret_test_and_use_authority(self) -> None:
|
|
policy = EffectiveMailProfilePolicy()
|
|
cases = (
|
|
(set(), False, False, False),
|
|
({"mail:secret:manage"}, True, False, False),
|
|
({"mail:profile:test"}, False, False, False),
|
|
({"mail:profile:use"}, False, False, True),
|
|
({"mail:profile:test", "mail:profile:use"}, False, True, True),
|
|
({"mail:secret:manage", "mail:profile:test", "mail:profile:use"}, True, True, True),
|
|
)
|
|
for extra_scopes, credentials, testing, use in cases:
|
|
with self.subTest(extra_scopes=extra_scopes):
|
|
topics = self.topics(self.context({"mail:profile:read", "mail:profile:write", *extra_scopes}), policy)
|
|
task = topics["mail.workflow.create-custom-profile"]
|
|
self.assertEqual(task.metadata["can_manage_credentials"], credentials)
|
|
self.assertEqual(task.metadata["can_test_profile"], testing)
|
|
self.assertEqual(task.metadata["can_use_profile"], use)
|
|
self.assertIn(
|
|
"you may save or replace" if credentials else "you cannot save or replace passwords",
|
|
task.body,
|
|
)
|
|
self.assertIn(
|
|
"you may run" if testing else "does not let you run connection tests",
|
|
task.body,
|
|
)
|
|
self.assertIn(
|
|
"you may select" if use else "does not let you select or use it",
|
|
task.body,
|
|
)
|
|
|
|
def test_custom_profile_task_preserves_host_allow_group_and_deny_precedence(self) -> None:
|
|
policy = EffectiveMailProfilePolicy(
|
|
whitelist_groups={
|
|
"smtp_hosts": [
|
|
["*.example.edu", "smtp.shared.test"],
|
|
["smtp-*.example.edu"],
|
|
],
|
|
"imap_hosts": [["imap.example.edu"]],
|
|
},
|
|
blacklist_patterns={
|
|
"smtp_hosts": ["smtp-blocked.example.edu"],
|
|
"imap_hosts": ["imap-legacy.example.edu"],
|
|
},
|
|
allowed_profile_id_sets=[{"private-profile-id"}],
|
|
)
|
|
topics = self.topics(
|
|
self.context({"mail:profile:read", "mail:profile:write", "mail:secret:manage", "mail:profile:test", "mail:profile:use"}),
|
|
policy,
|
|
)
|
|
task = topics["mail.workflow.create-custom-profile"]
|
|
constraints = {item["id"]: item for item in task.metadata["constraints"]}
|
|
constraints_text = " ".join(item["description"] for item in task.metadata["constraints"])
|
|
|
|
self.assertIn("Deny rules are checked first", constraints_text)
|
|
self.assertEqual(constraints["smtp-host-deny"]["values"], ["smtp-blocked.example.edu"])
|
|
self.assertEqual(constraints["smtp-host-allow-1"]["values"], ["*.example.edu", "smtp.shared.test"])
|
|
self.assertEqual(constraints["smtp-host-allow-2"]["values"], ["smtp-*.example.edu"])
|
|
self.assertIn("every active allow-list group", constraints_text)
|
|
self.assertEqual(constraints["imap-host-allow-1"]["values"], ["imap.example.edu"])
|
|
self.assertTrue(task.metadata["approval_required_before_use"])
|
|
self.assertIn("must be approved before the profile can be selected or used", task.body)
|
|
self.assertNotIn("private-profile-id", repr(task))
|
|
self.assertIn("every active allow-list group", constraints["smtp-host-allow-1"]["description"])
|
|
|
|
def test_custom_profile_task_explains_when_no_approval_list_is_active(self) -> None:
|
|
topics = self.topics(
|
|
self.context({"mail:profile:read", "mail:profile:write", "mail:profile:use"}),
|
|
EffectiveMailProfilePolicy(),
|
|
)
|
|
task = topics["mail.workflow.create-custom-profile"]
|
|
self.assertFalse(task.metadata["approval_required_before_use"])
|
|
self.assertIn("no approved-profile list currently blocks", task.body)
|
|
|
|
def test_user_policy_errors_are_generic_and_never_create_a_task(self) -> None:
|
|
context = self.context({"mail:profile:read", "mail:profile:write"})
|
|
with patch(
|
|
"govoplan_mail.backend.documentation.effective_mail_profile_policy_for_scope",
|
|
side_effect=MailProfileError("sensitive source-id profile-id username secret"),
|
|
):
|
|
topics = {topic.id: topic for topic in documentation_topics(context)}
|
|
|
|
self.assertNotIn("mail.workflow.create-custom-profile", topics)
|
|
unavailable = topics["mail.tenant-profile-policy-unavailable"]
|
|
self.assertEqual(
|
|
unavailable.body,
|
|
"The current Mail policy could not be loaded. Try again or ask a Mail administrator for help.",
|
|
)
|
|
self.assertNotIn("sensitive", repr(unavailable))
|
|
|
|
def test_manifest_workflows_declare_contextual_help_targets(self) -> None:
|
|
from govoplan_mail.backend.manifest import get_manifest
|
|
|
|
topics = {topic.id: topic for topic in get_manifest().documentation}
|
|
self.assertEqual(topics["mail.workflow.choose-and-test-profile"].metadata["help_contexts"], ["mail.profiles", "app.settings"])
|
|
self.assertEqual(topics["mail.workflow.read-mailbox"].metadata["help_contexts"], ["mail.list"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|