- 한눈 표(M02_SO_Def_Table): 군 · 종류 ▲▼ · 이름 · 목록 · 자리꼴 · 정본(고정) · 횡단도 · 카드 이름 · 형상(+ 벽 기본 형태 · 물빼기) · 알약 색 · 3D · 검사 · 처음 값 = 흐림 「기본」 · 바꿈 = 진함 · [처음 값으로] - 종류별: 반영 칸 + 명세 이름 나무 줄마다 보임(컨테이너 = 그 안 칸) · 못 숨김 · 미리보기 숨김 따름 · 「안 씀」 = 목록 B06 만 - 부품 탭(M02_SO_Def_Parts): 부품 · 읽는 칸 · 자리꼴 · 정본 · 쓰는 종류 · 켬/끔(정의 부품 = 끈 것만) - 편집 손질(M02_SO_Def_Reflect): 처음 값이면 반영에서 지움 · [저장] 때 enabled = 목록 both 일 때만 참 - 검사 짝 M02_SO_Def_Check.ts ↔ M02_SO_Check.py(글까지 같음): 반영 · 부품 꼴 · 형상 none → 횡단도 none · 부품 자리꼴 · 정본 · 기본값 없는 읽는 칸 새로 숨김 금지(앞 판 숨김 그대로) · 종류 지우기 · 자리꼴 · 정본 · 군 글자 · 선택지 값 고정 · 서버 check(doc, 지금 판) · 앞 판 없으면 꼴만 - 명세 폼: 쓰는 옵션 키가 모두 숨긴 칸 · 상자는 안 그림(값 그대로) · B05 · B06 시설 · 구조물 명세 폼이 정의 「표시」를 따름 - 판 알림: GET 구조물 목록에 definition_version · structures.json 판 ≠ 지금 판이면 「구조물 정의가 판 N 으로 바뀜 — 저장하면 반영」 한 번 - 시험: test_322_2_check_mirror(거울 · 규칙 · 반영 손질 · 서버 저장 · 422) · 판 알림 · 옛 정의 화면 시험 = 선택지 값 대신 이름 표 · 못 숨김 아닌 칸 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
125 lines
5.0 KiB
Python
125 lines
5.0 KiB
Python
"""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"] = "관 종류"
|
|
# 선택지 값은 만든 뒤 고정(322-2) — 이름은 이름 표(`종류.pipe.이름`)로
|
|
doc["종류"].setdefault("pipe", {}).setdefault("이름", {})["pipe.kind=흄관"] = "흄관(고침)"
|
|
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"] = {"wing_wall_type": False} # 부품이 안 읽는 칸(322-2 읽는 칸은 못 숨김)
|
|
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["판"]
|