merge(B01): main_laptop_1(549-2 시스템 관리 · 내 정보) 합침 — 옛 구역 정리 · 시험 옮김
- B01_Dashboard_UI_Legacy.ts: 세부 페이지가 다 갈아 끼워 옛 구역 · mountLegacy 지움 · 고른 회사 자리 · 상태 타입만 남김 - B01_Dashboard_UI_Views.ts: 쓰는 곳 없는 legacy 도우미 지움 - B01_Dashboard_UI_Style_Pages.css: 배지 색(549-1) · 시스템 관리 꼴(549-2) 둘 다 살림 - test_b01_settings_cards · test_309_2: 지운 옛 카드 부품 대신 settingLinks · 설계 기본값 페이지 · 사람 페이지를 읽음(낡은 시험) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -264,7 +264,7 @@ def test_카드_시스템_관리자만_글_다_있음():
|
||||
admin, rest = links.split('if (user.role === "SYSTEM_ADMIN") {')[1].split("// 회사 수정 =")
|
||||
assert 'key: "calcSites"' in admin and "calcSites" not in rest
|
||||
assert 'settingLinks(user, companyId).filter((link) => link.key !== "calcSites")' in nav
|
||||
page = _text("B01_Dashboard/B01_Dashboard_UI_Legacy.ts")
|
||||
page = _text("B01_Dashboard/B01_Dashboard_UI_View_SysDefaults.ts")
|
||||
assert 'link.key === "calcSites" ? void openCalcSitesDialog()' in page
|
||||
locale = _text("ui_template/ui_template_locale_m1.ts")
|
||||
for group in calc_sites.GROUPS:
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
"""549-2 시스템 관리 · 내 정보 세부 페이지 시험 — 화면 조작은 ORCA.
|
||||
|
||||
· 거르기 · 게이지 색 · 로그 글 = 순수 함수를 Node 로 바로 돌림
|
||||
· 여섯 열쇠가 새 페이지로 갈아 끼워짐 · 시스템 관리 페이지는 시스템 관리자만(등록부 + 서버 둘 다)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
B01 = ROOT / "B01_Dashboard"
|
||||
|
||||
|
||||
def _text(path: Path) -> str:
|
||||
return path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _slice(name: str, start: str, end: str) -> str:
|
||||
src = _text(B01 / name)
|
||||
return src[src.index(start) : src.index(end)]
|
||||
|
||||
|
||||
def _node(script: str) -> object:
|
||||
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
|
||||
return json.loads(result.stdout)
|
||||
|
||||
|
||||
needs_node = pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
|
||||
|
||||
|
||||
@needs_node
|
||||
def test_게이지_색_선():
|
||||
src = _slice(
|
||||
"B01_Dashboard_UI_View_SysOverview.ts", "export const GAUGE_WARN", "function statCard"
|
||||
)
|
||||
out = _node(
|
||||
src + "console.log(JSON.stringify([null, 0, 69.9, 70, 89.9, 90, 100].map(gaugeTone)));"
|
||||
)
|
||||
assert out == ["ok", "ok", "ok", "warn", "warn", "bad", "bad"]
|
||||
|
||||
|
||||
@needs_node
|
||||
def test_사용자_거르기():
|
||||
src = _slice("B01_Dashboard_UI_View_SysUsers.ts", "/** 역할 · 찾기", "const COLUMNS")
|
||||
users = [
|
||||
{"id": 1, "name": "홍길동", "email": "hong@a.kr", "company_name": "예시건설", "role": "ADMIN"},
|
||||
{"id": 2, "name": "김영희", "email": "kim@b.kr", "company_name": None, "role": "USER"},
|
||||
{"id": 3, "name": "관리", "email": "sys@c.kr", "department": "운영", "role": "SYSTEM_ADMIN"},
|
||||
] # fmt: skip
|
||||
out = _node(
|
||||
"type DashboardUser = any;\n"
|
||||
+ src
|
||||
+ f"const u = {json.dumps(users, ensure_ascii=False)};"
|
||||
+ "console.log(JSON.stringify(["
|
||||
"filterUsers(u, '', '').map(x => x.id),"
|
||||
"filterUsers(u, 'USER', '').map(x => x.id),"
|
||||
"filterUsers(u, '', '예시').map(x => x.id),"
|
||||
"filterUsers(u, '', 'KIM@B').map(x => x.id),"
|
||||
"filterUsers(u, '', 'null').map(x => x.id),"
|
||||
"filterUsers(u, 'ADMIN', '운영').map(x => x.id)]));"
|
||||
)
|
||||
assert out == [[1, 2, 3], [2], [1], [2], [], []]
|
||||
|
||||
|
||||
@needs_node
|
||||
def test_로그_거르기_글():
|
||||
src = _slice("B01_Dashboard_UI_View_SysLogs.ts", "export const ACTION_KO", "function dateInput")
|
||||
logs = [
|
||||
{"action": "PROJECT_CREATE", "email": "a@x.kr", "resource_type": "project",
|
||||
"resource_ref": "12345678-aaaa-bbbb-cccc-1234567890ab", "ip_address": "1.2.3.4",
|
||||
"timestamp": "2026-10-20T09:00:00"},
|
||||
{"action": "COMPANY_CREATE", "email": "b@x.kr", "resource_type": "COMPANY",
|
||||
"resource_id": 7, "ip_address": None, "timestamp": "2026-10-25T10:00:00"},
|
||||
{"action": "NEW_THING", "email": None, "timestamp": "2026-10-26T11:00:00"},
|
||||
] # fmt: skip
|
||||
out = _node(
|
||||
"type AuditLog = any;\n"
|
||||
+ src
|
||||
+ f"const l = {json.dumps(logs, ensure_ascii=False)};"
|
||||
+ "const f = (o: object) => filterLogs(l, { action: '', from: '', to: '', q: '', ...o }).length;"
|
||||
+ "console.log(JSON.stringify(["
|
||||
"actionLabel('PROJECT_CREATE'), actionLabel('NEW_THING'),"
|
||||
"targetText(l[0]), targetText(l[1]), targetText(l[2]),"
|
||||
"f({}), f({ action: 'COMPANY_CREATE' }),"
|
||||
"f({ from: '2026-10-25' }), f({ to: '2026-10-20' }), f({ from: '2026-10-25', to: '2026-10-25' }),"
|
||||
"f({ q: '프로젝트 생성' }), f({ q: '1.2.3' })]));"
|
||||
)
|
||||
assert out == [
|
||||
"프로젝트 생성", "NEW_THING", "프로젝트 12345678…", "회사 7", "-",
|
||||
3, 1, 2, 1, 1, 1, 1,
|
||||
] # fmt: skip
|
||||
|
||||
|
||||
def test_여섯_열쇠_새_페이지():
|
||||
views = _text(B01 / "B01_Dashboard_UI_Views.ts")
|
||||
want = {
|
||||
"sys-overview": "mountSysOverview", "sys-companies": "mountSysCompanies",
|
||||
"sys-users": "mountSysUsers", "sys-defaults": "mountSysDefaults",
|
||||
"sys-logs": "mountSysLogs", "me": "mountMe",
|
||||
} # fmt: skip
|
||||
for key, mount in want.items():
|
||||
line = next(x for x in views.splitlines() if f'key: "{key}"' in x)
|
||||
assert f"mount: {mount} " in line + " ", key
|
||||
if key != "me":
|
||||
assert 'roles: ["SYSTEM_ADMIN"]' in line, key # 시스템 관리 = 시스템 관리자만
|
||||
legacy = _text(B01 / "B01_Dashboard_UI_Legacy.ts")
|
||||
for old in ("sysOverview", "sysCompanies", "sysUsers", "sysDefaults", "sysLogs", " me: (s)"):
|
||||
assert old not in legacy, old # 옛 구역 지움
|
||||
|
||||
|
||||
def test_서버도_시스템_관리자만():
|
||||
router = _text(B01 / "B01_Dashboard_Router.py")
|
||||
for route in ("/admin/companies", "/admin/users", "/admin/audit-logs", "/admin/resources"):
|
||||
block = re.search(r'@router\.get\("%s"\)[\s\S]*?\n\n\n' % re.escape(route), router)
|
||||
assert block and "Depends(require_system_admin)" in block.group(0), route
|
||||
|
||||
|
||||
def test_로그_쪽_불러오기_있음():
|
||||
api = _text(B01 / "B01_Dashboard_Api_Fetch.ts")
|
||||
assert "export async function fetchAuditLogPage(" in api
|
||||
assert "/dashboard/admin/audit-logs?page=${page}&limit=${limit}" in api
|
||||
@@ -15,7 +15,7 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
PAGE = ROOT / "B01_Dashboard" / "B01_Dashboard_UI_Legacy.ts" # 옛 구역 부품(546)
|
||||
PAGE = ROOT / "B01_Dashboard" / "B01_Dashboard_UI_Legacy.ts" # 고른 회사 자리(546)
|
||||
SHELL = ROOT / "B01_Dashboard" / "B01_Dashboard_UI_Shell.ts"
|
||||
NAV = ROOT / "A00_Common" / "m_settings_nav.ts" # 카드 규칙 한 벌(334)
|
||||
|
||||
@@ -31,7 +31,7 @@ def _fn(src: str, name: str) -> str:
|
||||
|
||||
|
||||
def _cards(user: dict, company_id: int | None = None) -> list:
|
||||
src = _text(PAGE)
|
||||
# 카드 = settingLinks 한 벌 · 누름 = 설계 기본값 페이지(SysDefaults)와 같은 갈래(계산 자리만 창)
|
||||
script = (
|
||||
"interface DashboardUser { role: string; is_master: boolean }\n"
|
||||
"const ROUTES = { M01_MASTER_DATA: 'm01-master-data',"
|
||||
@@ -41,21 +41,13 @@ def _cards(user: dict, company_id: int | None = None) -> list:
|
||||
"const navigateTo = (r: string, q?: Record<string, string>) =>"
|
||||
" { went = r + (q ? '?' + new URLSearchParams(q) : ''); };\n"
|
||||
"const openCalcSitesDialog = async () => { went = 'calc-sites-dialog'; };\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(_text(NAV), "settingLinks")
|
||||
+ "\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)]; })));"
|
||||
+ "\nconst links = settingLinks(user, JSON.parse(process.argv[2]));"
|
||||
+ "\nconsole.log(JSON.stringify(links.map((l: any) => {"
|
||||
+ " if (l.key === 'calcSites') void openCalcSitesDialog();"
|
||||
+ " else navigateTo(l.route, l.query);"
|
||||
+ " return [l.title, l.desc, decodeURIComponent(went)]; })));"
|
||||
)
|
||||
result = subprocess.run(
|
||||
["node", "--experimental-strip-types", "--no-warnings",
|
||||
@@ -115,11 +107,10 @@ def test_회사_고르기_시스템_관리자만():
|
||||
assert "sessionStorage.setItem(ADMIN_COMPANY_KEY, String(company.id))" in pick
|
||||
assert "pickAdminCompany(me, companies)" in shell
|
||||
assert 'export 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
|
||||
assert "openAddMemberModal(undefined, s.adminCompanyId)" in src
|
||||
people = _text(ROOT / "B01_Dashboard" / "B01_Dashboard_UI_View_People.ts")
|
||||
for call in ("fetchAllUsers(ctx.companyId)", "fetchCompanyJoinRequests(system ? ctx.companyId",
|
||||
"openAddMemberModal(undefined, companyId)"): # fmt: skip
|
||||
assert call in people, call
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
|
||||
|
||||
Reference in New Issue
Block a user