"""구조물 조합 고르기 모달 로딩 속도 (PLAN 156-2 · 임시 저장소 · 임시 마스터 사본). - 목록 `?folder=` = 그 복사본 하나만(원본 대조도 그 하나만) · 값은 통째 목록의 그 줄과 같음 - 원본 대조(`build_source`)는 재료 후보 찾기를 같은 조건마다 한 번만 · `m01.logic`(검사 · 단가 미리보기) 안 부름 - 값은 그대로 — 기억을 비우고 다시 모아도 단가 묶음 · 원본판이 같음 - 모달 쪽 — 목록은 그 폴더만 · 복사본 부분 · 셈은 한 번 보이는 동안 한 번만 """ from __future__ import annotations import time from pathlib import Path from typing import Any from fastapi import FastAPI from fastapi.testclient import TestClient from common_util.common_util_auth import verify_session from M01_MasterData import M01_MasterData_Store as m01 from M02_MasterTemplete import M02_Item_Copy as copies from M02_MasterTemplete import M02_Item_Prices as prices from M02_MasterTemplete import M02_MasterTemplete_Router_Layers as layer_routes from M02_MasterTemplete import M02_Project_Items_Router as items_router from M02_MasterTemplete.M02_Item_Source import build_source, version_of from resources.tester.test_item_copy_complete import ( # noqa: F401 — 고정물 PIPE, _save, master_copy, world, ) UI = Path(__file__).resolve().parents[2] / "ui_template" WALL = {"layer": "system", "owner": None, "key": "SS000022", "name": "돌쌓기"} def _two(root: Path) -> None: """관 지점 둘 — 같은 항목 하나 · 다른 항목 하나.""" _save( root, [ {"chainage_m": 40.0, "source": "user", "summary_item": PIPE}, {"chainage_m": 80.0, "source": "user", "summary_item": WALL}, ], ) def test_목록_folder_는_그_하나만_값은_통째와_같음(world, monkeypatch): # noqa: F811 root = world["root"] _two(root) whole = {row["폴더"]: row for row in copies.list_items(root)} assert set(whole) == {"pipe@40.0", "pipe@80.0"} calls: list[Any] = [] real = copies.source_changed monkeypatch.setattr(copies, "source_changed", lambda m: calls.append(m) or real(m)) assert copies.list_items(root, "pipe@80.0") == [whole["pipe@80.0"]] assert len(calls) == 1 # 원본 대조 = 그 하나만 assert copies.list_items(root, "없음") == [] def test_길_folder_거름(world, monkeypatch): # noqa: F811 root = world["root"] _two(root) async def fake_row(project_id: str) -> dict[str, Any]: return {"company_id": 3, "storage_path": str(root)} monkeypatch.setattr(layer_routes, "_project_row", fake_row) monkeypatch.setattr(layer_routes, "resolve_stored_project_path", lambda path: path) app = FastAPI() app.include_router(items_router.router) app.dependency_overrides[verify_session] = lambda: {"role": "USER", "company_id": 3} client = TestClient(app) base = "/api/projects/p1/items" assert len(client.get(base).json()["항목"]) == 2 got = client.get(base, params={"folder": "pipe@40.0"}).json()["항목"] assert [row["폴더"] for row in got] == ["pipe@40.0"] def test_원본_대조는_같은_후보_찾기를_한_번만(world, monkeypatch): # noqa: F811 found: list[str] = [] real = prices.mf.candidates monkeypatch.setattr( prices.mf, "candidates", lambda w, c, *a: found.append(repr(c)) or real(w, c, *a) ) monkeypatch.setattr(prices, "_FOUND", (None, {})) def no_logic(key: str) -> dict: raise AssertionError("m01.logic 은 검사 · 단가 미리보기까지 돌아 느림 — 파일 이름만 찾음") monkeypatch.setattr(m01, "logic", no_logic) first = build_source(PIPE) once = len(found) assert once and len(set(found)) == once # 같은 조건은 한 번만 again = build_source(PIPE) assert len(found) == once # 두 번째 대조 = 후보 찾기 안 함 assert version_of(again) == version_of(first) def test_기억을_비워도_단가_묶음_원본판_같음(world, monkeypatch): # noqa: F811 root = world["root"] _two(root) before = {row["폴더"]: row["원본판"] for row in copies.list_items(root)} monkeypatch.setattr(prices, "_FOUND", (None, {})) for folder, item in (("pipe@40.0", PIPE), ("pipe@80.0", WALL)): assert version_of(build_source(item)) == before[folder] assert not any(row["마스터바뀜"] for row in copies.list_items(root)) def test_목록_시간_상한(world): # noqa: F811 """복사본 둘 · 두 번째 목록(기억 있음)은 0.5초 안 — 옛 길은 항목마다 2~5초.""" root = world["root"] _two(root) copies.list_items(root) start = time.perf_counter() copies.list_items(root) assert time.perf_counter() - start < 0.5 def test_모달은_그_폴더만_읽고_부분_셈은_한_번(): detail = (UI / "ui_template_item_picker_detail.ts").read_text(encoding="utf-8") assert "infos ??= await items.list(folder);" in detail assert "copyReads.set(part" in detail and "(copyCalc ??= " in detail assert "items.read(" not in detail and "items?.calc(" not in detail # 다 한 번 부름 길로 show = detail[detail.index("function show(") :] assert "copyReads = new Map();" in show and "copyCalc = null;" in show # 보일 때마다 새로 project = (UI / "ui_template_item_picker_project.ts").read_text(encoding="utf-8") assert "folder ? `${base}?folder=${enc(folder)}` : base" in project