Files
Aislo/resources/tester/test_m01_combo_inputs.py
T
eomsangdonandClaude Sonnet 5.5 e1b17a523c feat(732-1): M02 실무문서 칸 · 복사 탭 · 서버 길 걷음
- 왼쪽 「실무문서」 칸(Practice.ts) · 일위대가 가져오기 「실무문서」 탭 · 복사 원본 고르기 · 낱 CSS · locale 열쇠 걷음
- 서버 `from_layer=practice`(_copy_practice) · `DELETE /api/m02/practice`(drop_practice) · 조합 목록 · 키 하나 읽기 길 걷음 · 조합 길 시스템 관리자 문 걷음(담을 로직 읽기는 그대로)
- 옛 마스터 조합 파일 `일위대가조합.json`(UA 37) = 쓰는 곳 있음(구조물집계표 일위대가 칸 · labor 본뜬키 · 프로젝트 초기값 사본 · 불러오기 맞춤 씨앗) → 파일 · 읽기 길 유지 · 지우지 않음
- 낡은 시험: test_m02_practice 지움(화면 · 길 없음) · 조합 길 읽던 시험(combo · combo_inputs · combo_layers)은 저장소 함수로 · 실무문서 복사 시험(labor_copy · pair_copy) 줄 지움 · 탭 · 차례 단정(layer_screen · structure_options · structure_panel · 720 · precombo_scope · ui_loading) 고침

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 13:17:24 +09:00

156 lines
6.9 KiB
Python

