feat(M01): 조합 알약 키 「이름|단위」 · 쓰는 구조물 열이 있으면 조합 지우기 막음
- 알약 키를 첫 로직 코드에서 「이름|단위」 로 — 로직을 빼고 더하고 차례를 바꿔도 같은 물량이면 같은 키 - 조합 지우기 전 집계표 · 구조물집계표조합의 「일위대가」 칸을 훑어 쓰는 열이 있으면 409 + 구조물 이름들 - 시험 셋 더함(키 안 바뀜 · 쓰는 조합 못 지움 · 안 쓰는 조합 지워짐) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RoyUwuTFiDJfvCfHpvKKzp
This commit is contained in:
@@ -4,16 +4,24 @@
|
||||
- 물량 = 로직 결과단위 분모(원/㎥ → ㎥) · 설계 조건(고르기 · 범위)은 안 넣음(`조건` 은 빈 자리).
|
||||
- 이름 = 로직 이름 끝 괄호 뗀 것 · 같은 이름 · 단위면 한 알약(로직 여럿).
|
||||
- 원/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")
|
||||
@@ -36,13 +44,14 @@ def test_기슭막이_찰쌓기_인력_장비는_한_알약(client) -> None:
|
||||
assert len(got["물량"]) == 10 # 담은 로직 11 · 찰쌓기 둘이 합침
|
||||
assert by[("돌쌓기 찰쌓기", "㎡")]["로직"] == ["GF000222", "GF000221"]
|
||||
assert by[("레디믹스트콘크리트 타설", "㎥")]["로직"] == ["GC000198"]
|
||||
assert by[("유로폼 설치 및 해체", "㎡")]["키"] == "GC000214"
|
||||
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")
|
||||
pill = next(p for p in got["물량"] if p["로직"] == ["GF000226"])
|
||||
assert pill["키"] == f"{pill['이름']}|㎡"
|
||||
assert (pill["단위"], pill["배"]) == ("㎡", 10)
|
||||
|
||||
|
||||
@@ -53,3 +62,67 @@ def test_준공표지석_물량_여섯(client) -> None:
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user