Files
Aislo/resources/tester/test_821_2_machine_save.py
T
eomsangdonandClaude Opus 5.5 086f6d214a feat(821-2): M01 기계 화면 — 목록 열 줄이기 · 상세 페이지 · 새 기계(비슷한 기계에서 시작) · 저장 때 계수 서버 재계산
- 목록 = 이름 · 규격 · 구분 · 가격 · 손료계수 · 주연료 · 조종원 · 시간당 경비(서버 값) · 별칭 · 비고 (단위 · 출처 열 없음 · 가로 넘침 없음) · 다른 요소 표 그대로
- 상세 = 기계 양식 + 원문번호 · 원문 · 구분 · 상세구분 · 별칭 · 비고 · 쓰는 곳 · 지우기(쓰는 곳 보이고 확인) · 주소 ?machine= 로 바로 열림 · 뒤로 가기 = 목록 · 저장 안 한 고침 나가기 경고
- 고침 = 초안 → 페이지 [저장](자동저장 없음) · 서버 M01_MasterData_Machine.fix = 품셈 입력이 바뀌면 계수 짝으로 다시 셈 · 단위 · 출처 칸 채움(새 칸 = 마스터 단위 · 「자체」)
- API /api/m01/machine/lists · hourly · uses · find · row (시스템 관리자만) · 시험 test_821_2_machine_save.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 14:02:04 +09:00

157 lines
6.4 KiB
Python

