213 lines
7.4 KiB
Python
213 lines
7.4 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime, timedelta
|
|
import unittest
|
|
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
from govoplan_access.backend.db import models as access_models # noqa: F401
|
|
from govoplan_core.core.provider_governance import ExternalProviderStateContext
|
|
from govoplan_core.db.base import Base
|
|
from govoplan_mail.backend.db.models import (
|
|
MailBounceSource,
|
|
MailDeliveryCommand,
|
|
MailMailboxFolderIndex,
|
|
MailMailboxMessageIndex,
|
|
MailPop3Import,
|
|
MailServerEndpoint,
|
|
MailServerProfile,
|
|
)
|
|
from govoplan_mail.backend.manifest import manifest
|
|
from govoplan_mail.backend.provider_state import (
|
|
IMAP_PROVIDER_ID,
|
|
POP3_PROVIDER_ID,
|
|
SMTP_PROVIDER_ID,
|
|
imap_provider_states,
|
|
pop3_provider_states,
|
|
smtp_provider_states,
|
|
)
|
|
|
|
|
|
class MailProviderStateTests(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
self.engine = create_engine("sqlite+pysqlite:///:memory:", future=True)
|
|
Base.metadata.create_all(
|
|
self.engine,
|
|
tables=(
|
|
MailServerProfile.__table__,
|
|
MailServerEndpoint.__table__,
|
|
MailDeliveryCommand.__table__,
|
|
MailMailboxFolderIndex.__table__,
|
|
MailMailboxMessageIndex.__table__,
|
|
MailBounceSource.__table__,
|
|
MailPop3Import.__table__,
|
|
),
|
|
)
|
|
self.session = sessionmaker(bind=self.engine, expire_on_commit=False)()
|
|
self.profile = MailServerProfile(
|
|
id="profile-1",
|
|
tenant_id="tenant-1",
|
|
scope_type="tenant",
|
|
scope_id="tenant-1",
|
|
name="Mail",
|
|
slug="mail",
|
|
is_active=True,
|
|
smtp_config={"host": "smtp.example.test", "port": 587},
|
|
imap_config={"host": "imap.example.test", "port": 993},
|
|
)
|
|
self.session.add(
|
|
self.profile
|
|
)
|
|
self.session.add(
|
|
MailMailboxFolderIndex(
|
|
id="folder-1",
|
|
tenant_id="tenant-1",
|
|
profile_id=self.profile.id,
|
|
folder="INBOX",
|
|
indexed_at=datetime.now(UTC),
|
|
)
|
|
)
|
|
self.session.commit()
|
|
|
|
def tearDown(self) -> None:
|
|
self.session.close()
|
|
self.engine.dispose()
|
|
|
|
def test_smtp_state_exposes_reconciliation_without_server_or_secrets(self) -> None:
|
|
self.session.add(
|
|
MailDeliveryCommand(
|
|
id="command-1",
|
|
tenant_id="tenant-1",
|
|
command_type="message",
|
|
source_module="notifications",
|
|
source_resource_type="notification",
|
|
idempotency_key="notification-1",
|
|
canonical_request_hash="a" * 64,
|
|
profile_id=self.profile.id,
|
|
expected_smtp_transport_revision="revision-1",
|
|
message_sha256="b" * 64,
|
|
message_size_bytes=10,
|
|
recipient_count=1,
|
|
status="outcome_unknown",
|
|
expires_at=datetime.now(UTC) + timedelta(days=1),
|
|
)
|
|
)
|
|
self.session.flush()
|
|
|
|
state = smtp_provider_states(
|
|
ExternalProviderStateContext(session=self.session, tenant_id="tenant-1")
|
|
)[0]
|
|
|
|
self.assertEqual("warning", state.health)
|
|
self.assertEqual("pending", state.conflict)
|
|
self.assertEqual("attention", state.recovery)
|
|
self.assertEqual(1, state.metrics["outcome_unknown_commands"])
|
|
self.assertNotIn("smtp.example.test", str(state.to_dict()))
|
|
|
|
def test_imap_state_is_fresh_tenant_bounded_and_registered(self) -> None:
|
|
state = imap_provider_states(
|
|
ExternalProviderStateContext(session=self.session, tenant_id="tenant-1")
|
|
)[0]
|
|
|
|
self.assertEqual("healthy", state.health)
|
|
self.assertEqual("current", state.freshness)
|
|
self.assertEqual("external_mirror", state.authority_mode)
|
|
self.assertNotIn("imap.example.test", str(state.to_dict()))
|
|
self.assertEqual(
|
|
(),
|
|
imap_provider_states(
|
|
ExternalProviderStateContext(
|
|
session=self.session,
|
|
tenant_id="tenant-2",
|
|
)
|
|
),
|
|
)
|
|
self.assertEqual(
|
|
{SMTP_PROVIDER_ID, IMAP_PROVIDER_ID, POP3_PROVIDER_ID},
|
|
{item.id for item in manifest.external_providers},
|
|
)
|
|
self.assertEqual(
|
|
{SMTP_PROVIDER_ID, IMAP_PROVIDER_ID, POP3_PROVIDER_ID},
|
|
{
|
|
item.provider_id
|
|
for item in manifest.external_provider_state_providers
|
|
},
|
|
)
|
|
|
|
def test_pop3_state_is_disabled_by_default_and_projects_deletion_evidence(self) -> None:
|
|
endpoint = MailServerEndpoint(
|
|
id="pop3-server-1",
|
|
profile_id=self.profile.id,
|
|
tenant_id="tenant-1",
|
|
protocol="pop3",
|
|
name="Legacy POP3",
|
|
config={"host": "pop3.example.test", "legacy_import_enabled": False},
|
|
scope_type="tenant",
|
|
scope_id="tenant-1",
|
|
is_active=True,
|
|
)
|
|
self.session.add(endpoint)
|
|
self.session.flush()
|
|
|
|
disabled = pop3_provider_states(
|
|
ExternalProviderStateContext(session=self.session, tenant_id="tenant-1")
|
|
)[0]
|
|
self.assertFalse(disabled.active)
|
|
self.assertEqual("inactive", disabled.health)
|
|
|
|
endpoint.config = {
|
|
"host": "pop3.example.test",
|
|
"legacy_import_enabled": True,
|
|
}
|
|
self.session.add(
|
|
MailPop3Import(
|
|
id="pop3-import-1",
|
|
tenant_id="tenant-1",
|
|
profile_id=self.profile.id,
|
|
pop3_server_id=endpoint.id,
|
|
transport_revision=endpoint.transport_revision,
|
|
provider_uidl="uid-1",
|
|
fingerprint="a" * 64,
|
|
raw_sha256="b" * 64,
|
|
raw_message_encrypted="ciphertext-do-not-project",
|
|
size_bytes=42,
|
|
imported_at=datetime.now(UTC),
|
|
deletion_requested=True,
|
|
deletion_status="outcome_unknown",
|
|
)
|
|
)
|
|
self.session.add(
|
|
MailPop3Import(
|
|
id="pop3-import-tenant-2",
|
|
tenant_id="tenant-2",
|
|
profile_id=self.profile.id,
|
|
pop3_server_id=endpoint.id,
|
|
transport_revision=endpoint.transport_revision,
|
|
provider_uidl="uid-tenant-2",
|
|
fingerprint="c" * 64,
|
|
raw_sha256="d" * 64,
|
|
raw_message_encrypted="other-tenant-ciphertext",
|
|
size_bytes=42,
|
|
imported_at=datetime.now(UTC),
|
|
deletion_requested=True,
|
|
deletion_status="failed",
|
|
)
|
|
)
|
|
self.session.flush()
|
|
|
|
state = pop3_provider_states(
|
|
ExternalProviderStateContext(session=self.session, tenant_id="tenant-1")
|
|
)[0]
|
|
self.assertTrue(state.active)
|
|
self.assertEqual("warning", state.health)
|
|
self.assertEqual("pending", state.conflict)
|
|
self.assertEqual(1, state.metrics["outcome_unknown_deletions"])
|
|
self.assertEqual(0, state.metrics["failed_deletions"])
|
|
self.assertNotIn("pop3.example.test", str(state.to_dict()))
|
|
self.assertNotIn("ciphertext-do-not-project", str(state.to_dict()))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|