- GET /api/m02/items 줄에 names — 키 대신 보일 이름 · 없으면 null
- 개인 복사 응답 {key} = 새 SS 키 확인 시험
- 결정 기록: 일위대가 갈라 내기 SYSTEM_ADMIN 만 · 첫 진입 뒤 같은 이름 409 그대로
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUypcnp5d1gU2aeKh9F2H7
186 lines
9.6 KiB
Python
186 lines
9.6 KiB
Python
"""M02 층 집계표(PLAN 21-2) — 층별 읽기 · 쓰기 · 권한 · 첫 진입 뼈대 · 항목 목록 · 항목 복사 ·
|
|
시스템 회귀.
|
|
|
|
임시 storage(회사 · 개인 층) + 견본 시스템 집계표(`write_sample_table`)로만 봄 —
|
|
실제 양식은 안 읽음(시스템 층 두 자리 `store.FOLDER` · `layers.SYSTEM_ROOT` 모두 사본).
|
|
자재 코드는 실제 M01 마스터에 있는 MT 키를 씀(자재 조합 저장이 마스터 대조를 함).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pytest
|
|
from fastapi import FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
from common_util.common_util_auth import verify_session
|
|
from config import config_system
|
|
from M01_MasterData import M01_MasterData_Store as m01
|
|
from M02_MasterTemplete import M02_MasterTemplete_Router_Items as items_router
|
|
from M02_MasterTemplete import M02_MasterTemplete_Router_Layers as layers_router
|
|
from M02_MasterTemplete import M02_MasterTemplete_Store as store
|
|
from M02_MasterTemplete import M02_Template_Layers as layers
|
|
from resources.tester.test_m02_structure import write_sample_table
|
|
|
|
TABLE = "table/구조물집계표"
|
|
|
|
|
|
def _material() -> dict[str, Any]:
|
|
for data in m01.loaded()[0].values():
|
|
for row in data.get("줄") or []:
|
|
if str(row.get("키", "")).startswith("MT"):
|
|
return {"코드": row["키"], "이름": row.get("이름"), "규격": None, "단위": None}
|
|
raise AssertionError("마스터에 자재 키가 없음")
|
|
|
|
|
|
@pytest.fixture()
|
|
def world(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> dict[str, Any]:
|
|
system = tmp_path / "system"
|
|
system.mkdir()
|
|
write_sample_table(system)
|
|
monkeypatch.setattr(store, "FOLDER", system)
|
|
monkeypatch.setattr(layers, "SYSTEM_ROOT", system)
|
|
store.renumber()
|
|
storage = tmp_path / "storage"
|
|
storage.mkdir()
|
|
monkeypatch.setattr(config_system, "STORAGE_BASE_DIR", str(storage))
|
|
session: dict[str, Any] = {"user_id": 42, "company_id": 7, "role": "USER", "is_master": False}
|
|
app = FastAPI()
|
|
app.include_router(layers_router.router)
|
|
app.include_router(items_router.router)
|
|
app.dependency_overrides[verify_session] = lambda: session
|
|
return {"client": TestClient(app), "session": session, "storage": storage, "system": system}
|
|
|
|
|
|
def _as(world: dict, role: str = "USER", user: int = 42, company: int | None = 7) -> TestClient:
|
|
world["session"].update(role=role, user_id=user, company_id=company)
|
|
return world["client"]
|
|
|
|
|
|
def _add_column(client: TestClient, layer: str, col: dict[str, Any]) -> None:
|
|
url = f"/api/m02/layers/{layer}/templates/{TABLE}"
|
|
got = client.get(url).json()
|
|
got["문서"]["열"].append(col)
|
|
put = client.put(url, json={"판": got["판"], "문서": got["문서"]})
|
|
assert put.status_code == 200, put.text
|
|
|
|
|
|
COL = {"id": "cw", "머리": ["돌기슭막이", "찰쌓기", "회사형", None], "단위": "m", "꼴": "수"}
|
|
|
|
|
|
def test_시스템_층은_그대로_항목_목록은_SS_줄(world):
|
|
client = _as(world)
|
|
rows = client.get("/api/m02/items").json()
|
|
ss = store.read_combos()["줄"]
|
|
assert [r["key"] for r in rows] == [r["키"] for r in ss] and rows[0]["key"] == "SS000001"
|
|
assert set(rows[0]) == {"key", "names", *items_router.items_store.ITEM_FIELDS}
|
|
assert set(rows[0]["names"]) == {"일위대가", "자재조합", "도번", "면적번", "재료번"}
|
|
assert client.get("/api/m02/layers/system/structure-combos").json() == store.read_combos()
|
|
# 시스템 층 쓰기는 SYSTEM_ADMIN 만
|
|
assert client.post("/api/m02/layers/system/structures/rv").status_code == 403
|
|
url = f"/api/m02/layers/system/templates/{TABLE}"
|
|
got = client.get(url).json()
|
|
assert client.put(url, json={"판": got["판"], "문서": got["문서"]}).status_code == 403
|
|
assert client.delete(f"{url}?판={got['판']}").status_code == 403
|
|
admin = _as(world, "SYSTEM_ADMIN")
|
|
assert admin.put(url, json={"판": got["판"], "문서": got["문서"]}).status_code == 200
|
|
assert admin.delete(f"{url}?판={got['판']}").status_code == 409 # 쓰는 양식
|
|
|
|
|
|
def test_회사_층_첫_진입_뼈대와_쓰기_권한(world):
|
|
client = _as(world)
|
|
assert client.get("/api/m02/items?layer=company").json() == []
|
|
root = world["storage"] / "7" / "templates"
|
|
table = json.loads((root / "table/구조물집계표.json").read_text("utf-8"))
|
|
assert (
|
|
table["열"] == []
|
|
and table["층"]
|
|
== json.loads((world["system"] / "table/구조물집계표.json").read_text("utf-8"))["층"]
|
|
)
|
|
assert json.loads((root / "구조물집계표조합.json").read_text("utf-8"))["다음"] == 1
|
|
assert json.loads((root / "자재조합.json").read_text("utf-8"))["다음"] == 1
|
|
url = f"/api/m02/layers/company/templates/{TABLE}"
|
|
got = client.get(url).json()
|
|
got["문서"]["열"].append(COL)
|
|
assert client.put(url, json={"판": got["판"], "문서": got["문서"]}).status_code == 403
|
|
_add_column(_as(world, "ADMIN"), "company", COL)
|
|
items = _as(world).get("/api/m02/items?layer=company").json() # 같은 회사 일반 사용자
|
|
assert [(r["key"], r["열"]) for r in items] == [("SS000001", "cw")]
|
|
assert items[0]["도번"].startswith("SD") and (root / "structure/cw.json").is_file()
|
|
assert client.get("/api/m02/items?layer=company&owner=9").status_code == 403 # 남의 회사
|
|
# 일위대가(UA) 갈라 내기는 M01 시스템 마스터에 쓰므로 회사 관리자도 못 함
|
|
body = {"본뜬키": "UA000001", "담은로직": [], "이름": "x"}
|
|
admin = _as(world, "ADMIN")
|
|
assert admin.post("/api/m02/layers/company/structures/cw/combo", json=body).status_code == 403
|
|
assert admin.delete(f"{url}?판={client.get(url).json()['판']}").status_code == 409 # 쓰는 양식
|
|
|
|
|
|
def test_개인_층은_본인만_회사_없으면_409(world):
|
|
client = _as(world)
|
|
_add_column(client, "personal", {**COL, "id": "p1"})
|
|
assert [r["열"] for r in client.get("/api/m02/items?layer=personal").json()] == ["p1"]
|
|
assert (world["storage"] / "7" / "42" / "templates" / "area" / "p1.json").is_file()
|
|
other = client.get("/api/m02/items?layer=personal&owner=7/43")
|
|
assert other.status_code == 403
|
|
lonely = _as(world, company=None)
|
|
groups = lonely.get("/api/m02/items?all=1").json()
|
|
assert [(g["layer"], g["items"]) for g in groups][1:] == [("company", []), ("personal", [])]
|
|
assert len(groups[0]["items"]) == len(store.read_combos()["줄"]) # 마스터는 보임
|
|
made = lonely.post(
|
|
"/api/m02/layers/personal/material-combos", json={"줄": {"이름": "a", "담은자재": []}}
|
|
)
|
|
assert made.status_code == 409 and "회사부터" in made.json()["detail"]
|
|
|
|
|
|
def test_회사_항목을_개인으로_복사(world):
|
|
admin = _as(world, "ADMIN")
|
|
_add_column(admin, "company", COL)
|
|
base = "/api/m02/layers/company"
|
|
drawing = admin.get(f"{base}/templates/structure/cw").json()
|
|
doc = {**drawing["문서"], "도면": {**drawing["문서"]["도면"], "entities": [{"type": "line"}]}}
|
|
admin.put(f"{base}/templates/structure/cw", json={"판": drawing["판"], "문서": doc})
|
|
판 = admin.get(f"{base}/material-combos").json()["판"]
|
|
row = {"이름": "회사 자재", "담은자재": [_material()]}
|
|
um = admin.post(f"{base}/material-combos", json={"판": 판, "줄": row}).json()["키"]
|
|
admin.post(f"{base}/structures/cw/use-combo", json={"종류": "자재조합", "키": um})
|
|
admin.post(f"{base}/structures/cw/use-combo", json={"종류": "일위대가", "키": "UA000004"})
|
|
row = admin.get("/api/m02/items?layer=company").json()[0]
|
|
ss = row["key"]
|
|
ua_name = items_router.items_store._combo_names()["UA000004"]
|
|
assert row["names"] == { # 키 대신 화면 이름
|
|
"일위대가": ua_name,
|
|
"자재조합": "회사 자재",
|
|
"도번": "돌기슭막이 찰쌓기 회사형",
|
|
"면적번": "돌기슭막이 찰쌓기 회사형",
|
|
"재료번": "돌기슭막이 찰쌓기 회사형",
|
|
}
|
|
|
|
user = _as(world)
|
|
body = {"from_layer": "company", "from_owner": "7", "key": ss}
|
|
got = user.post("/api/m02/layers/personal/items/copy", json=body)
|
|
assert got.status_code == 200, got.text
|
|
made = got.json()
|
|
assert made["열"] == "cw" and made["key"] == "SS000001" # 응답 = 새 SS 키(바로 고름)
|
|
mine_rows = user.get("/api/m02/items?layer=personal").json()
|
|
assert mine_rows[0]["key"] == made["key"] and mine_rows[0]["names"]["일위대가"] == ua_name
|
|
mine = "/api/m02/layers/personal"
|
|
copy = user.get(f"{mine}/templates/structure/cw").json()["문서"]
|
|
assert copy["도면"]["entities"] == [{"type": "line"}] and copy["열"] == "cw"
|
|
col = next(c for c in user.get(f"{mine}/templates/{TABLE}").json()["문서"]["열"])
|
|
assert col["본뜬"] == {"layer": "company", "owner": "7", "key": ss}
|
|
mat = user.get(f"{mine}/material-combos/{col['자재조합']}").json()
|
|
assert mat["이름"] == "회사 자재" and mat["담은자재"][0]["코드"] == _material()["코드"]
|
|
# 또 복사하면 새 열 id · 새 SS 키 · 자재 조합 이름 (2)
|
|
again = user.post("/api/m02/layers/personal/items/copy", json=body).json()
|
|
assert (again["열"], again["key"]) == ("cw_2", "SS000002")
|
|
names = [r["이름"] for r in user.get(f"{mine}/material-combos").json()["줄"]]
|
|
assert names == ["회사 자재", "회사 자재 (2)"]
|
|
# 마스터 항목도 개인으로 · 없는 키 404
|
|
master = {"from_layer": "system", "key": "SS000001"}
|
|
assert user.post("/api/m02/layers/personal/items/copy", json=master).status_code == 200
|
|
missing = {**body, "key": "SS999999"}
|
|
assert user.post("/api/m02/layers/personal/items/copy", json=missing).status_code == 404
|