You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

86 lines
2.8 KiB
Python

# backend/tests/test_federation_real.py — C7 real(가짜) sync → event_bus 발행
# 같은 코드 경로(GmailConnector.normalize/write/sync)로 mail.received 가 실제 발행됨을 증명.
from app import seed as seedmod
from app.automation.event_bus import bus
from app.connectors.base import RawRecord
from app.connectors.mail.real_gmail import GmailConnector
from app.models import ConnectorAccount, ConnectorDomain, ConnectorMode, ConnState
def _mail_account(s):
# phase-16+: 메일 mock 계정 시드 제거 → real 연결 계정을 직접 생성.
a = ConnectorAccount(
id="ca-mail-personal", domain=ConnectorDomain.mail, provider="gmail",
mode=ConnectorMode.real, state=ConnState.connected, external_account_id="me@gmail.com",
name="me@gmail.com",
)
s.add(a)
s.commit()
return a
class FakeGmailConnector(GmailConnector):
"""network 없이 fetch 만 가짜로 — normalize/write/sync 는 real 코드 그대로."""
payloads: list = []
def fetch(self, session, *, full=False):
for p in self.payloads:
yield RawRecord(
external_id=p["id"],
payload=p,
etag=str(p.get("historyId", "")),
external_updated_at=None,
)
_MSG = {
"id": "gmail-real-1",
"historyId": "9001",
"labelIds": ["UNREAD", "IMPORTANT"],
"snippet": "현우님 온보딩 시안 v3 도착",
"internalDate": "1718000000000",
"payload": {
"headers": [
{"name": "From", "value": "hyunwoo@lumi.co"},
{"name": "Subject", "value": "온보딩 시안 v3"},
]
},
}
def test_real_sync_publishes_mail_received_once(session):
s, _ = session
seedmod.run_seed(session=s, reset=True)
acct = _mail_account(s)
before = len([e for e in bus.history if e.type == "mail.received"])
conn = FakeGmailConnector(acct)
conn.payloads = [_MSG]
r1 = conn.sync(s)
assert r1.upserted == 1 and r1.events_published == ["mail.received"]
# 같은 historyId(etag) 재공급 → 멱등(이벤트 0)
conn2 = FakeGmailConnector(acct)
conn2.payloads = [_MSG]
r2 = conn2.sync(s)
assert r2.upserted == 0 and r2.events_published == []
after = len([e for e in bus.history if e.type == "mail.received"])
assert after - before == 1 # 새 메일당 정확히 1회
def test_real_sync_failure_does_not_crash(session):
s, _ = session
seedmod.run_seed(session=s, reset=True)
acct = _mail_account(s)
class BoomConnector(GmailConnector):
def fetch(self, session, *, full=False):
raise RuntimeError("network down")
res = BoomConnector(acct).sync(s) # 예외가 앱을 멈추지 않음
assert res.errors == 1 and "network down" in res.detail
s.refresh(acct)
assert acct.state == "error"