Files
Aislo/resources/tester/test_m02_layers_store.py
T
eomsangdonandClaude Opus 5.5 b8195aff2e feat(M02): 층 인식 집계표 저장소 — 회사 · 개인 층 · 항목 목록 · 항목 복사 (PLAN 21-2)
- Store · Matset · Branch 가 지금 층 폴더(store.at · 기본 시스템 층)를 봄 — 시스템 층 동작 · 옛 길 그대로
- 회사 · 개인 층 첫 진입 때 빈 뼈대(집계표 열 0 · 조합 장부 다음 1 · 자재 조합 빈 표)
- /api/m02/layers/{layer}/… 집계표 길(번호 · 조합 장부 · 구조물 새로 · 복사 · 조합 갈라 내기 · 자재 조합) · 권한 = 시스템 SYSTEM_ADMIN · 회사 관리자 · 개인 본인
- 층 양식 저장(시스템 · 회사 · 개인)은 집계표 규칙(새 열 파일 · 번호 · SS) 거침 · 시스템 층 쓰기는 SYSTEM_ADMIN · 쓰는 양식 지우기 409
- GET /api/m02/items(한 층 · ?all=1 세 층) · POST /api/m02/layers/personal/items/copy · 회사 없음 409 「회사부터 등록」
- 시험 test_m02_layers_store

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUypcnp5d1gU2aeKh9F2H7
2026-09-29 12:01:08 +09:00

173 lines
8.8 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", *items_router.items_store.ITEM_FIELDS}
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})
ss = admin.get("/api/m02/items?layer=company").json()[0]["key"]
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"
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