"""조합 알약(저장소 `combo_inputs`) — 결과단위 분모가 물량 하나.
못박는 것
- 물량 = 로직 결과단위 분모(원/㎥ → ㎥) · 설계 조건(고르기 · 범위)은 안 넣음(`조건` 은 빈 자리).
- 이름 = 로직 이름 끝 괄호 뗀 것 · 같은 이름 · 단위면 한 알약(로직 여럿).
- 원/10㎡ 는 단위 ㎡ · `배` 10 · 없는 조합 404.
- 알약 키 = 「이름|단위」 — 로직을 빼고 · 더하고 · 차례를 바꿔도 같은 물량이면 같은 키.
- 조합 지우기 — 구조물 열이 「일위대가」 로 쓰면 409 + 쓰는 구조물 이름들 · 안 쓰면 지워짐.
정본은 읽기만 함(지우기 · 만들기 시험은 사본 폴더).
"""
from __future__ import annotations
import json
import shutil
from pathlib import Path
import pytest
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 store
from M01_MasterData import M01_MasterData_Store_Combo as combo_store
from M02_MasterTemplete import M02_MasterTemplete_Combo_Store as combos
from M02_MasterTemplete import M02_MasterTemplete_Store as m02
from M02_MasterTemplete.M02_MasterTemplete_Router_Combos import router as combos_router
ADMIN = {"user_id": 1, "company_id": 1, "role": "SYSTEM_ADMIN", "is_master": False}
def _app() -> TestClient:
app = FastAPI()
app.include_router(combos_router)
app.dependency_overrides[verify_session] = lambda: ADMIN
return TestClient(app)
@pytest.fixture(scope="module")
def client() -> TestClient:
return _app()
def _pills(client: TestClient, key: str) -> dict:
"""알약 길은 은퇴(39-1d) — 규칙은 저장소 `combo_inputs`(일위대가 문서 알약이 같은 규칙을 씀)."""
return combo_store.combo_inputs(key)
def test_기슭막이_찰쌓기_인력_장비는_한_알약(client) -> None:
got = _pills(client, "UA000004")
assert got["조건"] == []
by = {(p["이름"], p["단위"]): p for p in got["물량"]}
assert len(got["물량"]) == 10 # 담은 로직 11 · 찰쌓기 둘이 합침
assert by[("돌쌓기 찰쌓기", "㎡")]["로직"] == ["GF000222", "GF000221"]
assert by[("레디믹스트콘크리트 타설", "㎥")]["로직"] == ["GC000198"]
assert by[("유로폼 설치 및 해체", "㎡")]["키"] == "유로폼 설치 및 해체|㎡"
assert set(got["물량"][0]) == {"키", "이름", "단위", "로직"}
def test_원_10제곱미터는_배_10(client) -> None:
got = _pills(client, "UA000031")
pill = next(p for p in got["물량"] if p["로직"] == ["GF000226"])
assert pill["키"] == f"{pill['이름']}|㎡"
assert (pill["단위"], pill["배"]) == ("㎡", 10)
def test_준공표지석_물량_여섯(client) -> None:
got = _pills(client, "UA000002")
assert [p["단위"] for p in got["물량"]] == ["㎥", "㎡", "㎥", "㎥", "㎥", "㎡"]
def test_없는_조합은_404(client) -> None:
with pytest.raises(store.StoreError) as caught:
combo_store.combo_inputs("UA999999")
assert caught.value.status == 404
@pytest.fixture
def own(tmp_path: Path, monkeypatch) -> TestClient:
"""사본 마스터 폴더 · 사본 집계표 — 조합을 만들고 지워도 정본은 그대로."""
for path in store.FOLDER.glob("*.json"):
if not path.name.startswith("_") or path.name == store.mk.BOOK.name:
shutil.copy(path, tmp_path / path.name)
monkeypatch.setattr(store, "FOLDER", tmp_path)
sheet = tmp_path / "집계표.json"
sheet.write_text(json.dumps({"열": []}), encoding="utf-8")
monkeypatch.setattr(combo_store, "USERS", (sheet,))
return _app()
def _new(client: TestClient, name: str, codes: list[str]) -> str:
"""조합 만들기 길은 은퇴(39-1d) — 저장소 함수로(사본 마스터)."""
combo = {"이름": name, "담은로직": [{"로직": c, "메모": None} for c in codes]}
return combos.create(combo)["키"]
def test_알약_키는_로직을_빼고_더하고_차례를_바꿔도_같다(own) -> None:
both = ["GF000222", "GF000221", "GC000198"] # 찰쌓기 인력 · 장비 + 타설
full = _pills(own, _new(own, "전부", both))["물량"]
fewer = _pills(own, _new(own, "장비 뺌", ["GF000222", "GC000198"]))["물량"]
swapped = _pills(own, _new(own, "차례 바꿈", both[::-1]))["물량"]
keys = {p["키"] for p in full}
assert keys == {"돌쌓기 찰쌓기|㎡", "레디믹스트콘크리트 타설|㎥"}
assert {p["키"] for p in fewer} == keys
assert {p["키"] for p in swapped} == keys
assert len({p["키"] for p in full}) == len(full) # 키 겹침 없음
def _use(sheet: Path, key: str) -> None:
cols = [
{"id": "c5", "머리": ["돌기슭막이", "찰쌓기", "", ""], "일위대가": key},
{"id": "c6", "머리": ["돌붙임", "", "", ""], "일위대가": "UA000009"},
]
sheet.write_text(json.dumps({"열": cols}, ensure_ascii=False), encoding="utf-8")
def test_쓰는_열이_있으면_조합을_못_지우고_구조물_이름을_알린다(own) -> None:
key = _new(own, "쓰는 조합", ["GF000222"])
_use(combo_store.USERS[0], key)
version = combos.one(key)["판"]
with pytest.raises(m02.StoreError) as caught: # 지우기 길은 은퇴(39-1d) — 저장소 함수로
combos.delete(key, version)
assert caught.value.status == 409
detail = caught.value.detail
assert detail["users"] == ["돌기슭막이 찰쌓기"]
assert "돌기슭막이 찰쌓기" in detail["errors"][0]
assert combos.one(key)["combo"]["키"] == key # 그대로 남음
def test_안_쓰는_조합은_지워진다(own) -> None:
key = _new(own, "안 쓰는 조합", ["GF000222"])
_use(combo_store.USERS[0], "UA000009") # 다른 조합을 씀
version = combos.one(key)["판"]
combos.delete(key, version)
with pytest.raises(m02.StoreError) as gone:
combos.one(key)
assert gone.value.status == 404
def test_본뜬키는_목록에_나오고_없는_키_자기_키는_400(own) -> None:
base = _new(own, "원본", ["GF000222"])
combo = {"이름": "본뜬 것", "본뜬키": base, "담은로직": [{"로직": "GF000222", "메모": None}]}
got = combos.create(combo)
assert got["combo"]["본뜬키"] == base
rows = {r["키"]: r for r in combos.listing()["줄"]}
assert rows[got["키"]]["본뜬키"] == base and rows[base]["본뜬키"] is None
bad = {**combo, "이름": "없는 원본", "본뜬키": "UA999999"}
with pytest.raises(m02.StoreError) as caught:
combos.create(bad)
assert caught.value.status == 400
version = combos.one(base)["판"]
me = {"이름": "원본", "본뜬키": base, "담은로직": combo["담은로직"]}
with pytest.raises(store.StoreError) as caught: # 조합 고치기 길(PUT) 은퇴 — 저장소 함수로
combo_store.combo_edit(base, version, me)
assert caught.value.status == 400