BillTracker/client/api.sessionExpiry.test.ts

52 lines
1.6 KiB
TypeScript

// @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);
});
});