import test from 'node:test' import assert from 'node:assert/strict' import { parseCurrentUser, parseMenuTree } from '../../src/api/session-model.ts' import { roleKind } from '../../src/types/admin.ts' 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-task40-smoke' let sessionCookie = '' let bearerToken = '' /** 登录一次并按浏览器 Cookie 语义缓存会话(真实登录态)。 */ 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 setCookies = typeof res.headers.getSetCookie === 'function' ? res.headers.getSetCookie() : [] const setCookie = setCookies[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') } /** 模拟同源 Cookie 会话:带会话 cookie 或 Bearer 兜底请求受保护端点。 */ 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 }) } test('test_task_040_live_smoke_normal_primary_path', { skip: !HAS_CREDS }, async () => { // 正常主路径:真实登录后凭会话取当前用户。 await ensureSession() const res = await authedFetch('/api/admin/current-user') const user = parseCurrentUser(await res.json()) assert.equal(typeof user.id, 'number') assert.equal(user.username, USER) assert.equal(roleKind(user.role), 'super_admin') }) test('test_task_040_live_smoke_normal_variant_input', { skip: !HAS_CREDS }, async () => { // 正常变体:super_admin 菜单树含后台页面 key,路由级权限可放行。 await ensureSession() const res = await authedFetch('/api/admin/current-user/menus') const tree = parseMenuTree(await res.json()) const keys = new Set() const walk = (nodes: { key: string; children?: unknown }[] | undefined): void => { for (const n of nodes || []) { keys.add(n.key) walk((n.children || []) as { key: string; children?: unknown }[]) } } walk(tree) for (const expected of ['admin_users', 'admin_columns', 'admin_group_manage']) { assert.ok(keys.has(expected), `菜单树应含后台页面 key: ${expected}`) } }) test('test_task_040_live_smoke_normal_repeated_operation_is_idempotent', { skip: !HAS_CREDS }, async () => { // 正常重复:重复拉取用户/菜单结果稳定、不重排。 await ensureSession() const first = parseCurrentUser(await (await authedFetch('/api/admin/current-user')).json()) const second = parseCurrentUser(await (await authedFetch('/api/admin/current-user')).json()) assert.equal(first.id, second.id) const menusA = parseMenuTree(await (await authedFetch('/api/admin/current-user/menus')).json()) const menusB = parseMenuTree(await (await authedFetch('/api/admin/current-user/menus')).json()) assert.equal(JSON.stringify(menusA), JSON.stringify(menusB)) }) test('test_task_040_live_smoke_boundary_empty_input', { skip: !HAS_CREDS }, async () => { // 边界空值:无会话直接请求受保护端点 → 401,前端据此跳登录。 const res = await fetch(`${BASE}/api/admin/current-user`) const body = (await res.json()) as { success?: boolean; code?: number; message?: string } assert.equal(body.success, false) assert.equal(body.code, 401) }) test('test_task_040_live_smoke_boundary_single_item', { skip: !HAS_CREDS }, async () => { // 边界单元素:登录返回单会话 cookie 且可被管理员端点接受。 await ensureSession() assert.ok(sessionCookie || bearerToken, '已建立单会话') const res = await authedFetch('/api/admin/current-user') const user = parseCurrentUser(await res.json()) assert.equal(user.username, USER) }) test('test_task_040_live_smoke_boundary_limit_or_missing_field', { skip: !HAS_CREDS }, async () => { // 边界上限:super_admin 菜单树中的每个模块页面都带可进入的 route(路由级权限放行依据)。 await ensureSession() const tree = parseMenuTree(await (await authedFetch('/api/admin/current-user/menus')).json()) const routes = new Set() const walk = (nodes: { key: string; route?: string; children?: unknown }[] | undefined): void => { for (const n of nodes || []) { if (n.route) routes.add(n.route) walk((n.children || []) as { key: string; route?: string; children?: unknown }[]) } } walk(tree) for (const page of ['/account/users', '/account/menus', '/account/groups']) { assert.ok(routes.has(page), `菜单树应含可进入页面 route: ${page}`) } }) test('test_task_040_live_smoke_invalid_input_rejected', { skip: !HAS_CREDS }, async () => { // 异常输入:伪造 token 被拒绝,返回 401 而非业务数据。 const res = await fetch(`${BASE}/api/admin/current-user`, { headers: { Authorization: 'Bearer not-a-real-token' }, }) const body = (await res.json()) as { success?: boolean; code?: number } assert.equal(body.success, false) assert.equal(body.code, 401) }) test('test_task_040_live_smoke_dependency_failure_returns_actionable_message', { skip: !HAS_CREDS }, async () => { // 依赖失败:真实负载经同一解析器/角色归一后仍符合前端契约(保序、角色可判)。 await ensureSession() const tree = parseMenuTree(await (await authedFetch('/api/admin/current-user/menus')).json()) assert.ok(Array.isArray(tree) && tree.length > 0) assert.equal(typeof tree[0].name, 'string') const user = parseCurrentUser(await (await authedFetch('/api/admin/current-user')).json()) assert.ok(roleKind(user.role) !== null) }) test('test_task_040_live_smoke_logout_clears_cookie', { skip: !HAS_CREDS }, async () => { // 真实登出 E2E:POST /logout 下发清除 aiimage_token 的 Set-Cookie;清除后同源请求回到 401。 const login = await fetch(`${BASE}/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: USER, password: PASS, deviceId: `${DEVICE}-e2e-logout` }), }) const loginBody = (await login.json()) as { success?: boolean; data?: { token?: string } } assert.equal(loginBody.success, true) const token = loginBody.data?.token || '' assert.ok(token) const cookie = `aiimage_token=${token}` const out = await fetch(`${BASE}/logout`, { method: 'POST', headers: { Cookie: cookie, 'X-Requested-With': 'XMLHttpRequest' }, }) assert.equal(out.status, 200, '登出应返回 200 而非内置 LogoutFilter 的 204/500') assert.equal(((await out.json()) as { success?: boolean }).success, true) const clearCookies = typeof out.headers.getSetCookie === 'function' ? out.headers.getSetCookie() : [] const clearHeader = clearCookies[0] || out.headers.get('set-cookie') || '' assert.match(clearHeader, /aiimage_token=;/, '登出应清除 aiimage_token cookie') assert.match(clearHeader, /Max-Age=0|Expires=Thu, 01 Jan 1970/) const after = (await (await fetch(`${BASE}/api/admin/current-user`)).json()) as { success?: boolean; code?: number } assert.equal(after.success, false) assert.equal(after.code, 401) })