Files

86 lines
4.0 KiB
TypeScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { parseCurrentUser, parseMenuTree } from '../../src/api/session-model.ts'
import type { AdminMenuNode } from '../../src/types/admin.ts'
// module 13 task 270:本地 Java(默认 18080) 真实 smoke——登录、会话、菜单树与代表性后台列表可达。
// 门控:AIIMAGE_LIVE_USER/AIIMAGE_LIVE_PASS 缺省跳过(与既有 live 约定一致)。
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 DEVICE = 'claude-module13-align-smoke'
const HAS_CREDS = Boolean(USER && PASS)
async function loginToken(): Promise<string> {
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 } }
if (!body.success || !body.data?.token) throw new Error(body.message || 'login failed')
return body.data.token
}
async function getJson(token: string, path: string): Promise<{ status: number; body: unknown }> {
const res = await fetch(`${BASE}${path}`, { headers: { Authorization: `Bearer ${token}` } })
const body = (await res.json().catch(() => null)) as unknown
return { status: res.status, body }
}
test('test_live_270_login_and_current_user', { skip: !HAS_CREDS }, async () => {
const token = await loginToken()
assert.ok(token.length > 0, '登录应返回 token')
const { status, body } = await getJson(token, '/api/admin/current-user')
assert.equal(status, 200)
const user = parseCurrentUser(body)
assert.ok(user.username.length > 0)
assert.ok(['super_admin', 'admin', 'normal'].includes(user.role))
})
test('test_live_270_admin_menu_tree', { skip: !HAS_CREDS }, async () => {
const token = await loginToken()
const { status, body } = await getJson(token, '/api/admin/current-user/menus')
assert.equal(status, 200)
const tree = parseMenuTree(body)
assert.ok(tree.length > 0, '超级管理员应能取到后台菜单树')
// 兼容两种后端形态:嵌套分组(children) 或 扁平带 group-* 分组标记的列表(原始字段为 route_path)。
const hasNestedGroups = tree.some((n) => n.children?.length)
const routeOf = (n: AdminMenuNode): string => n.route || (n as { route_path?: string }).route_path || ''
const hasGroupMarkers = tree.some((n) => routeOf(n).startsWith('group-'))
assert.ok(hasNestedGroups || hasGroupMarkers, '菜单树含分组(嵌套或 group-* 标记)')
})
test('test_live_270_users_list_reachable', { skip: !HAS_CREDS }, async () => {
const token = await loginToken()
const { status, body } = await getJson(token, '/api/admin/users?page=1&page_size=10')
assert.equal(status, 200)
const env = body as { success?: boolean }
assert.equal(env.success, true, '用户列表接口 success=true')
})
test('test_live_270_shop_manage_groups_reachable', { skip: !HAS_CREDS }, async () => {
const token = await loginToken()
const { status, body } = await getJson(token, '/api/admin/shop-manages/groups')
assert.equal(status, 200)
const env = body as { success?: boolean }
assert.equal(env.success, true, '店铺分组接口 success=true')
})
test('test_live_270_invalid_asin_list_reachable', { skip: !HAS_CREDS }, async () => {
const token = await loginToken()
const { status, body } = await getJson(token, '/api/admin/invalid-asin-data?page=1&page_size=5')
assert.equal(status, 200)
const env = body as { success?: boolean }
assert.equal(env.success, true, '品牌数据库(不符合ASIN)列表 success=true')
})
test('test_live_270_product_categories_reachable', { skip: !HAS_CREDS }, async () => {
const token = await loginToken()
const { status, body } = await getJson(token, '/api/admin/product-categories')
assert.equal(status, 200)
const env = body as { success?: boolean }
assert.equal(env.success, true, '商品类目接口 success=true')
})