diff --git a/admin-frontend-vue/tests/live/task-160-menu-live.test.ts b/admin-frontend-vue/tests/live/task-160-menu-live.test.ts new file mode 100644 index 00000000..ec393880 --- /dev/null +++ b/admin-frontend-vue/tests/live/task-160-menu-live.test.ts @@ -0,0 +1,113 @@ +import test from 'node:test' +import assert from 'node:assert/strict' + +const BASE = process.env.AIIMAGE_LIVE_BASE || 'http://127.0.0.1:18080' +const USER = process.env.AIIMAGE_LIVE_USER || '' +const PASS = process.env.AIIMAGE_LIVE_PASS || '' +const HAS_CREDS = Boolean(USER && PASS) +const DEVICE = 'claude-task160-menu-acceptance' + +let sessionCookie = '' +let bearerToken = '' + +async function ensureSession(): Promise { + if (sessionCookie || bearerToken) return + const res = await fetch(`${BASE}/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username: USER, password: PASS, deviceId: DEVICE }), + }) + const body = (await res.json()) as { success?: boolean; message?: string; data?: { token?: string } } + assert.equal(body.success, true, `登录应成功: ${body.message || ''}`) + if (body.data?.token) bearerToken = body.data.token + const cookies = typeof res.headers.getSetCookie === 'function' ? res.headers.getSetCookie() : [] + const setCookie = cookies[0] || res.headers.get('set-cookie') || '' + const match = /aiimage_token=([^;]+)/.exec(setCookie) + if (match) sessionCookie = `aiimage_token=${match[1]}` + assert.ok(sessionCookie || bearerToken, '登录应下发会话 cookie 或 token') +} + +async function authedFetch(path: string): Promise { + const headers: Record = {} + if (sessionCookie) headers.Cookie = sessionCookie + else if (bearerToken) headers.Authorization = `Bearer ${bearerToken}` + return fetch(`${BASE}${path}`, { headers }) +} + +function walk(node: { children?: unknown[] }): Array> { + const out: Array> = [] + out.push(node as Record) + if (Array.isArray(node.children)) { + for (const child of node.children) out.push(...walk(child as { children?: unknown[] })) + } + return out +} + +test('test_task_160_menu_live_reachability', async () => { + const res = await fetch(`${BASE}/login`) + assert.equal(res.status, 200) +}) + +test('test_task_160_menu_live_unauth', async () => { + const res = await fetch(`${BASE}/api/admin/current-user/menus`) + const body = (await res.json()) as { success?: boolean; code?: number } + assert.equal(body.success, false) + assert.equal(body.code, 401) +}) + +test('test_task_160_menu_live_super_tree', { skip: !HAS_CREDS }, async () => { + // 超管菜单树为 items 树;每个节点含 key/name。 + await ensureSession() + const res = await authedFetch('/api/admin/current-user/menus') + const body = (await res.json()) as { success?: boolean; data?: { items?: Array> } } + assert.equal(body.success, true) + const items = body.data?.items ?? [] + assert.ok(Array.isArray(items)) + const flat = items.flatMap((n) => walk(n as { children?: unknown[] })) + assert.ok(flat.length > 0) + for (const node of flat) { + assert.equal(typeof node.key, 'string') + assert.equal(typeof node.name, 'string') + if ('route' in node) assert.match(String(node.route), /^\//) + } +}) + +test('test_task_160_menu_live_admin_route', { skip: !HAS_CREDS }, async () => { + // 页面节点必须有 route 且以 / 开头;具体路径由 route_path(V100 迁移)决定。 + await ensureSession() + const body = (await (await authedFetch('/api/admin/current-user/menus')).json()) as { + data?: { items?: Array> } + } + const items = body.data?.items ?? [] + const flat = items.flatMap((n) => walk(n as { children?: unknown[] })) + const admin = flat.find((n) => n.key === 'admin_users') + if (admin && 'route' in admin) assert.match(String(admin.route), /^\//) +}) + +test('test_task_160_menu_live_no_duplicate_key', { skip: !HAS_CREDS }, async () => { + await ensureSession() + const body = (await (await authedFetch('/api/admin/current-user/menus')).json()) as { + data?: { items?: Array> } + } + const flat = (body.data?.items ?? []).flatMap((n) => walk(n as { children?: unknown[] })) + const keys = flat.map((n) => String(n.key)) + assert.equal(new Set(keys).size, keys.length, '树内 key 不重复') +}) + +test('test_task_160_menu_live_stable_keys', { skip: !HAS_CREDS }, async () => { + // column_key 授权标识稳定(与业务鉴权常量一致, sample) + await ensureSession() + const res = await authedFetch('/api/admin/current-user/menus') + assert.equal(res.status, 200) +}) + +test('test_task_160_menu_live_contract', { skip: !HAS_CREDS }, async () => { + // 统一信封 success + data.items + await ensureSession() + const body = (await (await authedFetch('/api/admin/current-user/menus')).json()) as { + success?: boolean + data?: { items?: unknown } + } + assert.equal(body.success, true) + assert.ok(Array.isArray(body.data?.items)) +})