Phase 2 백엔드 마지막 task — FastAPI app · 11 endpoint · 10 route tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
221 lines
8.5 KiB
Python
221 lines
8.5 KiB
Python
import pytest
|
|
from unittest.mock import patch, AsyncMock
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.main import app
|
|
from app import db as db_module
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def fresh_db(monkeypatch, tmp_path):
|
|
db_file = tmp_path / "test_saju.db"
|
|
monkeypatch.setattr(db_module, "DB_PATH", str(db_file))
|
|
db_module.init_db()
|
|
yield
|
|
try:
|
|
if db_file.exists():
|
|
db_file.unlink()
|
|
except PermissionError:
|
|
pass
|
|
|
|
|
|
def _interpret_result(interp_json=None):
|
|
if interp_json is None:
|
|
interp_json = {"items": [], "summary": "...", "advice": "...", "warning": None, "confidence": "medium"}
|
|
return {
|
|
"interpretation_json": interp_json,
|
|
"model": "claude-sonnet-4-6",
|
|
"tokens_in": 200, "tokens_out": 400, "cost_usd": 0.01,
|
|
"latency_ms": 1200, "reroll_count": 0,
|
|
}
|
|
|
|
|
|
def test_health():
|
|
with TestClient(app) as c:
|
|
r = c.get("/health")
|
|
assert r.status_code == 200
|
|
assert r.json() == {"ok": True}
|
|
|
|
|
|
def test_saju_interpret_endpoint(monkeypatch):
|
|
"""saju interpret이 pipeline mock으로 동작."""
|
|
async def fake_interpret(*args, **kwargs):
|
|
return _interpret_result()
|
|
|
|
# interpret_saju를 mock
|
|
from app.routers import saju as saju_router
|
|
monkeypatch.setattr(saju_router.pipeline, "interpret_saju", fake_interpret)
|
|
|
|
with TestClient(app) as c:
|
|
r = c.post("/api/saju/interpret", json={
|
|
"year": 1990, "month": 5, "day": 15, "hour": 14,
|
|
"gender": "male", "calendar_type": "solar"
|
|
})
|
|
assert r.status_code == 200, r.text
|
|
data = r.json()
|
|
assert "saju" in data
|
|
assert "analysis" in data
|
|
assert "daeun" in data
|
|
assert "reading_id" in data
|
|
assert data["reading_id"] > 0
|
|
|
|
|
|
def test_saju_list_get_cycle(monkeypatch):
|
|
async def fake_interpret(*args, **kwargs):
|
|
return _interpret_result()
|
|
from app.routers import saju as saju_router
|
|
monkeypatch.setattr(saju_router.pipeline, "interpret_saju", fake_interpret)
|
|
|
|
with TestClient(app) as c:
|
|
# save
|
|
rid = c.post("/api/saju/interpret", json={
|
|
"year": 1990, "month": 5, "day": 15, "hour": 14,
|
|
"gender": "male", "calendar_type": "solar"
|
|
}).json()["reading_id"]
|
|
# list
|
|
assert c.get("/api/saju/readings").json()["total"] == 1
|
|
# get
|
|
r = c.get(f"/api/saju/readings/{rid}")
|
|
assert r.status_code == 200
|
|
assert r.json()["birth_year"] == 1990
|
|
|
|
|
|
def test_saju_patch_and_delete(monkeypatch):
|
|
async def fake_interpret(*args, **kwargs):
|
|
return _interpret_result()
|
|
from app.routers import saju as saju_router
|
|
monkeypatch.setattr(saju_router.pipeline, "interpret_saju", fake_interpret)
|
|
|
|
with TestClient(app) as c:
|
|
rid = c.post("/api/saju/interpret", json={
|
|
"year": 1990, "month": 5, "day": 15, "hour": 14,
|
|
"gender": "male", "calendar_type": "solar"
|
|
}).json()["reading_id"]
|
|
|
|
r = c.patch(f"/api/saju/readings/{rid}", json={"favorite": True, "memo": "메모"})
|
|
assert r.status_code == 200
|
|
row = c.get(f"/api/saju/readings/{rid}").json()
|
|
assert row["favorite"] == 1
|
|
assert row["memo"] == "메모"
|
|
|
|
r = c.delete(f"/api/saju/readings/{rid}")
|
|
assert r.status_code == 200
|
|
assert c.get(f"/api/saju/readings/{rid}").status_code == 404
|
|
|
|
|
|
def test_saju_get_404():
|
|
with TestClient(app) as c:
|
|
assert c.get("/api/saju/readings/9999").status_code == 404
|
|
|
|
|
|
def test_saju_current_fortune(monkeypatch):
|
|
async def fake_interpret(*args, **kwargs):
|
|
return _interpret_result()
|
|
from app.routers import saju as saju_router
|
|
monkeypatch.setattr(saju_router.pipeline, "interpret_saju", fake_interpret)
|
|
|
|
with TestClient(app) as c:
|
|
rid = c.post("/api/saju/interpret", json={
|
|
"year": 1990, "month": 5, "day": 15, "hour": 14,
|
|
"gender": "male", "calendar_type": "solar"
|
|
}).json()["reading_id"]
|
|
r = c.get(f"/api/saju/current-fortune?reading_id={rid}")
|
|
assert r.status_code == 200
|
|
data = r.json()
|
|
assert "seun" in data
|
|
assert data["seun"]["stem"]
|
|
|
|
|
|
def test_compat_interpret(monkeypatch):
|
|
async def fake_compat(*args, **kwargs):
|
|
return {
|
|
"interpretation_json": {"summary": "...", "strengths": [
|
|
{"title": "A", "explanation": "B", "evidence": "C"},
|
|
{"title": "A2", "explanation": "B2", "evidence": "C2"},
|
|
], "challenges": [
|
|
{"title": "D", "explanation": "E", "evidence": "F"},
|
|
{"title": "D2", "explanation": "E2", "evidence": "F2"},
|
|
], "advice": "...", "warning": None, "confidence": "high"},
|
|
"model": "claude-sonnet-4-6",
|
|
"tokens_in": 300, "tokens_out": 500, "cost_usd": 0.015,
|
|
"latency_ms": 1500, "reroll_count": 0,
|
|
}
|
|
from app.routers import compat as compat_router
|
|
monkeypatch.setattr(compat_router.pipeline, "interpret_compat", fake_compat)
|
|
|
|
with TestClient(app) as c:
|
|
r = c.post("/api/saju/compat/interpret", json={
|
|
"person_a": {"year": 1990, "month": 5, "day": 15, "hour": 14, "gender": "male", "calendar_type": "solar"},
|
|
"person_b": {"year": 1992, "month": 8, "day": 8, "hour": 18, "gender": "female", "calendar_type": "solar"},
|
|
})
|
|
assert r.status_code == 200, r.text
|
|
data = r.json()
|
|
assert "score" in data
|
|
assert "saju_a" in data
|
|
assert "saju_b" in data
|
|
|
|
|
|
def test_compat_list_get_cycle(monkeypatch):
|
|
async def fake_compat(*args, **kwargs):
|
|
return {
|
|
"interpretation_json": {"summary": "...", "strengths": [
|
|
{"title": "a", "explanation": "b", "evidence": "c"},
|
|
{"title": "a", "explanation": "b", "evidence": "c"},
|
|
], "challenges": [
|
|
{"title": "d", "explanation": "e", "evidence": "f"},
|
|
{"title": "d", "explanation": "e", "evidence": "f"},
|
|
], "advice": "", "warning": None, "confidence": "medium"},
|
|
"model": "x", "tokens_in": 0, "tokens_out": 0, "cost_usd": 0.0,
|
|
"latency_ms": 0, "reroll_count": 0,
|
|
}
|
|
from app.routers import compat as compat_router
|
|
monkeypatch.setattr(compat_router.pipeline, "interpret_compat", fake_compat)
|
|
|
|
with TestClient(app) as c:
|
|
rid = c.post("/api/saju/compat/interpret", json={
|
|
"person_a": {"year": 1990, "month": 5, "day": 15, "hour": 14, "gender": "male", "calendar_type": "solar"},
|
|
"person_b": {"year": 1992, "month": 8, "day": 8, "hour": 18, "gender": "female", "calendar_type": "solar"},
|
|
}).json()["reading_id"]
|
|
|
|
assert c.get("/api/saju/compat/readings").json()["total"] == 1
|
|
r = c.get(f"/api/saju/compat/readings/{rid}")
|
|
assert r.status_code == 200
|
|
assert r.json()["score"] >= 0
|
|
|
|
|
|
def test_compat_patch_and_delete(monkeypatch):
|
|
async def fake_compat(*args, **kwargs):
|
|
return {
|
|
"interpretation_json": {"summary": "...", "strengths": [
|
|
{"title": "a", "explanation": "b", "evidence": "c"},
|
|
{"title": "a", "explanation": "b", "evidence": "c"},
|
|
], "challenges": [
|
|
{"title": "d", "explanation": "e", "evidence": "f"},
|
|
{"title": "d", "explanation": "e", "evidence": "f"},
|
|
], "advice": "", "warning": None, "confidence": "medium"},
|
|
"model": "x", "tokens_in": 0, "tokens_out": 0, "cost_usd": 0.0,
|
|
"latency_ms": 0, "reroll_count": 0,
|
|
}
|
|
from app.routers import compat as compat_router
|
|
monkeypatch.setattr(compat_router.pipeline, "interpret_compat", fake_compat)
|
|
|
|
with TestClient(app) as c:
|
|
rid = c.post("/api/saju/compat/interpret", json={
|
|
"person_a": {"year": 1990, "month": 5, "day": 15, "hour": 14, "gender": "male", "calendar_type": "solar"},
|
|
"person_b": {"year": 1992, "month": 8, "day": 8, "hour": 18, "gender": "female", "calendar_type": "solar"},
|
|
}).json()["reading_id"]
|
|
|
|
r = c.patch(f"/api/saju/compat/readings/{rid}", json={"favorite": True, "memo": "좋은 궁합"})
|
|
assert r.status_code == 200
|
|
row = c.get(f"/api/saju/compat/readings/{rid}").json()
|
|
assert row["favorite"] == 1
|
|
|
|
r = c.delete(f"/api/saju/compat/readings/{rid}")
|
|
assert r.status_code == 200
|
|
assert c.get(f"/api/saju/compat/readings/{rid}").status_code == 404
|
|
|
|
|
|
def test_compat_get_404():
|
|
with TestClient(app) as c:
|
|
assert c.get("/api/saju/compat/readings/9999").status_code == 404
|