mirror of
https://github.com/cnlimiter/codex-register.git
synced 2026-09-09 01:16:36 +08:00
fix: 扩展邮箱验证码去重到现有账号链路
This commit is contained in:
@@ -1681,6 +1681,10 @@ class RegistrationEngine:
|
|||||||
try:
|
try:
|
||||||
# 获取默认 client_id
|
# 获取默认 client_id
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
|
metadata = dict(result.metadata or {})
|
||||||
|
verification_state = self.email_service.export_verification_state(result.email or self.email)
|
||||||
|
if verification_state["used_codes"] or verification_state["seen_messages"]:
|
||||||
|
metadata["verification_state"] = verification_state
|
||||||
|
|
||||||
with get_db() as db:
|
with get_db() as db:
|
||||||
# 保存账户信息
|
# 保存账户信息
|
||||||
@@ -1699,7 +1703,7 @@ class RegistrationEngine:
|
|||||||
id_token=result.id_token,
|
id_token=result.id_token,
|
||||||
cookies=result.cookies,
|
cookies=result.cookies,
|
||||||
proxy_used=self.proxy_url,
|
proxy_used=self.proxy_url,
|
||||||
extra_data=result.metadata,
|
extra_data=metadata,
|
||||||
source=result.source
|
source=result.source
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -271,7 +271,12 @@ class DuckMailService(BaseEmailService):
|
|||||||
)
|
)
|
||||||
messages = response.get("hydra:member", [])
|
messages = response.get("hydra:member", [])
|
||||||
|
|
||||||
for message in messages:
|
ordered_messages = self._sort_items_by_message_time(
|
||||||
|
messages,
|
||||||
|
lambda item: item.get("createdAt") if isinstance(item, dict) else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
for message in ordered_messages:
|
||||||
message_id = str(message.get("id") or "").strip()
|
message_id = str(message.get("id") or "").strip()
|
||||||
if not message_id or message_id in seen_message_ids:
|
if not message_id or message_id in seen_message_ids:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -316,7 +316,17 @@ class MeoMailEmailService(BaseEmailService):
|
|||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for message in messages:
|
ordered_messages = self._sort_items_by_message_time(
|
||||||
|
messages,
|
||||||
|
lambda item: (
|
||||||
|
item.get("created_at")
|
||||||
|
or item.get("createdAt")
|
||||||
|
or item.get("received_at")
|
||||||
|
or item.get("receivedAt")
|
||||||
|
) if isinstance(item, dict) else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
for message in ordered_messages:
|
||||||
message_id = message.get("id")
|
message_id = message.get("id")
|
||||||
if not message_id or message_id in seen_message_ids:
|
if not message_id or message_id in seen_message_ids:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -241,7 +241,12 @@ class TempmailService(BaseEmailService):
|
|||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for msg in email_list:
|
ordered_emails = self._sort_items_by_message_time(
|
||||||
|
email_list,
|
||||||
|
lambda item: item.get("date") if isinstance(item, dict) else None,
|
||||||
|
)
|
||||||
|
|
||||||
|
for msg in ordered_emails:
|
||||||
if not isinstance(msg, dict):
|
if not isinstance(msg, dict):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -1565,6 +1565,29 @@ def _build_inbox_config(db, service_type, email: str) -> dict:
|
|||||||
return cfg
|
return cfg
|
||||||
|
|
||||||
|
|
||||||
|
def _load_account_verification_state(account: Account) -> dict:
|
||||||
|
"""从账号扩展信息中读取验证码去重状态。"""
|
||||||
|
extra = account.extra_data or {}
|
||||||
|
state = extra.get("verification_state") if isinstance(extra, dict) else {}
|
||||||
|
if not isinstance(state, dict):
|
||||||
|
state = {}
|
||||||
|
return {
|
||||||
|
"used_codes": [str(code) for code in (state.get("used_codes") or []) if code],
|
||||||
|
"seen_messages": [str(marker) for marker in (state.get("seen_messages") or []) if marker],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _save_account_verification_state(db, account: Account, service) -> None:
|
||||||
|
"""将当前收件箱消费状态持久化到账号表,支持跨请求延续。"""
|
||||||
|
state = service.export_verification_state(account.email)
|
||||||
|
if not state["used_codes"] and not state["seen_messages"]:
|
||||||
|
return
|
||||||
|
|
||||||
|
extra = dict(account.extra_data or {})
|
||||||
|
extra["verification_state"] = state
|
||||||
|
crud.update_account(db, account.id, extra_data=extra)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/{account_id}/inbox-code")
|
@router.post("/{account_id}/inbox-code")
|
||||||
async def get_account_inbox_code(account_id: int):
|
async def get_account_inbox_code(account_id: int):
|
||||||
"""查询账号邮箱收件箱最新验证码"""
|
"""查询账号邮箱收件箱最新验证码"""
|
||||||
@@ -1586,6 +1609,10 @@ async def get_account_inbox_code(account_id: int):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
svc = EmailServiceFactory.create(service_type, config)
|
svc = EmailServiceFactory.create(service_type, config)
|
||||||
|
svc.load_verification_state(
|
||||||
|
account.email,
|
||||||
|
**_load_account_verification_state(account),
|
||||||
|
)
|
||||||
code = svc.get_verification_code(
|
code = svc.get_verification_code(
|
||||||
account.email,
|
account.email,
|
||||||
email_id=account.email_service_id,
|
email_id=account.email_service_id,
|
||||||
@@ -1597,4 +1624,6 @@ async def get_account_inbox_code(account_id: int):
|
|||||||
if not code:
|
if not code:
|
||||||
return {"success": False, "error": "未收到验证码邮件"}
|
return {"success": False, "error": "未收到验证码邮件"}
|
||||||
|
|
||||||
|
_save_account_verification_state(db, account, svc)
|
||||||
|
|
||||||
return {"success": True, "code": code, "email": account.email}
|
return {"success": True, "code": code, "email": account.email}
|
||||||
|
|||||||
@@ -0,0 +1,156 @@
|
|||||||
|
import asyncio
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from src.config.constants import EmailServiceType
|
||||||
|
from src.core.register import RegistrationEngine, RegistrationResult
|
||||||
|
from src.database.models import Account, Base
|
||||||
|
from src.database.session import DatabaseSessionManager
|
||||||
|
from src.services.base import BaseEmailService
|
||||||
|
from src.web.routes import accounts as accounts_routes
|
||||||
|
|
||||||
|
|
||||||
|
class DummySettings:
|
||||||
|
openai_client_id = "client-1"
|
||||||
|
openai_auth_url = "https://auth.openai.test/authorize"
|
||||||
|
openai_token_url = "https://auth.openai.test/token"
|
||||||
|
openai_redirect_uri = "https://localhost/callback"
|
||||||
|
openai_scope = "openid profile email offline_access"
|
||||||
|
tempmail_base_url = "https://api.tempmail.test"
|
||||||
|
tempmail_timeout = 30
|
||||||
|
tempmail_max_retries = 3
|
||||||
|
|
||||||
|
|
||||||
|
class FakeStatefulTempmailService(BaseEmailService):
|
||||||
|
def __init__(self, config=None, name=None):
|
||||||
|
super().__init__(EmailServiceType.TEMPMAIL, name)
|
||||||
|
self.messages = [
|
||||||
|
("id:msg-1", "111111"),
|
||||||
|
("id:msg-2", "222222"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def create_email(self, config=None):
|
||||||
|
return {"email": "tester@example.com", "service_id": "token-1"}
|
||||||
|
|
||||||
|
def get_verification_code(
|
||||||
|
self,
|
||||||
|
email: str,
|
||||||
|
email_id: str = None,
|
||||||
|
timeout: int = 120,
|
||||||
|
pattern: str = r"(?<!\d)(\d{6})(?!\d)",
|
||||||
|
otp_sent_at=None,
|
||||||
|
):
|
||||||
|
for marker, code in self.messages:
|
||||||
|
if self._accept_verification_code(email, code, marker):
|
||||||
|
return code
|
||||||
|
return None
|
||||||
|
|
||||||
|
def list_emails(self, **kwargs):
|
||||||
|
return []
|
||||||
|
|
||||||
|
def delete_email(self, email_id: str) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def check_health(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _build_test_db(name: str) -> DatabaseSessionManager:
|
||||||
|
runtime_dir = Path("tests_runtime")
|
||||||
|
runtime_dir.mkdir(exist_ok=True)
|
||||||
|
db_path = runtime_dir / name
|
||||||
|
if db_path.exists():
|
||||||
|
db_path.unlink()
|
||||||
|
|
||||||
|
manager = DatabaseSessionManager(f"sqlite:///{db_path}")
|
||||||
|
Base.metadata.create_all(bind=manager.engine)
|
||||||
|
return manager
|
||||||
|
|
||||||
|
|
||||||
|
def test_account_inbox_code_persists_verification_state_across_requests(monkeypatch):
|
||||||
|
manager = _build_test_db("account_inbox_code_state.db")
|
||||||
|
|
||||||
|
with manager.session_scope() as session:
|
||||||
|
account = Account(
|
||||||
|
email="tester@example.com",
|
||||||
|
email_service="tempmail",
|
||||||
|
email_service_id="token-1",
|
||||||
|
status="active",
|
||||||
|
extra_data={},
|
||||||
|
)
|
||||||
|
session.add(account)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(account)
|
||||||
|
account_id = account.id
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def fake_get_db():
|
||||||
|
session = manager.SessionLocal()
|
||||||
|
try:
|
||||||
|
yield session
|
||||||
|
finally:
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
monkeypatch.setattr(accounts_routes, "get_db", fake_get_db)
|
||||||
|
monkeypatch.setattr(accounts_routes, "get_settings", lambda: DummySettings())
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"src.services.base.EmailServiceFactory.create",
|
||||||
|
lambda service_type, config, name=None: FakeStatefulTempmailService(config, name),
|
||||||
|
)
|
||||||
|
|
||||||
|
first = asyncio.run(accounts_routes.get_account_inbox_code(account_id))
|
||||||
|
second = asyncio.run(accounts_routes.get_account_inbox_code(account_id))
|
||||||
|
|
||||||
|
assert first["success"] is True
|
||||||
|
assert first["code"] == "111111"
|
||||||
|
assert second["success"] is True
|
||||||
|
assert second["code"] == "222222"
|
||||||
|
|
||||||
|
with manager.session_scope() as session:
|
||||||
|
saved = session.query(Account).filter(Account.id == account_id).first()
|
||||||
|
verification_state = (saved.extra_data or {}).get("verification_state") or {}
|
||||||
|
assert verification_state["used_codes"] == ["111111", "222222"]
|
||||||
|
assert verification_state["seen_messages"] == ["id:msg-1", "id:msg-2"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_to_database_persists_verification_state(monkeypatch):
|
||||||
|
manager = _build_test_db("registration_verification_state.db")
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def fake_get_db():
|
||||||
|
session = manager.SessionLocal()
|
||||||
|
try:
|
||||||
|
yield session
|
||||||
|
finally:
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
monkeypatch.setattr("src.core.register.get_db", fake_get_db)
|
||||||
|
monkeypatch.setattr("src.core.register.get_settings", lambda: DummySettings())
|
||||||
|
|
||||||
|
email_service = FakeStatefulTempmailService()
|
||||||
|
email_service._accept_verification_code("tester@example.com", "111111", "id:msg-1")
|
||||||
|
|
||||||
|
engine = RegistrationEngine(email_service=email_service, proxy_url="http://proxy.test")
|
||||||
|
engine.email_info = {"service_id": "token-1"}
|
||||||
|
|
||||||
|
result = RegistrationResult(
|
||||||
|
success=True,
|
||||||
|
email="tester@example.com",
|
||||||
|
password="secret",
|
||||||
|
account_id="acct-1",
|
||||||
|
workspace_id="ws-1",
|
||||||
|
access_token="access-token",
|
||||||
|
refresh_token="refresh-token",
|
||||||
|
id_token="id-token",
|
||||||
|
session_token="session-token",
|
||||||
|
metadata={"registered_at": "2026-03-26T00:00:00"},
|
||||||
|
source="register",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert engine.save_to_database(result) is True
|
||||||
|
|
||||||
|
with manager.session_scope() as session:
|
||||||
|
saved = session.query(Account).filter(Account.email == "tester@example.com").first()
|
||||||
|
verification_state = (saved.extra_data or {}).get("verification_state") or {}
|
||||||
|
assert verification_state["used_codes"] == ["111111"]
|
||||||
|
assert verification_state["seen_messages"] == ["id:msg-1"]
|
||||||
@@ -325,15 +325,15 @@ def test_duck_mail_service_skips_previously_used_code_even_with_small_timestamp_
|
|||||||
),
|
),
|
||||||
FakeResponse(
|
FakeResponse(
|
||||||
payload={
|
payload={
|
||||||
"id": "msg-1",
|
"id": "msg-2",
|
||||||
"text": "Your OpenAI verification code is 111111",
|
"text": "Your OpenAI verification code is 654321",
|
||||||
"html": [],
|
"html": [],
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
FakeResponse(
|
FakeResponse(
|
||||||
payload={
|
payload={
|
||||||
"id": "msg-2",
|
"id": "msg-1",
|
||||||
"text": "Your OpenAI verification code is 654321",
|
"text": "Your OpenAI verification code is 111111",
|
||||||
"html": [],
|
"html": [],
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -0,0 +1,258 @@
|
|||||||
|
from src.services.duck_mail import DuckMailService
|
||||||
|
from src.services.freemail import FreemailService
|
||||||
|
from src.services.moe_mail import MeoMailEmailService
|
||||||
|
from src.services.temp_mail import TempMailService
|
||||||
|
from src.services.tempmail import TempmailService
|
||||||
|
|
||||||
|
|
||||||
|
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 FakeRequestHTTPClient:
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class FakeGetHTTPClient:
|
||||||
|
def __init__(self, responses):
|
||||||
|
self.responses = list(responses)
|
||||||
|
self.calls = []
|
||||||
|
|
||||||
|
def get(self, url, **kwargs):
|
||||||
|
self.calls.append({
|
||||||
|
"method": "GET",
|
||||||
|
"url": url,
|
||||||
|
"kwargs": kwargs,
|
||||||
|
})
|
||||||
|
if not self.responses:
|
||||||
|
raise AssertionError(f"未准备响应: GET {url}")
|
||||||
|
return self.responses.pop(0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_tempmail_service_prefers_latest_matching_message_without_otp_timestamp():
|
||||||
|
service = TempmailService({"base_url": "https://api.tempmail.test"})
|
||||||
|
service.http_client = FakeGetHTTPClient([
|
||||||
|
FakeResponse(
|
||||||
|
payload={
|
||||||
|
"emails": [
|
||||||
|
{
|
||||||
|
"date": 1000,
|
||||||
|
"from": "noreply@openai.com",
|
||||||
|
"subject": "Your verification code",
|
||||||
|
"body": "Your OpenAI verification code is 111111",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": 1003,
|
||||||
|
"from": "noreply@openai.com",
|
||||||
|
"subject": "Your verification code",
|
||||||
|
"body": "Your OpenAI verification code is 654321",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
|
code = service.get_verification_code(
|
||||||
|
email="tester@example.com",
|
||||||
|
email_id="token-1",
|
||||||
|
timeout=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert code == "654321"
|
||||||
|
|
||||||
|
|
||||||
|
def test_temp_mail_service_prefers_latest_matching_message_without_otp_timestamp():
|
||||||
|
service = TempMailService({
|
||||||
|
"base_url": "https://mail.example.com",
|
||||||
|
"admin_password": "admin-secret",
|
||||||
|
"domain": "example.com",
|
||||||
|
})
|
||||||
|
service.http_client = FakeRequestHTTPClient([
|
||||||
|
FakeResponse(
|
||||||
|
payload={
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": "msg-1",
|
||||||
|
"source": "OpenAI <noreply@openai.com>",
|
||||||
|
"subject": "Your verification code",
|
||||||
|
"body": "Your OpenAI verification code is 111111",
|
||||||
|
"createdAt": "2026-03-19T10:00:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "msg-2",
|
||||||
|
"source": "OpenAI <noreply@openai.com>",
|
||||||
|
"subject": "Your verification code",
|
||||||
|
"body": "Your OpenAI verification code is 654321",
|
||||||
|
"createdAt": "2026-03-19T10:00:03Z",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
|
code = service.get_verification_code(
|
||||||
|
email="tester@example.com",
|
||||||
|
timeout=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert code == "654321"
|
||||||
|
|
||||||
|
|
||||||
|
def test_moe_mail_service_prefers_latest_matching_message_without_otp_timestamp():
|
||||||
|
service = MeoMailEmailService({
|
||||||
|
"base_url": "https://mail.example.com",
|
||||||
|
"api_key": "api-key",
|
||||||
|
})
|
||||||
|
|
||||||
|
def fake_make_request(method, endpoint, **kwargs):
|
||||||
|
if endpoint == "/api/emails/email-1":
|
||||||
|
return {
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"id": "msg-1",
|
||||||
|
"from_address": "noreply@openai.com",
|
||||||
|
"subject": "Your verification code",
|
||||||
|
"received_at": 1742378400000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "msg-2",
|
||||||
|
"from_address": "noreply@openai.com",
|
||||||
|
"subject": "Your verification code",
|
||||||
|
"received_at": 1742378403000,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
if endpoint == "/api/emails/email-1/msg-1":
|
||||||
|
return {
|
||||||
|
"message": {
|
||||||
|
"content": "Your OpenAI verification code is 111111",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if endpoint == "/api/emails/email-1/msg-2":
|
||||||
|
return {
|
||||||
|
"message": {
|
||||||
|
"content": "Your OpenAI verification code is 654321",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
raise AssertionError(f"未准备响应: {method} {endpoint}")
|
||||||
|
|
||||||
|
service._make_request = fake_make_request
|
||||||
|
|
||||||
|
code = service.get_verification_code(
|
||||||
|
email="tester@example.com",
|
||||||
|
email_id="email-1",
|
||||||
|
timeout=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert code == "654321"
|
||||||
|
|
||||||
|
|
||||||
|
def test_freemail_service_prefers_latest_matching_message_without_otp_timestamp():
|
||||||
|
service = FreemailService({
|
||||||
|
"base_url": "https://mail.example.com",
|
||||||
|
"admin_token": "jwt-token",
|
||||||
|
})
|
||||||
|
service.http_client = FakeRequestHTTPClient([
|
||||||
|
FakeResponse(
|
||||||
|
payload=[
|
||||||
|
{
|
||||||
|
"id": "msg-1",
|
||||||
|
"sender": "noreply@openai.com",
|
||||||
|
"subject": "Your verification code",
|
||||||
|
"preview": "Your OpenAI verification code is 111111",
|
||||||
|
"verification_code": "111111",
|
||||||
|
"created_at": "2026-03-19T10:00:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "msg-2",
|
||||||
|
"sender": "noreply@openai.com",
|
||||||
|
"subject": "Your verification code",
|
||||||
|
"preview": "Your OpenAI verification code is 654321",
|
||||||
|
"verification_code": "654321",
|
||||||
|
"created_at": "2026-03-19T10:00:03Z",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
),
|
||||||
|
])
|
||||||
|
|
||||||
|
code = service.get_verification_code(
|
||||||
|
email="tester@example.com",
|
||||||
|
timeout=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert code == "654321"
|
||||||
|
|
||||||
|
|
||||||
|
def test_duck_mail_service_prefers_latest_matching_message_without_otp_timestamp():
|
||||||
|
service = DuckMailService({
|
||||||
|
"base_url": "https://api.duckmail.test",
|
||||||
|
"default_domain": "duckmail.sbs",
|
||||||
|
})
|
||||||
|
service.http_client = FakeRequestHTTPClient([
|
||||||
|
FakeResponse(
|
||||||
|
payload={
|
||||||
|
"hydra:member": [
|
||||||
|
{
|
||||||
|
"id": "msg-1",
|
||||||
|
"from": {
|
||||||
|
"name": "OpenAI",
|
||||||
|
"address": "noreply@openai.com",
|
||||||
|
},
|
||||||
|
"subject": "Your verification code",
|
||||||
|
"createdAt": "2026-03-19T10:00:00Z",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "msg-2",
|
||||||
|
"from": {
|
||||||
|
"name": "OpenAI",
|
||||||
|
"address": "noreply@openai.com",
|
||||||
|
},
|
||||||
|
"subject": "Your verification code",
|
||||||
|
"createdAt": "2026-03-19T10:00:03Z",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
FakeResponse(
|
||||||
|
payload={
|
||||||
|
"id": "msg-2",
|
||||||
|
"text": "Your OpenAI verification code is 654321",
|
||||||
|
"html": [],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
])
|
||||||
|
service._accounts_by_email["tester@duckmail.sbs"] = {
|
||||||
|
"email": "tester@duckmail.sbs",
|
||||||
|
"service_id": "account-1",
|
||||||
|
"account_id": "account-1",
|
||||||
|
"token": "token-123",
|
||||||
|
}
|
||||||
|
|
||||||
|
code = service.get_verification_code(
|
||||||
|
email="tester@duckmail.sbs",
|
||||||
|
email_id="account-1",
|
||||||
|
timeout=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert code == "654321"
|
||||||
Reference in New Issue
Block a user