Files
Aislo/resources/tester/test_735_1_library_delete.py
T
eomsangdonandClaude Opus 5.5 1d8642fd76 feat(735-1): 서고 나무 지우기 — 쓰는 곳 경고 표 뒤 그 줄째 지움 · 되돌림
- 새 M02_Library_Delete: 쓰는 줄 미리 보기(종류 · 이름 · 키 · 줄 · 층) · 권한 밖 409 막힘 목록 ·
  확인 몸이면 쓰는 문서의 그 줄 지움(store.write 정본 다시 셈) 뒤 문서 지움 · _합친것/ 기록
  (지운 문서 · 자리 · 고친 문서 앞판) · 되돌림(그새 고친 문서는 건너뜀) · 단가산출 바탕 · 더한줄 열쇠 당김
- 미리 보기 길 POST {kind}-tree/delete-check · undo 길이 지움 기록도 되돌림 · 합친 것 목록에 「지움」
- 나무 고침 몸 확인 칸 · 세션을 나무 고침에 넘김 · 옛 _refuse_used 걷음
- 화면: 지우기 앞 경고 창(쓰는 곳 N · 표 · 프로젝트 사본 영향 없음 · 되돌림 자리) · 부품 상자
  확인창에 detail 칸(넓은 판 · 안 스크롤)
- 시험: 새 test_735_1 · test_716_4 의 쓰는 곳 409 시험을 확인 없음 409 + 목록 꼴로 고침
  (지움이 막힘에서 경고 뒤 지움으로 바뀜)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 15:50:56 +09:00

179 lines
8.4 KiB
Python

# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀
"""서고 나무 지우기 = 경고 뒤 지움 · 쓰는 곳 그 줄도 지움(PLAN 735-1 · 요구 574) — 임시 storage.
미리 보기(쓰는 줄 = 종류 · 이름 · 키 · 줄) · 확인 없으면 409 목록 · 확인 = 그 줄 지움
(금액 다시 셈) + 문서 지움 + `_합친것/` 기록 · 되돌림(문서 · 자리 · 고친 문서 앞판 ·
그새 고친 문서는 건너뜀) ·
권한 밖 409(아무것도 안 바뀜) · 폴더 통째 = 한 기록 · 단가산출 바탕 · 더한줄 열쇠 당김(`strip`).
"""
import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient
from common_util.common_util_auth import verify_session
from M02_MasterTemplete import M02_Labor_Ref as refs
from M02_MasterTemplete import M02_Labor_Router as labor_router
from M02_MasterTemplete import M02_Library_Delete as deleting
from M02_MasterTemplete import M02_Library_Same_Router as same_router
from M02_MasterTemplete import M02_MasterTemplete_Store as store
from resources.tester.test_m02_labor_ref import MINE, world # noqa: F401
USER = {"user_id": 42, "company_id": 7, "role": "USER", "is_master": False, "name": "갑"}
TREE = "/api/m02/layers/personal/precombo-tree"
SAME = "/api/m02/layers/personal/precombo-same"
SK_TREE = "/api/m02/layers/personal/sangeun-tree"
SK_SAME = "/api/m02/layers/personal/sangeun-same"
FIX = {"고정": {"이름": "운반", "단위": "식", "단가": {"경비": 5000}}, "수량": {"손": 1}}
@pytest.fixture()
def api(world):
app = FastAPI()
app.include_router(labor_router.router)
app.include_router(same_router.router)
app.dependency_overrides[verify_session] = lambda: USER
return TestClient(app)
def _sc(folder, name, lines):
with store.at(folder):
return refs.create("precombo", {"이름": name, "단위": "m", "담은로직": lines})["이름"]
def _labor(folder, lines):
with store.at(folder):
store.write("labor", "x1", "", {"열": "x1", "담은로직": lines, "자재": []})
def _read(folder, kind, key):
with store.at(folder):
return store.read(kind, key)["문서"]
def _sum(folder):
return _read(folder, "labor", "x1")["계산값"]["반영합"]["계"]
def _tree(api, url=TREE, **body):
판 = api.get(url).json()["판"]
return api.post(url, json={"판": 판, **body})
def _world(world):
"""단가산출 하나(운반 3000 · 1 단위)를 구조물 조합 x1(2 단위) · 일위대가가 물게."""
me = world["personal"]
line = {"고정": {"이름": "운반", "단위": "식", "단가": {"경비": 3000}}, "수량": 1}
with store.at(me):
doc = {"이름": "임시 단가산출", "단위": "m", "바탕": None, "더한줄": [line]}
sk = refs.create("sangeun", doc)["이름"]
top = _sc(me, "위 일위대가", [{"참조": {**MINE, "키": sk}}, FIX])
_labor(me, [{"참조": {**MINE, "키": sk}, "수량": {"손": 2}}, FIX])
return me, sk, top
def test_미리보기_확인없으면_409_확인하면_그줄_지움_금액_다시셈_되돌림(api, world):
me, sk, top = _world(world)
before = _sum(me)
assert before == pytest.approx(5000 + 2 * 3000)
got = api.post(SK_TREE + "/delete-check", json={"대상": [{"키": sk}]})
assert got.status_code == 200, got.text
rows = got.json()["쓰는곳"]
assert got.json()["문서"] == [sk]
assert sorted((r["종류"], r["이름"], r["키"], r["줄"], r["쓰기"]) for r in rows) == [
("구조물 조합", "x1", sk, "1째", True),
("일위대가", "위 일위대가", sk, "1째", True),
]
res = _tree(api, SK_TREE, op="delete", 대상=[{"키": sk}])
assert res.status_code == 409 and len(res.json()["detail"]["쓰는곳"]) == 2
assert (me / "sangeun" / f"{sk}.json").is_file()
res = _tree(api, SK_TREE, op="delete", 대상=[{"키": sk}], 확인=True)
assert res.status_code == 200, res.text
assert res.json()["알림"]["기록"] == sk
assert not (me / "sangeun" / f"{sk}.json").exists()
assert [x.get("고정", {}).get("이름") for x in _read(me, "labor", "x1")["담은로직"]] == ["운반"]
assert len(_read(me, "precombo", top)["담은로직"]) == 1
assert _sum(me) == pytest.approx(5000) # 서버가 다시 셈
gone = api.get(SK_SAME).json()["합친것"]
assert [(g["키"], g["남김"], g["옮긴곳수"]) for g in gone] == [(sk, None, 2)]
back = api.post(SK_SAME + "/undo", json={"키": sk})
assert back.status_code == 200, back.text
assert back.json()["되돌린곳수"] == 2 and back.json()["못한곳"] == []
assert (me / "sangeun" / f"{sk}.json").is_file()
assert _read(me, "labor", "x1")["담은로직"][0]["참조"]["키"] == sk
assert _sum(me) == pytest.approx(before)
assert api.get(SK_SAME).json()["합친것"] == []
def test_쓰는곳_없으면_그냥_지움_기록은_남음(api, world):
me = world["personal"]
sc = _sc(me, "혼자", [FIX])
assert api.post(TREE + "/delete-check", json={"대상": [{"키": sc}]}).json()["쓰는곳"] == []
assert _tree(api, op="delete", 대상=[{"키": sc}]).status_code == 200
assert not (me / "precombo" / f"{sc}.json").exists()
assert api.post(SAME + "/undo", json={"키": sc}).status_code == 200
assert (me / "precombo" / f"{sc}.json").is_file()
def test_권한_밖이면_409_목록_아무것도_안_바뀜(api, world, monkeypatch):
me, sk, _top = _world(world)
monkeypatch.setattr(deleting, "can_write", lambda session, at: False)
res = _tree(api, SK_TREE, op="delete", 대상=[{"키": sk}], 확인=True)
assert res.status_code == 409
detail = res.json()["detail"]
assert "권한 밖" in detail["까닭"] and "x1" in detail["까닭"] and sk in detail["까닭"]
assert {r["이름"] for r in detail["막힘"]} == {"x1", "위 일위대가"}
assert (me / "sangeun" / f"{sk}.json").is_file()
assert len(_read(me, "labor", "x1")["담은로직"]) == 2
def test_폴더_통째_한_기록_되돌림_자리까지_그새_고친_문서는_건너뜀(api, world):
me = world["personal"]
a = _sc(me, "가", [FIX])
b = _sc(me, "나", [FIX])
_labor(me, [{"참조": {**MINE, "키": a}, "수량": {"손": 1}}, FIX])
other = _sc(me, "다른", [{"참조": {**MINE, "키": b}}, FIX])
assert _tree(api, op="add-folder", 경로=[], 이름="묶음").status_code == 200
for key in (a, b):
assert _tree(api, op="move", 키=key, 새경로=["묶음"]).status_code == 200
check = api.post(TREE + "/delete-check", json={"대상": [{"경로": ["묶음"]}]}).json()
assert sorted(check["문서"]) == [a, b]
assert sorted(r["키"] for r in check["쓰는곳"]) == [a, b]
res = _tree(api, op="delete", 대상=[{"경로": ["묶음"]}], 확인=True)
assert res.status_code == 200, res.text
name = res.json()["알림"]["기록"]
assert [g["키"] for g in api.get(SAME).json()["합친것"]] == [name]
with store.at(me): # 그새 「다른」 을 고침 → 그 문서는 앞판으로 안 돌림
have = store.read("precombo", other)
store.write("precombo", other, have["판"], {**have["문서"], "이름": "다른 고침"})
back = api.post(SAME + "/undo", json={"키": name}).json()
assert back["되돌린곳수"] == 1 and "그새 고침" in back["못한곳"][0]
assert (me / "precombo" / f"{a}.json").is_file() and (me / "precombo" / f"{b}.json").is_file()
assert api.get(TREE).json()["자리"] == {a: ["묶음"], b: ["묶음"]}
assert _read(me, "labor", "x1")["담은로직"][0]["참조"]["키"] == a
def test_단가산출_바탕_더한줄_지움_열쇠_당김():
gone = {**MINE, "키": "SK-000002"}
keep = {**MINE, "키": "SK-000003"}
doc = {
"바탕": {"참조": gone},
"열린입력": ["높이"],
"더한줄": [{"참조": gone, "수량": 1}, {"참조": keep, "수량": 1}, {"종류": "로직"}],
"덮개": {"labor:A#1": {"qty": 1}, "add:#1": {"배율": 2}, "add:#2/labor:B#1": {"qty": 3}},
"뺀줄": ["labor:A#2", "add:#0"],
"고름": {"add:#2/material:C#1": "MT1"},
"자재표시": {},
}
out = deleting.strip(doc, {refs.ref_id(gone)})
assert out["바탕"] is None and out["열린입력"] == []
assert [x.get("참조") for x in out["더한줄"]] == [keep, None]
assert out["덮개"] == {"add:#0": {"배율": 2}, "add:#1/labor:B#1": {"qty": 3}}
assert out["뺀줄"] == []
assert out["고름"] == {"add:#1/material:C#1": "MT1"}
assert deleting.strip(doc, set()) == doc # 안 걸리면 그대로