"""302-3 인쇄 미리보기 「수량산출」 — B08 탭 표를 화면 없이 PrintSheet 로 모음. 견본 = 검증_sub8 서버 GET 응답(구조물 집계표만 손 견본 · `fixtures/b08_302_3_print_quantity.json`). `helper_302_3_print_quantity.mjs` 가 화면 코드를 node 로 묶어 같은 응답 · 같은 캐시로 모으기(`collectQuantitySheets`) 와 B08 화면이 부르는 표 짜기를 나란히 냄 → 쪽 차례 · 이름 · 열 머리 · 줄 대조. """ from __future__ import annotations import base64 import copy import json import shutil import subprocess from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] HELPER = Path(__file__).with_name("helper_302_3_print_quantity.mjs") FIXTURE = json.loads( (Path(__file__).with_name("fixtures") / "b08_302_3_print_quantity.json").read_text("utf-8") ) PNG = b"\x89PNG\r\n\x1a\n302-3" B08 = ROOT / "B08_Quantity" def _run(responses: dict, drafts: list | None = None, screen: bool = True) -> dict: if shutil.which("node") is None: pytest.skip("node 가 없음") job = { "project": "p302", "responses": responses, "image_b64": base64.b64encode(PNG).decode(), "drafts": drafts or [], "screen": screen, } out = subprocess.run( ["node", str(HELPER), str(ROOT)], input=json.dumps(job, ensure_ascii=False), cwd=ROOT, capture_output=True, text=True, encoding="utf-8", timeout=300, check=True, ) return json.loads(out.stdout.strip().splitlines()[-1]) def _responses() -> dict: data = copy.deepcopy(FIXTURE) routes = data["/quantity/milestone"]["data"]["routes"] # 경로 그림 둘 — 사용자 그림 · 서버 캡처(auto) 꼴 data["/quantity/milestone"]["images"] = { routes[0]["id"]: "a.png", routes[2]["id"]: "auto/b.png", } return data @pytest.fixture(scope="module") def base() -> dict: return _run(_responses()) def _same(sheet: dict, screen: dict) -> None: for key in ("heads", "rows", "total", "mergeSame", "frozen"): assert sheet[key] == screen[key], (sheet["id"], key) def test_쪽_차례_이름(base): stations = FIXTURE["/quantity/hydro"]["stations"] names = [s["name"] for s in base["sheets"]] head = ["구조물 집계표", "총괄자재수량집계표", "콘크리트소요자재집계표", "자재수량집계표"] assert names[:4] == head assert names[4:8] == base["screen"]["earthworkNames"] == [ "토공집계표", "토적표", "평균운반거리", "종방향 무대운반거리" ] # fmt: skip hydro = names[8:-1] assert len(hydro) == len(stations) == 11 # 배수지점 측점마다 한 장 assert all(n.startswith("수리집수면적표 ") for n in hydro) kinds = [n for n, st in zip(hydro, stations) if st["kind"]] assert kinds and all(n.endswith(" 세월교") for n in kinds) assert names[-1] == "거리이정표" assert len({s["id"] for s in base["sheets"]}) == len(names) def test_표_짜기_B08_화면과_같음(base): sheets = {s["id"]: s for s in base["sheets"]} screen = base["screen"] _same(sheets["summary"], screen["summary"]) for sid in ("material:material", "material:concrete", "material:quantity", "earthwork:summary", "earthwork:sheet"): # fmt: skip _same(sheets[sid], screen[sid]) # 운반 = 화면 표 + 아래 평균운반거리 줄(번호 칸 글) for sid in ("earthwork:haul", "earthwork:free"): want = screen[sid] assert sheets[sid]["heads"] == want["heads"] and sheets[sid]["total"] == want["total"] assert sheets[sid]["rows"] == want["rows"] + [{"no": a} for a in want["averages"]] assert "평균운반거리" in want["averages"][0] _same(sheets["milestone"], {**screen["milestone"]}) def test_견본_수치(base): sheets = {s["id"]: s for s in base["sheets"]} summary = sheets["summary"] assert summary["heads"] == ["측점", "관 공›Φ800m/m›관매설›m", "관 공›Φ800m/m›커플링›ea"] assert len(summary["rows"]) == 2 and summary["total"]["pipe:len|800"] == 20.5 sheet = sheets["earthwork:sheet"] assert ( len(sheet["heads"]) == 35 and len(sheet["rows"]) == 8 ) # 견본 32칸 + 측점 · 거리 · 보정량계 assert ( sheet["orientation"] == "landscape" and sheets["earthwork:summary"]["orientation"] == "portrait" ) assert sheets["material:concrete"]["rows"] and len(sheets["material:concrete"]["rows"]) == 5 hydro = next(s for s in base["sheets"] if s["id"].startswith("hydro:")) assert hydro["heads"] == ["구분", "항목", "값"] value = {r["item"]: r["value"] for r in hydro["rows"]} calc = FIXTURE["/quantity/hydro/calc"] assert value["Q : 홍수량 (㎥/s)"] == f"{calc['flow']:.2f}" assert value["Qd : 설계 홍수량 (㎥/s) = Q × 2.0"] == f"{calc['design_flow']:.2f}" assert value["R24 : 확률강우량 (mm/day)"] == f"{calc['r24_mm']:.1f}" # 서버 계산 = 화면과 같은 입력(기본 < 장 기본 < 연결 < 저장 < 초안) assert base["posts"] == base["screen"]["hydroInputs"] def test_거리이정표_지도_그림(base): milestone = base["sheets"][-1] routes = FIXTURE["/quantity/milestone"]["data"]["routes"] assert len(milestone["images"]) == 2 for image, route in zip(milestone["images"], (routes[0], routes[2])): head, body = image["src"].split(",", 1) assert head == "data:image/png;base64" and base64.b64decode(body) == PNG assert image["caption"] == (route["name"].strip() or route["rows"][0]["item"]) def test_캐시_값이면_화면에_보이는_그_값(): responses = _responses() stations = responses["/quantity/hydro"]["stations"] key = f"{stations[0]['chainage_m']:.3f}" milestone = copy.deepcopy(responses["/quantity/milestone"]["data"]) milestone["routes"][0]["name"] = "가나상회" got = _run(responses, [["b08-hydro", {key: {"area_km2": 0.5}}], ["b08-milestone", milestone]]) assert got["posts"][0]["area_km2"] == 0.5 == got["screen"]["hydroInputs"][0]["area_km2"] hydro = next(s for s in got["sheets"] if s["id"] == f"hydro:{key}") assert any(r["value"].startswith("0.5 (") for r in hydro["rows"]) sheet = got["sheets"][-1] assert any(r.get("company") == "가나상회" for r in sheet["rows"]) _same(sheet, got["screen"]["milestone"]) def test_영역_하나_실패_까닭_한_장(): responses = _responses() del responses["/quantity/earthwork"] got = _run(responses, screen=False) earth = [s for s in got["sheets"] if s["id"].startswith("earthwork")] assert len(earth) == 1 and earth[0]["name"] == "토공" assert earth[0]["rows"][0]["note"].startswith("불러오지 못했습니다") assert got["sheets"][0]["name"] == "구조물 집계표" and got["sheets"][-1]["name"] == "거리이정표" def test_B08_화면이_같은_함수를_씀(): read = lambda name: (B08 / f"B08_Quantity_{name}.ts").read_text("utf-8") # noqa: E731 assert "summaryTable(view)" in read("UI_Summary") and "loadSummary(projectId)" in read( "UI_Summary" ) material = read("UI_Material") assert "materialOptions(built)" in material and "loadMaterials(projectId)" in material assert "earthworkSheetOptions(table, interval)" in read("UI_Earthwork") sheets = read("UI_Earthwork_Sheets") assert "earthworkSummaryOptions(table)" in sheets and "haulOptions(groups, caption)" in sheets assert "hydroInputs(sheet, station, drafts)" in read("UI_Hydro") assert "milestoneTable(data, siteInfo()" in read("UI_Milestone") # 표 짜기 파일은 화면 없음 for name in ( "Summary_Table", "Material_Tables", "Earthwork_Tables", "Hydro_Table", "Milestone_Table", ): text = read(name) assert "document." not in text and "createDataTable" not in text, name assert len(text.splitlines()) <= 700 printer = (ROOT / "B09_Estimation/draft/B09_Draft_Print_Quantity.ts").read_text("utf-8") assert 'from "./B09_Draft_Print_Types"' in printer and "createDataTable" not in printer