- 항목 경로 = 폴더 이름 배열(깊이 자유) · 집계표 열 머리(빈 칸 건너뜀)가 곧 경로 · 열에 경로를 같이 적음 · 머리 줄 수(층)는 가장 깊은 경로에 맞춤(넷 밑으로는 안 줄임) · 빈 폴더는 집계표 폴더 칸(M02_MasterTemplete_Tree)
- 표 저장과 나무 고침 한 줄기 — 나무 고침은 집계표 문서를 고쳐 같은 집계표 저장(store.write)을 탐(번호 · SS 장부 · 세 파일 · 입력 연결 파일 지우기가 함께)
- 나무 고침 길 POST /api/m02/layers/{layer}/tree(add-folder · add-item · rename · move · delete · 판 409 · 422 · 404 · 층 권한) · 읽기 GET 같은 길 {판, 폴더, items}
- 번호 = 새 항목은 자리 뜻 없는 일련번호(있던 번호 · 지운 번호 가장 큰 수 + 1) · 있던 번호 그대로
- SS 장부 = M02_MasterTemplete_Book 로 떼어 냄(Store 543 → 478) · 줄에 경로 · 이름 · 옛 종류 · 분류1~3 = 경로 앞 넷 · 경로 없는 옛 줄은 읽을 때 채움 · 옮기는 함수 migrate_paths
- 목록 길 /api/m02/items 줄에 경로 · 이름(경로 이음) · 자재 조합 · 갈라 내기 이름도 경로 이음
- 마스터 집계표 · 장부를 경로 모양으로 옮김(51 항목 · 키 · 번호 그대로 · 빈 글 머리는 null)
- 시험 test_m02_tree 5(옛 51 항목 · 옮기기 두 번 같음 · 깊이 1 · 2 · 5 · 이름 · 옮기기 · 지우기 · 마지막 항목 · 409 · 403 · 표 저장 한 줄기) · 번호 시험을 일련번호 규칙으로
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmELpdXFDRZHhfbGuRhSmL
111 lines
4.6 KiB
Python
111 lines
4.6 KiB
Python
"""구조물집계표조합 — 서버가 집계표 [저장] 때 맞추는 장부(키 SS · 열마다 한 줄).
|
||
|
||
견본 표(`write_sample_table`)로만 봄 — 실제 마스터 양식은 안 읽음.
|
||
"""
|
||
|
||
import json
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
from fastapi import FastAPI
|
||
from fastapi.testclient import TestClient
|
||
|
||
from M02_MasterTemplete import M02_MasterTemplete_Book as book
|
||
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
||
from M02_MasterTemplete import M02_Template_Layers as layers
|
||
from M02_MasterTemplete.M02_MasterTemplete_Router import router
|
||
from resources.tester.test_m02_structure import write_sample_table
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
TABLE = "/api/m02/templates/table/구조물집계표"
|
||
|
||
|
||
@pytest.fixture()
|
||
def client(tmp_path, monkeypatch):
|
||
write_sample_table(tmp_path)
|
||
monkeypatch.setattr(store, "FOLDER", tmp_path)
|
||
app = FastAPI()
|
||
app.include_router(router)
|
||
return TestClient(app)
|
||
|
||
|
||
def _save_table(client, change) -> None:
|
||
got = client.get(TABLE).json()
|
||
change(got["문서"])
|
||
assert client.put(TABLE, json={"판": got["판"], "문서": got["문서"]}).status_code == 200
|
||
|
||
|
||
def _keys(client) -> dict[str, str]:
|
||
return {row["열"]: row["키"] for row in client.get("/api/m02/structure-combos").json()["줄"]}
|
||
|
||
|
||
def test_처음_한_번_만듦_글_열_빼고_칸은_집계표_그대로(client, tmp_path):
|
||
assert not (tmp_path / store.COMBO_FILE).exists()
|
||
doc = client.get("/api/m02/structure-combos").json()
|
||
assert (doc["그룹"], doc["다음"]) == ("구조물집계표조합", 14)
|
||
rows = doc["줄"]
|
||
assert [r["열"] for r in rows][:3] == ["rv", "c1", "pv_b"]
|
||
assert {"no", "sta", "memo"}.isdisjoint(r["열"] for r in rows) # 글 열 빼기
|
||
assert (rows[0]["키"], rows[-1]["키"]) == ("SS000001", "SS000013")
|
||
assert rows[-1]["열"] == "h_rip_m"
|
||
fb = next(r for r in rows if r["열"] == "fb")
|
||
assert (fb["종류"], fb["분류1"], fb["분류2"], fb["분류3"]) == (
|
||
"세월교",
|
||
"(관경 × 련수마다)",
|
||
None,
|
||
None,
|
||
)
|
||
assert (tmp_path / store.COMBO_FILE).is_file()
|
||
|
||
|
||
def test_집계표_저장_때_새_열_지운_열_고친_열을_맞춤_키_유지(client):
|
||
before = _keys(client)
|
||
|
||
def change(table):
|
||
cols = table["열"]
|
||
cols[:] = [c for c in cols if c["id"] != "h_rip_m"] # 맨 끝 키 SS000013 열 지움
|
||
rv = next(c for c in cols if c["id"] == "rv")
|
||
rv["머리"] = ["돌기슭막이", "메쌓기", "1.2m", "기초유"]
|
||
rv.update(단위="m", 일위대가="UA000004", 펼침=True, 바인딩={"종류": "revetment"})
|
||
cols.insert(3, {"id": "new1", "머리": ["옹벽", None, None, None], "단위": "m", "꼴": "수"})
|
||
|
||
_save_table(client, change)
|
||
doc = client.get("/api/m02/structure-combos").json()
|
||
rows = {r["열"]: r for r in doc["줄"]}
|
||
assert "h_rip_m" not in rows
|
||
assert rows["new1"]["키"] == "SS000014" # 지운 열의 SS000013 은 다시 안 씀
|
||
assert [r["열"] for r in doc["줄"]][:4] == ["rv", "new1", "c1", "pv_b"] # 집계표 차례
|
||
assert all(rows[c]["키"] == k for c, k in before.items() if c != "h_rip_m")
|
||
rv = rows["rv"]
|
||
assert (rv["분류1"], rv["분류3"], rv["단위"], rv["일위대가"]) == (
|
||
"메쌓기",
|
||
"기초유",
|
||
"m",
|
||
"UA000004",
|
||
)
|
||
assert (rv["바인딩"], rv["펼침"]) == ({"종류": "revetment"}, True)
|
||
# 번호는 세 파일과 같음 — 새 열도 [저장] 한 번에 SD · SA · SM 이 섬
|
||
numbers = client.get("/api/m02/structures/numbers").json()
|
||
assert {k: rows["new1"][k] for k in ("도번", "면적번", "재료번")} == numbers["new1"]
|
||
assert rows["new1"]["도번"].startswith("SD")
|
||
|
||
# 또 저장해도(바뀐 것 없음) 키 · 다음 그대로
|
||
_save_table(client, lambda table: None)
|
||
again = client.get("/api/m02/structure-combos").json()
|
||
assert (again["다음"], again["줄"]) == (15, doc["줄"])
|
||
|
||
|
||
def test_SS_머리글은_키대장과_안_겹침():
|
||
ledger = json.loads((ROOT / "resources/master_data/_키대장.json").read_text("utf-8"))
|
||
assert "SS" not in ledger["다음"]
|
||
assert book.HEAD == "SS" # 장부는 `M02_MasterTemplete_Book`(26-2a 나눔)
|
||
|
||
|
||
def test_새_프로젝트_복사에_실림(client, tmp_path, monkeypatch):
|
||
client.get("/api/m02/structure-combos")
|
||
monkeypatch.setattr(layers, "SYSTEM_ROOT", tmp_path)
|
||
copied = layers.seed_project(tmp_path / "project")
|
||
assert "구조물집계표조합" in copied["작업본"] and "구조물집계표조합" in copied["초기"]
|
||
target = layers.project_dir(tmp_path / "project") / store.COMBO_FILE
|
||
assert json.loads(target.read_bytes())["줄"][0]["키"] == "SS000001"
|