Files
Aislo/resources/tester/test_m02_structure.py
T
eomsangdonandClaude Opus 5.5 c608b9554f test(M02): 구조물 번호 시험이 실제 구조물집계표 대신 견본 표를 읽음
- 사용자가 화면에서 제목 · 열을 고쳐도 번호 시험이 안 밀림 · 번호 규칙 시험은 그대로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUypcnp5d1gU2aeKh9F2H7
2026-09-28 14:52:52 +09:00

218 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""M02 구조물 도면(structure) — 새로 · 도번 SD+8자리 · 409 · 저장 · 지운 뒤 번호 ·
제목행 번호 매김(분류3 까지) · 집계표 저장 때 만들기/지우기 · 복사 가져오기 · 프로젝트 복사.
면적산출(area) · 재료산출(material)은 따로 파일 —
`resources/tester/spreadsheet/test_spreadsheet_store.py`."""
import json
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
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
# 견본 구조물집계표 — 실제 마스터 양식은 사용자가 화면에서 고치므로 시험은 이 견본만 읽음
_SAMPLE_COLUMNS = [
("no", ["NO", None, None, None], "글"),
("sta", ["측점", None, None, None], "글"),
("rv", ["돌기슭막이", "(형태마다)", "H=(높이마다)", None], "수"),
("c1", ["돌기슭막이", "(형태마다)", "새 열", None], "수"),
("pv_b", ["콘크리트포장", "T=(두께마다)", "B", None], "수"),
("pv_l", ["콘크리트포장", "T=(두께마다)", "L", None], "수"),
("pv_a", ["콘크리트포장", "T=(두께마다)", "A", None], "수"),
("pv_jt", ["콘크리트포장", "T=(두께마다)", "줄눈", None], "수"),
("pp_len", ["관공", "(관종 · 관경마다)", "L", None], "수"),
("pp_cp", ["관공", "(관종 · 관경마다)", "커플링밴드", None], "수"),
("fb", ["세월교", "(관경 × 련수마다)", None, None], "수"),
("ms", ["돌쌓기", "(쌓기마다)", "H=(높이마다)", None], "수"),
("be", ["소단", None, None, None], "수"),
("h_rip_c", ["돌붙임", "찰붙임", None, None], "수"),
("h_rip_m", ["돌붙임", "메붙임", None, None], "수"),
("memo", ["비고", None, None, None], "글"),
]
def write_sample_table(folder) -> None:
"""임시 폴더에 종류 폴더 · 견본 구조물집계표를 만듦(`store.FOLDER` 자리)."""
for kind in store.KINDS:
(folder / kind).mkdir(exist_ok=True)
table = {
"양식": store.STRUCTURE_TABLE,
"종류": "표",
"판": 1,
"층": ["종류", "분류1", "분류2", "분류3"],
"열": [{"id": i, "머리": h, "단위": None, "꼴": k} for i, h, k in _SAMPLE_COLUMNS],
"줄": [],
"합계줄": [],
"보기": {},
}
(folder / "table" / f"{store.STRUCTURE_TABLE}.json").write_text(
json.dumps(table, ensure_ascii=False), encoding="utf-8"
)
@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 _num(code: str) -> dict:
"""세 번호 — 같은 8자리."""
return {"도번": f"SD{code}", "면적번": f"SA{code}", "재료번": f"SM{code}"}
def test_새로_도번_자동_409_없는_열(client):
made = client.post("/api/m02/structures/pp_len")
assert made.status_code == 200
doc = made.json()["문서"]
assert (doc["양식"], doc["종류"], doc["열"], doc["도번"]) == (
"구조물도면",
"structure",
"pp_len",
"SD01010100",
)
# 도각 없는 전용 화면 — 그린 만큼이 설계 영역 · 기본 도면층 하나(없으면 CAD 가 못 엶)
assert doc["도면"]["format"] == 6
assert doc["도면"]["entities"] == []
assert len(doc["도면"]["layers"]) == 1
# 면적산출 · 재료산출은 구조물 문서 밖 — 같은 열 id 로 따로 한 통합문서씩
assert "산출근거" not in doc
for kind in ("area", "material"):
assert client.get(f"/api/m02/templates/{kind}/pp_len").json()["문서"]["종류"] == "통합문서"
assert client.post("/api/m02/structures/rv").json()["문서"]["도번"] == "SD02010100"
assert client.post("/api/m02/structures/pp_len").status_code == 409
assert client.post("/api/m02/structures/없는열").status_code == 404
assert client.post("/api/m02/structures/no").status_code == 404 # 글 열은 구조물 아님
assert client.get("/api/m02/structures/numbers").json() == {
"pp_len": _num("01010100"),
"rv": _num("02010100"),
}
rows = client.get("/api/m02/templates").json()
assert {(r["종류"], r["이름"]) for r in rows if r["종류"] == "structure"} == {
("structure", "pp_len"),
("structure", "rv"),
}
def test_저장_뼈대_거름(client):
got = client.post("/api/m02/structures/pp_len").json()
doc = got["문서"]
doc["도면"]["entities"] = [{"type": "line"}]
saved = client.put("/api/m02/templates/structure/pp_len", json={"판": got["판"], "문서": doc})
assert saved.status_code == 200 and saved.json()["판"] != got["판"]
for bad in ({}, {**doc, "도면": {}}, {**doc, "도면": {"entities": "x"}}, {**doc, "도면": []}):
r = client.put(
"/api/m02/templates/structure/pp_len", json={"판": saved.json()["판"], "문서": bad}
)
assert r.status_code == 400
assert client.get("/api/m02/templates/structure/pp_len").json()["문서"] == doc
now = client.get("/api/m02/templates/structure/pp_len").json()["판"]
client.put(
"/api/m02/templates/structure/pp_len", json={"판": now, "문서": {**doc, "도번": "SD9"}}
)
got = client.get("/api/m02/templates/structure/pp_len").json()["문서"]
assert got["도번"] == "SD01010100" # 번호는 서버 것 — 화면 값을 안 받음
def test_지워도_번호를_당기지_않음_짝은_같은_번호(client):
for column in ("pp_len", "rv", "ms"):
client.post(f"/api/m02/structures/{column}")
assert client.delete("/api/m02/templates/structure/rv").status_code == 200
numbers = client.get("/api/m02/structures/numbers").json()
assert numbers["rv"] == {**_num("02010100"), "도번": ""} # 면적 · 재료는 남아 번호를 쥠
assert numbers["ms"] == _num("03010100") # 다른 열 번호는 그대로
# 같은 종류(관공) 새 열 = 그 무리 다음 번호 · 다시 만든 도면은 짝(면적산출) 번호를 이어 받음
assert client.post("/api/m02/structures/pp_cp").json()["문서"]["도번"] == "SD01010200"
assert client.post("/api/m02/structures/rv").json()["문서"]["도번"] == "SD02010100"
def test_제목행으로_번호_매김_한_번_옮김(client, tmp_path):
"""옛 구-NN 파일 · 글 열 파일 → 새 번호(열 차례 = 종류 · 분류1 · 분류2 · 분류3 차례) ·
내용 그대로."""
for column in ("no", "rv", "pv_l"):
doc = {"양식": "구조물도면", "종류": "structure", "판": 1, "열": column, "도번": "구-07"}
doc["도면"] = {"format": 6, "entities": [{"type": "line"}], "layers": []}
(tmp_path / "structure" / f"{column}.json").write_text(
json.dumps(doc, ensure_ascii=False), encoding="utf-8"
)
done = store.renumber()
assert done["지움"] == ["structure/no"] # 글 열(NO · 측점 · 비고)은 구조물 아님
numbers = client.get("/api/m02/structures/numbers").json()
assert set(numbers) == set(store._column_ids()) and "sta" not in numbers
expect = {
"rv": "01010100", # 돌기슭막이 · (형태마다) · H=(높이마다) · 분류3 없음 = 00
"c1": "01010200", # 같은 종류 · 분류1 · 다음 분류2
"pv_b": "02010100", # 콘크리트포장 · T=(두께마다) · B
"pv_l": "02010200",
"pp_len": "03010100",
"fb": "04010000", # 세월교 · (관경 × 련수마다) · 분류2 없음 = 00
"be": "06000000", # 소단 — 분류 모두 없음
"h_rip_c": "07010000", # 돌붙임 · 찰붙임
"h_rip_m": "07020000", # 돌붙임 · 메붙임 — 분류1 다음 번호
}
for column, code in expect.items():
assert numbers[column] == _num(code), column
assert store.read("structure", "rv")["문서"]["도면"]["entities"] == [{"type": "line"}]
again = {"옮김": [], "지움": [], "바꿈": [], "새로": []}
assert store.renumber() == again # 두 번 돌려도 같음
def test_집계표_저장_때_새_열은_만들고_지운_열은_같이_지움(client):
store.renumber()
got = client.get("/api/m02/templates/table/구조물집계표").json()
table = got["문서"]
table["열"] = [c for c in table["열"] if c["id"] != "pv_a"]
new = {"id": "pv_x", "머리": ["콘크리트포장", "T=(두께마다)", "X"], "단위": "m", "꼴": "수"}
kind = {"id": "zz", "머리": ["새 구조물", None, None], "단위": "개소", "꼴": "수"}
text = {"id": "note2", "머리": ["메모", None, None], "단위": None, "꼴": "글"}
third = {"id": "pv_y", "머리": ["콘크리트포장", "T=(두께마다)", "B", "H=1"], "꼴": "수"}
table["열"] += [new, kind, text, third]
saved = client.put(
"/api/m02/templates/table/구조물집계표", json={"판": got["판"], "문서": table}
)
assert saved.status_code == 200
numbers = client.get("/api/m02/structures/numbers").json()
assert "pv_a" not in numbers and "note2" not in numbers
for kind_ in ("structure", "area", "material"):
assert client.get(f"/api/m02/templates/{kind_}/pv_a").status_code == 404
assert numbers["pv_x"] == _num("02010500") # 지운 pv_a(03)는 비워 둠 · 무리 다음
assert numbers["zz"] == _num("08000000") # 새 종류 = 종류 다음 번호
assert numbers["pv_y"] == _num("02010101") # pv_b 무리 안 분류3 다음 번호
def test_다른_열_도면을_복사해_가져옴(client):
store.renumber()
src = client.get("/api/m02/templates/structure/rv").json()
src["문서"]["도면"]["entities"] = [{"type": "circle"}]
client.put("/api/m02/templates/structure/rv", json=src)
r = client.post("/api/m02/structures/ms/copy", params={"from": "rv"})
assert r.status_code == 200
ms = client.get("/api/m02/templates/structure/ms").json()["문서"]
assert ms["도면"]["entities"] == [{"type": "circle"}]
assert (ms["도번"], ms["열"]) == ("SD05010100", "ms") # 번호 · 열은 받는 쪽 그대로
assert client.get("/api/m02/templates/structure/rv").json()["문서"]["도번"] == "SD01010100"
bad = client.post("/api/m02/structures/ms/copy", params={"from": "없는열"})
assert bad.status_code == 404
def test_프로젝트_복사와_층_저장에_실림(client, tmp_path, monkeypatch):
doc = client.post("/api/m02/structures/pp_len").json()["문서"]
monkeypatch.setattr(layers, "SYSTEM_ROOT", tmp_path)
project = tmp_path / "project"
copied = layers.seed_project(project)
assert "structure/pp_len" in copied["작업본"] and "structure/pp_len" in copied["초기"]
work = layers.project_dir(project)
assert layers.read_template(work, "structure", "pp_len")["문서"] == doc
assert layers.read_template(layers.initial_dir(project), "structure", "pp_len")["문서"] == doc
assert ("structure", "pp_len") in {(r["종류"], r["이름"]) for r in layers.list_templates(work)}
layers.check_skeleton("structure", doc)
with pytest.raises(ValueError):
layers.check_skeleton("structure", {**doc, "도면": None})