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()