test(725-5): M02 낡은 시험 8 파일 고침 - 소스 글·시간 표기 변화를 새 글로 따라감 (코드 안 고침)

- test_m02_item_list: 헬퍼 가짜 모듈에 진짜 common_util_time 연결 · TZ=Asia/Seoul 고정 · 서버 수정일 = UTC Z 20자(593-1)
- test_m02_hopyo_screen: 도면 양식 칸이 조건 없이 놓임(720-4) - 찾는 글 drawing ? 를 drawing, 로
- test_m02_layer_screen: const drawing = opt.isSystem 를 조건 없는 section(L("M02_KindDrawing")) 글로
- test_m02_practice: 차례 확인 글 drawing ? 를 drawing, 로
- test_m02_structure_def_screen: 지난 판 저장일 = UTC Z 초 단위(593-1) - 길이 16 을 꼴 확인으로
- test_m02_structure_option_groups: 옵션 줄은 716-3 에서 좌측 칸을 떠남 - 있다는 단언을 없다는 단언으로
- test_m02_structure_options: 같은 까닭 - 옵션 줄 단언을 없음으로, 구조물 조합 → 도면 양식 → 실무문서 차례만 확인
- test_m02_df_convert: 옛 사본 폴더가 낡아 어긋남 - 정본과 다르면 다시 복사 · 전수 대조는 열 줄에 하나(약 1분, AISLO_DF_FULL=1 이면 전수 약 12분) · 일꾼 동시 Node 호출로 드문 실패는 혼자 한 번 더

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-02 00:47:00 +09:00
co-authored by Claude Sonnet 5.5
parent 79c48ee480
commit 05234f1f6a
9 changed files with 44 additions and 24 deletions
@@ -15,8 +15,10 @@ const load = (name, stubs) => {
return box.exports;
};
const model = load("M02_MasterTemplete_TreePanel_Model.ts", {});
const time = load("../common_util/common_util_time.ts", {}); // 날짜 글 = 진짜(PC 시간대는 시험이 고정)
const m = load("M02_MasterTemplete_ItemList.ts", {
"./M02_MasterTemplete_TreePanel_Model": model,
"../common_util/common_util_time": time,
});
const row = (열, 경로, extra = {}) => ({
+25 -7
View File
@@ -22,6 +22,9 @@ ROOT = Path(__file__).resolve().parents[2]
COPY = ROOT / "tmp" / "master_copy_36"
REPORT = ROOT / "tmp" / "convert_report_36.json"
WORKERS = 6
STEP = (
1 if os.environ.get("AISLO_DF_FULL") else 10
) # 지킴 = 열 줄에 하나(약 1분) · 전수(약 12분) = AISLO_DF_FULL=1
# 일꾼(따로 뜬 파이썬 · 사본 폴더) — 표준 입력 = 키 목록 JSON · 표준 출력 = 줄마다 결과 JSON
WORKER = r"""
@@ -85,11 +88,16 @@ for key in json.loads(sys.stdin.read()):
def _copy() -> None:
"""사본 폴더 — 없으면 정본 JSON 을 복사(정본은 읽기만)."""
if not any(COPY.glob("*.json")):
COPY.mkdir(parents=True, exist_ok=True)
for f in (ROOT / "resources" / "master_data").glob("*.json"):
shutil.copy2(f, COPY / f.name)
"""사본 폴더 — 정본과 다르거나 없는 JSON 만 복사(정본은 읽기만 · 옛 사본이 남아 어긋나지 않게)."""
COPY.mkdir(parents=True, exist_ok=True)
for f in (ROOT / "resources" / "master_data").glob("*.json"):
to = COPY / f.name
if (
not to.exists()
or to.stat().st_mtime != f.stat().st_mtime
or to.stat().st_size != f.stat().st_size
):
shutil.copy2(f, to)
def _env() -> dict:
@@ -237,13 +245,23 @@ def test_꼴_상자_선_규칙_배치() -> None:
# ── 전수 대조 ────────────────────────────────────────────────────────
def test_전수_대조_옛_셈_같음() -> None:
_copy()
keys = _keys()
assert len(keys) >= 1400
all_keys = _keys()
assert len(all_keys) >= 1400
keys = all_keys[::STEP]
chunks = [keys[i::WORKERS] for i in range(WORKERS)]
with ThreadPoolExecutor(WORKERS) as pool:
outs = list(pool.map(lambda c: _run(WORKER, json.dumps(c)), chunks))
rows = [json.loads(x) for out in outs for x in out.splitlines() if x.startswith("{")]
assert len(rows) == len(keys)
# 일꾼이 동시에 Node 계산 묶음을 부르다 드물게 한 번 실패 — 멈춘 줄만 혼자 한 번 더(혼자서도 멈추면 진짜 흠)
again = [
r["key"]
for r in rows
if "흐름 셈 멈춤" in (r["시험입력"]["state"], r["기본 입력"]["state"])
]
if again:
redo = {r["key"]: r for r in map(json.loads, _run(WORKER, json.dumps(again)).splitlines())}
rows = [redo.get(r["key"], r) for r in rows]
report: dict = {"로직": len(rows)}
for kind in ("시험입력", "기본 입력"):
states = [r[kind]["state"] for r in rows]
+1 -1
View File
@@ -341,7 +341,7 @@ def test_좌측_판_자리와_권한():
# 716-3 차례 — 일위대가 › 구조물 조합 › 구분선 › 일위대가 불러오기 › 도면 양식
order = ("precomboSec,", "summary,", "createDivider(),", "section(hopyo.title, hopyo.body),")
assert [host.index(k) for k in order] == sorted(host.index(k) for k in order)
assert host.index("section(hopyo.title, hopyo.body),") < host.index("drawing ?")
assert host.index("section(hopyo.title, hopyo.body),") < host.index("drawing,")
assert "canWrite: opt.canWrite," in side # 구조물 조합 층 쓰기 권한(720-4 함수)
assert "hopyo.paint();" in side.split("function paint(): void {")[1]
hopyo_side = _text("M02_MasterTemplete_Hopyo_Side.ts")
+5 -1
View File
@@ -11,6 +11,7 @@ from __future__ import annotations
import json
import os
import re
import subprocess
import pytest
@@ -30,6 +31,7 @@ def ts() -> dict:
text=True,
encoding="utf-8",
timeout=60,
env={**os.environ, "TZ": "Asia/Seoul"}, # 날짜 칸 = 보는 PC 시간대(593-1) — 한국으로 고정
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
@@ -95,7 +97,9 @@ def test_서버_목록_칸_수정일_labor(world):
root = world["storage"] / "7" / "42" / "templates"
ditch, wall = t.item(["배수", "측구"]), t.item(["옹벽"])
assert set(ditch["수정일"]) >= {"structure", "area"}
assert len(ditch["수정일"]["structure"]) == 19 # 초 단위 ISO
assert re.fullmatch(
r"\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ", ditch["수정일"]["structure"]
) # 초 단위 UTC Z(593-1)
assert (
ditch["labor"]["로직수"] == 0 and ditch["labor"]["합계"] is None
) # 새 열 = 빈 일위대가(39-1b)
+1 -1
View File
@@ -227,7 +227,7 @@ def test_층_길_접두어와_고정_풀림():
)
side = _text(M02 / "M02_MasterTemplete_UI_Side.ts")
assert 'const layer: Layer = "system";' in side # 도면 양식은 시스템 층에만
assert "const drawing = opt.isSystem" in side # 720-4 — 시스템 관리자면 늘
assert 'const drawing = section(L("M02_KindDrawing")' in side # 720-4 — 조건 없이 누구나 늘
main = _text(M02 / "M02_MasterTemplete_UI_Main.ts")
assert 'sel.layer !== "system"' not in main
assert 'return layer === "project" || isAdmin;' in main
+1 -1
View File
@@ -53,7 +53,7 @@ def test_화면_시스템_관리자_마스터만_물음_두_번_읽기만():
side = (M02 / "M02_MasterTemplete_UI_Side.ts").read_text("utf-8")
assert "const practice = opt.isSystem" in side # 720-4 — 시스템 관리자면 늘
order = side.split('className: "m02-side__lists ui-sidebar-panel",')[1].split("});")[0]
assert order.index("drawing ?") < order.index("practice ?") # 도면 양식 아래
assert order.index("drawing,") < order.index("practice ?") # 도면 양식 아래
view = (M02 / "M02_MasterTemplete_Practice.ts").read_text("utf-8")
assert view.count("showConfirmDialog(") == 2
assert "dropPractice(" in view and "createInputField" not in view # 고칠 칸 없음
@@ -11,6 +11,7 @@
from __future__ import annotations
import json
import re
from pathlib import Path
import pytest
@@ -99,7 +100,9 @@ def test_화면_고침을_그대로_받아_판_더하기_1_지난_판_길(copy)
assert got["types"] == [retired(t) for t in sent["types"]]
assert list(got["구조물군"]) == list(sent["구조물군"]) # 순서 그대로
rows = admin.get(f"{URL}/history").json()
assert [r["판"] for r in rows] == [doc["판"]] and len(rows[0]["저장일"]) == 16
assert [r["판"] for r in rows] == [doc["판"]] and re.fullmatch(
r"\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ", rows[0]["저장일"]
)
old = admin.get(f"{URL}/history/{doc['판']}").json()
assert old == doc
assert admin.get(f"{URL}/history/999").status_code == 404
@@ -105,7 +105,7 @@ def test_틀린_그룹은_422_낡은_판은_409(client) -> None:
def test_좌측_정의_줄은_시스템_층에_늘_고쳐_씀은_시스템_관리자만() -> None:
"""PLAN 48-2 — 다른 역할은 읽기만(화면이 칸을 잠금 · 서버 PUT 403)."""
side = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_UI_Side.ts").read_text("utf-8")
assert "옵션: section(options.title, options.body)," in side
assert "section(options.title" not in side # 716-3 — 옵션 줄은 M01 로 옮겨 좌측 칸에서 뺌
folder = ROOT / "M02_MasterTemplete/StructOptions"
assert "buildGroupBlock(" in (folder / "M02_SO_Def.ts").read_text("utf-8")
assert "buildPlaceSelect(" in (folder / "M02_SO_Def_Options.ts").read_text("utf-8")
+4 -11
View File
@@ -109,20 +109,13 @@ def test_없는_종류_키_참거짓_아님은_422_낡은_판은_409(client) ->
def test_좌측_줄은_구조물_집계_양식_아래_한_줄_같은_한_곳에서_지움() -> None:
side = (ROOT / "M02_MasterTemplete/M02_MasterTemplete_UI_Side.ts").read_text("utf-8")
assert "section(options.title, options.body)" in side
# 차례 = 구조물 조합 → 옵션 → 도면 양식(시스템 층에만 · 해시 진입 `reveal` 이 이름으로 · 27-1d)
assert side.index("section(options.title") < side.index('도면: section(L("M02_KindDrawing")')
# 차례 = 구조물 조합(일위대가는 그 하위 · 39-2) → 옵션 → 도면 양식 → 실무문서
# 716-3 — 옵션 줄은 M01 로 옮겨 좌측 칸에서 뺌(차례 = 일위대가 → 구조물 조합 → 불러오기 → 도면 양식 → 실무문서)
assert "options.title" not in side and "options.clear" not in side
order = side.split('className: "m02-side__lists ui-sidebar-panel",')[1].split("});")[0]
assert (
order.index("summary") < order.index("extras.옵션, extras.도면") < order.index("practice")
)
# 실무문서를 열면 이 줄 고름 지움 / 이 줄을 열면 실무문서 고름 지움(자재 조합 · 「전체 항목」 없앰)
assert "onOpen: () => (options.clear(), paint())" in side
assert order.index("summary") < order.index("drawing,") < order.index("practice")
# 실무문서를 열면 이 줄 고름 지움(자재 조합 · 「전체 항목」 없앰)
assert "onOpen: () => (practice?.clear(), paint())" in side
assert "matset" not in side
paint = side.split("function paint(): void {")[1].split("\n }\n")[0]
assert "if (opt.getOpen()) options.clear();" in paint # 양식을 열면 이 줄 고름 지움
# 화면은 정의 한 벌로 읽고 씀(PLAN 48-2) · 보이기 체크는 정의 화면 옵션 줄
api = (ROOT / "M02_MasterTemplete/StructOptions/M02_SO_Api.ts").read_text("utf-8")
assert 'call("/structure-definition")' in api