"""PLAN 821-2 M01 기계 저장 — 지킴 시험(임시 사본 폴더만 · 정본 안 씀).
① 새 기계 = 단위 · 출처 자동(마스터 단위 · 「자체」) · 계수 서버 재계산 · 다른 줄 그대로
② 고침 = 그 칸 한 줄만 · 품셈 입력이 같으면 저장 계수 그대로(점보드릴 777) · 바뀌면 다시 셈
③ 기계 길은 시스템 관리자만(아니면 403) · 양식 목록 · 비슷한 기계 찾기 · 쓰는 곳
"""
from __future__ import annotations
import difflib
import json
import shutil
import sys
from pathlib import Path
import pytest
from fastapi import Depends, FastAPI
from fastapi.testclient import TestClient
from common_util import common_util_auth as auth
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[2] / "resources" / "master_data" / "scripts")
)
import master_machine_coef as mc # noqa: E402
REAL = store.FOLDER
MACHINE = "기계.json"
@pytest.fixture
def copy(tmp_path: Path, monkeypatch) -> Path:
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)
return tmp_path
def _app(role: str = "SYSTEM_ADMIN") -> TestClient:
app = FastAPI()
app.dependency_overrides[auth.verify_session] = lambda: {
"user_id": 1,
"name": "시험",
"role": role,
"company_id": 1,
}
deps = [Depends(auth.verify_session), Depends(auth.require_system_admin)]
app.include_router(router_module.router, dependencies=deps)
return TestClient(app)
def _save(client: TestClient, version: str, change: dict) -> None:
res = client.post(
"/api/m01/save",
json={"files": [{"file": MACHINE, "version": version, "changes": [change]}]},
)
assert res.status_code == 200, res.text
def _diff(before: str, after: str) -> list[str]:
return [
x
for x in difflib.unified_diff(before.splitlines(), after.splitlines(), lineterm="", n=0)
if x[:1] in "+-" and not x.startswith(("+++", "---"))
]
def test_새_기계_단위_출처_자동_계수_서버_재계산(copy: Path) -> None:
client = _app()
lists = client.get("/api/m01/machine/lists").json()
assert {"경유", "휘발유"} <= {f["value"] for f in lists["fuels"]}
assert any(j["value"] == "1048" for j in lists["jobs"]) and lists["hour"] == 0.125
found = client.get("/api/m01/machine/find", params={"q": "굴착기 1.0㎥"}).json()["rows"]
seed = next(r for r in found if r["키"] == "EQ000022")
before = (copy / MACHINE).read_text(encoding="utf-8")
version = client.get("/api/m01/machine/row", params={"key": "EQ000022"}).json()["version"]
values = {**seed["값"], "내용시간": 8000, "손료계수": 1, "상각비계수": 1} # 계수 = 틀린 값
row = {
"키": "",
"원문번호": "",
**{k: seed[k] for k in ("원문", "구분", "상세구분")},
"이름": "시험기계sub1",
"규격": "9.9㎥",
"단위": {},
"값": values,
"출처": {},
"비고": None,
}
_save(client, version, {"op": "add", "row": row})
after = (copy / MACHINE).read_text(encoding="utf-8")
rows = json.loads(after)["줄"]
new = rows[-1]
assert new["이름"] == "시험기계sub1" and new["키"].startswith("EQ")
assert new["값"] | mc.coef(8000, 1250, 0.9, 0.7, 0.1) == new["값"] # 서버가 다시 셈
assert new["값"]["손료계수"] == 1125 + 875 + 496
assert set(new["단위"]) == set(new["값"]) == set(new["출처"])
assert new["단위"] == {
k: seed_unit for k, seed_unit in _units(before).items() if k in new["값"]
}
assert set(new["출처"].values()) == {"자체"}
assert json.loads(before)["줄"] == rows[:-1] # 다른 줄 그대로
assert all(x.startswith("+") for x in _diff(before, after)) # 글은 더하기만
def _units(text: str) -> dict:
return next(r for r in json.loads(text)["줄"] if r["키"] == "EQ000022")["단위"]
def _edit(client: TestClient, key: str, change) -> tuple[dict, dict]:
got = client.get("/api/m01/machine/row", params={"key": key}).json()
row = got["row"]
_save(client, got["version"], {"op": "edit", "key": key, "row": change(row)})
now = client.get("/api/m01/machine/row", params={"key": key}).json()["row"]
return row, now
def test_고침_그_줄_그_칸만(copy: Path) -> None:
client = _app()
before = (copy / MACHINE).read_text(encoding="utf-8")
row, now = _edit(client, "EQ000022", lambda r: {**r, "값": {**r["값"], "가격": 150000}})
assert now == {**row, "값": {**row["값"], "가격": 150000}}
lines = _diff(before, (copy / MACHINE).read_text(encoding="utf-8"))
assert len(lines) == 2 and "142069" in lines[0] and "150000" in lines[1] # 그 칸 한 줄만
def test_입력_같으면_저장_계수_그대로_바뀌면_다시_셈(copy: Path) -> None:
client = _app()
# 가격만 고침 + 화면이 계수를 틀리게 보내도 — 입력이 같으면 원문 표 값(정비비계수 777) 그대로
row, now = _edit(
client,
"EQ000347",
lambda r: {**r, "값": {**r["값"], "가격": 999, "정비비계수": 778, "손료계수": 1}},
)
assert now["값"] == {**row["값"], "가격": 999}
_, now = _edit(client, "EQ000347", lambda r: {**r, "값": {**r["값"], "내용시간": 10000}})
assert {k: now["값"][k] for k in mc.COEFS} == mc.coef(10000, 800, 0.9, 0.7, 0.1)
def test_기계_길은_시스템_관리자만() -> None:
for role in ("USER", "COMPANY_ADMIN"):
client = _app(role)
assert client.get("/api/m01/machine/lists").status_code == 403
assert client.get("/api/m01/machine/hourly", params={"keys": "EQ000022"}).status_code == 403
assert client.post("/api/m01/save", json={"files": []}).status_code == 403
def test_쓰는_곳_찾기(copy: Path, monkeypatch) -> None:
from M02_MasterTemplete import M02_Template_Layers as layers
monkeypatch.setattr(layers, "layer_files", lambda kind: []) # 공유 storage 안 읽음
uses = _app().get("/api/m01/machine/uses", params={"key": "EQ000022"}).json()["uses"]
assert any("GC000267" in u for u in uses)
assert _app().get("/api/m01/machine/uses", params={"key": "EQ999999"}).status_code == 404