// @vitest-environment jsdom // 1e (QA follow-up) — a 401 outside /auth/* means the session died mid-use: // api.ts must dispatch 'auth:expired' (AuthProvider clears the user and // RequireAuth redirects). A 401 under /auth/* is "wrong credential input" // (e.g. change-password with a bad current password) and must NOT dispatch. import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { api } from '@/api'; function mock401(): typeof fetch { return vi.fn( async () => new Response( JSON.stringify({ error: 'AUTH_ERROR', message: 'Unauthorized', code: 'AUTH_ERROR' }), { status: 401, headers: { 'Content-Type': 'application/json' }, }, ), ) as unknown as typeof fetch; } describe('session-expiry dispatch on 401', () => { let expiredEvents: number; const onExpired = () => { expiredEvents += 1; }; beforeEach(() => { expiredEvents = 0; window.addEventListener('auth:expired', onExpired); vi.stubGlobal('fetch', mock401()); }); afterEach(() => { window.removeEventListener('auth:expired', onExpired); vi.unstubAllGlobals(); }); it('dispatches auth:expired for a 401 on a data endpoint', async () => { await expect(api.bills()).rejects.toMatchObject({ status: 401 }); expect(expiredEvents).toBe(1); }); it('does NOT dispatch for a 401 under /auth/* (wrong credential input)', async () => { await expect( api.changePassword({ current_password: 'wrong', new_password: 'longenough1' }), ).rejects.toMatchObject({ status: 401 }); expect(expiredEvents).toBe(0); }); });