fix(M02): 도면 양식 저장이 빈 {} 로 덮이던 흠 — getDoc 을 await · 서버는 뼈대 없는 문서를 400 으로 거절
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0148XFUpPfpiuTjxF1EK9c95
This commit is contained in:
@@ -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 ""):
|
||||
|
||||
@@ -23,7 +23,7 @@ export interface Selection {
|
||||
}
|
||||
|
||||
interface EditorHandle {
|
||||
getDoc: () => unknown;
|
||||
getDoc: () => unknown | Promise<unknown>;
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user