Files
web-page-backend/saju-lab/tests/test_core.py

79 lines
2.3 KiB
Python

"""calculator/core.py — saju-web TS 엔진과 1:1 매칭 검증.
fixtures/reference_saju.json 의 30 case (TS calculateSaju 결과)와 Python 구현 일치 여부 검증.
"""
import json
from pathlib import Path
import pytest
from app.calculator.core import calculate_saju
REF_PATH = Path(__file__).parent / "fixtures" / "reference_saju.json"
REF = json.loads(REF_PATH.read_text(encoding="utf-8"))
def _camel_to_snake(name: str) -> str:
out = []
for ch in name:
if ch.isupper():
out.append("_" + ch.lower())
else:
out.append(ch)
return "".join(out)
def _normalize(d):
"""TS camelCase → Python snake_case 변환 (deep)."""
if isinstance(d, dict):
return {_camel_to_snake(k): _normalize(v) for k, v in d.items()}
if isinstance(d, list):
return [_normalize(x) for x in d]
return d
@pytest.mark.parametrize(
"case",
REF,
ids=lambda c: f"{c['input']['year']}-{c['input']['month']:02d}-{c['input']['day']:02d}-{c['input'].get('hour')}-{c['input']['gender']}",
)
def test_calculate_saju_matches_reference(case):
inp = case["input"]
expected = _normalize(case["expected"]["saju"])
actual = calculate_saju(
inp["year"], inp["month"], inp["day"],
inp.get("hour"), inp["gender"],
)
# 4기둥 비교
for pillar in ["year", "month", "day", "hour"]:
if expected.get(pillar) is None:
assert actual.get(pillar) is None, f"{pillar} should be None"
continue
if pillar == "hour" and inp.get("hour") is None:
assert actual.get(pillar) is None
continue
ep = expected[pillar]
ap = actual[pillar]
for field in ["stem", "branch", "stem_kr", "branch_kr", "element", "ten_god", "fortune"]:
assert ap[field] == ep[field], (
f"{pillar}.{field}: actual={ap[field]} expected={ep[field]} "
f"(input={inp})"
)
# day_stem
assert actual["day_stem"] == expected["day_stem"]
# gender
assert actual["gender"] == expected["gender"]
# birth_date
bd = actual["birth_date"]
assert bd["year"] == inp["year"]
assert bd["month"] == inp["month"]
assert bd["day"] == inp["day"]
if inp.get("hour") is not None:
assert bd.get("hour") == inp["hour"]