"""빈 마스터(PLAN 26-5a) — 마스터 구조물 항목을 다 지운 뒤에도 M02 가 돎 · 프로젝트 복사본은 그대로. 마스터 초기화와 같은 줄기(나무 지우기 `tree.apply` → 집계표 저장 `store.write`)를 시험 안 견본 사본(`fixtures/m02_master_sample`)에 돌림 — 실제 마스터는 안 읽음. ① 지운 뒤 — 항목 0 · 남는 열 no · sta · 비고 · 빈 나무 · 장부 `다음` 유지 · 지운 번호 기록 · M02 첫 화면 길 · 새 폴더 · 새 항목(다음 키) · 지우기 · 고르기 모달의 마스터 0. ② 프로젝트 복사본 — 마스터가 비어도 계산 그대로 · 목록은 원본없음. """ from __future__ import annotations from pathlib import Path from typing import Any import pytest from fastapi import FastAPI from fastapi.testclient import TestClient from common_util.common_util_auth import verify_session from config import config_system from M02_MasterTemplete import M02_Item_Copy as copies from M02_MasterTemplete import M02_MasterTemplete_Router as main_router from M02_MasterTemplete import M02_MasterTemplete_Router_Items as items_router from M02_MasterTemplete import M02_MasterTemplete_Router_Layers as layers_router from M02_MasterTemplete import M02_MasterTemplete_Store as store from M02_MasterTemplete import M02_MasterTemplete_Tree as tree from M02_MasterTemplete import M02_Template_Layers as layers from resources.tester.helper_m02_master_sample import copy_master_sample from resources.tester.test_item_copy_complete import ( # noqa: F401 — 복사본 world 고정물 FOLDER, _fill_values, _pipe, _save, master_copy, world, ) def empty_master(folder: Path) -> list[str]: """마스터 초기화 — 맨 위 폴더마다 나무 지우기 · 지운 열 너비 뺌 · 집계표 저장 한 번(세 파일 · 장부가 따라옴).""" with store.at(folder): got = store.read("table", store.STRUCTURE_TABLE) doc = tree.normalize_table(got["문서"]) tops = {tree.path_of(c)[0] for c in doc["열"] if tree.is_item(c)} gone: list[str] = [] for top in sorted(tops | {f[0] for f in tree.folders_of(doc)}): doc, note = tree.apply(doc, {"op": "delete", "경로": [top]}) gone += note["지운항목"] widths = doc.get("보기", {}).get("열너비") or {} keep = {c["id"] for c in doc["열"]} if widths: doc["보기"]["열너비"] = {k: v for k, v in widths.items() if k in keep} store.write("table", store.STRUCTURE_TABLE, got["판"], doc) return gone @pytest.fixture() def empty(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> dict[str, Any]: system = copy_master_sample(tmp_path / "master") monkeypatch.setattr(store, "FOLDER", system) monkeypatch.setattr(layers, "SYSTEM_ROOT", system) monkeypatch.setattr(config_system, "STORAGE_BASE_DIR", str(tmp_path / "storage")) gone = empty_master(system) session = {"user_id": 42, "company_id": 7, "role": "SYSTEM_ADMIN", "is_master": True} app = FastAPI() for module in (main_router, items_router, layers_router): app.include_router(module.router) app.dependency_overrides[verify_session] = lambda: session return {"client": TestClient(app), "system": system, "gone": gone} def test_지운_뒤_항목_0_틀은_남음(empty): system, client = empty["system"], empty["client"] assert len(empty["gone"]) == 51 table = client.get("/api/m02/templates/table/구조물집계표").json()["문서"] assert [c["id"] for c in table["열"]] == ["no", "sta", "memo"] and table["폴더"] == [] assert table["변수"] and table["쪽줄"] == 50 and table["합계줄"] and table["바인딩규칙"] assert set(table["보기"]["열너비"]) <= {"no", "sta", "memo"} combos = client.get("/api/m02/structure-combos").json() assert combos["줄"] == [] and combos["다음"] == 52 assert len(combos["지운번호"]) == 4 # 견본에 세 파일이 있던 넷(rv · c4 · pp_len · ms) for kind in ("structure", "area", "material", "inputs"): assert not list((system / kind).glob("*.json")), kind assert (system / "자재조합.json").is_file() def test_빈_마스터에서_첫_화면_나무_새_항목_지우기(empty): client = empty["client"] # M02 첫 화면 길 — 양식 목록 · 집계표 · 번호 · 장부 · 나무 · 옵션 listed = client.get("/api/m02/layers/system/templates").json()["양식"] assert "구조물집계표" in [r["이름"] for r in listed] assert "구조물집계표" in [r["이름"] for r in client.get("/api/m02/templates").json()] assert client.get("/api/m02/structures/numbers").json() == {} assert client.get("/api/m02/structure-options").status_code == 200 assert client.get("/api/m02/items").json() == [] groups = client.get("/api/m02/items?all=1").json() # 고르기 모달 — 마스터 0 assert groups[0]["layer"] == "system" and groups[0]["items"] == [] url = "/api/m02/layers/system/tree" now = client.get(url).json() assert now["폴더"] == [] and now["items"] == [] now = client.post(url, json={"판": now["판"], "op": "add-folder", "경로": [], "이름": "옹벽"}) assert now.status_code == 200, now.text now = now.json() assert now["폴더"] == [["옹벽"]] body = {"판": now["판"], "op": "add-item", "경로": ["옹벽"], "이름": "L형", "단위": "m"} now = client.post(url, json=body).json() [item] = now["items"] assert item["key"] == "SS000052" and item["경로"] == ["옹벽", "L형"] # 지운 키 다시 안 씀 assert item["도번"] == "SD-000001" # 옛 8자리 지운 번호와 안 겹치는 새 모양 now = client.post(url, json={"판": now["판"], "op": "delete", "키": item["key"]}).json() assert now["items"] == [] and now["폴더"] == [["옹벽"]] now = client.post(url, json={"판": now["판"], "op": "delete", "경로": ["옹벽"]}).json() assert now["폴더"] == [] and client.get("/api/m02/structure-combos").json()["다음"] == 53 def test_마스터를_비워도_프로젝트_복사본_계산_그대로(world): # noqa: F811 — 고정물 root, system = world["root"], world["system"] _save(root, [_pipe()]) _fill_values(root) before = copies.calc(root, FOLDER) assert len(empty_master(system)) == 51 assert copies.calc(root, FOLDER) == before row = copies.list_items(root)[0] assert row["원본없음"] is True and row["마스터바뀜"] is False