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.
204 lines
7.8 KiB
Python
204 lines
7.8 KiB
Python
# backend/tests/test_api_mail.py
|
|
# phase-16+: 메일 mock 시드 제거 → 계정/메일을 테스트가 직접 생성. 발송은 연결된 계정 필요.
|
|
from app.connectors.mail.normalize import mail_account_id_for
|
|
from tests._factories import make_connected_mail_account, make_email, make_mail_account
|
|
|
|
M1_AI = {
|
|
"summary": "온보딩 시안 v3 검토 요청",
|
|
"priority": "높음",
|
|
"category": "검토 요청",
|
|
"tasks": [
|
|
{"text": "온보딩 시안 v3 피드백 정리", "project": "온보딩 리디자인 · 와이어프레임",
|
|
"due": "", "prio": "높음"},
|
|
{"text": "푸시 알림 문구 확인", "project": "온보딩 리디자인", "due": "", "prio": "보통"},
|
|
],
|
|
"events": [{"title": "온보딩 리뷰", "date": "6/10", "day": "오늘", "time": "14:00",
|
|
"dur": "30", "place": "Figma"}],
|
|
"replies": [{"tone": "정중", "preview": "", "body": "확인했습니다. 금요일까지 회신드릴게요."}],
|
|
"file": {"project": "온보딩 리디자인", "reason": "온보딩 자료"},
|
|
}
|
|
M2_AI = {
|
|
"summary": "회의 일정", "priority": "보통", "category": "일정", "tasks": [],
|
|
"events": [{"title": "스프린트 미팅", "date": "6/11", "day": "내일", "time": "15:00",
|
|
"dur": "60", "place": ""}],
|
|
"replies": [], "file": None,
|
|
}
|
|
M7_AI = {
|
|
"summary": "결제 영수증", "priority": "낮음", "category": "영수증",
|
|
"tasks": [], "events": [], "replies": [], "file": None,
|
|
}
|
|
|
|
|
|
def _setup(s):
|
|
make_mail_account(s, id="work", name="회사", tone="coral", sort_order=0)
|
|
make_mail_account(s, id="personal", name="개인", tone="blue", sort_order=1)
|
|
make_mail_account(s, id="side", name="사이드", tone="violet", sort_order=2)
|
|
make_email(s, id="m1", account="work", from_key="hyunwoo", subject="온보딩 시안 v3",
|
|
ai_json=M1_AI, sort_order=0)
|
|
make_email(s, id="m2", account="work", from_key="minseo", subject="회의 일정",
|
|
ai_json=M2_AI, sort_order=1)
|
|
make_email(s, id="m5", account="side", from_key="book", subject="독서모임", sort_order=2)
|
|
make_email(s, id="m7", account="personal", from_key="stripe", subject="영수증",
|
|
ai_json=M7_AI, sort_order=3)
|
|
|
|
|
|
def _flatten_titles(nodes):
|
|
out = []
|
|
for n in nodes:
|
|
out.append(n["title"])
|
|
out += _flatten_titles(n.get("children", []))
|
|
return out
|
|
|
|
|
|
def test_list_accounts(client, session):
|
|
s, _ = session
|
|
_setup(s)
|
|
r = client.get("/api/mail/accounts")
|
|
assert [a["id"] for a in r.json()] == ["work", "personal", "side"]
|
|
assert r.json()[0]["tone"] == "coral"
|
|
|
|
|
|
def test_list_mail_count(client, session):
|
|
s, _ = session
|
|
_setup(s)
|
|
rows = client.get("/api/mail").json()
|
|
assert len(rows) == 4
|
|
assert rows[0]["from"] == "hyunwoo" # alias, sort_order 0
|
|
|
|
|
|
def test_email_detail_marks_read(client, session):
|
|
s, _ = session
|
|
_setup(s)
|
|
assert client.get("/api/mail/m1").json()["read"] is True
|
|
assert client.get("/api/mail/m1").json()["from"] == "hyunwoo"
|
|
|
|
|
|
def test_extract_task_creates_task(client, session):
|
|
s, _ = session
|
|
_setup(s)
|
|
body = client.post("/api/mail/m1/extract", json={"kind": "task", "index": 0}).json()
|
|
assert body["kind"] == "task"
|
|
assert body["task"]["project_id"] == "onb-wire"
|
|
assert body["task"]["prio"] == "높음"
|
|
assert "온보딩 시안 v3 피드백 정리" in _flatten_titles(client.get("/api/tasks").json())
|
|
|
|
|
|
def test_extract_event_creates_event(client, session):
|
|
s, _ = session
|
|
_setup(s)
|
|
r = client.post("/api/mail/m2/extract", json={"kind": "event", "index": 0})
|
|
assert r.json()["kind"] == "event" and r.json()["event_id"]
|
|
|
|
|
|
def test_extract_all(client, session):
|
|
s, _ = session
|
|
_setup(s)
|
|
r = client.post("/api/mail/m1/extract-all", json={})
|
|
assert len(r.json()["created_task_ids"]) == 2
|
|
assert r.json()["filed_project"] == "온보딩 리디자인"
|
|
|
|
|
|
def test_reply_draft_picks_seeded(client, session):
|
|
s, _ = session
|
|
_setup(s)
|
|
r = client.post("/api/mail/m1/reply-draft", json={"reply_index": 0})
|
|
assert r.json()["subject"].startswith("Re: ")
|
|
assert "금요일" in r.json()["body"]
|
|
|
|
|
|
def test_send_requires_connected_account(client, session):
|
|
s, _ = session
|
|
_setup(s)
|
|
# 미연결 계정으로는 발송 거부(가짜 발송 없음)
|
|
bad = client.post(
|
|
"/api/mail/send",
|
|
json={"from_account": "work", "to": "현우 <hyunwoo@lumi.co>", "subject": "Re", "body": "x"},
|
|
)
|
|
assert bad.status_code == 400
|
|
|
|
|
|
def test_send_creates_high_risk_approval(client, session):
|
|
s, _ = session
|
|
make_connected_mail_account(s, email="me@gmail.com")
|
|
slug = mail_account_id_for("me@gmail.com")
|
|
r = client.post(
|
|
"/api/mail/send",
|
|
json={
|
|
"from_account": slug,
|
|
"to": "현우 <hyunwoo@lumi.co>",
|
|
"subject": "Re: 온보딩",
|
|
"body": "확인했어요",
|
|
},
|
|
)
|
|
assert r.json()["status"] == "pending"
|
|
appr = client.get(f"/api/approvals/{r.json()['approval_id']}").json()
|
|
assert appr["risk"] == "high" and appr["source"] == "mail"
|
|
|
|
|
|
def test_m7_empty_ai(client, session):
|
|
s, _ = session
|
|
_setup(s)
|
|
ai = client.get("/api/mail/m7").json()["ai"]
|
|
assert ai["priority"] == "낮음" and ai["tasks"] == [] and ai["events"] == []
|
|
|
|
|
|
def test_extract_404(client):
|
|
assert client.post("/api/mail/nope/extract", json={"kind": "task"}).status_code == 404
|
|
|
|
|
|
def test_account_filter(client, session):
|
|
s, _ = session
|
|
_setup(s)
|
|
rows = client.get("/api/mail?account=side").json()
|
|
assert all(r["account"] == "side" for r in rows) and len(rows) == 1 # m5
|
|
|
|
|
|
# ── 보낸편지함/임시보관함 = OutboundMail(sent/pending) ──
|
|
def _make_outbound(s, *, id, mail_account, status, subject, body="본문 내용", to="대표님",
|
|
sort=0):
|
|
from app.models import OutboundMail
|
|
|
|
ob = OutboundMail(
|
|
id=id, approval_id=f"ap-{id}", connector_account_id=None, mail_account=mail_account,
|
|
to=to, subject=subject, body=body, status=status,
|
|
)
|
|
s.add(ob)
|
|
s.commit()
|
|
return ob
|
|
|
|
|
|
def test_sent_folder_returns_sent_outbound(client, session):
|
|
s, _ = session
|
|
make_mail_account(s, id="work", name="회사", email="me@work.com")
|
|
_make_outbound(s, id="ob1", mail_account="work", status="sent", subject="보고서 보냄",
|
|
body="첨부 확인 부탁드립니다.")
|
|
_make_outbound(s, id="ob2", mail_account="work", status="pending", subject="대기 중인 메일")
|
|
rows = client.get("/api/mail/sent").json()
|
|
ids = [r["id"] for r in rows]
|
|
assert ids == ["ob1"] # pending 은 제외
|
|
r = rows[0]
|
|
assert r["subject"] == "보고서 보냄"
|
|
assert r["read"] is True
|
|
assert r["from"] == "me@work.com" # 발신 계정 이메일
|
|
assert "첨부 확인" in r["preview"]
|
|
|
|
|
|
def test_drafts_folder_returns_pending_outbound(client, session):
|
|
s, _ = session
|
|
make_mail_account(s, id="work", name="회사", email="me@work.com")
|
|
_make_outbound(s, id="obA", mail_account="work", status="sent", subject="이미 보냄")
|
|
_make_outbound(s, id="obB", mail_account="work", status="pending", subject="결재 대기")
|
|
rows = client.get("/api/mail/drafts").json()
|
|
assert [r["id"] for r in rows] == ["obB"]
|
|
assert rows[0]["subject"] == "결재 대기"
|
|
|
|
|
|
def test_sent_folder_account_filter(client, session):
|
|
s, _ = session
|
|
make_mail_account(s, id="work", name="회사", email="me@work.com")
|
|
make_mail_account(s, id="personal", name="개인", email="me@home.com", sort_order=1)
|
|
_make_outbound(s, id="obW", mail_account="work", status="sent", subject="회사 발신")
|
|
_make_outbound(s, id="obP", mail_account="personal", status="sent", subject="개인 발신")
|
|
rows = client.get("/api/mail/sent?account=personal").json()
|
|
assert [r["id"] for r in rows] == ["obP"]
|