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.

107 lines
4.7 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""연합 시나리오 통합 테스트 — 캡처→분류→확인→tasks 등장→risks→dashboard 집계.
LLM은 HeuristicProvider로 고정(conftest, 결정론적). golden case 기준.
"""
GOLDEN_RAW = "다음 주에 한국 놀러가는 비행기 티켓 사기"
def _flatten(nodes):
out = []
for n in nodes:
out.append(n)
out.extend(_flatten(n.get("children", []) or []))
return out
def test_health(client):
assert client.get("/api/health").json() == {"status": "ok"}
def test_capture_classifies_to_task_life_travel(client):
"""① 캡처 → 분류: 비행기 티켓 = 작업 / life / 개인 여행 — 한국"""
r = client.post("/api/inbox/capture", json={"kind": "text", "raw": GOLDEN_RAW})
assert r.status_code == 200
c = r.json()["classification"]
assert c["type"] == "task"
assert c["sphere"] == "life"
assert "여행" in c["proj_label"] and "한국" in c["proj_label"]
assert "작업" in c["reason"]
assert 0.0 <= c["confidence"] <= 1.0
assert r.json()["item"]["status"] == "classified"
def test_confirm_materializes_into_task_tree(client):
"""① 확인(실체화): confirm 시 task 생성 + tasks 트리·life 필터에 등장"""
cap = client.post("/api/inbox/capture", json={"kind": "text", "raw": GOLDEN_RAW}).json()
item_id = cap["item"]["id"]
confirmed = client.post(f"/api/inbox/{item_id}/confirm").json()
new_task_id = confirmed["task"]["id"]
assert new_task_id
inbox = client.get("/api/inbox").json()
target = next(i for i in inbox if i["id"] == item_id)
assert target["status"] == "confirmed"
assert target["materialized_task_id"] == new_task_id
life = client.get("/api/tasks", params={"area": "life"}).json()
flat = _flatten(life)
created = next(t for t in flat if t["id"] == new_task_id)
assert created["project_id"] == "life-trip"
assert "비행기 티켓" in created["title"]
assert created["status"] == "todo"
assert "가격 추적" in created["notes"]
def test_confirm_idempotent(client):
"""confirm 두 번 호출 — 동일 materialized_task_id, 중복 task 생성 금지"""
cap = client.post("/api/inbox/capture", json={"kind": "text", "raw": GOLDEN_RAW}).json()
iid = cap["item"]["id"]
first = client.post(f"/api/inbox/{iid}/confirm").json()
first_tid = first["task"]["id"]
# 두 번째 confirm: 이미 confirmed → task 미생성(없음), materialized_task_id 유지
second = client.post(f"/api/inbox/{iid}/confirm").json()
assert second["task"] is None
inbox = {i["id"]: i for i in client.get("/api/inbox").json()}
assert inbox[iid]["materialized_task_id"] == first_tid
def test_risk_recompute_after_task_change(client):
"""② 작업 데이터 → 리스크 레이더 자동 계산(최대 3건, TODAY=8)"""
risks = client.get("/api/risks", params={"area": "work"}).json()
assert len(risks) <= 3
kinds = [r["kind"] for r in risks]
assert "지연 위험" in kinds
delay = next(r for r in risks if r["kind"] == "지연 위험")
assert delay["tone"] == "coral" and delay["icon"] == "clock"
if "의존성" in kinds:
dep = next(r for r in risks if r["kind"] == "의존성")
assert dep["tone"] == "violet" and dep["icon"] == "link"
def test_risk_disappears_when_done(client):
"""k1(분기 리포트)을 done 으로 PATCH 후 → 지연 위험에서 k1 사라짐(재계산)"""
before = client.get("/api/risks", params={"area": "work"}).json()
delay_before = next((r for r in before if r["kind"] == "지연 위험"), None)
assert delay_before and delay_before["task_id"] == "k1"
client.patch("/api/tasks/k1", json={"status": "done"})
after = client.get("/api/risks", params={"area": "work"}).json()
delay_after = next((r for r in after if r["kind"] == "지연 위험"), None)
# k1 이 done → 지연 위험이 없거나 다른 작업으로 바뀜(k1 아님)
assert delay_after is None or delay_after["task_id"] != "k1"
def test_dashboard_aggregates_tasks_inbox_approvals(client):
"""③ 대시보드 집계 — 작업/인박스/결재함 요약 + 배지"""
cap = client.post("/api/inbox/capture", json={"kind": "text", "raw": GOLDEN_RAW}).json()
client.post(f"/api/inbox/{cap['item']['id']}/confirm")
d = client.get("/api/dashboard").json()
assert "user" in d and d["user"]["name"]
assert "briefing" in d and "schedule" in d
assert "task_summary" in d and "goals" in d
assert "approvals_summary" in d and "inbox_recent" in d
assert "open_count" in d["task_summary"] and "items" in d["task_summary"]
assert len(d["approvals_summary"]) <= 3
assert set(d["badges"]) == {"appr", "task", "noti"}
assert isinstance(d["inbox_recent"], list)