Files
Aislo/resources/tester/test_b01_settings_cards.py
T
eomsangdonandClaude Opus 5.5 e7d8ea8002 feat(B01): 회사 고르기를 알약으로 — 회사 따라 바뀌는 컨테이너마다(29-1 고침)
- 고르기 select 뺌 · 「시스템 설정」 · 프로젝트 · 사용자 관리 · 가입 요청 · 회사 관리 제목 오른쪽에 고른 회사 알약(공용 ui-tag 모양)
- 누르면 등록된 회사 작은 메뉴(공용 createMapContextMenu) · 어디서 골라도 함께 바뀜 · 알약 누름은 접힘으로 안 샘
- 회사 관리자 · 개인 화면은 알약 없음 · 임시 보관함 그대로
- 시험 test_b01_settings_cards 알약 판정으로 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ly2oHAaTDYRjcniAmHR81z
2026-09-30 17:35:59 +09:00

190 lines
9.0 KiB
Python

"""B01 대시보드 「시스템 설정」 컨테이너 시험(27-3) — 화면 조작은 ORCA.
· 역할별 카드(제목 · 설명 · 이동 해시)를 Node 로 바로 돌림 — 가짜 DOM
· 모든 역할 한 줄 · 통째로 접힘 · 「구조물 관리」 컨테이너 없음
· 회사 고르기(29-1) — 시스템 관리자만 · 리소스 현황 아래 · 해시 company · 서버 인자 한 곳
"""
from __future__ import annotations
import json
import re
import shutil
import subprocess
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
PAGE = ROOT / "B01_Dashboard" / "B01_Dashboard_UI_Page.ts"
def _text(path: Path) -> str:
return path.read_text(encoding="utf-8")
def _fn(src: str, name: str) -> str:
found = re.search(rf"^function {name}\([\s\S]*?^}}", src, re.M)
assert found, f"{name} 를 못 찾음"
return found.group(0)
def _cards(user: dict, company_id: int | None = None) -> list:
src = _text(PAGE)
script = (
"interface DashboardUser { role: string; is_master: boolean }\n"
"const ROUTES = { M01_MASTER_DATA: 'm01-master-data',"
" M02_MASTER_TEMPLATE: 'm02-master-template' };\n"
"const L = (k: string) => k;\n"
"let went = '';\n"
"const navigateTo = (r: string, q?: Record<string, string>) =>"
" { went = r + (q ? '?' + new URLSearchParams(q) : ''); };\n"
"const mk = (): any => ({ children: [] as any[], textContent: '', className: '',"
" on: null as any,"
" append(...c: any[]) { this.children.push(...c); },"
" addEventListener(_: string, f: any) { this.on = f; } });\n"
"const document = { createElement: () => mk() };\n"
+ _fn(src, "settingCard")
+ "\n"
+ _fn(src, "buildSystemSettingsPanel")
+ "\nconst user = JSON.parse(process.argv[1]);"
+ "\nconst panel = buildSystemSettingsPanel(user, JSON.parse(process.argv[2]));"
+ "\nconsole.log(JSON.stringify(panel.children.map((c: any) => {"
+ " c.on(); return [c.children[0].textContent, c.children[1].textContent,"
+ " decodeURIComponent(went)]; })));"
)
result = subprocess.run(
["node", "--experimental-strip-types", "--no-warnings",
"--input-type=module-typescript", "-e", script, json.dumps(user), json.dumps(company_id)],
capture_output=True, text=True, encoding="utf-8", timeout=60,
) # fmt: skip
assert result.returncode == 0, result.stderr
return json.loads(result.stdout)
M02 = "m02-master-template"
READ_CO = (
"B01_Dashboard_PermMasterRead · B01_Dashboard_PermCompanyRead · B01_Dashboard_PermPersonalWrite"
)
WRITE_CO = READ_CO.replace("CompanyRead", "CompanyWrite")
@pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
def test_시스템_관리자_카드_다섯_고른_회사_해시():
m = "B01_Dashboard_PermMasterWrite"
assert _cards({"role": "SYSTEM_ADMIN", "is_master": False}, 3) == [
["B01_Dashboard_MasterElements", "B01_Dashboard_PermSourceRead", "m01-master-data"],
["B01_Dashboard_CardUnitCombo", m, f"{M02}?open=일위대가&root=master&company=3"],
["B01_Dashboard_CardStructCombo", m, f"{M02}?open=구조물조합&root=master&company=3"],
["B01_Dashboard_CardStructOption", m, f"{M02}?open=옵션&root=master&company=3"],
["B01_Dashboard_CardDrawing", m, f"{M02}?open=도면&root=master&company=3"],
]
# 회사가 없으면 해시에 안 실음
assert _cards({"role": "SYSTEM_ADMIN", "is_master": False})[1][2] == (
f"{M02}?open=일위대가&root=master"
)
@pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
def test_회사_관리자_개인_카드_둘():
company = [
["B01_Dashboard_CardUnitCombo", WRITE_CO, f"{M02}?open=일위대가&root=company"],
["B01_Dashboard_CardStructCombo", WRITE_CO, f"{M02}?open=구조물조합&root=company"],
]
assert _cards({"role": "ADMIN", "is_master": False}, 3) == company # 회사 인자 안 실음
assert _cards({"role": "USER", "is_master": True}) == company # 회사 마스터 = 회사 수정
assert _cards({"role": "USER", "is_master": False}) == [
["B01_Dashboard_CardUnitCombo", READ_CO, f"{M02}?open=일위대가&root=personal"],
["B01_Dashboard_CardStructCombo", READ_CO, f"{M02}?open=구조물조합&root=personal"],
]
def test_모든_역할_한_줄_통째로_접힘():
src = _text(PAGE)
build = _fn(src, "buildPage")
# 세 갈래 모두 한 번씩 · 시스템 관리자는 리소스 현황 아래(29-1) · 프로젝트 위
assert build.count("buildSystemSettingsSection(state)") == 3
sys_part = build.split('} else if (state.user.role === "ADMIN")')[0]
assert (
sys_part.index('L("B01_Dashboard_Resources")')
< sys_part.index("buildSystemSettingsSection(state)")
< sys_part.index('L("B01_Dashboard_Projects")')
)
assert "StructureManage" not in src and "buildStructurePanel" not in src
assert "top-row" not in src
sect = _fn(src, "buildSystemSettingsSection")
assert 'L("B01_Dashboard_SystemSettings"), panel, true, pill)' in sect # 한 줄 통째
assert '"ui-collapsible"' in sect and '"ui-collapsible__title"' in sect
assert "attachCollapsible(card)" in sect
config = _text(ROOT / "config" / "config_frontend.ts")
assert re.search(r"PROTECTED_ROUTES[\s\S]*?ROUTES\.M02_MASTER_TEMPLATE,[\s\S]*?\];", config)
def test_회사_알약_시스템_관리자만():
src = _text(PAGE)
sect = _fn(src, "buildSystemSettingsSection")
assert 'user.role === "SYSTEM_ADMIN" ? [companyPill(state)] : []' in sect
pill = _fn(src, "companyPill")
assert "sessionStorage.setItem(ADMIN_COMPANY_KEY, String(c.id))" in pill
assert "event.stopPropagation()" in pill # 알약 누름이 접힘으로 새지 않음
assert 'createMapContextMenu("b01-dashboard")' in pill # 공용 작은 메뉴
assert '"ui-tag ui-tag--accent b01-dashboard__company-pill"' in pill # 공용 알약 모양
assert 'const ADMIN_COMPANY_KEY = "aislo.adminCompany";' in src # M02 와 같은 키
load = src.split("async function loadRoleData")[1].split("\n}\n")[0]
for call in ("fetchAllUsers(companyId)", "fetchCompanyJoinRequests(companyId)",
"fetchCompanyProjects(companyId)"): # fmt: skip
assert call in load, call
build = _fn(src, "buildPage")
# 회사 따라 바뀌는 컨테이너 넷(프로젝트 · 사용자 관리 · 가입 요청 · 회사 관리)마다 알약
sys_part = build.split('} else if (state.user.role === "ADMIN")')[0]
assert sys_part.count("companyPill(state)") == 4
assert "companyPill" not in build.split('} else if (state.user.role === "ADMIN")')[1]
assert "companyTable(picked, state.user)" in build
assert "openAddMemberModal(undefined, state.adminCompanyId)" in build
@pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
def test_고른_회사_저장값_검사():
src = _text(PAGE)
script = (
"interface DashboardUser { company_id?: number | null }\n"
"interface CompanyInfo { id: number }\n"
"const ADMIN_COMPANY_KEY = 'aislo.adminCompany';\n"
"const store: Record<string, string> = {};\n"
"const sessionStorage = { getItem: (k: string) => store[k] ?? null };\n"
+ _fn(src, "pickAdminCompany")
+ "\nconst cs = [{ id: 1 }, { id: 3 }];"
+ "\nconst out = [pickAdminCompany({ company_id: 1 }, cs)];"
+ "\nstore[ADMIN_COMPANY_KEY] = '3'; out.push(pickAdminCompany({ company_id: 1 }, cs));"
+ "\nstore[ADMIN_COMPANY_KEY] = '9'; out.push(pickAdminCompany({ company_id: 1 }, cs));"
+ "\nout.push(pickAdminCompany({ company_id: null }, cs));"
+ "\nconsole.log(JSON.stringify(out));"
)
result = subprocess.run(
["node", "--experimental-strip-types", "--no-warnings",
"--input-type=module-typescript", "-e", script],
capture_output=True, text=True, encoding="utf-8", timeout=60,
) # fmt: skip
assert result.returncode == 0, result.stderr
# 기본 자기 회사 · 고른 회사 · 없는 회사면 자기 회사 · 자기 회사 없으면 첫 회사
assert json.loads(result.stdout) == [1, 3, 1, 1]
def test_서버_회사_인자_한_곳():
scope = _text(ROOT / "B01_Dashboard" / "B01_Dashboard_Scope.py")
assert 'if session["role"] == "SYSTEM_ADMIN" and company_id is not None:' in scope
router = _text(ROOT / "B01_Dashboard" / "B01_Dashboard_Router.py")
for name in (
"admin_add_member",
"admin_invite_member",
"admin_join_requests",
"admin_projects",
):
body = router.split(f"async def {name}(")[1].split("@router.")[0]
assert "company_id: int | None = Query(None, gt=0)" in body, name
assert "_scope_company(session, company_id)" in body, name
users = router.split("async def system_users(")[1].split("@router.")[0]
assert "require_system_admin" in users and "list_all_users(company_id)" in users
assert "router.include_router(assets_router)" in router
assert len(router.splitlines()) <= 700