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 需携带当前目标路径') })