From 05234f1f6ac6c5026ccb4b480308e879ef474b4f Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 2 Nov 2026 00:47:00 +0900 Subject: [PATCH] =?UTF-8?q?test(725-5):=20M02=20=EB=82=A1=EC=9D=80=20?= =?UTF-8?q?=EC=8B=9C=ED=97=98=208=20=ED=8C=8C=EC=9D=BC=20=EA=B3=A0?= =?UTF-8?q?=EC=B9=A8=20-=20=EC=86=8C=EC=8A=A4=20=EA=B8=80=C2=B7=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=ED=91=9C=EA=B8=B0=20=EB=B3=80=ED=99=94=EB=A5=BC=20?= =?UTF-8?q?=EC=83=88=20=EA=B8=80=EB=A1=9C=20=EB=94=B0=EB=9D=BC=EA=B0=90=20?= =?UTF-8?q?(=EC=BD=94=EB=93=9C=20=EC=95=88=20=EA=B3=A0=EC=B9=A8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- resources/tester/helper_m02_item_list.cjs | 2 ++ resources/tester/test_m02_df_convert.py | 32 +++++++++++++++---- resources/tester/test_m02_hopyo_screen.py | 2 +- resources/tester/test_m02_item_list.py | 6 +++- resources/tester/test_m02_layer_screen.py | 2 +- resources/tester/test_m02_practice.py | 2 +- .../tester/test_m02_structure_def_screen.py | 5 ++- .../test_m02_structure_option_groups.py | 2 +- .../tester/test_m02_structure_options.py | 15 +++------ 9 files changed, 44 insertions(+), 24 deletions(-) diff --git a/resources/tester/helper_m02_item_list.cjs b/resources/tester/helper_m02_item_list.cjs index b1f6baee2..2c3ea7d19 100644 --- a/resources/tester/helper_m02_item_list.cjs +++ b/resources/tester/helper_m02_item_list.cjs @@ -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 = {}) => ({ diff --git a/resources/tester/test_m02_df_convert.py b/resources/tester/test_m02_df_convert.py index 1305f70ef..f103e916d 100644 --- a/resources/tester/test_m02_df_convert.py +++ b/resources/tester/test_m02_df_convert.py @@ -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] diff --git a/resources/tester/test_m02_hopyo_screen.py b/resources/tester/test_m02_hopyo_screen.py index ae2f60164..18f86b438 100644 --- a/resources/tester/test_m02_hopyo_screen.py +++ b/resources/tester/test_m02_hopyo_screen.py @@ -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") diff --git a/resources/tester/test_m02_item_list.py b/resources/tester/test_m02_item_list.py index 45d69c367..760ec858e 100644 --- a/resources/tester/test_m02_item_list.py +++ b/resources/tester/test_m02_item_list.py @@ -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) diff --git a/resources/tester/test_m02_layer_screen.py b/resources/tester/test_m02_layer_screen.py index 24ab18297..744e73968 100644 --- a/resources/tester/test_m02_layer_screen.py +++ b/resources/tester/test_m02_layer_screen.py @@ -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 diff --git a/resources/tester/test_m02_practice.py b/resources/tester/test_m02_practice.py index 68a5284e9..fd303e582 100644 --- a/resources/tester/test_m02_practice.py +++ b/resources/tester/test_m02_practice.py @@ -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 # 고칠 칸 없음 diff --git a/resources/tester/test_m02_structure_def_screen.py b/resources/tester/test_m02_structure_def_screen.py index 108ff9196..6caec3093 100644 --- a/resources/tester/test_m02_structure_def_screen.py +++ b/resources/tester/test_m02_structure_def_screen.py @@ -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 diff --git a/resources/tester/test_m02_structure_option_groups.py b/resources/tester/test_m02_structure_option_groups.py index 144ac9314..ff0022b8f 100644 --- a/resources/tester/test_m02_structure_option_groups.py +++ b/resources/tester/test_m02_structure_option_groups.py @@ -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") diff --git a/resources/tester/test_m02_structure_options.py b/resources/tester/test_m02_structure_options.py index 0bf9bd334..4b91cad56 100644 --- a/resources/tester/test_m02_structure_options.py +++ b/resources/tester/test_m02_structure_options.py @@ -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