"""Protected template test (2026-10-13): save / list / create from template / protection. No Rhino. Uses its own data root 검증/template_test_data (recreated) with copies of the two real templates.""" import json import os import shutil import sys from pathlib import Path APP = Path(__file__).resolve().parents[1] TD = APP / "검증" / "template_test_data" if TD.exists(): shutil.rmtree(TD) shutil.copytree(APP / "data" / "_템플릿", TD / "_템플릿", ignore=shutil.ignore_patterns("_이전")) shutil.copytree(APP / "data" / "운곡교P1", TD / "원본", ignore=shutil.ignore_patterns("runs", "backup", "drawings")) os.environ["PIERAPP_DATA"] = str(TD) sys.path.insert(0, str(APP / "web")) import app as webapp # noqa: E402 import store # noqa: E402 ok = True def check(name, cond, detail=""): global ok ok &= bool(cond) print("PASS" if cond else "FAIL", name, str(detail)[:200], flush=True) c = webapp.app.test_client() names = [t["name"] for t in store.templates()] # 2026-10-13: + Ramp-A교P1형 (third form) check("templates listed", names == ["Ramp-A교P1형", "산외교울산P1형", "산외교울산P2형", "새재1교P1형", "옥성교P1형", "운곡교P1형"], names) # + 옥성교P1형 2026-10-14, + 산외교울산P2형 2026-10-15 check("templates are not piers", store.piers() == ["원본"], store.piers()) h = c.get("/").get_data(as_text=True) check("index shows protected group and table", 'label="보호 템플릿"' in h and 'value="tpl:산외교울산P1형"' in h and "직접기초(말뚝 없음)" in h and 'label="기존 교각 복사"' in h) r = c.post("/pier/new", data={"name": "울산형새교각", "type": "T형", "template": "tpl:산외교울산P1형"}, follow_redirects=True) d = TD / "울산형새교각" same = all((d / f).read_bytes() == (TD / "_템플릿" / "산외교울산P1형" / f).read_bytes() for f in ("params.json", "expected.json")) info = json.loads((d / "pier.json").read_text(encoding="utf-8")) check("pier from protected template", r.status_code == 200 and same and info["template"] == "템플릿: 산외교울산P1형", info) p = store.Pier("울산형새교각") check("new pier has the Ulsan scope and needs confirmation", p.scope() == ["G1", "G2", "G3", "G4", "G5"] and webapp.unconfirmed(p), len(webapp.unconfirmed(p))) check("pier page renders", c.get("/pier/울산형새교각").status_code == 200) # editing / deleting the new pier leaves the template unchanged p.set_section("params", "pier_id", "변경") before = (TD / "_템플릿" / "산외교울산P1형" / "params.json").read_bytes() c.post("/pier/울산형새교각/delete", data={"confirm_name": "울산형새교각"}) check("template unchanged after edit + delete", (TD / "_템플릿" / "산외교울산P1형" / "params.json").read_bytes() == before and json.loads(before)["pier_id"] == "P1") # copy of an existing pier still works (plain name) r = c.post("/pier/new", data={"name": "복사본", "type": "T형", "template": "원본"}, follow_redirects=True) check("copy of an existing pier", (TD / "복사본" / "params.json").exists() and json.loads((TD / "복사본" / "pier.json").read_text(encoding="utf-8"))["template"] == "원본") # refusals r = c.post("/pier/new", data={"name": "_템플릿", "type": "T형", "template": "원본"}, follow_redirects=True) check("reserved name refused", "앱이 쓰는 폴더 이름" in r.get_data(as_text=True) and not (TD / "_템플릿" / "pier.json").exists()) r = c.post("/pier/new", data={"name": "없는템플릿", "type": "T형", "template": "tpl:없음"}, follow_redirects=True) check("missing template refused", "템플릿이 없습니다" in r.get_data(as_text=True) and not (TD / "없는템플릿").exists()) check("template folder cannot be deleted as a pier", c.post("/pier/_템플릿/delete", data={"confirm_name": "_템플릿"}).status_code == 404) # re-save keeps the old snapshot store.save_template("원본", "운곡교P1형", "재저장 시험") old = list((TD / "_템플릿" / "_이전").glob("운곡교P1형_*")) check("re-save keeps old snapshot, _이전 not listed", len(old) == 1 and [t["name"] for t in store.templates()] == names, [t["name"] for t in store.templates()]) sys.exit(0 if ok else 1)