- 카드 높이 360px → min(640px, 70vh) · 도면 CAD 칸은 카드를 채움 - 엑셀 카드(면적 · 재료) 격자 = 카드 몫 최소 높이(카드 - 8rem) · 판이 없으면 채움 · 자재 조합 판은 그 아래 카드 안 스크롤(격자 높이 0 이던 흠 함께) - 시험 한 줄 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
144 lines
7.6 KiB
Python
144 lines
7.6 KiB
Python
"""M02 구조물 라이브러리 여러 개 · 층 사이 붙여넣기(PLAN 30-2) — 임시 storage.
|
|
|
|
① 회사 항목 · 폴더를 일반 사용자 개인 층에 붙임 — 새 SP 키 · 본뜬 = 원본 · 세 파일 복사 ·
|
|
폴더는 안의 것 · 빈 폴더까지 · 다시 붙이면 「이름 (2)」 · 고른 폴더 안의 것은 한 번만.
|
|
② 권한 — 쓰기 층만(일반 사용자 회사 층 403 · 남의 층 403) · 복사한 층은 읽기 권한(남의 회사 403).
|
|
③ 여러 개 옮기기 · 지우기 한 번에(하나라도 막히면 아무것도 안 바뀜).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Any
|
|
|
|
from resources.tester import test_m02_tree as base
|
|
from resources.tester.test_m02_tree import Tree, _as
|
|
|
|
world = base.world # 같은 임시 storage 판
|
|
|
|
PASTE = "/api/m02/layers/{}/items/paste"
|
|
|
|
|
|
def _company(world: dict[str, Any]) -> Tree:
|
|
t = Tree(_as(world, "ADMIN"), "company")
|
|
t.do(op="add-folder", 경로=[], 이름="배수")
|
|
t.do(op="add-folder", 경로=["배수"], 이름="빈")
|
|
t.do(op="add-item", 경로=["배수"], 이름="측구", 단위="m")
|
|
t.do(op="add-item", 경로=[], 이름="옹벽", 단위="m")
|
|
return t
|
|
|
|
|
|
def test_회사_항목_폴더를_개인에_붙임_새_키_파일_이름2(world):
|
|
company = _company(world)
|
|
wall = company.item(["옹벽"])["key"]
|
|
client = _as(world) # 일반 사용자 42
|
|
ditch = company.item(["배수", "측구"])["key"]
|
|
body = {
|
|
"from_layer": "company",
|
|
"대상": [{"경로": ["배수"]}, {"경로": ["배수"], "키": ditch}, {"경로": [], "키": wall}],
|
|
"경로": [],
|
|
}
|
|
res = client.post(PASTE.format("personal"), json=body)
|
|
assert res.status_code == 200, res.text
|
|
got = res.json()
|
|
paths = [r["경로"] for r in got["items"]]
|
|
assert paths == [["배수", "측구"], ["옹벽"]] # 폴더 안 측구는 한 번만
|
|
assert ["배수", "빈"] in got["폴더"] # 빈 폴더까지
|
|
assert all(r["key"].startswith("SP42-") for r in got["items"])
|
|
assert len(got["알림"]["열"]) == 2
|
|
root = world["storage"] / "7" / "42" / "templates"
|
|
table = json.loads((root / "table/구조물집계표.json").read_bytes())
|
|
made = next(c for c in table["열"] if c.get("경로") == ["옹벽"])
|
|
assert made["본뜬"] == {"layer": "company", "owner": "7", "key": wall}
|
|
for kind in ("structure", "area", "material"):
|
|
assert (root / kind / f"{made['id']}.json").is_file()
|
|
# 다시 붙이면 같은 이름 「(2)」 · 붙일 폴더 안으로
|
|
again = client.post(PASTE.format("personal"), json={**body, "경로": []}).json()
|
|
assert ["배수 (2)", "측구"] in [r["경로"] for r in again["items"]]
|
|
assert ["옹벽 (2)"] in [r["경로"] for r in again["items"]]
|
|
inner = client.post(PASTE.format("personal"), json={**body, "대상": [body["대상"][2]],
|
|
"경로": ["배수"]}).json() # fmt: skip
|
|
assert ["배수", "옹벽"] in [r["경로"] for r in inner["items"]]
|
|
|
|
|
|
def test_붙여넣기_권한(world):
|
|
company = _company(world)
|
|
body = {"from_layer": "company", "대상": [{"경로": ["배수"]}], "경로": []}
|
|
client = _as(world)
|
|
assert client.post(PASTE.format("company"), json=body).status_code == 403 # 일반 = 회사 읽기만
|
|
assert client.post(PASTE.format("personal") + "?owner=7/43", json=body).status_code == 403
|
|
far = {**body, "from_owner": "9"}
|
|
assert client.post(PASTE.format("personal"), json=far).status_code == 403 # 남의 회사 읽기 막힘
|
|
missing = {**body, "대상": [{"경로": [], "키": "SC7-999999"}]}
|
|
assert client.post(PASTE.format("personal"), json=missing).status_code == 404
|
|
# 회사 관리자 = 개인 → 회사 붙여넣기
|
|
admin = _as(world, "ADMIN")
|
|
mine = Tree(admin, "personal")
|
|
mine.do(op="add-item", 경로=[], 이름="내것", 단위="m")
|
|
up = {"from_layer": "personal", "대상": [{"경로": [], "키": mine.item(["내것"])["key"]}]}
|
|
res = admin.post(PASTE.format("company"), json={**up, "경로": ["배수"]})
|
|
assert res.status_code == 200, res.text
|
|
assert ["배수", "내것"] in [r["경로"] for r in res.json()["items"]]
|
|
assert company.client is admin
|
|
|
|
|
|
def test_여러_개_옮기기_지우기_한_번에(world):
|
|
t = Tree(_as(world), "personal")
|
|
t.do(op="add-folder", 경로=[], 이름="가")
|
|
t.do(op="add-folder", 경로=["가"], 이름="안")
|
|
t.do(op="add-item", 경로=["가", "안"], 이름="깊은")
|
|
t.do(op="add-item", 경로=[], 이름="하나")
|
|
t.do(op="add-item", 경로=[], 이름="둘")
|
|
t.do(op="add-folder", 경로=[], 이름="나")
|
|
one, two = t.item(["하나"])["key"], t.item(["둘"])["key"]
|
|
deep = t.item(["가", "안", "깊은"])["key"]
|
|
moves = [
|
|
{"경로": [], "키": one},
|
|
{"경로": [], "키": two},
|
|
{"경로": ["가"]},
|
|
{"경로": ["가", "안"], "키": deep},
|
|
] # fmt: skip — 가 안의 깊은 것은 폴더와 함께
|
|
t.do(op="move", 대상=moves, 새경로=["나"])
|
|
assert sorted(t.paths()) == [["나", "가", "안", "깊은"], ["나", "둘"], ["나", "하나"]]
|
|
before = t.now["판"]
|
|
t.do(422, op="move", 대상=[{"경로": ["나"], "키": one}, {"경로": ["나"]}], 새경로=["나"])
|
|
assert t.client.get(t.url).json()["판"] == before # 막히면 아무것도 안 씀
|
|
t.do(422, op="rename", 대상=[{"경로": ["나"]}], 이름="x")
|
|
note = t.do(op="delete", 대상=[{"경로": ["나"], "키": one}, {"경로": ["나", "가"]}])
|
|
assert len(note["알림"]["지운항목"]) == 2
|
|
assert t.paths() == [["나", "둘"]]
|
|
|
|
|
|
def test_항목_추가_일위대가_단위_없이(world):
|
|
"""PLAN 31-2 — 항목 추가 = 종류(폴더) + 이름 + 일위대가 · 단위 안 받아도 됨."""
|
|
t = Tree(_as(world), "personal")
|
|
t.do(op="add-item", 경로=["옹벽"], 이름="석축", 일위대가="UA000001")
|
|
made = t.item(["옹벽", "석축"])
|
|
assert made["일위대가"] == "UA000001" and made["단위"] is None
|
|
t.do(op="add-item", 경로=["옹벽"], 이름="빈") # 일위대가 비워도 됨
|
|
assert t.item(["옹벽", "빈"])["일위대가"] is None
|
|
|
|
|
|
def test_항목_추가_창_카드_격자_표_접기():
|
|
"""PLAN 31-2 화면 — 창 = 종류(있는 것 · 새 이름 · 고른 폴더면 안 물음) + 이름 + 일위대가 ·
|
|
단위 칸 없음 · 카드 2행 2열 · 높이 고정 · 넘치면 카드 · 상세 칸 스크롤 · 제목 눌러 표 접힘."""
|
|
from pathlib import Path
|
|
|
|
m02 = Path(__file__).resolve().parents[2] / "M02_MasterTemplete"
|
|
dlg = (m02 / "M02_MasterTemplete_TreePanel_Dialogs.ts").read_text("utf-8")
|
|
assert (
|
|
"export function askNewItem(" in dlg and 'kind.input.setAttribute("list", list.id);' in dlg
|
|
)
|
|
assert "const kind = opt.at.length ? null :" in dlg and "M02_TreeUnit" not in dlg
|
|
panel = (m02 / "M02_MasterTemplete_TreePanel.ts").read_text("utf-8")
|
|
assert "...(got.combo ? { 일위대가: got.combo } : {})," in panel
|
|
assert "askNewItem({" in panel and "M02_TreeUnit" not in panel
|
|
css = (m02 / "M02_MasterTemplete_Structure.css").read_text("utf-8")
|
|
assert "grid-template-columns: repeat(2, minmax(0, 1fr));" in css
|
|
assert "grid-auto-rows: var(--m02-struct-card-h);" in css
|
|
assert "--m02-struct-card-h: min(640px, 70vh);" in css # 31-6 화면 높이에 맞춤
|
|
assert "flex: 1 0 var(--m02-struct-grid-min);" in css # 엑셀 격자가 카드 높이를 채움
|
|
assert "max-height: calc(100vh - var(--spacing-64));\n overflow-y: auto;" in css
|
|
main = (m02 / "M02_MasterTemplete_UI_Main.ts").read_text("utf-8")
|
|
assert "host.hidden = !host.hidden;" in main and 'title.classList.toggle("is-folded"' in main
|