- 셈 구동 떼기: 새 M01_MasterData_UI_Logic_Drive.ts(driveRun · previewText · autoFill · createDrive) — M01 상세는 숨은 시험 계산 칸 단추 대신 구동 함수 · 시험 계산 칸(Logic_Calc)은 로직 복사 화면이 쓰므로 남기고 같은 구동을 부름 · 재료 고름은 rerun 고리 - M02: 새 _Sangeun_Tabs.ts(수식 · 단가산출 상세 보기 고르기) · Detail 의 시험 계산 · 인력/재료/경비 걷음 - 걷은 파일(git rm): M01 LogicLab_Sheet · Logic_Sheet_Model · LogicLab_Formula(.ts .css) · FormulaTokens · LogicLab_Est / M02 Sangeun_Sheet(.ts .css) · SheetEdit · SheetFormula · logic Sheet_Model · Const_Model · Formula_Model · 부품 시트 줄(createCalcSheetRow) · Cover_Model.sheetGroups · Calc 의 M02 고리(answer · shown · held) · 죽은 CSS · 글 - 서버 길 · 문서 칸(변수줄 · 글줄 · 덮개)은 남김 — 금액 그대로 - 낡은 시험: test_790_1(시트 화면 전부) · test_714_3 · test_748_2(+ helper)는 화면이 없어져 지움 · test_794_1 · test_790_2 는 화면 · TS 거울만 덜고 서버 시험 남김 · test_714_2 · 717_2b · 721 · 722(원래 틀린 한 줄 고침) · 734 · 737 · 750_2 · 793_3b · 796_2 · 796_2b 는 보기 둘 · 새 파일에 맞춤 · 새 test_799_2_old_tabs_gone Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
127 lines
6.1 KiB
Python
127 lines
6.1 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)
|
||
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
|