"""M02 층 화면(`?layer=personal|company`) 붙는 자리 시험 — 화면 조작은 ORCA. · 권한 규칙(`accessFor`)을 Node 로 바로 돌림 — 시스템 SYSTEM_ADMIN · 회사 ADMIN/is_master · 개인 본인 · 회사 없음 · 주소 인자를 라우트에서 떼는지 · 대시보드 카드 역할별 단추 · 층 길 접두어 """ from __future__ import annotations import json import re import shutil import subprocess from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] M02 = ROOT / "M02_MasterTemplete" LAYER = M02 / "M02_MasterTemplete_Layer.ts" def _text(path: Path) -> str: return path.read_text(encoding="utf-8") @pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음") def test_층별_고칠_권한(): found = re.search(r"^export function accessFor[\s\S]*?^}", _text(LAYER), re.M) assert found, "accessFor 를 못 찾음" script = ( 'type ManageLayer = "system" | "company" | "personal";\n' "interface SessionUser { role?: string; company_id?: number | null; is_master?: boolean }\n" "interface LayerAccess { canWrite: boolean; needCompany: boolean }\n" + found.group(0).replace("export ", "") + "\nconst cases = JSON.parse(process.argv[1]);" + "\nconsole.log(JSON.stringify(cases.map((c) => accessFor(c[0], c[1]))));" ) sysadmin = {"role": "SYSTEM_ADMIN", "company_id": None} admin = {"role": "ADMIN", "company_id": 3} master = {"role": "USER", "company_id": 3, "is_master": True} user = {"role": "USER", "company_id": 3} orphan = {"role": "USER", "company_id": None} cases = [ ["system", sysadmin], ["system", admin], ["system", None], ["company", admin], ["company", master], ["company", user], ["company", orphan], ["personal", user], ["personal", admin], ["personal", orphan], ["personal", sysadmin], ] # fmt: skip result = subprocess.run( ["node", "--experimental-strip-types", "--no-warnings", "--input-type=module-typescript", "-e", script, json.dumps(cases)], capture_output=True, text=True, encoding="utf-8", timeout=60, ) # fmt: skip assert result.returncode == 0, result.stderr ok = {"canWrite": True, "needCompany": False} ro = {"canWrite": False, "needCompany": False} no_company = {"canWrite": False, "needCompany": True} assert json.loads(result.stdout) == [ ok, ro, ro, # 시스템 = SYSTEM_ADMIN 만 ok, ok, ro, no_company, # 회사 = ADMIN · is_master · 일반은 읽기만 · 회사 없음 안내 ok, ok, no_company, no_company, # 개인 = 본인 · 회사 없으면 안내 ] # fmt: skip def test_주소_인자를_라우트에서_뗌(): router = _text(ROOT / "A00_Common" / "router.ts") assert '.split("?")[0]' in router assert "export function currentQuery()" in router assert "query?: Record" in router layer = _text(LAYER) assert 'value === "company" || value === "personal" ? value : "system"' in layer def test_대시보드_카드_역할별_단추(): page = _text(ROOT / "B01_Dashboard" / "B01_Dashboard_UI_Page.ts") assert ( page.count('section(L("B01_Dashboard_StructureManage"), buildStructurePanel(state))') == 3 ) body = page.split("function buildStructurePanel")[1].split("function buildPage")[0] # 개인용은 모두 · 회사용은 ADMIN · SYSTEM_ADMIN 만 assert 'state.user.role === "ADMIN" || state.user.role === "SYSTEM_ADMIN"' in body assert body.index('go("company"') < body.index('go("personal"') assert "ROUTES.M02_MASTER_TEMPLATE, { layer }" in body config = _text(ROOT / "config" / "config_frontend.ts") assert re.search(r"PROTECTED_ROUTES[\s\S]*?ROUTES\.M02_MASTER_TEMPLATE,[\s\S]*?\];", config) def test_층_길_접두어와_고정_풀림(): api = _text(M02 / "M02_MasterTemplete_Api_Fetch.ts") assert 'activeLayer === "system" ? "" : `/layers/${activeLayer}`' in api for path in ("/structures/numbers", "/structure-combos", "/material-combos"): assert re.search(rf'scoped\(\s*[`"]{re.escape(path)}', api), path assert "/layers/personal/items/copy" in api for name in ("Structure", "Combo_Api"): assert '"system"' not in _text(M02 / f"M02_MasterTemplete_{name}.ts") side = _text(M02 / "M02_MasterTemplete_UI_Side.ts") assert "const layer = opt.layer;" in side assert 'layer === "system"' in side # 도면 양식은 시스템 층에만 main = _text(M02 / "M02_MasterTemplete_UI_Main.ts") assert 'sel.layer !== "system"' not in main assert 'return layer === "project" || isAdmin;' in main def test_읽기만_회사용_개인용으로_복사_막대(): page = _text(M02 / "M02_MasterTemplete_UI_Page.ts") assert 'layer === "company" && !isAdmin ? buildReadOnlyBar(user)' in page assert "onColumn: (column) => readOnlyBar?.setColumn(column)" in page assert "access.needCompany" in page and "buildNeedCompany()" in page assert "titleOf(layer)" in page layer = _text(LAYER) assert 'fromLayer: "company"' in layer and "M02_CopyToPersonal" in layer