diff --git a/M02_MasterTemplete/M02_MasterTemplete_Router_Layers.py b/M02_MasterTemplete/M02_MasterTemplete_Router_Layers.py index 151d3ec6..1e342c88 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Router_Layers.py +++ b/M02_MasterTemplete/M02_MasterTemplete_Router_Layers.py @@ -253,6 +253,10 @@ async def save_layer_template( ) -> dict[str, Any]: folder = await _layer_dir(session, layer, project_id=project_id, write=True) document = body.문서 + try: + layers.check_skeleton(kind, document) # 빈 `{}` · 뼈대 없는 문서는 파일 안 씀 + except ValueError as error: + raise _bad(error) from error if kind == "table" and fill.is_fillable(document): # 채운 표가 와도 설계값은 안 받아 적음 — 양식 + 손 값만(5장 · 브라우저 값을 믿지 않음) document = fill.strip_design(document) diff --git a/M02_MasterTemplete/M02_Template_Layers.py b/M02_MasterTemplete/M02_Template_Layers.py index 82adfe26..7d48de77 100644 --- a/M02_MasterTemplete/M02_Template_Layers.py +++ b/M02_MasterTemplete/M02_Template_Layers.py @@ -159,6 +159,15 @@ def read_template(layer_dir: str | Path, kind: str, name: str) -> dict[str, Any] } +def check_skeleton(kind: str, document: Any) -> None: + """뼈대 없는 문서는 거절 — 표 = `열` 목록 · 도면 = `entities` 목록(빈 `{}` 저장 막기).""" + key = {"table": "열", "drawing": "entities"}.get(kind) + if not isinstance(document, dict): + raise ValueError("양식 문서는 JSON 객체여야 합니다.") + if key and not isinstance(document.get(key), list): + raise ValueError(f"양식 문서에 `{key}` 목록이 없습니다 — 빈 문서는 저장하지 않습니다.") + + def write_template( layer_dir: str | Path, kind: str, diff --git a/resources/tester/test_m02_template_layers.py b/resources/tester/test_m02_template_layers.py index e1d56911..4b79b0b3 100644 --- a/resources/tester/test_m02_template_layers.py +++ b/resources/tester/test_m02_template_layers.py @@ -102,9 +102,9 @@ def test_작업본_저장_판_다르면_409_초기화는_초기본으로(world: client = world["client"] got = client.get(_url()).json() assert got["층"] == "project" and got["문서"]["판"] == 1 - saved = client.put(_url(), json={"판": got["판"], "문서": {"판": 2}}) + saved = client.put(_url(), json={"판": got["판"], "문서": {"판": 2, "열": []}}) assert saved.status_code == 200 - stale = client.put(_url(), json={"판": got["판"], "문서": {"판": 3}}) + stale = client.put(_url(), json={"판": got["판"], "문서": {"판": 3, "열": []}}) assert stale.status_code == 409 reset = client.post(f"/api/m02/projects/{P1}/templates/reset", json={}) assert "table/구조물집계표" in reset.json()["초기화"] @@ -115,7 +115,7 @@ def test_작업본_저장_판_다르면_409_초기화는_초기본으로(world: def test_회사_공식_저장은_관리자만_적용은_작업본만(world: dict[str, Any]) -> None: client, session = world["client"], world["session"] - client.put(_url(), json={"판": client.get(_url()).json()["판"], "문서": {"회사": 1}}) + client.put(_url(), json={"판": client.get(_url()).json()["판"], "문서": {"회사": 1, "열": []}}) body = {"to": "company", "종류": "table", "이름": "구조물집계표"} assert client.post(f"/api/m02/projects/{P1}/templates/save-as", json=body).status_code == 403 session["role"] = "ADMIN" @@ -123,7 +123,10 @@ def test_회사_공식_저장은_관리자만_적용은_작업본만(world: dict applied = client.post(f"/api/m02/projects/{P2}/templates/apply", json={"from": "company"}) assert applied.status_code == 200 root2 = _root(world, P2, user=43) - assert layers.read_template(root2 / "templates", "table", "구조물집계표")["문서"] == {"회사": 1} + assert layers.read_template(root2 / "templates", "table", "구조물집계표")["문서"] == { + "회사": 1, + "열": [], + } assert ( layers.read_template(root2 / "templates/_initial", "table", "구조물집계표")["문서"]["판"] == 1 @@ -142,7 +145,7 @@ def test_개인_양식은_본인만_쓰고_같은_회사는_읽어_가져옴(wor assert client.get(theirs).status_code == 200 put = client.put( f"/api/m02/layers/personal/templates/table/구조물집계표?project_id={P1}&user_id=42", - json={"판": None, "문서": {}}, + json={"판": None, "문서": {"열": []}}, ) assert put.status_code == 200 # user_id 는 쓰기에서 무시 — 본인(43) 자리에 새로 씀 assert (world["storage"] / "7/43/templates/table/구조물집계표.json").is_file() @@ -336,3 +339,18 @@ def test_옛_표_틀은_새_판으로_손_값은_지킴(world: dict[str, Any]) - # 손댄 것 없는 작업본은 시스템 파일 그대로(판이 같음) migrate.refresh_tables(root2) assert layers.version_of(root2 / "templates/table/구조물집계표.json") == system_version + + +def test_뼈대_없는_문서는_400으로_거절하고_파일을_안_씀(world: dict[str, Any]) -> None: + client = world["client"] + path = _root(world, P1) / "templates/drawing/A1_도각.json" + before = path.read_bytes() + url = f"/api/m02/layers/project/templates/drawing/A1_도각?project_id={P1}" + version = layers.version_of(path) + for document in ({}, {"format": 6}, {"entities": {}}): + got = client.put(url, json={"판": version, "문서": document}) + assert got.status_code == 400 and "entities" in got.json()["detail"] + assert path.read_bytes() == before + table = _url() + assert client.put(table, json={"판": None, "문서": {}}).status_code == 400 + assert client.put(url, json={"판": version, "문서": {"entities": []}}).status_code == 200