108 lines
3.3 KiB
Python
108 lines
3.3 KiB
Python
"""daeun.py — 대운 8개 계산 검증.
|
|
|
|
fixtures/reference_saju.json 의 30 case (TS) 와 Python 구현 1:1 일치 검증.
|
|
"""
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from app.calculator.core import calculate_saju
|
|
from app.calculator.daeun import calculate_daeun, get_current_daeun, get_daeun_description
|
|
|
|
|
|
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']['gender']}",
|
|
)
|
|
def test_calculate_daeun_matches_reference(case):
|
|
inp = case["input"]
|
|
expected = [_normalize(x) for x in case["expected"]["daeun"]]
|
|
|
|
saju = calculate_saju(
|
|
inp["year"], inp["month"], inp["day"], inp.get("hour"), inp["gender"]
|
|
)
|
|
actual = calculate_daeun(
|
|
inp["year"],
|
|
inp["month"],
|
|
inp["day"],
|
|
inp["gender"],
|
|
saju["month"]["stem"],
|
|
saju["month"]["branch"],
|
|
)
|
|
|
|
assert len(actual) == len(expected), (
|
|
f"len mismatch for {inp}: {len(actual)} vs {len(expected)}"
|
|
)
|
|
|
|
for i, (a, e) in enumerate(zip(actual, expected)):
|
|
for key in ("stem", "branch", "stem_kr", "branch_kr"):
|
|
assert a.get(key) == e.get(key), (
|
|
f"{inp} daeun[{i}].{key}: actual={a.get(key)} expected={e.get(key)}"
|
|
)
|
|
# age + start_year/end_year
|
|
assert a.get("age") == e.get("age"), (
|
|
f"{inp} daeun[{i}].age: {a.get('age')} vs {e.get('age')}"
|
|
)
|
|
assert a.get("start_year") == e.get("start_year"), (
|
|
f"{inp} daeun[{i}].start_year: {a.get('start_year')} vs {e.get('start_year')}"
|
|
)
|
|
assert a.get("end_year") == e.get("end_year"), (
|
|
f"{inp} daeun[{i}].end_year: {a.get('end_year')} vs {e.get('end_year')}"
|
|
)
|
|
|
|
|
|
def test_get_current_daeun_returns_match():
|
|
daeun_list = [
|
|
{"age": 10, "start_year": 2000, "end_year": 2009},
|
|
{"age": 20, "start_year": 2010, "end_year": 2019},
|
|
{"age": 30, "start_year": 2020, "end_year": 2029},
|
|
]
|
|
assert get_current_daeun(daeun_list, 2005) == daeun_list[0]
|
|
assert get_current_daeun(daeun_list, 2015) == daeun_list[1]
|
|
assert get_current_daeun(daeun_list, 2025) == daeun_list[2]
|
|
assert get_current_daeun(daeun_list, 1999) is None
|
|
assert get_current_daeun(daeun_list, 2030) is None
|
|
|
|
|
|
def test_get_daeun_description_returns_string():
|
|
daeun = {
|
|
"age": 10,
|
|
"start_year": 2000,
|
|
"end_year": 2009,
|
|
"stem": "壬",
|
|
"branch": "午",
|
|
"stem_kr": "임",
|
|
"branch_kr": "오",
|
|
}
|
|
desc = get_daeun_description(daeun, "辛")
|
|
assert isinstance(desc, str)
|
|
assert "임오" in desc
|
|
assert "壬午" in desc
|
|
assert "10세" in desc
|
|
assert "19세" in desc
|