52 lines
1.6 KiB
TypeScript
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);
|
||
|
|
});
|
||
|
|
});
|