From 9bf3258509ff097e0c780bdc847ca1859ac67763 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Fri, 25 Sep 2026 12:43:03 +0900 Subject: [PATCH] =?UTF-8?q?fix(M02):=20=EB=8F=84=EB=A9=B4=20=EC=96=91?= =?UTF-8?q?=EC=8B=9D=20=EC=A0=80=EC=9E=A5=EC=9D=B4=20=EB=B9=88=20{}=20?= =?UTF-8?q?=EB=A1=9C=20=EB=8D=AE=EC=9D=B4=EB=8D=98=20=ED=9D=A0=20=E2=80=94?= =?UTF-8?q?=20getDoc=20=EC=9D=84=20await=20=C2=B7=20=EC=84=9C=EB=B2=84?= =?UTF-8?q?=EB=8A=94=20=EB=BC=88=EB=8C=80=20=EC=97=86=EB=8A=94=20=EB=AC=B8?= =?UTF-8?q?=EC=84=9C=EB=A5=BC=20400=20=EC=9C=BC=EB=A1=9C=20=EA=B1=B0?= =?UTF-8?q?=EC=A0=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_0148XFUpPfpiuTjxF1EK9c95 --- .../M02_MasterTemplete_Store.py | 8 +++++++ .../M02_MasterTemplete_UI_Main.ts | 4 ++-- resources/tester/test_m02_store.py | 21 ++++++++++++++++--- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/M02_MasterTemplete/M02_MasterTemplete_Store.py b/M02_MasterTemplete/M02_MasterTemplete_Store.py index 519a58bf..5ee4c9e7 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_Store.py +++ b/M02_MasterTemplete/M02_MasterTemplete_Store.py @@ -65,9 +65,17 @@ def read(kind: str, name: str) -> dict[str, Any]: return {"종류": kind, "이름": name, "판": version_of(raw), "문서": json.loads(raw)} +def _check_skeleton(kind: str, doc: Any) -> None: + """빈 문서·뼈대 없는 문서는 거절 — 표는 열 목록 · 도면은 entities 목록.""" + key = "열" if kind == "table" else "entities" + if not isinstance(doc, dict) or not isinstance(doc.get(key), list): + raise StoreError(400, f"양식 문서에 「{key}」 목록이 없음 — 저장하지 않음") + + def write(kind: str, name: str, version: str, doc: Any) -> dict[str, Any]: """`version` 이 빈 글이면 새로 만듦(이미 있으면 409) · 아니면 그 판일 때만 덮어씀.""" path = _path(kind, name) + _check_skeleton(kind, doc) with _LOCK: have = version_of(path.read_bytes()) if path.is_file() else "" if have != (version or ""): diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts b/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts index 5418cd04..4da5b2a4 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts @@ -23,7 +23,7 @@ export interface Selection { } interface EditorHandle { - getDoc: () => unknown; + getDoc: () => unknown | Promise; destroy: () => void; } @@ -162,7 +162,7 @@ export function createMain(isAdmin: boolean): MainHandle { if (!sel) return; const at = sel; try { - const doc = editor ? editor.getDoc() : loaded; + const doc = editor ? await editor.getDoc() : loaded; const info = await saveTemplate(at.layer, at.kind, at.name, at.projectId, version, doc); version = info.판; loaded = doc; diff --git a/resources/tester/test_m02_store.py b/resources/tester/test_m02_store.py index e271c248..c6090378 100644 --- a/resources/tester/test_m02_store.py +++ b/resources/tester/test_m02_store.py @@ -25,7 +25,9 @@ def test_new_save_reopen_delete_and_stale(client): assert made.status_code == 200 v1 = made.json()["판"] assert ( - client.put("/api/m02/templates/table/집계표", json={"판": "", "문서": {}}).status_code + client.put( + "/api/m02/templates/table/집계표", json={"판": "", "문서": {"열": []}} + ).status_code == 409 ) @@ -34,7 +36,7 @@ def test_new_save_reopen_delete_and_stale(client): saved = client.put("/api/m02/templates/table/집계표", json={"판": v1, "문서": {"열": [1, 2]}}) assert saved.status_code == 200 and saved.json()["판"] != v1 - stale = client.put("/api/m02/templates/table/집계표", json={"판": v1, "문서": {}}) + stale = client.put("/api/m02/templates/table/집계표", json={"판": v1, "문서": {"열": []}}) assert stale.status_code == 409 and stale.json()["detail"]["stale"] == ["집계표"] rows = client.get("/api/m02/templates").json() @@ -46,9 +48,22 @@ def test_new_save_reopen_delete_and_stale(client): def test_bad_names_and_kinds(client): assert ( - client.put("/api/m02/templates/table/a..b", json={"판": "", "문서": {}}).status_code == 200 + client.put("/api/m02/templates/table/a..b", json={"판": "", "문서": {"열": []}}).status_code + == 200 ) assert ( client.put("/api/m02/templates/table/.숨김", json={"판": "", "문서": {}}).status_code == 422 ) assert client.put("/api/m02/templates/other/x", json={"판": "", "문서": {}}).status_code == 404 + + +def test_empty_doc_rejected_and_file_untouched(client): + good = {"format": 6, "entities": [{"type": "line"}]} + made = client.put("/api/m02/templates/drawing/도", json={"판": "", "문서": good}) + v = made.json()["판"] + for bad in ({}, {"format": 6}, {"entities": "x"}, []): + r = client.put("/api/m02/templates/drawing/도", json={"판": v, "문서": bad}) + assert r.status_code == 400 + assert client.put("/api/m02/templates/table/표", json={"판": "", "문서": {}}).status_code == 400 + assert client.get("/api/m02/templates/drawing/도").json()["문서"] == good + assert client.get("/api/m02/templates/table/표").status_code == 404