Files
group_fqcd_jr/tests/unit/service/test_offsite_smtp_adapter.py
T

175 lines
5.7 KiB
Python

from __future__ import annotations
import smtplib
from email.message import EmailMessage
from app.core.config import Settings
from app.service.offsite_smtp_adapter import (
OffsiteMailReplyRequest,
OffsiteSmtpSender,
SmtpAttachment,
)
class FakeSmtpConnection:
def __init__(self, *, fail_send: bool = False) -> None:
self.fail_send = fail_send
self.login_calls: list[tuple[str, str]] = []
self.sent_messages: list[EmailMessage] = []
self.quit_count = 0
def login(self, user: str, password: str) -> object:
self.login_calls.append((user, password))
return {}
def send_message(self, msg: EmailMessage) -> object:
if self.fail_send:
raise smtplib.SMTPException("send failed")
self.sent_messages.append(msg)
return {}
def quit(self) -> object:
self.quit_count += 1
return {}
def test_health_check_reports_disabled_and_dry_run_modes() -> None:
disabled = OffsiteSmtpSender(_settings()).health_check()
assert disabled == {"status": "disabled", "message": "场外 SMTP 未启用"}
dry_run = OffsiteSmtpSender(
_settings(offsite_smtp_enabled=True, offsite_smtp_dry_run=True)
).health_check()
assert dry_run == {"status": "dry_run", "message": "场外 SMTP 处于 dry-run 模式"}
def test_send_reply_requires_operator_confirmation_before_any_send() -> None:
connection = FakeSmtpConnection()
sender = OffsiteSmtpSender(
_settings(offsite_smtp_enabled=True, offsite_smtp_dry_run=False),
connection,
)
result = sender.send_reply(_request(operator_confirmed=False))
assert result.status == "发送失败"
assert result.failure_reason == "邮件发送前必须完成运营确认"
assert connection.sent_messages == []
def test_send_reply_defaults_to_dry_run_without_real_smtp_call() -> None:
connection = FakeSmtpConnection()
sender = OffsiteSmtpSender(_settings(), connection)
result = sender.send_reply(_request())
assert result.status == "待发送"
assert result.dry_run is True
assert result.provider_message_id is None
assert result.failure_reason is None
assert connection.sent_messages == []
assert result.request_summary["attachment_count"] == 1
def test_enabled_real_send_builds_reply_message_and_attaches_original_file() -> None:
connection = FakeSmtpConnection()
sender = OffsiteSmtpSender(
_settings(offsite_smtp_enabled=True, offsite_smtp_dry_run=False),
connection,
)
result = sender.send_reply(_request(reply_to_message_id="<source@example.local>"))
assert result.status == "发送成功"
assert result.dry_run is False
assert result.retry_count == 0
assert connection.login_calls == []
assert len(connection.sent_messages) == 1
message = connection.sent_messages[0]
assert message["From"] == "15273589815@163.com"
assert message["To"] == "yuan80818843@163.com"
assert message["In-Reply-To"] == "<source@example.local>"
assert message["References"] == "<source@example.local>"
assert message["Message-ID"] == result.provider_message_id
attachments = list(message.iter_attachments())
assert len(attachments) == 1
assert attachments[0].get_filename() == "申购申请单.pdf"
assert attachments[0].get_payload(decode=True) == b"pdf-bytes"
def test_enabled_real_send_reports_failure_and_increments_retry_count() -> None:
connection = FakeSmtpConnection(fail_send=True)
sender = OffsiteSmtpSender(
_settings(offsite_smtp_enabled=True, offsite_smtp_dry_run=False),
connection,
)
result = sender.send_reply(_request(retry_count=2))
assert result.status == "发送失败"
assert result.failure_reason == "SMTPException"
assert result.retry_count == 3
assert connection.quit_count == 1
def test_enabled_real_send_fails_closed_when_config_is_missing() -> None:
sender = OffsiteSmtpSender(
_settings(
offsite_smtp_enabled=True,
offsite_smtp_dry_run=False,
offsite_smtp_host="",
offsite_smtp_username="",
offsite_smtp_password="",
offsite_smtp_sender="",
)
)
result = sender.send_reply(_request(retry_count=1))
assert result.status == "发送失败"
assert result.retry_count == 2
assert "OFFSITE_SMTP_HOST" in str(result.failure_reason)
def _request(
*,
operator_confirmed: bool = True,
retry_count: int = 0,
reply_to_message_id: str | None = None,
) -> OffsiteMailReplyRequest:
return OffsiteMailReplyRequest(
to_address="yuan80818843@163.com",
subject="场外基金申购赎回处理结果",
body="已处理。",
operator_id="operator-001",
operator_confirmed=operator_confirmed,
reply_to_message_id=reply_to_message_id,
retry_count=retry_count,
attachments=(
SmtpAttachment(
filename="申购申请单.pdf",
media_type="application/pdf",
payload=b"pdf-bytes",
),
),
)
def _settings(**updates: object) -> Settings:
values: dict[str, object] = {
"jwt_issuer": "jr-local",
"jwt_audience": "jr-agent-platform",
"mysql_dsn": "sqlite+aiosqlite:///test.db",
"redis_url": "redis://127.0.0.1:6379/0",
"milvus_uri": "http://127.0.0.1:19530",
"neo4j_uri": "bolt://127.0.0.1:7687",
"offsite_smtp_host": "smtp.example.local",
"offsite_smtp_username": "15273589815@163.com",
"offsite_smtp_password": "test-auth-code",
"offsite_smtp_sender": "15273589815@163.com",
"offsite_smtp_enabled": False,
"offsite_smtp_dry_run": True,
}
values.update(updates)
return Settings(**values)