feat: route email notifications through mail capability
This commit is contained in:
+106
-1
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
from fastapi import HTTPException
|
||||
@@ -11,6 +12,7 @@ from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from govoplan_core.auth import ApiPrincipal
|
||||
from govoplan_core.core.access import PrincipalRef
|
||||
from govoplan_core.core.mail import CAPABILITY_MAIL_NOTIFICATION_DELIVERY
|
||||
from govoplan_core.core.modules import ModuleContext
|
||||
from govoplan_core.core.notifications import NotificationDispatchRequest
|
||||
from govoplan_core.db.base import Base
|
||||
@@ -28,6 +30,32 @@ from govoplan_notifications.backend.service import (
|
||||
)
|
||||
|
||||
|
||||
class _NotificationMailProvider:
|
||||
def __init__(self) -> None:
|
||||
self.request = None
|
||||
|
||||
def submit_notification_mail(self, session, request):
|
||||
self.request = request
|
||||
return {
|
||||
"status": "accepted",
|
||||
"provider": "mail.delivery_outbox",
|
||||
"external_message_id": "mail-command-1",
|
||||
}
|
||||
|
||||
|
||||
class _CapabilityRegistry:
|
||||
def __init__(self, provider: object) -> None:
|
||||
self.provider = provider
|
||||
|
||||
def has_capability(self, name: str) -> bool:
|
||||
return name == CAPABILITY_MAIL_NOTIFICATION_DELIVERY
|
||||
|
||||
def require_capability(self, name: str) -> object:
|
||||
if not self.has_capability(name):
|
||||
raise LookupError(name)
|
||||
return self.provider
|
||||
|
||||
|
||||
class NotificationServiceTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.engine = create_engine("sqlite:///:memory:")
|
||||
@@ -290,7 +318,11 @@ class NotificationServiceTests(unittest.TestCase):
|
||||
|
||||
def test_mail_delivery_uses_local_file_transport(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmpdir, self.Session() as session:
|
||||
settings = type("Settings", (), {"mock_mailbox_dir": tmpdir})()
|
||||
settings = type(
|
||||
"Settings",
|
||||
(),
|
||||
{"app_env": "dev", "mock_mailbox_dir": tmpdir},
|
||||
)()
|
||||
notification = create_notification(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
@@ -310,6 +342,79 @@ class NotificationServiceTests(unittest.TestCase):
|
||||
self.assertEqual(notification.status, "sent")
|
||||
self.assertTrue(notification.external_message_id.endswith(".eml"))
|
||||
|
||||
def test_production_mail_delivery_pauses_without_mail_capability(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmpdir, self.Session() as session:
|
||||
notification = create_notification(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
payload=NotificationCreateRequest(
|
||||
source_module="test",
|
||||
source_resource_type="thing",
|
||||
event_kind="created",
|
||||
channel="mail",
|
||||
recipient="person@example.test",
|
||||
subject="Created",
|
||||
),
|
||||
)
|
||||
|
||||
result = deliver_pending(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
settings=SimpleNamespace(
|
||||
app_env="production",
|
||||
mock_mailbox_dir=tmpdir,
|
||||
),
|
||||
registry=object(),
|
||||
)
|
||||
|
||||
self.assertEqual(result["paused"], 1)
|
||||
self.assertEqual(notification.status, "paused")
|
||||
self.assertEqual(
|
||||
notification.last_error,
|
||||
"Mail-backed notification delivery is unavailable.",
|
||||
)
|
||||
self.assertEqual(list(Path(tmpdir).rglob("*.eml")), [])
|
||||
|
||||
def test_mail_delivery_uses_optional_mail_capability(self) -> None:
|
||||
provider = _NotificationMailProvider()
|
||||
registry = _CapabilityRegistry(provider)
|
||||
with self.Session() as session:
|
||||
notification = create_notification(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
payload=NotificationCreateRequest(
|
||||
source_module="test",
|
||||
source_resource_type="thing",
|
||||
event_kind="created",
|
||||
channel="mail",
|
||||
recipient="person@example.test",
|
||||
subject="Created",
|
||||
body_text="Hello",
|
||||
metadata={
|
||||
"mail_delivery": {
|
||||
"mail_profile_id": "profile-1",
|
||||
"from_address": "notifications@example.test",
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
result = deliver_pending(
|
||||
session,
|
||||
tenant_id="tenant-1",
|
||||
settings=SimpleNamespace(app_env="production"),
|
||||
registry=registry,
|
||||
)
|
||||
|
||||
self.assertEqual(result["accepted"], 1)
|
||||
self.assertEqual(notification.status, "accepted")
|
||||
self.assertEqual(notification.external_message_id, "mail-command-1")
|
||||
self.assertEqual(provider.request.mail_profile_id, "profile-1")
|
||||
self.assertEqual(
|
||||
provider.request.from_address,
|
||||
"notifications@example.test",
|
||||
)
|
||||
|
||||
def test_dispatch_capability_enqueues_notification(self) -> None:
|
||||
with self.Session() as session:
|
||||
capability = dispatch_capability(ModuleContext(registry=object(), settings=object()))
|
||||
|
||||
Reference in New Issue
Block a user