task-160(Java 菜单树与 Flyway): 本地 Java 真实验收通过(树契约/路由形态/无重复key)
本地 18080 启动后端(dev 库, Flyway 关闭)验证菜单树接口。
This commit is contained in:
@@ -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<void> {
|
||||
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<Response> {
|
||||
const headers: Record<string, string> = {}
|
||||
if (sessionCookie) headers.Cookie = sessionCookie
|
||||
else if (bearerToken) headers.Authorization = `Bearer ${bearerToken}`
|
||||
return fetch(`${BASE}${path}`, { headers })
|
||||
}
|
||||
|
||||
function walk(node: { children?: unknown[] }): Array<Record<string, unknown>> {
|
||||
const out: Array<Record<string, unknown>> = []
|
||||
out.push(node as Record<string, unknown>)
|
||||
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<Record<string, unknown>> } }
|
||||
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<Record<string, unknown>> }
|
||||
}
|
||||
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<Record<string, unknown>> }
|
||||
}
|
||||
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))
|
||||
})
|
||||
Reference in New Issue
Block a user