增加duckmail支持

This commit is contained in:
rockxsj
2026-03-19 16:41:30 +08:00
parent 93ab984200
commit 91120a2fb4
15 changed files with 1294 additions and 47 deletions

View File

@@ -0,0 +1,143 @@
from src.services.duck_mail import DuckMailService
class FakeResponse:
def __init__(self, status_code=200, payload=None, text=""):
self.status_code = status_code
self._payload = payload
self.text = text
self.headers = {}
def json(self):
if self._payload is None:
raise ValueError("no json payload")
return self._payload
class FakeHTTPClient:
def __init__(self, responses):
self.responses = list(responses)
self.calls = []
def request(self, method, url, **kwargs):
self.calls.append({
"method": method,
"url": url,
"kwargs": kwargs,
})
if not self.responses:
raise AssertionError(f"未准备响应: {method} {url}")
return self.responses.pop(0)
def test_create_email_creates_account_and_fetches_token():
service = DuckMailService({
"base_url": "https://api.duckmail.test",
"default_domain": "duckmail.sbs",
"api_key": "dk_test_key",
"password_length": 10,
})
fake_client = FakeHTTPClient([
FakeResponse(
status_code=201,
payload={
"id": "account-1",
"address": "tester@duckmail.sbs",
"authType": "email",
},
),
FakeResponse(
payload={
"id": "account-1",
"token": "token-123",
}
),
])
service.http_client = fake_client
email_info = service.create_email()
assert email_info["email"] == "tester@duckmail.sbs"
assert email_info["service_id"] == "account-1"
assert email_info["account_id"] == "account-1"
assert email_info["token"] == "token-123"
create_call = fake_client.calls[0]
assert create_call["method"] == "POST"
assert create_call["url"] == "https://api.duckmail.test/accounts"
assert create_call["kwargs"]["json"]["address"].endswith("@duckmail.sbs")
assert len(create_call["kwargs"]["json"]["password"]) == 10
assert create_call["kwargs"]["headers"]["Authorization"] == "Bearer dk_test_key"
token_call = fake_client.calls[1]
assert token_call["method"] == "POST"
assert token_call["url"] == "https://api.duckmail.test/token"
assert token_call["kwargs"]["json"] == {
"address": "tester@duckmail.sbs",
"password": email_info["password"],
}
def test_get_verification_code_reads_message_detail_and_extracts_code():
service = DuckMailService({
"base_url": "https://api.duckmail.test",
"default_domain": "duckmail.sbs",
})
fake_client = FakeHTTPClient([
FakeResponse(
status_code=201,
payload={
"id": "account-1",
"address": "tester@duckmail.sbs",
"authType": "email",
},
),
FakeResponse(
payload={
"id": "account-1",
"token": "token-123",
}
),
FakeResponse(
payload={
"hydra:member": [
{
"id": "msg-1",
"from": {
"name": "OpenAI",
"address": "noreply@openai.com",
},
"subject": "Your verification code",
"createdAt": "2026-03-19T10:00:00Z",
}
]
}
),
FakeResponse(
payload={
"id": "msg-1",
"text": "Your OpenAI verification code is 654321",
"html": [],
}
),
])
service.http_client = fake_client
email_info = service.create_email()
code = service.get_verification_code(
email=email_info["email"],
email_id=email_info["service_id"],
timeout=1,
)
assert code == "654321"
messages_call = fake_client.calls[2]
assert messages_call["method"] == "GET"
assert messages_call["url"] == "https://api.duckmail.test/messages"
assert messages_call["kwargs"]["headers"]["Authorization"] == "Bearer token-123"
detail_call = fake_client.calls[3]
assert detail_call["method"] == "GET"
assert detail_call["url"] == "https://api.duckmail.test/messages/msg-1"
assert detail_call["kwargs"]["headers"]["Authorization"] == "Bearer token-123"

View File

@@ -0,0 +1,94 @@
import asyncio
from contextlib import contextmanager
from pathlib import Path
from src.config.constants import EmailServiceType
from src.database.models import Base, EmailService
from src.database.session import DatabaseSessionManager
from src.services.base import EmailServiceFactory
from src.web.routes import email as email_routes
from src.web.routes import registration as registration_routes
class DummySettings:
custom_domain_base_url = ""
custom_domain_api_key = None
def test_duck_mail_service_registered():
service_type = EmailServiceType("duck_mail")
service_class = EmailServiceFactory.get_service_class(service_type)
assert service_class is not None
assert service_class.__name__ == "DuckMailService"
def test_email_service_types_include_duck_mail():
result = asyncio.run(email_routes.get_service_types())
duckmail_type = next(item for item in result["types"] if item["value"] == "duck_mail")
assert duckmail_type["label"] == "DuckMail"
field_names = [field["name"] for field in duckmail_type["config_fields"]]
assert "base_url" in field_names
assert "default_domain" in field_names
assert "api_key" in field_names
def test_filter_sensitive_config_marks_duckmail_api_key():
filtered = email_routes.filter_sensitive_config({
"base_url": "https://api.duckmail.test",
"api_key": "dk_test_key",
"default_domain": "duckmail.sbs",
})
assert filtered["base_url"] == "https://api.duckmail.test"
assert filtered["default_domain"] == "duckmail.sbs"
assert filtered["has_api_key"] is True
assert "api_key" not in filtered
def test_registration_available_services_include_duck_mail(monkeypatch):
runtime_dir = Path("tests_runtime")
runtime_dir.mkdir(exist_ok=True)
db_path = runtime_dir / "duckmail_routes.db"
if db_path.exists():
db_path.unlink()
manager = DatabaseSessionManager(f"sqlite:///{db_path}")
Base.metadata.create_all(bind=manager.engine)
with manager.session_scope() as session:
session.add(
EmailService(
service_type="duck_mail",
name="DuckMail 主服务",
config={
"base_url": "https://api.duckmail.test",
"default_domain": "duckmail.sbs",
"api_key": "dk_test_key",
},
enabled=True,
priority=0,
)
)
@contextmanager
def fake_get_db():
session = manager.SessionLocal()
try:
yield session
finally:
session.close()
monkeypatch.setattr(registration_routes, "get_db", fake_get_db)
import src.config.settings as settings_module
monkeypatch.setattr(settings_module, "get_settings", lambda: DummySettings())
result = asyncio.run(registration_routes.get_available_email_services())
assert result["duck_mail"]["available"] is True
assert result["duck_mail"]["count"] == 1
assert result["duck_mail"]["services"][0]["name"] == "DuckMail 主服务"
assert result["duck_mail"]["services"][0]["type"] == "duck_mail"
assert result["duck_mail"]["services"][0]["default_domain"] == "duckmail.sbs"