feat(saju-lab): main.py + routers (saju 6 + compat 5) + route tests
Phase 2 백엔드 마지막 task — FastAPI app · 11 endpoint · 10 route tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
220
saju-lab/tests/test_routes.py
Normal file
220
saju-lab/tests/test_routes.py
Normal file
@@ -0,0 +1,220 @@
|
||||
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
|
||||
Reference in New Issue
Block a user