Files
Aislo/resources/tester/test_m02_labor_copy.py
T
eomsangdonandClaude Opus 5.5 5617f9e891 feat(M02): 항목 하나 복사 층 넘김 · 실무문서 조합 → 일위대가 · 구조물 복사 = 새 일위대가번 (39-1b B5 · B6)
- POST /layers/{층}/{structures|labor|area|material}/{열}/copy 에 from_layer · from_owner(원본 읽기 권한 · 받는 층 쓰기) · from_layer=practice = 실무문서 조합(UA) → 일위대가 문서(시스템 관리자만 · 일위대가만)
- copy_from 원본 층 · 원본 문서 받음 · 면적산출 복사 뒤 그 열 일위대가 계산값 다시
- 구조물 조합 복사(copy_item) · 분류 더하기 = 일위대가 문서를 새 SU 로 복사 · 열 일위대가 조합 키는 안 받음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYJJRCfHfdCGNhmXTjQKV7
2026-10-04 17:51:17 +09:00

135 lines
6.3 KiB
Python

"""일위대가 복사(PLAN 39-1b B5 · B6) — 항목 하나 복사(같은 층 · 다른 층 · 실무문서 조합) ·
구조물 조합 복사 · 분류 더하기 = 새 일위대가번(조합 키는 안 받음).
시스템 층 = 견본 표(임시 폴더) · 조합 · 로직은 M01 마스터(읽기만) · 개인 층 = 임시 storage.
"""
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 M02_MasterTemplete import M02_MasterTemplete_Items as items_store
from M02_MasterTemplete import M02_MasterTemplete_Router_Items as items_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
TEN = "GC000421"
URL = "/api/m02/layers/{}/{}/{}/copy"
@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)
(tmp_path / "storage").mkdir()
monkeypatch.setattr(config_system, "STORAGE_BASE_DIR", str(tmp_path / "storage"))
with store.at(system):
for cid in ("rv", "c1"):
store.create_structure(cid)
got = store.read("labor", "rv")
logic = [{"로직": TEN, "입력값": {}, "수량": {"손": 2}}]
store.write("labor", "rv", got["판"], {**got["문서"], "단위": "개소", "담은로직": logic})
personal = layers.personal_dir(7, 42)
items_store.ensure(personal)
with store.at(personal):
got = store.read("table", store.STRUCTURE_TABLE)
got["문서"]["열"] = [{"id": "a", "머리": ["돌", "가"], "꼴": "수"}]
store.write("table", store.STRUCTURE_TABLE, got["판"], got["문서"])
session: dict[str, Any] = {"user_id": 42, "company_id": 7, "role": "USER", "is_master": False}
app = FastAPI()
app.include_router(items_router.router)
app.dependency_overrides[verify_session] = lambda: session
return {"client": TestClient(app), "session": session, "system": system, "personal": personal}
def _as(world: dict, role: str = "USER") -> TestClient:
world["session"]["role"] = role
return world["client"]
def _labor(folder: Path, cid: str) -> dict:
return json.loads((folder / "labor" / f"{cid}.json").read_bytes())
def test_같은_층_일위대가_하나_복사_번호_열은_받는_쪽(world):
system = world["system"]
before = _labor(system, "c1")
got = _as(world, "SYSTEM_ADMIN").post(URL.format("system", "labor", "c1") + "?from=rv")
assert got.status_code == 200
c1 = _labor(system, "c1")
assert (c1["열"], c1["일위대가번"]) == ("c1", before["일위대가번"])
assert (c1["단위"], c1["담은로직"][0]["로직"]) == ("개소", TEN)
assert c1["계산값"]["반영합"]["계"] > 0 # 서버가 다시 셈
def test_다른_층에서_내_개인으로_저장_뒤에도_덮어씀(world):
client, personal = _as(world), world["personal"]
url = URL.format("personal", "labor", "a") + "?from=rv&from_layer=system"
for _ in range(2): # 저장된 데이터도 다시 바꿈
assert client.post(url).status_code == 200
assert _labor(personal, "a")["담은로직"][0]["로직"] == TEN
area = URL.format("personal", "area", "a") + "?from=rv&from_layer=system"
assert client.post(area).status_code == 200
def test_권한_없는_원본_층_403_없는_원본_404(world):
client = _as(world)
other = URL.format("personal", "labor", "a") + "?from=x&from_layer=personal&from_owner=7/99"
assert client.post(other).status_code == 403
missing = URL.format("personal", "labor", "a") + "?from=없는열&from_layer=system"
assert client.post(missing).status_code == 404
# 시스템 층 쓰기는 시스템 관리자만
assert client.post(URL.format("system", "labor", "c1") + "?from=rv").status_code == 403
def test_실무문서_조합에서_일위대가로_시스템_관리자만(world):
url = URL.format("system", "labor", "c1") + "?from=UA000008&from_layer=practice"
assert _as(world).post(url).status_code == 403
admin = _as(world, "SYSTEM_ADMIN")
assert admin.post(url).status_code == 200
c1 = _labor(world["system"], "c1")
assert c1["본뜬"] == {"층": "system", "주인": None, "키": "UA000008"}
assert len(c1["담은로직"]) == 14 and all(x["입력값"] == {} for x in c1["담은로직"])
area = URL.format("system", "area", "c1") + "?from=UA000008&from_layer=practice"
assert admin.post(area).status_code == 403 # 실무문서는 일위대가로만
bad = URL.format("system", "labor", "c1") + "?from=UP1-000001&from_layer=practice"
assert admin.post(bad).status_code == 400
def test_구조물_조합_복사는_새_일위대가번_조합_키는_안_받음(world):
system, personal = world["system"], world["personal"]
with store.at(system):
key = next(r["키"] for r in store.read_combos()["줄"] if r["열"] == "rv")
body = {"from_layer": "system", "key": key}
made = _as(world).post("/api/m02/layers/personal/items/copy", json=body).json()
got = _labor(personal, made["열"])
src = _labor(system, "rv")
assert got["담은로직"] == src["담은로직"] and got["일위대가번"] != src["일위대가번"]
assert got["일위대가번"].startswith("SU-")
table = json.loads((personal / "table" / f"{store.STRUCTURE_TABLE}.json").read_bytes())
assert next(c for c in table["열"] if c["id"] == made["열"]).get("일위대가") is None
def test_분류_더하기_복사도_새_번호로_일위대가_문서(world):
system = world["system"]
with store.at(system):
got = store.read("table", store.STRUCTURE_TABLE)
doc = got["문서"]
rv = next(c for c in doc["열"] if c["id"] == "rv")
doc["열"].append({**rv, "id": "rv2", "본뜬열": "rv", "일위대가": None})
store.write("table", store.STRUCTURE_TABLE, got["판"], doc)
new, src = _labor(system, "rv2"), _labor(system, "rv")
assert new["담은로직"] == src["담은로직"] and new["일위대가번"] != src["일위대가번"]