Files
Aislo/resources/tester/test_b08_structure_library.py
T
eomsangdonandClaude Opus 5 4f1521eea9 feat(b08): 구조물도 양식 가져오기 — 3단 목록은 고를 때만, 표는 프로젝트에 박힌 양식만 읽음
- 라이브러리 항목을 목록으로(항목마다 AX-ST-<8hex>.json) · 기본 찰쌓기에 코드 부여
- 가져오기: 로그인한 사람의 개인·회사 단 + 프로그램 기본에서 골라 {프로젝트}/B08_Quantity/library 에 박음 · 그 종류의 고친 식은 비움
- 표·원단위·자재총괄·인계는 박힌 양식 → 없으면 프로그램 기본만 읽음(여는 사람마다 값이 안 갈림)
- [확정] 때 아직 안 박힌 기본 양식을 박음 · 장 머리에 「어느 단에서 가져왔나 · 고친 식 N줄」
- 시험 test_b08_structure_library.py 6개 · 전체 1524 통과

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016VBGFXB9AbJBwXP19z75Qq
2026-09-13 18:21:19 +09:00

175 lines
7.0 KiB
Python

"""구조물도 양식 라이브러리 — 가져오기·박기 (2026-09-13, PLAN 4장 ①).
겨누는 것
① 가져오기 전은 프로그램 기본 · 머리 「가져오기 전」
② 로그인한 사람의 개인 단에서 가져오면 **프로젝트에 박히고** 원단위·자재총괄도 그 양식을 봄
③ ⛔ 박힌 뒤에는 라이브러리를 안 읽음 — 개인 파일을 지우거나 다른 사람이 열어도 값이 같음
④ 가져오면 그 종류의 고친 식이 비워짐
⑤ [확정] 때 안 박힌 기본 양식을 박음 · 코드 모양이 아니면 거절
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
import B08_Quantity.B08_Quantity_Engine_StructureLibrary as library_module # noqa: E402
import B08_Quantity.B08_Quantity_Router_Material as material_module # noqa: E402
import B08_Quantity.B08_Quantity_Router_StructureSheet as router_module # noqa: E402
from B05_Profile.B05_Profile_Structures_Repository import save_structures # noqa: E402
from B05_Profile.B05_Profile_Structures_Schema import StructureInstance # noqa: E402
from B08_Quantity.B08_Quantity_Engine_StructureTemplate import load_template # noqa: E402
from common_util.common_util_auth import verify_session # noqa: E402
PROJECT_ID = "33333333-3333-3333-3333-333333333333"
SHEETS = f"/api/projects/{PROJECT_ID}/quantity/structure-sheets"
PERSONAL_CODE = "AX-ST-0badc0de"
@pytest.fixture()
def storage(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
base = tmp_path / "storage"
monkeypatch.setattr(library_module, "STORAGE_BASE_DIR", str(base))
# 개인 단(회사 7 · 사람 42)에 모르터 식을 두 배로 고친 찰쌓기 한 벌.
mine = load_template("masonry_wet")
for row in mine["rows"]:
if row["name"] == "모르터":
row["formula"] = "A*0.018"
mine.update(code=PERSONAL_CODE, library_tier="personal", name="돌쌓기(찰) 내 것")
folder = base / "7" / "42" / "library"
folder.mkdir(parents=True)
(folder / f"{PERSONAL_CODE}.json").write_text(json.dumps(mine), encoding="utf-8")
return base
@pytest.fixture()
def project(tmp_path: Path) -> Path:
root = tmp_path / "project"
root.mkdir()
wall = StructureInstance.model_validate(
{
"type_id": "masonry_wet",
"placement": "interval",
"start_m": 100.0,
"end_m": 110.0,
"options": {"height_m": 2.5, "back_len_cm": 45},
}
)
save_structures(str(root), [wall], base_revision=0)
return root
@pytest.fixture()
def client(project: Path, storage: Path, monkeypatch: pytest.MonkeyPatch) -> TestClient:
async def fake_root(project_id):
return str(project)
async def no_route(project_id):
return {}
monkeypatch.setattr(router_module, "_project_root", fake_root)
monkeypatch.setattr(material_module, "_section_modes", no_route)
monkeypatch.setattr(material_module, "_ground_types", no_route)
app = FastAPI()
app.include_router(router_module.router)
app.dependency_overrides[verify_session] = lambda: {"company_id": 7, "user_id": 42}
return TestClient(app)
def _sheet(client: TestClient) -> dict:
response = client.get(SHEETS)
assert response.status_code == 200, response.text
return response.json()["sheets"][0]
def _mortar(sheet: dict) -> dict:
return next(row for row in sheet["rows"] if row["name"] == "모르터")
def test_가져오기_전은_프로그램_기본(client: TestClient) -> None:
item = _sheet(client)["library_item"]
assert item["imported_from"] is None
assert item["code"] == load_template("masonry_wet")["code"]
def test_개인_단에서_가져오면_박히고_라이브러리를_다시_안_읽는다(
client: TestClient, project: Path, storage: Path
) -> None:
before = _mortar(_sheet(client))["unit_amount"]
listed = client.get(f"{SHEETS}/library", params={"type_id": "masonry_wet"}).json()["items"]
assert [item["tier"] for item in listed] == ["personal", "program"]
taken = client.put(
f"{SHEETS}/library/import",
json={"type_id": "masonry_wet", "tier": "personal", "code": PERSONAL_CODE},
)
assert taken.status_code == 200, taken.text
sheet = _sheet(client)
assert sheet["library_item"]["imported_from"] == "personal"
assert _mortar(sheet)["unit_amount"] == pytest.approx(before * 2)
# 가져온 식은 「사용자 식」이 아니라 그 양식의 식.
assert _mortar(sheet)["source"] == "library"
# 원단위·자재총괄 창구도 박힌 양식을 봄.
from B08_Quantity.B08_Quantity_Engine_UnitQuantity import build_table
structures, names, _ = material_module._collect_structures(str(project))
table = build_table(
structures,
names,
structure_templates=library_module.project_templates(project),
)
mortar = next(c for c in table["structures"][0]["components"] if c["name"] == "모르터")
assert mortar["amount"] == pytest.approx(_mortar(sheet)["unit_amount"] * 10.0)
# ⛔ 개인 파일이 사라지고 다른 사람이 열어도 값이 그대로.
(storage / "7" / "42" / "library" / f"{PERSONAL_CODE}.json").unlink()
client.app.dependency_overrides[verify_session] = lambda: {"company_id": 7, "user_id": 99}
assert _mortar(_sheet(client))["unit_amount"] == pytest.approx(before * 2)
def test_가져오면_그_종류의_고친_식이_비워진다(client: TestClient, project: Path) -> None:
sheet = _sheet(client)
seq = _mortar(sheet)["no"]
client.put(
f"{SHEETS}/formulas",
json={"sheet_key": sheet["key"], "rows": [{"seq": seq, "formula": "A*0.03"}]},
)
program_code = load_template("masonry_wet")["code"]
taken = client.put(
f"{SHEETS}/library/import",
json={"type_id": "masonry_wet", "tier": "program", "code": program_code},
)
assert taken.json()["cleared_formulas"] == 1
stored = json.loads((project / "project_settings.json").read_text(encoding="utf-8"))
assert stored["quantity"]["structure_formula_overrides"] == {}
assert _sheet(client)["library_item"]["imported_from"] == "program"
def test_없는_항목_가져오기는_404(client: TestClient) -> None:
missing = client.put(
f"{SHEETS}/library/import",
json={"type_id": "masonry_wet", "tier": "company", "code": "AX-ST-12345678"},
)
assert missing.status_code == 404
def test_확정_때_기본_양식을_박는다(project: Path) -> None:
assert library_module.pin_program_templates(project) >= 1
pinned = library_module.project_templates(project)["masonry_wet"]
assert pinned["imported_from"] == "program"
assert library_module.pin_program_templates(project) == 0 # 이미 박힌 것은 그대로
def test_코드_모양이_아니면_박지_않는다(project: Path) -> None:
with pytest.raises(ValueError):
library_module.import_item(project, {"type_id": "masonry_wet", "code": "../x"}, "personal")