Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VwJQ5eRrCKf3nH9EFeKvJz
133 lines
6.8 KiB
Python
133 lines
6.8 KiB
Python
"""M02 자재 조합(`/api/m02/material-combos…` · 마스터 템플릿 `자재조합.json` · 키 UM).
|
|
|
|
못박는 것
|
|
- 정본 표는 마스터 템플릿 자리 · 틀대로(머리 「다음」) · 키 대장에 UM 없음 · M01 길 없음.
|
|
- 새로 만들면 키 = 표 「다음」 번호 · 열 한 벌 · 담은 자재는 네 칸 · 지운 키는 다시 안 씀.
|
|
- 없는 자재 코드 · 같은 자재 두 번 · 이름 없음 · 이름 겹침 · 없는 본뜬키는 막음.
|
|
- 고치기 · 지우기는 낡은 판이면 409 · 본뜬키는 고쳐도 그대로 · 쓰는 열이 있으면 못 지움.
|
|
- 정본은 읽기만 함(사본 폴더 · 견본 집계표).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
from M01_MasterData import M01_MasterData_Router as m01_router
|
|
from M01_MasterData import M01_MasterData_Store as m01
|
|
from M02_MasterTemplete import M02_MasterTemplete_Matset as matset
|
|
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
|
from M02_MasterTemplete.M02_MasterTemplete_Router import router
|
|
from resources.tester.test_m02_structure import write_sample_table
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
M1, M2 = (
|
|
{"코드": "MT000008", "이름": "시멘트", "규격": "40kg", "단위": "포"},
|
|
{"코드": "MT000009", "이름": "모래", "규격": "", "단위": "㎥"},
|
|
)
|
|
URL = "/api/m02/material-combos"
|
|
TABLE = "/api/m02/templates/table/구조물집계표"
|
|
|
|
|
|
@pytest.fixture
|
|
def client(tmp_path: Path, monkeypatch) -> TestClient:
|
|
write_sample_table(tmp_path)
|
|
monkeypatch.setattr(store, "FOLDER", tmp_path)
|
|
app = FastAPI()
|
|
app.include_router(router)
|
|
return TestClient(app)
|
|
|
|
|
|
def _new(client: TestClient, name: str, mats: list[dict], 판: str | None = None, **more):
|
|
if 판 is None:
|
|
판 = client.get(URL).json()["판"]
|
|
return client.post(URL, json={"판": 판, "줄": {"이름": name, "담은자재": mats, **more}})
|
|
|
|
|
|
def test_정본_표는_마스터_템플릿_자리_틀대로_UM_은_대장_밖() -> None:
|
|
path = ROOT / "resources/master_template/자재조합.json"
|
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
assert data["그룹"] == "자재조합" and isinstance(data["다음"], int)
|
|
assert matset.mmc.check_file(path) == []
|
|
assert not (ROOT / "resources/master_data/자재조합.json").exists()
|
|
book = (ROOT / "resources/master_data/_키대장.json").read_text(encoding="utf-8")
|
|
assert '"UM"' not in book.splitlines()[1] # 대장 머리 「다음」
|
|
assert not any("matset" in getattr(r, "path", "") for r in m01_router.router.routes)
|
|
|
|
|
|
def test_새로_만들면_다음_번호_열_한_벌_지운_키는_다시_안_씀(client) -> None:
|
|
res = _new(client, "합판 자재", [{**M1, "덤": 1}])
|
|
assert res.status_code == 200, res.text
|
|
made = res.json()
|
|
assert made["키"] == "UM000001" and tuple(made["줄"]) == matset.mmc.COLS
|
|
assert made["줄"]["담은자재"] == [M1] and made["줄"]["본뜬키"] is None
|
|
listed = client.get(URL).json()
|
|
assert listed["판"] == made["판"] and [r["키"] for r in listed["줄"]] == ["UM000001"]
|
|
assert client.get(f"{URL}/UM000001").json()["이름"] == "합판 자재"
|
|
assert client.delete(f"{URL}/UM000001", params={"판": made["판"]}).status_code == 200
|
|
again = _new(client, "다음 것", [M2]).json()
|
|
assert again["키"] == "UM000002" # 지운 번호는 다시 안 씀
|
|
assert json.loads((store.FOLDER / matset.FILE).read_text(encoding="utf-8"))["다음"] == 3
|
|
|
|
|
|
def test_없는_자재_두_번_이름_본뜬키는_막는다(client) -> None:
|
|
assert _new(client, "없는 코드", [{**M1, "코드": "MT999999"}]).status_code == 422
|
|
assert _new(client, "두 번", [M1, M1]).status_code == 422
|
|
assert _new(client, " ", [M1]).status_code == 400
|
|
assert _new(client, "빈 것", []).status_code == 422
|
|
assert _new(client, "없는 원본", [M1], 본뜬키="UM999999").status_code == 400
|
|
ok = _new(client, "원본", [M1]).json()["키"]
|
|
copy = _new(client, "본뜬 것", [M1, M2], 본뜬키=ok).json()
|
|
assert copy["줄"]["본뜬키"] == ok and copy["키"] != ok
|
|
assert _new(client, "원본", [M2]).status_code == 422 # 이름 겹침
|
|
assert _new(client, "낡은 판", [M2], 판="0000").status_code == 409
|
|
|
|
|
|
def test_고치기는_낡은_판이면_409_본뜬키는_그대로(client) -> None:
|
|
src = _new(client, "원본", [M1]).json()["키"]
|
|
made = _new(client, "고칠 것", [M1], 본뜬키=src).json()
|
|
row = {**made["줄"], "이름": "고침", "담은자재": [M1, M2], "본뜬키": None}
|
|
res = client.put(f"{URL}/{made['키']}", json={"판": made["판"], "줄": row})
|
|
assert res.status_code == 200, res.text
|
|
assert res.json()["줄"]["이름"] == "고침" and res.json()["줄"]["본뜬키"] == src
|
|
assert client.put(f"{URL}/{made['키']}", json={"판": made["판"], "줄": row}).status_code == 409
|
|
assert client.put(f"{URL}/UM999999", json={"판": "", "줄": row}).status_code == 404
|
|
|
|
|
|
def test_쓰는_열이_있으면_못_지우고_없으면_지워진다(client) -> None:
|
|
made = _new(client, "쓰는 것", [M1]).json()
|
|
table = client.get(TABLE).json()
|
|
for col in table["문서"]["열"]:
|
|
if col["id"] == "rv":
|
|
col["자재조합"] = made["키"]
|
|
assert client.put(TABLE, json={"판": table["판"], "문서": table["문서"]}).status_code == 200
|
|
res = client.delete(f"{URL}/{made['키']}", params={"판": made["판"]})
|
|
assert client.get(URL).json()["쓰는곳"] == {made["키"]: ["돌기슭막이 (형태마다) H=(높이마다)"]}
|
|
assert res.status_code == 409 and res.json()["detail"]["users"] == [
|
|
"돌기슭막이 (형태마다) H=(높이마다)"
|
|
]
|
|
rows = client.get("/api/m02/structure-combos").json()["줄"]
|
|
assert next(r for r in rows if r["열"] == "rv")["자재조합"] == made["키"] # SS 장부 칸
|
|
table = client.get(TABLE).json()
|
|
for col in table["문서"]["열"]:
|
|
col.pop("자재조합", None)
|
|
assert client.put(TABLE, json={"판": table["판"], "문서": table["문서"]}).status_code == 200
|
|
assert client.delete(f"{URL}/{made['키']}", params={"판": "낡음"}).status_code == 409
|
|
assert client.delete(f"{URL}/{made['키']}", params={"판": made["판"]}).status_code == 200
|
|
assert client.get(f"{URL}/{made['키']}").status_code == 404
|
|
|
|
|
|
def test_M01_마스터는_UM_을_모른다() -> None:
|
|
assert "UM" not in m01.cm.mk.IDS and "자재조합" not in m01.cm.mk.ROW_ORDER
|
|
|
|
|
|
def test_새로_본떠_만들기는_열자마자_이름_칸에_초점() -> None:
|
|
ui = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_Matset.ts").read_text(encoding="utf-8")
|
|
assert "'[data-matset-field=\"이름\"]')?.focus()" in ui.split("if (isNew) {")[1]
|
|
copy = ui.split("btnCopy.addEventListener")[1].split("});")[0]
|
|
assert '이름: ""' in copy # 본떠 만들기는 이름 칸 비움
|