Files
Aislo/resources/tester/test_827_1_machine_window.py
T
eomsangdonandClaude Opus 5.5 2149351b34 fix(827-1): 기계 창 [확인] 직후 금액 즉시 — 확인 길이 그 식 셈 기록을 줌
- 서버 확인 길(my-machines/draft) = 화면이 정한 표지로 그 식 한 줄 셈 기록(요소 · 표 · 식 문서) 돌려줌
- 화면은 기록을 계산기 캐시에 합침 → 서버 왕복 전 0 원 깜빡임 없음(미리 셈 값과 같음)
- 기계 창 목록 열 이름 「구분」 · 폭 조정
- 시험 test_827_1 ⑦ 셈 기록 확인 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 10:36:33 +09:00

230 lines
11 KiB
Python

# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
"""827-1 단가산출 기계 창 초안 — 임시 storage 만(M01 정본 · 공유 storage 안 씀).
① 저장 한 번 = 초안 내 기계 만들기 + 줄 연결(표지 → 진짜 키) + 사본 박기 · 미리 셈 = 저장 뒤 셈 · 새 SK(수식 저장 전)
② 같은 초안 두 줄 = 내 기계 하나 ③ 저장 안 함(미리 셈만) = 내 기계 0
④ 실패(판 다름 · 저장 중 오류) = 내 기계 · SK 그대로(만든 것 지움 · 고친 것 되돌림)
⑤ 내 기계 고침 초안 → 그 SK 는 새 값 · 그 기계 쓰는 다른 SK 「단가새값」 → [단가 갱신]
⑥ 회사 층 · 남의 층 거절 ⑦ 확인 길 = 쓰기 없음 · 셈 기록(화면 즉시 셈) · 틀린 몸 400 ⑧ M01 기계 목록 시간당 = 정본 시간당.
"""
from __future__ import annotations
import json
import pytest
from fastapi import HTTPException
from M02_MasterTemplete import M02_Doc_Router as doc_route
from M02_MasterTemplete import M02_Doc_Sk_Path as path
from M02_MasterTemplete import M02_Labor_Ref as refs
from M02_MasterTemplete import M02_Machine_Hourly as mh
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from M02_MasterTemplete import M02_MyMachine as mym
from M02_MasterTemplete import M02_MyMachine_Draft as draft
from M02_MasterTemplete import M02_MyMachine_Router as route
from resources.tester.test_m02_labor_ref import world # noqa: F401 — 같은 임시 층
DIG = "EQ000022" # 굴착기(무한궤도) 1.0㎥ — 연료 · 조종원 있음
NAME = "sub1임시 기계"
USER = {"role": "USER", "company_id": 7, "user_id": 42}
def _at(folder, fn, *args):
with store.at(folder):
return fn(*args)
def _body(price=1500):
vals = {"가격": price, "손료계수": 2500, "주연료": 2, "주연료종류": "경유", "잡재료율": 10,
"조종원": 1, "조종원직종": "1048"} # fmt: skip
return {"이름": NAME, "규격": "1㎾", "방식": "바로", "값": vals}
def _var(n, no, logic):
return {"기호": "P", "아래": f"기계{n}", "위": "", "이름": NAME, "단위": "",
"연결": {"갈래": "단가산출", "키": logic, "입력": {"기계": f"'{no}'"}}} # fmt: skip
def _doc(key, nos, logic, drafts=None):
"""줄 쌓기 기계 줄(화면이 보내는 꼴) — 줄마다 기계='…' · 초안 칸."""
lines = [{"이름": NAME, "단위": "hr", "비목": "묶음", "수량": "1", "단가": f"P_기계{n}"}
for n in range(len(nos))] # fmt: skip
out = {"꼴": "수식문서", "판": 1, "키": key, "이름": "sub1임시", "결과단위": "원",
"변수": [_var(n, no, logic) for n, no in enumerate(nos)], "식": [], "금액": lines} # fmt: skip
return out | ({draft.FIELD: drafts} if drafts is not None else {})
def _sk(folder, name="sub1임시"):
body = {"이름": name, "단위": "시간", "더한줄": []}
return _at(folder, lambda: refs.create("sangeun", body)["이름"])
def _save(folder, key, doc):
판 = _at(folder, path.read, key)["판"]
return _at(folder, path.save, key, {"문서": doc, "판": 판})
def _file(folder, key):
return _at(folder, lambda: store.read("sangeun", key)["문서"])
def _new_draft(folder, tag="EQP초안-7", base=DIG, price=1500):
got = _at(folder, draft.check, {"줄": _body(price), "본뜬": base})
item = {"표지": tag, "본뜬": base, "줄": _body(price)}
return item, got["시간당"]
def test_save_creates_links_and_stamps(world):
"""① 새 SK(수식 저장 전) — 미리 셈 = 확인 시간당 = 저장 뒤 셈 · 내 기계 1 · 표지 → 진짜 키 · 사본에 박힘."""
me = world["personal"]
item, hour = _new_draft(me)
assert hour["ok"] and hour["로직"]
sk = _sk(me)
doc = _doc(sk, ["개인 EQP초안-7"], hour["로직"], [item])
look = _at(me, path.preview, sk, {"문서": doc})
assert look["셈"]["계"] == hour["계"]
assert _at(me, mym.rows) == [] # 미리 셈 = 쓰기 없음
after = _save(me, sk, doc)
rows = _at(me, mym.rows)
assert len(rows) == 1 and rows[0]["본뜬"] == DIG and rows[0]["값"]["가격"] == 1500
key = rows[0]["키"]
saved = _file(me, sk)["수식"]
text = json.dumps(saved["문서"], ensure_ascii=False)
assert "EQP초안" not in text and draft.FIELD not in saved["문서"]
assert f"'개인 {key}'" in text
assert key in [r["키"] for r in saved["단가"]] # 사본에 박힘
assert after["셈"]["계"] == look["셈"]["계"] and after["사본"]["단가새값"] is False
assert _at(me, mym.uses)[key] == [{"키": sk, "이름": "sub1임시"}]
def test_same_draft_two_lines(world):
"""② 같은 초안을 두 줄이 고름 = 내 기계 하나 · 두 줄 모두 그 키."""
me = world["personal"]
item, hour = _new_draft(me)
sk = _sk(me)
doc = _doc(sk, ["개인 EQP초안-7", "개인 EQP초안-7"], hour["로직"], [item])
look = _at(me, path.preview, sk, {"문서": doc})
_save(me, sk, doc)
rows = _at(me, mym.rows)
assert len(rows) == 1
text = json.dumps(_file(me, sk)["수식"]["문서"], ensure_ascii=False)
assert text.count(f"'개인 {rows[0]['키']}'") == 2
assert _at(me, path.read, sk)["셈"]["계"] == look["셈"]["계"] == 2 * hour["계"]
def test_unused_draft_not_made(world):
"""③ 문서가 안 쓰는 초안 = 안 만듦 · 미리 셈만 = 0."""
me = world["personal"]
item, hour = _new_draft(me)
sk = _sk(me)
_at(me, path.preview, sk, {"문서": _doc(sk, ["개인 EQP초안-7"], hour["로직"], [item])})
assert _at(me, mym.rows) == []
_save(me, sk, _doc(sk, ["0101-0010"], "GC000269", [item])) # 줄을 다른 기계로 바꾼 뒤 저장
assert _at(me, mym.rows) == []
with pytest.raises(store.StoreError) as e: # 초안 없는 표지
_save(me, sk, _doc(sk, ["개인 EQP초안-9"], hour["로직"], [item]))
assert e.value.status == 400 and _at(me, mym.rows) == []
def test_fail_leaves_nothing(world, monkeypatch):
"""④ 판 다름 409 · 저장 중 오류 = 만든 내 기계 지움 · 고친 내 기계 되돌림 · SK 그대로."""
me = world["personal"]
item, hour = _new_draft(me)
sk = _sk(me)
doc = _doc(sk, ["개인 EQP초안-7"], hour["로직"], [item])
with pytest.raises(store.StoreError) as e:
_at(me, path.save, sk, {"문서": doc, "판": "옛판"})
assert e.value.status == 409 and _at(me, mym.rows) == []
old = _at(me, mym.create, _body(1000))["줄"]
edit = {"표지": "EQP초안-8", "키": old["키"], "줄": _body(4000)}
both = _doc(sk, ["개인 EQP초안-7", "개인 EQP초안-8"], hour["로직"], [item, edit])
before = _file(me, sk)
def boom(*_a, **_k):
raise store.StoreError(500, "시험 실패")
monkeypatch.setattr(store, "write", boom)
with pytest.raises(store.StoreError):
_save(me, sk, both)
assert _at(me, mym.rows) == [old] # 만든 것 없음 · 고친 것 되돌림
assert _file(me, sk) == before
def test_edit_mine_other_sk_new_value(world):
"""⑤ 내 기계 고침 초안 — 저장한 SK = 새 값 그대로 · 그 기계 쓰는 다른 SK = 단가새값 → [단가 갱신]."""
me = world["personal"]
made = _at(me, mym.create, _body(1000))
key, no, logic, was = (made["줄"]["키"], made["줄"]["원문번호"], made["시간당"]["로직"],
made["시간당"]["계"]) # fmt: skip
first = _sk(me, "sub1임시 첫째")
_save(me, first, _doc(first, [no], logic))
assert _at(me, path.read, first)["셈"]["계"] == was
second = _sk(me, "sub1임시 둘째")
edit = {"표지": "EQP초안-3", "키": key, "줄": _body(5000)}
doc = _doc(second, ["개인 EQP초안-3"], logic, [edit])
look = _at(me, path.preview, second, {"문서": doc})
assert look["셈"]["계"] > was
assert _at(me, mym.rows)[0]["값"]["가격"] == 1000 # 미리 셈 = 원본 그대로
after = _save(me, second, doc)
assert after["셈"]["계"] == look["셈"]["계"] and after["사본"]["단가새값"] is False
assert _at(me, mym.rows)[0]["값"]["가격"] == 5000 and len(_at(me, mym.rows)) == 1
now = _at(me, path.read, first)
assert now["셈"]["계"] == was and now["사본"]["단가새값"] is True
job = _at(me, path.preview, first, {"할일": "단가갱신"})
assert [x["키"] for x in job["바뀐자원"]] == [key] and job["후"]["계"] == look["셈"]["계"]
seen = _at(me, path.machine, first, f"'{no}'")
assert (
seen["사본"]["값"]["가격"] == 1000 and seen["지금"]["값"]["가격"] == 5000 and seen["새값"]
)
def test_company_and_other_refused(world):
"""⑥ 회사 층 = 404(내 기계는 개인 층만) · 남의 개인 층 = 403(일반 사용자)."""
item, hour = _new_draft(world["personal"])
sk = _sk(world["company"])
doc = _doc(sk, ["개인 EQP초안-7"], hour["로직"], [item])
for fn in (path.preview, path.save):
with pytest.raises(store.StoreError) as e:
_at(world["company"], fn, sk, {"문서": doc, "판": ""})
assert e.value.status in (404, 409)
with pytest.raises(store.StoreError) as e:
_at(world["company"], path.preview, sk, {"문서": doc})
assert e.value.status == 404
with pytest.raises(HTTPException) as e:
route.my_machine_draft("personal", route.MachineBody(줄=_body()), "7/43", USER)
assert e.value.status_code == 403
with pytest.raises(HTTPException) as e:
doc_route.save_doc("personal", sk, doc_route.DocBody(문서=doc, 판=""), "7/43", USER)
assert e.value.status_code == 403
def test_check_no_write(world):
"""⑦ 확인 길 = 쓰기 없음 · 직접 = GC001199 · 빈 이름 400 · 없는 내 기계 404."""
me = world["personal"]
got = _at(me, draft.check, {"줄": {"이름": NAME, "방식": "직접",
"시간당": {"노무비": 10, "재료비": 20, "경비": 30}}}) # fmt: skip
assert got["시간당"]["로직"] == "GC001199" and got["시간당"]["계"] == 60
assert got["셈값"] == {"노무비": 10, "재료비": 20, "경비": 30}
rec = _at(me, draft.check, {"줄": _body(), "표지": "EQP초안-55"})["연결"] # 화면 계산기 캐시
assert {"키": "EQ:개인 EQP초안-55", "칸": "가격"} in [e["p"] for e in rec["요소"]]
assert rec["문서"] and "CC000336" in rec["표"]
for raw, code in (({"줄": {"이름": ""}}, 400), ({"줄": _body(), "키": "EQP42-0099"}, 404)):
with pytest.raises(store.StoreError) as e:
_at(me, draft.check, raw)
assert e.value.status == code
assert _at(me, mym.rows) == []
def test_brief_same_as_hourly():
"""⑧ 기계 창 M01 목록 시간당(`brief`) = 정본 시간당(`hourly`) · 원문번호로도 찾음."""
keys = ["EQ000002", "EQ000010", DIG]
rows = mh.brief(keys)
assert [r["키"] for r in rows] == keys
for r in rows:
h = mh.hourly(r["키"])
assert r["시간당"]["ok"] == h["ok"]
if h["ok"]:
assert r["시간당"]["계"] == h["계"] and r["시간당"]["로직"] == h["로직"]
assert mh.brief([rows[0]["원문번호"]])[0]["키"] == "EQ000002"