Files
Aislo/resources/tester/test_166_4_b09_quote.py
T
eomsangdonandClaude Opus 5.5 40b80ff1b6 feat(b09): 자재 견적서 여러 파일 첨부 길 · 견적 · 고른 출처 저장(166-4)
Router_Quotes 새 길(목록 · 올리기 · 내려받기 · 지우기 · v1/quotes/<자재 키>/) · Store quote · quote_page · pick 칸 · 읽을 때 files 목록

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sDZSjFvoiDjdGpgj3muc3
2026-10-10 00:09:37 +09:00

106 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""166-4 자재단가대비표 견적 열 · 적용 출처 고르기 · 견적서 여러 파일(임시 storage) · 거울."""
import sys
from pathlib import Path
from uuid import uuid4
import pytest
ROOT = Path(__file__).resolve().parents[2]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from B09_Estimation import B09_Estimation_Store as store # noqa: E402
from B09_Estimation.B09_Estimation_Calc_Base import compute_base # noqa: E402
from resources.tester.test_144_b09_base_calc import _run_ts # noqa: E402
MATERIALS = [
{"key": "MT1", "name": "자갈", "prices": {"물가정보": 23000, "물가자료": 22000}, "qty": 2},
{"key": "MT2", "name": "폭약", "prices": {"물가자료": 12000}},
{"key": "b08:번호판", "name": "국가지점번호판", "prices": {}},
{"key": "MT4", "name": "모래", "prices": {"물가자료": 30000}},
]
INPUTS = {"labor": [], "machines": [], "materials": MATERIALS}
USER = {
"materials": {
"MT1": {"quote": 21000, "quote_page": "견적 A"}, # 견적이 최저 → 자동
"MT2": {"quote": 12712, "pick": "견적"}, # 견적이 높아도 고름
"b08:번호판": {"quote": 520000}, # 견적만 있는 줄 = 견적 자동
"MT4": {"quote": 29000, "applied": 31000, "pick": "물가자료"}, # 고름 › 손 수
}
}
def test_견적_최저_고름_손수():
s = compute_base(INPUTS, USER)
rows = {r["key"]: r for r in s["compare"]}
got = {
k: (r["lowest"], r["lowest_from"], r["applied"], r["applied_from"]) for k, r in rows.items()
}
assert got == {
"MT1": (21000, "견적", 21000, "견적"),
"MT2": (12000, "물가자료", 12712, "견적"),
"b08:번호판": (520000, "견적", 520000, "견적"),
"MT4": (29000, "견적", 30000, "물가자료"),
}
assert rows["MT1"]["pages"]["견적"] == "견적 A" and rows["MT2"]["pick"] == "견적"
assert {r["key"]: r["price"] for r in s["material"]}["MT2"] == 12712 # 목록표 단가 따라감
assert s["material"][0]["amount"] == 42000
# 손 수만 있으면 「사용자」 · 고름 없으면 최저(견적 포함)
s2 = compute_base(INPUTS, {"materials": {"MT4": {"quote": 29000, "applied": 31000}}})
assert s2["compare"][3]["applied_from"] == "사용자"
def test_거울_TS():
assert _run_ts(INPUTS, USER) == compute_base(INPUTS, USER)
def test_저장_다듬기_파일_목록(tmp_path):
raw = {"materials": {"MT2": {"quote": 12712, "quote_page": " 견적 B ", "pick": "없는 출처",
"files": ["x"]}, "MT1": {"pick": "견적"}}} # fmt: skip
clean = store.write_user(tmp_path, raw)
assert clean["materials"] == {
"MT2": {"quote": 12712, "quote_page": "견적 B"},
"MT1": {"pick": "견적"},
} # files 는 저장 안 함(폴더가 목록)
folder = store.quote_dir(tmp_path, "b08:이름|㎥/hr")
folder.mkdir(parents=True)
(folder / "a.pdf").write_bytes(b"1")
(folder / ".a.pdf.123.upload").write_bytes(b"temp")
assert folder.parent == store.folder(tmp_path) / "quotes"
user = store.read_user(tmp_path)
assert user["materials"]["b08:이름|㎥/hr"] == {"files": ["a.pdf"]}
assert store.safe_name("../../x\\y:z.pdf") == "y_z.pdf" and store.safe_name("..") == ""
with pytest.raises(ValueError):
store.quote_dir(tmp_path, "..")
def test_올리기_목록_내려받기_지우기(tmp_path, monkeypatch):
from fastapi import FastAPI
from fastapi.testclient import TestClient
from B09_Estimation import B09_Estimation_Router_Quotes as quotes
async def root(_project_id):
return tmp_path
monkeypatch.setattr(quotes, "_project_root", root)
app = FastAPI()
app.include_router(quotes.router, prefix="/api/projects")
client = TestClient(app)
base = f"/api/projects/{uuid4()}/estimation/quotes"
key = {"key": "b08:pipe_600"}
files = [("files", ("견적1.pdf", b"one", "application/pdf")),
("files", ("sub/견적2.xlsx", b"two!", "application/octet-stream"))] # fmt: skip
listed = client.post(base, params=key, files=files).json()
assert [(f["name"], f["size"]) for f in listed] == [("견적1.pdf", 3), ("견적2.xlsx", 4)]
assert (store.quote_dir(tmp_path, "b08:pipe_600") / "견적1.pdf").read_bytes() == b"one"
assert len(client.get(base, params=key).json()) == 2
got = client.get(base + "/file", params={**key, "name": "견적2.xlsx"})
assert got.status_code == 200 and got.content == b"two!"
assert client.get(base + "/file", params={**key, "name": "../x"}).status_code == 404
assert client.get(base, params={"key": ".."}).status_code == 400
left = client.delete(base, params={**key, "name": "견적1.pdf"}).json()
assert [f["name"] for f in left] == ["견적2.xlsx"]
assert store.read_user(tmp_path)["materials"] == {"b08:pipe_600": {"files": ["견적2.xlsx"]}}