# -*- coding: utf-8 -*- """B08 조합 [연결 끊기](PLAN 156-1 · 156-3) — 임시 저장소(154-2 시험 바탕 그대로). - 링크 길에 null = 그 시설 칸만 비움 · 다른 시설 · 칸 · 관 지점 노선 지문 그대로 · 구조물 판 +1 - 끊기는 사본 맞춤을 안 부름 — 사본 폴더 그대로(고아 정리 안 함) - 집계 응답 `facilities` = 모든 시설 칸(연결된 칸 포함) · 끊은 시설은 `unlinked` 로 - 화면 = 연결된 칸만 [연결 끊기] · 확인 창 · 초안이면 막음 · 단추 = 모달 [닫기] 양식 """ from __future__ import annotations import asyncio from pathlib import Path from B08_Quantity import B08_Quantity_Router_Summary as summary from M02_MasterTemplete import M02_Item_Snapshot as snap from M02_MasterTemplete import M02_Project_Items_Router as items_router from M02_MasterTemplete.StructOptions import M02_SO_Definition as definition from resources.tester.test_154_2_b08_direct_link import ( # noqa: F401 — 시험 저장소 MS, PIPE, ROOT, _points, _structures, root, ) def _folders(root: Path) -> set[str]: return {p.name for p in snap.structures_dir(root).iterdir() if p.is_dir()} def _unlink(monkeypatch, root: Path, copy: str, facility: str | None) -> dict: async def fake_root(session, project_id): return root def no_sync(*args): raise AssertionError("끊기는 사본 맞춤을 부르지 않음") monkeypatch.setattr(items_router, "_root", fake_root) monkeypatch.setattr(items_router, "safe_sync", no_sync) body = items_router.LinkBody(summary_item=None, facility=facility) return asyncio.run(items_router.link_item("proj", copy, body, session={})) def test_구조물_끊기_그_칸만_사본_그대로(root, monkeypatch): folders, points = _folders(root), _points(root) assert snap.folder_name("st1") in folders reply = _unlink(monkeypatch, root, "st1", None) assert reply == {"폴더": snap.folder_name("st1"), "snapshot_failed": []} got = _structures(root) assert got["revision"] == 5 st0, st1 = got["structures"] assert st1.get("summary_item") is None and st1["chainage_m"] == 400 assert st0.get("summary_item") is None and st0["chainage_m"] == 300 assert _points(root) == points # 관 지점 · 노선 지문 그대로 assert _folders(root) == folders # 사본 폴더 그대로 assert "st1" not in snap.linked_snapshots(root) def test_관_그룹_끊기_다른_시설_그대로(root, monkeypatch): folders, before = _folders(root), _points(root) _unlink(monkeypatch, root, "pipe@100.0__grv6lny", "pipe") after = _points(root) assert after["route_signature"] == "sig-1" assert "group_items" not in after["points"][0] assert after["points"][1:] == before["points"][1:] # 같은 측점 박스 · 다른 관 그대로 assert _structures(root)["structures"][1]["summary_item"] == MS assert _folders(root) == folders def test_집계_모든_시설_칸_끊으면_미연결(root, monkeypatch): names = definition.read()["종류"]["pipe"]["이름"] designs = [{"chainage_m": 100.0, "design": {"pipe_length_m": 12}}] first = summary.pipe_summary(root, designs, names, 6.0) assert not any(u["id"] == "st1" for u in first["unlinked"]) st1 = next(f for f in first["facilities"] if f["id"] == "st1") assert st1["slots"][0] == {"copy": "st1", "group": None, "item": MS, "linked": True} pipe = next( f for f in first["facilities"] if f["id"] == "pipe@100.0" and f["type_id"] == "pipe" ) assert any(s["copy"] == "pipe@100.0__grv6lny" and s["linked"] for s in pipe["slots"]) assert {u["id"] for u in first["unlinked"]} <= {f["id"] for f in first["facilities"]} _unlink(monkeypatch, root, "st1", None) after = summary.pipe_summary(root, designs, names, 6.0) st1 = next(u for u in after["unlinked"] if u["id"] == "st1") assert st1["slots"][0]["linked"] is False and st1["chainage_m"] == 400 assert PIPE == _points(root)["points"][0]["group_items"]["grv6lny"] # 다른 칸 그대로 def test_화면_연결된_칸만_끊기_닫기_양식(): ui = (ROOT / "B08_Quantity/B08_Quantity_UI_Link.ts").read_text(encoding="utf-8") assert "if (slot.linked) {" in ui assert 'act(L("B08_Quantity_Link_Unlink"), () => unlink(projectId, facility, slot))' in ui assert ".link(slot.copy, facility.facility, null)" in ui assert 'showConfirmDialog(L("B08_Quantity_Link_UnlinkAsk")' in ui assert ui.count("hasPendingDesign(projectId)") == 2 # 선택 · 끊기 둘 다 막음 assert 'variant: "ghost",\n size: "sm"' in ui # 칩 안 작은 ghost(163-1) modal = (ROOT / "ui_template/ui_template_modal.ts").read_text(encoding="utf-8") assert 'label: opts.closeLabel,\n variant: "ghost",\n onClick' in modal page = (ROOT / "B08_Quantity/B08_Quantity_UI_Summary.ts").read_text(encoding="utf-8") assert "facilities: unlinkedAt(saved?.facilities ?? [], station)" in page sheets = (ROOT / "B08_Quantity/B08_Quantity_UI_Drawing.ts").read_text(encoding="utf-8") assert "linkSection(request.projectId, request.facilities" in sheets def test_156_4_구조물도_모달_큰_창_세_칸(): sheets = (ROOT / "B08_Quantity/B08_Quantity_UI_Drawing.ts").read_text(encoding="utf-8") assert "wide: true" in sheets css = (ROOT / "B08_Quantity/B08_Quantity_UI_Drawing.css").read_text(encoding="utf-8") assert "min(1320px" not in css # 자기 폭 없음 — 공용 wide 그대로 # 170-1 — 차례 늘 도면 › 일위대가 › 면적산출(뒤집기 없음) · 폭 2 : 3 : 3 assert "minmax(0, 2fr) minmax(0, 3fr) minmax(0, 3fr)" in css assert " order: " not in css assert ".b08-sheets__combo + .b08-sheets__combo" in css