Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BW6Jdsh18WPtYUR6THZJqn
115 lines
5.4 KiB
Python
115 lines
5.4 KiB
Python
"""Z01 공종 축 API 틀 — 산림·건설 두 kind 를 기초단가와 같은 길로(2026-09-17 브레인 · PLAN_공종축 8-4).
|
||
|
||
못박는 것: 평평한 잎 목록 · 전체 경로 이름(겹침 없음) · 계산 규칙(choose_one 91 · sum_steps 3) ·
|
||
불변 열쇠가 `@id` · 건설도 같은 길(자료가 있어야 상자가 섬) · 고치기는 막힘.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
from fastapi import FastAPI
|
||
from fastapi.testclient import TestClient
|
||
|
||
from Z01_MasterData import Z01_MasterData_Overrides as overrides
|
||
from Z01_MasterData import Z01_MasterData_Router as router_module
|
||
from Z01_MasterData import Z01_MasterData_WorkItems as work_items
|
||
|
||
|
||
@pytest.fixture
|
||
def client(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> TestClient:
|
||
from common_util.common_util_auth import verify_session
|
||
|
||
monkeypatch.setattr(overrides, "OVERRIDE_DIR", tmp_path / "data_master_override")
|
||
app = FastAPI()
|
||
app.include_router(router_module.router)
|
||
app.dependency_overrides[verify_session] = lambda: {"user_id": 7, "role": "SYSTEM_ADMIN"}
|
||
return TestClient(app)
|
||
|
||
|
||
def _get(client: TestClient, kind: str, **params) -> dict:
|
||
res = client.get(f"/api/master-data/base-prices/{kind}", params=params)
|
||
assert res.status_code == 200, res.text
|
||
return res.json()
|
||
|
||
|
||
def test_산림_평평한_잎_목록과_계산_규칙(client: TestClient) -> None:
|
||
listed = client.get("/api/master-data/base-prices").json()["kinds"]
|
||
forest = next(i for i in listed if i["kind"] == "work_item_forest")
|
||
assert forest["group"] == "work_item" and forest["rows"] == 360
|
||
|
||
table = _get(client, "work_item_forest", size=500)
|
||
assert table["total"] == 360 and table["stats"] == {"leaves": 360, "units": 620}
|
||
assert table["editable"] == [] and set(table["locked"]) == set(table["sortable"])
|
||
rows = table["rows"]
|
||
assert len({r["@id"] for r in rows}) == 360
|
||
assert (
|
||
len({r["name"] for r in rows}) == 360
|
||
) # 잎 이름 겹침 — 경로로 풀림 · 목차 오기 바로잡아 358 → 360(12-17-2 · 12-24-1)
|
||
by_code = {r["work_item_code"]: r for r in rows}
|
||
assert by_code["FP-09-03-02"]["name"] == "토공 › 토사깍기 › 기계"
|
||
|
||
modes = [r["mode"] for r in table["rules"]]
|
||
assert modes.count("choose_one") == 91 and modes.count("sum_steps") == 3
|
||
blast = next(r for r in table["rules"] if r["work_item_code"] == "FP-09-05")
|
||
assert [s["weight"] for s in blast["steps"]] == ["0.1", "0.9", "1"]
|
||
assert by_code["FP-09-05-02"]["rule"] == "sum_steps"
|
||
assert by_code["FP-09-05-02"]["step_weight"] == "0.9"
|
||
assert all(r["@id"].startswith("FW-") for r in rows) # 불변 열쇠(데스크탑 서브 7dd2c515)
|
||
assert not any("불변 열쇠" in line for line in table["notice"])
|
||
|
||
found = _get(client, "work_item_forest", q="발파암 ›")
|
||
assert found["total"] == 3
|
||
|
||
|
||
def test_건설_공종_축도_같은_길(client: TestClient) -> None:
|
||
"""건설(2026-09-17 뽑음) — 부문이 뿌리라 경로가 부문부터 · 열쇠 CW- · 계산 규칙은 전부 choose_one."""
|
||
table = _get(client, "work_item_const", size=500)
|
||
assert table["total"] == table["stats"]["leaves"] > 900
|
||
rows = table["rows"]
|
||
assert all(r["@id"].startswith("CW-") for r in rows)
|
||
assert all(r["name"].split(" › ")[0].endswith("부문") for r in rows)
|
||
assert {r["mode"] for r in table["rules"]} == {"choose_one"}
|
||
assert not any("불변 열쇠" in line for line in table["notice"])
|
||
found = _get(client, "work_item_const", q="굴착(인력/토사)")
|
||
assert [r["name"] for r in found["rows"]] == ["공통부문 › 토공사 › 굴착 › 굴착(인력/토사)"]
|
||
|
||
|
||
def test_불변_열쇠가_오면_id_로_씀_자료_파일은_판_id_로_가림(
|
||
client: TestClient, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||
) -> None:
|
||
folder = tmp_path / "data_work_item_master"
|
||
folder.mkdir()
|
||
fake = {
|
||
"dataset_version": {"dataset_id": "pum_const"},
|
||
"effective_date": "2026-01-01",
|
||
"work_items": [
|
||
{"work_item_code": "CP-01", "work_item_key": "CW-00001", "number": "1",
|
||
"name": "토공", "parent_code": None, "tables": [], "parent_mode": "sum_steps",
|
||
"steps": [{"code": "CP-01-01", "weight": "0.5"}], "steps_basis": "시험", "variant_keys": []},
|
||
{"work_item_code": "CP-01-01", "work_item_key": "CW-00002", "number": "1-1",
|
||
"name": "인력", "parent_code": "CP-01", "tables": [{"pum_table_id": "C0001"}],
|
||
"variant_keys": ["갑", "을"]},
|
||
],
|
||
} # fmt: skip
|
||
(folder / "const_work_item_master_2026-01-01.json").write_text(
|
||
json.dumps(fake, ensure_ascii=False), encoding="utf-8"
|
||
)
|
||
monkeypatch.setattr(work_items, "FOLDER", folder)
|
||
|
||
table = _get(client, "work_item_const")
|
||
assert [r["@id"] for r in table["rows"]] == ["CW-00002"]
|
||
assert table["rows"][0]["work_item_code"] == "CP-01-01" # 목차 코드는 칸으로 남음
|
||
assert table["rows"][0]["name"] == "토공 › 인력"
|
||
assert table["stats"] == {"leaves": 1, "units": 2}
|
||
assert table["rules"][0]["steps"] == [{"@id": "CW-00002", "weight": "0.5"}]
|
||
assert _get(client, "work_item_forest")["total"] == 0 # 산림 파일은 이 폴더에 없음
|
||
|
||
res = client.put(
|
||
"/api/master-data/base-prices/work_item_const/CW-00002",
|
||
json={"values": {"name": "바꿈"}},
|
||
)
|
||
assert res.status_code == 400 and "공종 축 뼈대" in res.json()["detail"]
|