"""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