"""M01 읽는 식 줄(`POST /text`) — 계약 `resources/master_data/_화면_계약.md` 3장. 견본 셋: 산림 13-4-1 메쌓기 · 12-4 합판거푸집 · 12-17-1 펌프카 타설. """ from __future__ import annotations import shutil import sys from decimal import Decimal 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 sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "master_data/scripts")) import master_text as mt # noqa: E402 REAL = store.FOLDER STACK = "GF000219" # 13-4-1 메쌓기(인력) — 증가율이 걸린 줄 FORM = "GF000160" # 12-4 합판거푸집 — 재료 여럿 · 수량 0 줄 PUMP = "GF000182" # 12-17-1 펌프카 타설 — 하위 로직이 세 비목에 걸리고 덧줄이 있음 IDLE = "GC000999" # 일반전정 — 세 비목에 걸린 장비 줄이 수량 0 이라 몫이 모두 0 @pytest.fixture def client(tmp_path: Path, monkeypatch) -> TestClient: for path in REAL.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) app = FastAPI() app.include_router(router_module.router) return TestClient(app) def _inputs(key: str) -> dict: """그 로직의 일괄 시험 입력 한 벌(`check_master.시험입력`).""" row = store.cm.master(REAL).logic(key) return {k: str(v) for k, v in store.cm.시험입력(row).items()} def _both(client: TestClient, key: str) -> tuple[dict, dict]: body = {"key": key, "inputs": _inputs(key)} text = client.post("/api/m01/text", json=body) calc = client.post("/api/m01/calc", json=body) assert text.status_code == 200 and calc.status_code == 200, text.text return text.json(), calc.json() def _all_lines(got: dict) -> list[dict]: return [one for group in got["groups"] for one in group["줄"]] def test_자릿수는_화면과_같다() -> None: assert mt.fmt(Decimal("31500")) == "31,500" assert mt.fmt(Decimal("2803.50")) == "2,803.5" assert mt.fmt(Decimal("1") / Decimal(3)) == "0.3333" assert mt.fmt(Decimal("-1234.5")) == "-1,234.5" def test_메쌓기_줄은_수로만_적히고_계가_시험계산과_같다(client: TestClient) -> None: got, calc = _both(client, STACK) assert got["ok"] is True and Decimal(str(got["계"])) == Decimal(str(calc["sums"]["계"])) rows = _all_lines(got) assert [one["이름"] for one in rows] == ["석공", "보통인부"] for one in rows: assert "찾기(" not in one["글"] and "만약(" not in one["글"] and "로직(" not in one["글"] assert one["글"].count("=") == 1 and " * " in one["글"] assert "%" in rows[0]["글"] # 증가율은 따로 줄이 아니라 이 줄의 식에 녹아 있음 assert rows[0]["글"].startswith("268,908 * ") def test_합판거푸집은_비목마다_묶이고_안_쓰는_줄에_까닭이_있다(client: TestClient) -> None: got, calc = _both(client, FORM) assert [g["비목"] for g in got["groups"]] == ["노무비", "재료비"] for group in got["groups"]: assert Decimal(str(group["소계"])) == Decimal(str(calc["sums"][group["비목"]])) assert sum(Decimal(str(one["금액"])) for one in group["줄"]) == Decimal(str(group["소계"])) why = [one for one in _all_lines(got) if one.get("까닭")] assert why and all("0" in one["까닭"] for one in why) def test_펌프카는_하위로직이_비목마다_한_줄씩이고_덧줄이_섞인다(client: TestClient) -> None: got, calc = _both(client, PUMP) assert [g["비목"] for g in got["groups"]] == ["노무비", "재료비", "경비"] assert Decimal(str(got["계"])) == Decimal(str(calc["sums"]["계"])) pump = [one for one in _all_lines(got) if one["이름"] == "콘크리트펌프차"] assert len(pump) == 3 # 노무비 · 재료비 · 경비 몫이 따로 extra = next(one for one in _all_lines(got) if one["이름"].startswith("공구손료")) assert extra["글"].endswith(f"= {mt.fmt(Decimal(str(extra['금액'])))}") and "%" in extra["글"] def test_몫이_모두_0인_장비_줄도_글에_남는다(client: TestClient) -> None: got, calc = _both(client, IDLE) idle = [one for one in _all_lines(got) if one["이름"] == "고소작업차"] assert len(idle) == 1 # 비목 셋 몫이 다 0 이라도 줄이 사라지지 않음 assert Decimal(str(idle[0]["금액"])) == 0 and "수량 0" in idle[0]["까닭"] assert idle[0]["글"].endswith(" * 0 = 0") for group in got["groups"]: assert Decimal(str(group["소계"])) == Decimal(str(calc["sums"][group["비목"]])) def test_멈추는_로직은_까닭_한_줄(client: TestClient) -> None: got = client.post("/api/m01/text", json={"key": STACK, "inputs": {}}).json() assert got["ok"] is False and "입력" in got["reason"] def test_끝수는_줄이_아니라_소계에_붙는다(client: TestClient) -> None: got = client.get("/api/m01/logic", params={"key": STACK}).json() row = dict(got["logic"], 끝수={"대상": "계", "자리": 0, "방법": "버림"}) body = {"key": STACK, "inputs": _inputs(STACK), "row": row, "file": got["file"]} cut = client.post("/api/m01/text", json=body).json() assert cut["ok"] is True group = cut["groups"][0] assert group["끝수"] == "원 미만 버림" assert Decimal(str(group["소계"])) == Decimal(str(group["소계"])).to_integral_value() assert not any("버림" in one["글"] for one in group["줄"])