837fc9bcc0
用真实 admin 凭据打本地后端完成登录/cookie 会话/current-user/菜单树/401/ 权限路由 smoke(8 场景全过)。smoke 暴露前端登出 URL 错误:session.ts 走 /api/admin/logout(后端无此映射,500),改为 auth 模块根端点 POST /logout; 同步修正 task-25/36 断言。注:当前本地后端 POST /logout 亦返回 500,属 Java 侧缺陷,待后端模块修复后补登出 E2E。
76 lines
4.4 KiB
TypeScript
76 lines
4.4 KiB
TypeScript
import test from 'node:test'
|
||
import assert from 'node:assert/strict'
|
||
import { readSource } from './helpers.ts'
|
||
import { loginRedirectTarget } from '../src/api/envelope.ts'
|
||
|
||
const httpSrc = readSource('src/api/http.ts')
|
||
const sessionSrc = readSource('src/api/session.ts')
|
||
|
||
test('test_task_025_cookie_session_config_normal_primary_path', () => {
|
||
// 正常主路径:共享传输开启同源 Cookie/Session(Axios withCredentials 必须开启)。
|
||
assert.match(httpSrc, /withCredentials:\s*true/, 'Cookie 规则:Axios withCredentials 必须开启')
|
||
assert.match(httpSrc, /baseURL:\s*'\/'/, '请求使用同源 Cookie/Session')
|
||
})
|
||
|
||
test('test_task_025_cookie_session_config_normal_variant_input', () => {
|
||
// 正常变体:current-user / menus / logout 三类会话调用全部复用同一共享 http 实例。
|
||
assert.match(sessionSrc, /import \{ http \} from '\.\/http'/)
|
||
const getCalls = (sessionSrc.match(/\bhttp\.get\b/g) || []).length
|
||
assert.equal(getCalls, 2, 'current-user 与 menus 两个 GET 均走共享 http')
|
||
assert.equal((sessionSrc.match(/\bhttp\.post\b/g) || []).length, 1, 'logout 走共享 http')
|
||
assert.match(sessionSrc, /current-user/)
|
||
assert.match(sessionSrc, /current-user\/menus/)
|
||
})
|
||
|
||
test('test_task_025_cookie_session_config_normal_repeated_operation_is_idempotent', () => {
|
||
// 正常重复:传输模块只创建一次 axios 实例,会话模块不另建客户端、不污染状态。
|
||
assert.equal((httpSrc.match(/axios\.create/g) || []).length, 1, '共享实例只创建一次')
|
||
assert.equal((sessionSrc.match(/axios\.create/g) || []).length, 0, '会话模块不自行创建新客户端')
|
||
assert.equal((sessionSrc.match(/import \{ http \}/g) || []).length, 1)
|
||
})
|
||
|
||
test('test_task_025_cookie_session_config_boundary_empty_input', () => {
|
||
// 边界空值:无登录态时请求层不把凭证写入本地/会话存储(前端不保存 JWT/密码)。
|
||
for (const rel of ['src/api/http.ts', 'src/api/session.ts']) {
|
||
const src = readSource(rel)
|
||
assert.equal((src.match(/localStorage/g) || []).length, 0, `${rel} 不得使用 localStorage`)
|
||
assert.equal((src.match(/sessionStorage/g) || []).length, 0, `${rel} 不得使用 sessionStorage`)
|
||
assert.equal(/document\.cookie\s*=/.test(src), false, `${rel} 不得直接写 document.cookie`)
|
||
}
|
||
})
|
||
|
||
test('test_task_025_cookie_session_config_boundary_single_item', () => {
|
||
// 边界单元素:登出是单次 POST,以 AJAX 标识发到共享实例,后端据此返回 JSON 而非页面。
|
||
assert.match(sessionSrc, /X-Requested-With/, '登出走 AJAX 标识以便后端识别为 JSON 请求')
|
||
assert.match(sessionSrc, /http\.post/)
|
||
assert.match(sessionSrc, /\/logout/, '登出走 auth 模块根端点 POST /logout')
|
||
assert.equal(sessionSrc.includes('/api/admin/logout'), false, '不应指向不存在的 /api/admin/logout')
|
||
})
|
||
|
||
test('test_task_025_cookie_session_config_boundary_limit_or_missing_field', () => {
|
||
// 边界上限/缺字段:即便会话在身,传输层也不注入 token/Authorization 头——
|
||
// 凭证只由同源 Cookie 携带,前端禁止保存并在后续请求回填。
|
||
for (const rel of ['src/api/http.ts', 'src/api/session.ts', 'src/api/session-model.ts']) {
|
||
const src = readSource(rel)
|
||
assert.equal(/\bAuthorization\b/.test(src), false, `${rel} 不应注入 Authorization 头`)
|
||
assert.equal(/\bBearer\b/.test(src), false, `${rel} 不应出现 Bearer token`)
|
||
}
|
||
})
|
||
|
||
test('test_task_025_cookie_session_config_invalid_input_rejected', () => {
|
||
// 异常输入:Cookie 规则要求 withCredentials 必须开启、同源根路径、有界超时。
|
||
assert.match(httpSrc, /withCredentials:\s*true/, '传输配置必须字面量开启 withCredentials')
|
||
assert.match(httpSrc, /baseURL:\s*'\/'/, '传输配置必须使用同源根路径')
|
||
assert.match(httpSrc, /timeout:\s*\d+/, '传输配置必须声明数值型有界超时')
|
||
})
|
||
|
||
test('test_task_025_cookie_session_config_dependency_failure_returns_actionable_message', () => {
|
||
// 依赖失败:会话失效/网络异常走同一拦截与解包兜底,且登录跳转携带当前目标路径。
|
||
assert.match(httpSrc, /interceptors\.response/, '存在 401/异常统一响应拦截')
|
||
assert.match(httpSrc, /unwrap/)
|
||
assert.match(httpSrc, /isUnauthorized/)
|
||
assert.match(httpSrc, /login\?redirect=/)
|
||
const target = loginRedirectTarget({ pathname: '/admin-vue/account/users', search: '?x=1' })
|
||
assert.equal(target, encodeURIComponent('/admin-vue/account/users?x=1'), 'redirect 需携带当前目标路径')
|
||
})
|