Files
Aislo/resources/tester/test_m01_combo_inputs.py
T
eomsangdonandClaude Sonnet 5 b6e71c729d feat(M01): 조합 알약 키 「이름|단위」 · 쓰는 구조물 열이 있으면 조합 지우기 막음
- 알약 키를 첫 로직 코드에서 「이름|단위」 로 — 로직을 빼고 더하고 차례를 바꿔도 같은 물량이면 같은 키
- 조합 지우기 전 집계표 · 구조물집계표조합의 「일위대가」 칸을 훑어 쓰는 열이 있으면 409 + 구조물 이름들
- 시험 셋 더함(키 안 바뀜 · 쓰는 조합 못 지움 · 안 쓰는 조합 지워짐)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RoyUwuTFiDJfvCfHpvKKzp
2026-09-28 18:46:12 +09:00

129 lines
5.6 KiB
Python

"""M01 조합 알약(`GET /api/m01/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 M01_MasterData import M01_MasterData_Router as router_module
from M01_MasterData import M01_MasterData_Store as store
from M01_MasterData import M01_MasterData_Store_Combo as combo_store
@pytest.fixture(scope="module")
def client() -> TestClient:
app = FastAPI()
app.include_router(router_module.router)
return TestClient(app)
def _pills(client: TestClient, key: str) -> dict:
res = client.get(f"/api/m01/combo/{key}/inputs")
assert res.status_code == 200, res.text
return res.json()
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:
assert client.get("/api/m01/combo/UA999999/inputs").status_code == 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,))
app = FastAPI()
app.include_router(router_module.router)
return TestClient(app)
def _new(client: TestClient, name: str, codes: list[str]) -> str:
combo = {"이름": name, "담은로직": [{"로직": c, "메모": None} for c in codes]}
res = client.post("/api/m01/combo/new", json={"combo": combo})
assert res.status_code == 200, res.text
return res.json()["key"]
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 = own.get(f"/api/m01/combo?key={key}").json()["version"]
res = own.post("/api/m01/combo/delete", json={"key": key, "version": version})
assert res.status_code == 409
detail = res.json()["detail"]
assert detail["users"] == ["돌기슭막이 찰쌓기"]
assert "돌기슭막이 찰쌓기" in detail["errors"][0]
assert own.get(f"/api/m01/combo?key={key}").status_code == 200 # 그대로 남음
def test_안_쓰는_조합은_지워진다(own) -> None:
key = _new(own, "안 쓰는 조합", ["GF000222"])
_use(combo_store.USERS[0], "UA000009") # 다른 조합을 씀
version = own.get(f"/api/m01/combo?key={key}").json()["version"]
assert (
own.post("/api/m01/combo/delete", json={"key": key, "version": version}).status_code == 200
)
assert own.get(f"/api/m01/combo?key={key}").status_code == 404