- 기계.json 한 파일 → 폴더 기계/(EQ 키마다 파일 + _머리.json · 차례 = 키 차례) · 옮긴 빌더 build_기계폴더_826_2 (655 대 머리 · 줄 · 차례 대조 뒤 옛 파일 지움) - 읽기 도우미 master_machine_files — cm.load · Store_Cache · Store.read 가 옛 「기계.json」 한 표 꼴로 지어 줌(API · 화면 · 색인 그대로) · 쓰기 = 바뀐 대 파일만 - 기계.json 직접 열던 곳 도우미로: M02_San_Basis_Index · M02_Logic_Standard 지문 · build_용도 · master_keys.adopt · _틀 · _갱신절차 - 저장 몸 M01_MasterData_Store_Save — 전 = 기억 · 후 = 고친 파일만 얕은 사본 · 틀은 고친 줄만 · 조합 · 일위대가 문서는 로직 줄이 빠질 때만 · 재료 찾기 기억 이어받음 · 저장 뒤 쓴 파일만 기억에 갈아 끼움 - 마스터 사본 시험 22 개 = 기계 폴더도 복사 · 821_2 · 815_2 글 대조 = 대 파일 하나 대조(옛 한 파일 글 대조 낡음) - 새 시험 test_826_2 — 폴더 = 한 표 · 한 대 한 파일 · 새 · 지움 · 409 · 옛 길과 같은 오류 스무 견본 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
128 lines
6.2 KiB
Python
128 lines
6.2 KiB
Python
# ruff: noqa: E501
|
||
"""794-1 M01 관리자 줄 식 시트 — 서버 길(`/logic/sheet` · `/logic/sheet/check`) 지킴.
|
||
|
||
- 799-2 — 시트 화면 · 모델(`_LogicLab_Sheet.ts` · `logic/M01_Logic_Sheet_Model.ts`)은 걷음 · 이 서버 길은 수식 탭 검사와 같은 엔진이라 남김
|
||
- 서버(사본 폴더): 이름 꼴 식 글 · 칠 때마다 검사(없는 이름 · 아래 줄 · 괄호 · 빈 식 · 길이 · 없는 자리) ·
|
||
검사 = 쓰기 없음 · 공구손료 식 1 → 2 % 저장(개정 확인 409 → 200) 뒤 셈 51,802 · 틀린 식 저장 422 · 시스템 관리자가 아니면 403
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import copy
|
||
import hashlib
|
||
import shutil
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
from fastapi import Depends, FastAPI
|
||
from fastapi.testclient import TestClient
|
||
|
||
from common_util.common_util_auth import require_system_admin, verify_session
|
||
from M01_MasterData import M01_MasterData_Router as m01_router
|
||
from M01_MasterData import M01_MasterData_Sheet as line_sheet
|
||
from M01_MasterData import M01_MasterData_Store as store
|
||
|
||
REAL = store.FOLDER
|
||
KEY = "GC000211" # 합판거푸집 설치 및 해체
|
||
INPUTS = {"자재지역": "포항", "유형": "보통", "사용횟수": "6회", "높이감률": 0, "지붕슬래브": "아님",
|
||
"폼타이": "아님", "폼타이규격": "12.7㎜", "측압": "3t/㎡", "구멍땜": 0} # fmt: skip
|
||
TOOL_1 = "(형틀목공 × [노임 형틀목공] + 보통인부 × [노임 보통인부]) × 1 / 100"
|
||
TOOL_2 = TOOL_1.replace("× 1 / 100", "× 2 / 100")
|
||
|
||
|
||
def _text(p: Path) -> str:
|
||
return p.read_text("utf-8")
|
||
|
||
|
||
def _hashes(folder: Path) -> dict[str, str]:
|
||
return {p.name: hashlib.md5(p.read_bytes()).hexdigest() for p in sorted(folder.glob("*.json"))}
|
||
|
||
|
||
# ── 서버 ─────────────────────────────────────────────────────────────
|
||
@pytest.fixture
|
||
def folder(tmp_path: Path, monkeypatch) -> Path:
|
||
master = tmp_path / "master"
|
||
master.mkdir()
|
||
for path in REAL.glob("*.json"):
|
||
shutil.copy(path, master / path.name)
|
||
shutil.copytree(REAL / "기계", master / "기계") # 기계 = 폴더(826-2)
|
||
monkeypatch.setattr(store, "FOLDER", master)
|
||
return master
|
||
|
||
|
||
@pytest.fixture
|
||
def client(folder: Path) -> TestClient:
|
||
app = FastAPI()
|
||
app.include_router(m01_router.router)
|
||
return TestClient(app)
|
||
|
||
|
||
def _post(client: TestClient, url: str, body: dict, want: int = 200) -> dict:
|
||
res = client.post(url, json=body)
|
||
assert res.status_code == want, res.text
|
||
return res.json()
|
||
|
||
|
||
def test_이름_꼴_검사_쓰기_없음(client: TestClient, folder: Path) -> None:
|
||
before, real = _hashes(folder), _hashes(REAL)
|
||
one = client.get("/api/m01/logic", params={"key": KEY}).json()
|
||
row = one["logic"]
|
||
names = _post(client, "/api/m01/logic/sheet", {"row": row})
|
||
assert names["덧줄"][0] == TOOL_1
|
||
assert names["중간"][3] == "[합판거푸집설치및해체일당].형틀목공 / 시공량"
|
||
assert (
|
||
len(names["호표"]) == len(row["호표"])
|
||
and {"글": "시공량", "갈래": "중간"} in names["이름표"]
|
||
)
|
||
ask = lambda part, at, text: _post( # noqa: E731
|
||
client, "/api/m01/logic/sheet/check", {"row": row, "갈래": part, "차례": at, "식": text}
|
||
)
|
||
good = ask("덧줄", 0, TOOL_2)
|
||
assert good["되나"] and good["식"] == "(형틀목공 * LB000007 + 보통인부 * LB000002) * 2 / 100"
|
||
assert good["글"] == TOOL_2
|
||
assert ask("덧줄", 0, "형틀목공 × [노임 없음]")["까닭"] == ["없는 이름 「[노임 없음]」"]
|
||
below = ask("중간", 0, "시공량 × 2") # 시공량 = 아래 줄
|
||
assert not below["되나"] and "시공량" in below["까닭"][0]
|
||
assert not ask("중간", 2, "(1 +")["되나"]
|
||
assert ask("호표", 0, " ")["까닭"] == ["식이 빔"]
|
||
assert not ask("호표", 0, "1" + " + 1" * 200)["되나"] # 길이 상한
|
||
assert ask("덧줄", 9, "1")["까닭"] == ["없는 자리 「덧줄 9」"]
|
||
assert ask("호표", 0, "형틀목공 × 2")["되나"]
|
||
assert row == client.get("/api/m01/logic", params={"key": KEY}).json()["logic"] # 줄 안 바뀜
|
||
assert _hashes(folder) == before and _hashes(REAL) == real
|
||
|
||
|
||
def test_저장_길_개정_확인_다시_셈_틀린_식_422(client: TestClient, folder: Path) -> None:
|
||
real = _hashes(REAL)
|
||
one = client.get("/api/m01/logic", params={"key": KEY}).json()
|
||
row = copy.deepcopy(one["logic"])
|
||
got = line_sheet.check(row, "덧줄", 0, TOOL_2)
|
||
row["덧줄"][0]["식"] = got["식"]
|
||
body = {"files": [{"file": one["file"], "version": one["version"],
|
||
"changes": [{"op": "edit", "key": KEY, "row": row}]}]} # fmt: skip
|
||
assert client.post("/api/m01/save", json=body).status_code == 409 # 정본 = 개정 확인
|
||
_post(client, "/api/m01/save", {**body, "개정확인": True})
|
||
saved = client.get("/api/m01/logic", params={"key": KEY}).json()
|
||
assert saved["logic"]["덧줄"][0]["식"] == got["식"]
|
||
sums = _post(client, "/api/m01/calc", {"key": KEY, "inputs": INPUTS})["sums"]
|
||
assert sums["계"] == 51802 and sums["경비"] == 765 # _분석_650-H2 4 장 공구손료 2 %
|
||
bad = copy.deepcopy(saved["logic"])
|
||
bad["덧줄"][0]["식"] = "형틀목공 * 없는이름"
|
||
body = {"files": [{"file": saved["file"], "version": saved["version"],
|
||
"changes": [{"op": "edit", "key": KEY, "row": bad}]}], "개정확인": True} # fmt: skip
|
||
before = _hashes(folder)
|
||
assert client.post("/api/m01/save", json=body).status_code == 422
|
||
assert _hashes(folder) == before and _hashes(REAL) == real
|
||
|
||
|
||
def test_시스템_관리자가_아니면_403(folder: Path) -> None:
|
||
app = FastAPI()
|
||
app.include_router(
|
||
m01_router.router, dependencies=[Depends(verify_session), Depends(require_system_admin)]
|
||
)
|
||
app.dependency_overrides[verify_session] = lambda: {"role": "USER", "company_id": 1}
|
||
client = TestClient(app)
|
||
for url, body in (("/api/m01/logic/sheet", {"row": {}}),
|
||
("/api/m01/logic/sheet/check", {"row": {}, "갈래": "중간", "차례": 0, "식": "1"})): # fmt: skip
|
||
assert client.post(url, json=body).status_code == 403
|