# ruff: noqa: F811 — 임시 층 fixture(world)를 test_m02_labor_ref 에서 받아 씀 """서고 [중복 찾기] · 합치기 · 되돌림(PLAN 733-3 · 요구서_572 3장 4) — 임시 storage. 묶음 = 이 층 + 위 층 같은 지문 · 합치기 = 쓰는 곳 참조 → 남김(금액 같음) · 지운 문서 `_합친것/` · 되돌림 = 문서 · 참조 되살림 · 지문 · 판 다르면 409 · 권한 밖 · 같은 참조 두 번 = 그 문서 남김 · 쓰는 곳 = 구조물 조합(740-1 — 단가산출 · 일위대가끼리는 펼쳐 담겨 참조 없음) · 위 층과 같은 아래 층 문서 = 남김 = 위 층 키 · 일반 사용자 회사 층 = 403. """ 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_Library_Same_Router as same_router from M02_MasterTemplete import M02_MasterTemplete_Store as store from M02_MasterTemplete import M02_Sangeun_Calc as calc from resources.tester.test_m02_labor_ref import COMPANY, MINE, world # noqa: F401 PCT = "GC001109" # 원/㎡ · 인력 둘 + 「노무비 * 3 / 100」 USER = {"user_id": 42, "company_id": 7, "role": "USER", "is_master": False, "name": "갑"} ADMIN = {**USER, "role": "ADMIN", "name": "관리"} URL = "/api/m02/layers/{}/sangeun-same" @pytest.fixture() def api(world): app = FastAPI() app.include_router(same_router.router) who = dict(USER) app.dependency_overrides[verify_session] = lambda: who got = TestClient(app) got.who = who return got def _make(folder, **doc): with store.at(folder): return refs.create("sangeun", {"단위": "㎡", "더한줄": [], **doc})["이름"] def _read(folder, key): with store.at(folder): return store.read("sangeun", key)["문서"] def _total(folder, key): with store.at(folder): return calc.calc_doc(store.read("sangeun", key)["문서"])["기준"]["계"] def _top(ref_keys, at=MINE): return [{"참조": {**at, "키": k}, "수량": 2} for k in ref_keys] + [ {"고정": {"이름": "운반", "단위": "식", "단가": {"경비": 5000}}, "수량": 1} ] def _user(folder, ref_keys, at=MINE, col="x1"): """쓰는 곳 = 구조물 조합(740-1 — 단가산출 · 일위대가끼리는 펼쳐 담겨 참조 없음).""" lines = [{**x, "수량": {"손": x["수량"]}} for x in _top(ref_keys, at)] with store.at(folder): store.write("labor", col, "", {"열": col, "담은로직": lines, "자재": []}) return col def _lines(folder, col): with store.at(folder): return store.read("labor", col)["문서"]["담은로직"] def _sum(folder, col): from M02_MasterTemplete import M02_Labor_Doc as labor with store.at(folder): doc = labor.clean({**store.read("labor", col)["문서"], "열": col}) return float(labor.calc(doc, {})["반영합"]["계"]) def _merge(api, keep, gone, 판, layer="personal"): body = {"남김": keep, "지움": gone, "판": 판} return api.post(URL.format(layer) + "/merge", json=body) def test_권한_밖이면_안_지움(api, world, monkeypatch): me = world["personal"] a = _make(me, 이름="가", 바탕={"로직": PCT}) c = _make(me, 이름="다", 바탕={"로직": PCT}) top = _user(me, [c]) monkeypatch.setattr(same_router, "_can_write", lambda session, at: False) 판 = api.get(URL.format("personal")).json()["판"] res = _merge(api, {**MINE, "키": a}, [c], 판) left = res.json()["남김"] assert res.status_code == 200 and left[0]["키"] == c and "권한 밖" in left[0]["쓰는곳"][0] assert res.json()["합침"] == [] and (me / "sangeun" / f"{c}.json").is_file() assert _lines(me, top)[0]["참조"]["키"] == c def test_합치기_참조_옮김_금액_같음_되돌림(api, world): me = world["personal"] a = _make(me, 이름="밑", 바탕={"로직": PCT}) b = _make(me, 이름="다른 이름 같은 내용", 바탕={"로직": PCT}) _make(me, 이름="다른 내용", 바탕={"로직": PCT}, 더한줄=_top([])[-1:]) top = _user(me, [b]) before = _sum(me, top) got = api.get(URL.format("personal")) assert got.status_code == 200, got.text view = got.json() assert len(view["묶음"]) == 1 rows = view["묶음"][0]["문서"] assert [r["키"] for r in rows] == [a, b] and view["묶음"][0]["꼬리표"] == "내용 같음" assert [r["쓰는곳수"] for r in rows] == [0, 1] and not any(r["위층"] for r in rows) res = _merge(api, {**MINE, "키": a}, [b], view["판"]) assert res.status_code == 200, res.text out = res.json() assert out["합침"] == [{"키": b, "옮긴곳수": 1}] and out["남김"] == [] assert out["묶음"] == [] and [g["키"] for g in out["합친것"]] == [b] assert not (me / "sangeun" / f"{b}.json").exists() assert (me / same_router.GONE / f"{b}.json").is_file() assert _lines(me, top)[0]["참조"]["키"] == a assert _sum(me, top) == pytest.approx(before) back = api.post(URL.format("personal") + "/undo", json={"키": b}) assert back.status_code == 200, back.text assert back.json()["되돌린곳수"] == 1 and back.json()["합친것"] == [] assert (me / "sangeun" / f"{b}.json").is_file() assert not (me / same_router.GONE / f"{b}.json").exists() assert _lines(me, top)[0]["참조"]["키"] == b assert _sum(me, top) == pytest.approx(before) assert api.post(URL.format("personal") + "/undo", json={"키": b}).status_code == 404 def test_지문_판_다르면_409(api, world): me = world["personal"] a = _make(me, 이름="가", 바탕={"로직": PCT}) b = _make(me, 이름="나", 바탕={"로직": PCT}) 판 = api.get(URL.format("personal")).json()["판"] assert _merge(api, {**MINE, "키": a}, [b], "옛판").status_code == 409 with store.at(me): # 그새 b 내용이 바뀜 doc = store.read("sangeun", b) store.write("sangeun", b, doc["판"], {**doc["문서"], "더한줄": _top([])[-1:]}) res = _merge(api, {**MINE, "키": a}, [b], 판) assert res.status_code == 409 and "달라짐" in res.text assert (me / "sangeun" / f"{b}.json").is_file() assert _merge(api, {**MINE, "키": a}, [a], 판).status_code == 400 # 남김 = 지움 assert _merge(api, {**MINE, "키": a}, ["SK-000099"], 판).status_code == 404 def test_권한_밖_남김_둘다_담은_문서(api, world, monkeypatch): me = world["personal"] a = _make(me, 이름="가", 바탕={"로직": PCT}) b = _make(me, 이름="나", 바탕={"로직": PCT}) c = _make(me, 이름="다", 바탕={"로직": PCT}) both = _user(me, [a, b]) before = _sum(me, both) 판 = api.get(URL.format("personal")).json()["판"] # 둘 다 담은 구조물 조합 = 같은 참조 두 번이 되어 못 옮김 → b 남김 · c 만 합침 · 금액 같음 res = _merge(api, {**MINE, "키": a}, [b, c], 판) assert res.status_code == 200, res.text assert [r["키"] for r in res.json()["합침"]] == [c] assert [r["키"] for r in res.json()["남김"]] == [b] assert [x["참조"]["키"] for x in _lines(me, both)[:2]] == [a, b] assert _sum(me, both) == pytest.approx(before) assert api.post(URL.format("personal") + "/undo", json={"키": c}).status_code == 200 def test_위층과_같으면_남김_위층_키(api, world): me, firm = world["personal"], world["company"] up = _make(firm, 이름="회사 것", 바탕={"로직": PCT}) mine = _make(me, 이름="내 것", 바탕={"로직": PCT}) top = _user(me, [mine]) before = _sum(me, top) view = api.get(URL.format("personal")).json() rows = view["묶음"][0]["문서"] assert [(r["층"], r["키"], r["위층"]) for r in rows] == [ ("company", up, True), ("personal", mine, False), ] res = _merge(api, {**COMPANY, "키": up}, [mine], view["판"]) assert res.status_code == 200, res.text assert _lines(me, top)[0]["참조"] == {**COMPANY, "키": up} assert _sum(me, top) == pytest.approx(before) assert (firm / "sangeun" / f"{up}.json").is_file() # 위 층 문서는 지울 수 없음 · 일반 사용자는 회사 층 합치기 403 assert _merge(api, {**MINE, "키": mine}, [up], view["판"]).status_code in (403, 404) assert api.get(URL.format("company")).status_code == 403 api.who.update(ADMIN) assert api.get(URL.format("company")).status_code == 200 def test_일위대가_수량만_다름_꼬리표(api, world): me = world["personal"] line = {"고정": {"이름": "운반", "단위": "식", "단가": {"경비": 5000}}, "수량": None} with store.at(me): keys = [ refs.create("precombo", {"이름": n, "단위": "m", "담은로직": [line], "본뜬": src})[ "이름" ] for n, src in ( ( "수로BOX 4.0×2.0", {"층": "실무자료", "키": "HP-1 제1호표", "실무수량": {"줄": {"0": 3}}}, ), ( "수로BOX 2.5×2.5", {"층": "실무자료", "키": "HP-1 제2호표", "실무수량": {"줄": {"0": 7}}}, ), ) ] got = api.get("/api/m02/layers/personal/precombo-same") assert got.status_code == 200, got.text group = got.json()["묶음"][0] assert group["꼬리표"] == "수량만 다름" and [r["키"] for r in group["문서"]] == keys