import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { createPinia, setActivePinia } from 'pinia' import { request, ApiError } from './request' import { useAuthStore } from '@/stores/auth' /** happy-dom 提供的当前页 URL 控制入口(标准 DOM 无此能力) */ function setPageURL(url: string) { ;(window as unknown as { happyDOM: { setURL: (u: string) => void } }).happyDOM.setURL(url) } /** 构造最小 401 响应;rotate 在响应返回前换发新会话,模拟 OAuth 绑定回跳 */ function stub401(rotateTo?: string) { vi.stubGlobal('fetch', async () => { if (rotateTo !== undefined) useAuthStore().setSession(rotateTo, '') return { ok: false, status: 401, url: '/api/v1/x', json: async () => ({ error: '未认证' }), } as unknown as Response }) } let assignSpy: ReturnType beforeEach(() => { localStorage.clear() setActivePinia(createPinia()) assignSpy = vi.spyOn(location, 'assign').mockImplementation(() => {}) }) afterEach(() => { vi.unstubAllGlobals() vi.restoreAllMocks() }) describe('401 令牌快照', () => { it('当前 token 收到 401:登出并带 redirect 跳登录', async () => { setPageURL('http://localhost/instances?tab=list') useAuthStore().setSession('tokA', '') stub401() await expect(request('/x')).rejects.toMatchObject({ status: 401, message: '未认证' }) expect(useAuthStore().token).toBe('') expect(assignSpy).toHaveBeenCalledWith( `/login?redirect=${encodeURIComponent('/instances?tab=list')}`, ) }) it('响应到达前会话已换新:旧 token 的迟到 401 不得清掉新会话', async () => { setPageURL('http://localhost/settings') useAuthStore().setSession('tokA', '') stub401('tokB') await expect(request('/x')).rejects.toBeInstanceOf(ApiError) expect(useAuthStore().token).toBe('tokB') expect(assignSpy).not.toHaveBeenCalled() }) it('登录页密码错误的 401:不跳转,错误留在表单内提示', async () => { setPageURL('http://localhost/login') stub401() await expect(request('/auth/login', { method: 'POST', body: {} })).rejects.toMatchObject({ message: '未认证', }) expect(assignSpy).not.toHaveBeenCalled() }) })