"""M02 구조물 정의 편집 화면(PLAN 48-2) — 지난 판 길 · 화면이 보내는 꼴을 서버가 받음. 못박는 것 - 지난 판 목록 `[{판, 저장일}]` 새 판부터 · 한 벌 읽기 · 없는 판 404 · 읽기는 누구나. - 화면이 고친 정의(이름 · 순서 · 안 씀 · 기준측점 · 선택지 이름 · 배치 · 그룹 형상 · 규칙)를 그대로 PUT 하면 판 +1 · 같은 값으로 다시 읽힘. - 시스템 관리자 아니면 PUT 403(화면은 읽기만). - 마스터 정본은 안 씀(사본). """ from __future__ import annotations import json from pathlib import Path import pytest from fastapi import FastAPI from fastapi.testclient import TestClient from B05_Profile import B05_Profile_Structures_Schema as schema from common_util.common_util_auth import verify_session from M02_MasterTemplete.StructOptions.M02_SO_Router import read_router URL = "/api/m02/structure-definition" ADMIN = {"role": "SYSTEM_ADMIN", "user_id": 1} USER = {"role": "USER", "user_id": 2, "company_id": 1} @pytest.fixture def copy(tmp_path: Path, monkeypatch) -> Path: path = tmp_path / "구조물정의.json" path.write_text(Path(schema.DEFINITION_PATH).read_text("utf-8"), "utf-8") monkeypatch.setattr(schema, "DEFINITION_PATH", str(path)) return path def _client(session: dict) -> TestClient: app = FastAPI() app.include_router(read_router) app.dependency_overrides[verify_session] = lambda: session return TestClient(app) def _screen_edit(doc: dict) -> dict: """화면이 하는 고침 한 벌 — `M02_SO_Def*.ts` 가 만드는 꼴 그대로.""" pipe = next(t for t in doc["types"] if t["type_id"] == "pipe") pipe["name"] = "배수관(고침)" kind = next(o for o in pipe["options"] if o["key"] == "pipe_kind") kind["label"] = "관 종류" kind["choices"] = ["흄관(고침)" if c == "흄관" else c for c in kind["choices"]] pipe["options"].append( { "key": "opt_ab12cd", "label": "새 옵션 1", "input": "select", "choices": ["새 선택지 1", "새 선택지 2"], "default": None, } ) pipe["options"][1]["enabled"] = False # 안 씀 first = list(doc["구조물군"]) doc["구조물군"] = {k: doc["구조물군"][k] for k in [first[1], first[0], *first[2:]]} doc["종류"].setdefault("pipe", {})["기준측점"] = False doc["배치"]["pipe"] = {"pipe_kind": {"줄": 1, "칸": 1, "너비": 2}} doc["표시"]["pipe"] = {"inlet_type": False} doc["그룹"]["pipe"][1]["형상"] = { "길이": {"받기": True, "뜻": "유입 보호공 연장"}, "높이": {"받기": False, "뜻": ""}, } doc["규칙"] = [ { "종류": "pipe", "만약": {"옵션": "pipe_kind", "값들": ["흄관(고침)"]}, "그러면": {"옵션": "opt_ab12cd", "선택지": ["새 선택지 2"], "기본값": "새 선택지 2"}, }, { "종류": "pipe", "만약": {"옵션": "inlet_type", "값들": ["집수정"]}, "그러면": {"옵션": "opt_ab12cd", "보이기": False}, }, ] return doc def test_화면_고침을_그대로_받아_판_더하기_1_지난_판_길(copy) -> None: admin = _client(ADMIN) doc = admin.get(URL).json() assert admin.get(f"{URL}/history").json() == [] sent = _screen_edit(json.loads(json.dumps(doc))) res = admin.put(URL, json={"판": doc["판"], "정의": sent}) assert res.status_code == 200, res.text got = res.json() assert got["판"] == doc["판"] + 1 for key in ("구조물군", "종류", "배치", "표시", "그룹", "규칙", "types"): assert got[key] == sent[key], key assert list(got["구조물군"]) == list(sent["구조물군"]) # 순서 그대로 rows = admin.get(f"{URL}/history").json() assert [r["판"] for r in rows] == [doc["판"]] and len(rows[0]["저장일"]) == 16 old = admin.get(f"{URL}/history/{doc['판']}").json() assert old == doc assert admin.get(f"{URL}/history/999").status_code == 404 def test_지난_판은_누구나_읽고_쓰기는_시스템_관리자만(copy) -> None: user = _client(USER) doc = user.get(URL).json() assert user.get(f"{URL}/history").status_code == 200 assert user.put(URL, json={"판": doc["판"], "정의": _screen_edit(doc)}).status_code == 403 assert json.loads(copy.read_text("utf-8"))["판"] == doc["판"] def test_화면이_못_거른_틀린_규칙은_422_안_씀(copy) -> None: admin = _client(ADMIN) doc = admin.get(URL).json() doc["규칙"] = [ { "종류": "pipe", "만약": {"옵션": "pipe_kind", "값들": ["없음"]}, "그러면": {"옵션": "inlet_type"}, } ] assert admin.put(URL, json={"판": doc["판"], "정의": doc}).status_code == 422 assert json.loads(copy.read_text("utf-8"))["판"] == doc["판"]