- Code\rebar_pipeline, review_pipeline, pier-program을 정션에서 실제 폴더로 전환 - Code\rhino_mcp 및 CLAUDE_rhino배경.md 추가, .mcp.json은 PYTHONPATH로 새 위치 사용 - pier-program 내부 .git 제거(이력은 backup\pier-program_20261028.bundle에 보관) - worktree 가이드 반영(CLAUDE.local.md, HANDOFF.md) - D:\Rhino-MCP 경로 참조를 새 위치로 치환 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
72 lines
3.4 KiB
Python
72 lines
3.4 KiB
Python
"""Web flow test for a pier with a reduced gate scope (Ulsan P1 copy limited to G1, G5), 2026-10-13. Uses a copy
|
|
of data/산외교울산P1 in 검증/web_ulsan_test_data (recreated) whose params scope is set to G1, G5 (the real pier
|
|
has G1~G5 since the rebar stage); real Rhino runs (one batch session). Full scope: test_web_ulsan_full.py."""
|
|
import json
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
APP = Path(__file__).resolve().parents[1]
|
|
TD = APP / "검증" / "web_ulsan_test_data"
|
|
if TD.exists():
|
|
shutil.rmtree(TD)
|
|
# source: the protected template (the pier data/산외교울산P1 itself was deleted in the app on 2026-10-13)
|
|
shutil.copytree(APP / "data" / "_템플릿" / "산외교울산P1형", TD / "울산시험", ignore=shutil.ignore_patterns("template.json"))
|
|
_pp = TD / "울산시험" / "params.json"
|
|
_pj = json.loads(_pp.read_text(encoding="utf-8"))
|
|
_pj["scope"] = ["G1", "G5"]
|
|
_pp.write_text(json.dumps(_pj, ensure_ascii=False, indent=1), encoding="utf-8")
|
|
os.environ["PIERAPP_DATA"] = str(TD)
|
|
sys.path.insert(0, str(APP / "web"))
|
|
import app as webapp # noqa: E402
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
from testlib import confirm_required # noqa: E402
|
|
import store # noqa: E402
|
|
|
|
P, BY = "울산시험", "시험자"
|
|
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()
|
|
p = store.Pier(P)
|
|
check("scope = G1, G5", p.scope() == ["G1", "G5"], p.scope())
|
|
need = webapp.unconfirmed(p)
|
|
check("required sections exclude params.piles and rebar sections",
|
|
("params", "piles") not in need and not any(k in ("column_rebar", "footing_rebar", "coping_rebar") for _, k in need),
|
|
need)
|
|
h = c.get(f"/pier/{P}").get_data(as_text=True)
|
|
check("pier page lists out-of-scope gates", "범위 밖: G2, G3, G4" in h and ">G1~G5 일괄 승인<" in h)
|
|
confirm_required(c, webapp, store, P, BY)
|
|
check("bulk confirm", not webapp.unconfirmed(store.Pier(P)), len(need))
|
|
r = c.post(f"/pier/{P}/gate/G3/run", follow_redirects=True)
|
|
check("out-of-scope gate cannot run", "범위 밖" in r.get_data(as_text=True))
|
|
c.post(f"/pier/{P}/gates/run_all", data={"by": BY})
|
|
t0 = time.time()
|
|
while time.time() - t0 < 900:
|
|
b = webapp.batch_state(store.Pier(P))
|
|
if b and b["state"] != "실행 중":
|
|
break
|
|
time.sleep(5)
|
|
p = store.Pier(P)
|
|
check("batch completes", b["state"] == "완료", b["log"])
|
|
check("G1 and G5 approved", p.gate_status("G1")["approved"] and p.gate_status("G5")["approved"])
|
|
check("G1 PASS 104/104, G5 PASS 75/75",
|
|
p.gate_status("G1")["report"]["summary"]["passed"] == 104 and p.gate_status("G5")["report"]["summary"]["passed"] == 75,
|
|
{g: p.gate_status(g)["report"]["summary"] for g in ("G1", "G5")})
|
|
r = c.post(f"/pier/{P}/report", data={"by": BY}, follow_redirects=True)
|
|
md = next(p.run_dir.glob("보고서_*.md")).read_text(encoding="utf-8")
|
|
check("report: 2/2 gates, out-of-scope rows", "게이트 진행: 2/2 실행 (범위 밖: G2, G3, G4)" in md and md.count("| 범위 밖 |") == 3
|
|
and "전 게이트 PASS·승인" in md)
|
|
check("G5 model download", c.get(f"/pier/{P}/download/G5").status_code == 200)
|
|
for page in ("/", f"/pier/{P}", f"/pier/{P}/gates", f"/pier/{P}/candidates", f"/pier/{P}/search?q=EL"):
|
|
check(f"page renders {page}", c.get(page).status_code == 200)
|
|
sys.exit(0 if ok else 1)
|