55 lines
2.0 KiB
TypeScript
55 lines
2.0 KiB
TypeScript
// @vitest-environment jsdom
|
|
|
|
// Session-expiry contract: the auth middleware is the only source of the
|
|
// AUTH_REQUIRED code (no valid session) — api.ts dispatches 'auth:expired' on
|
|
// it so AuthProvider clears the user and RequireAuth redirects. Any other 401
|
|
// (code AUTH_ERROR — wrong credential input, e.g. change-password with a bad
|
|
// current password, on ANY path) must NOT dispatch. A path prefix cannot make
|
|
// that distinction — /profile/change-password proved it in review.
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import { api } from '@/api';
|
|
|
|
function mock401(code: string): typeof fetch {
|
|
return vi.fn(
|
|
async () =>
|
|
new Response(JSON.stringify({ error: code, message: 'Unauthorized', code }), {
|
|
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);
|
|
});
|
|
|
|
afterEach(() => {
|
|
window.removeEventListener('auth:expired', onExpired);
|
|
vi.unstubAllGlobals();
|
|
});
|
|
|
|
it('dispatches auth:expired for AUTH_REQUIRED (dead session) on a data endpoint', async () => {
|
|
vi.stubGlobal('fetch', mock401('AUTH_REQUIRED'));
|
|
await expect(api.bills()).rejects.toMatchObject({ status: 401 });
|
|
expect(expiredEvents).toBe(1);
|
|
});
|
|
|
|
it('does NOT dispatch for AUTH_ERROR (wrong credential) — even outside /auth/*', async () => {
|
|
vi.stubGlobal('fetch', mock401('AUTH_ERROR'));
|
|
await expect(
|
|
api.changeProfilePassword({ current_password: 'wrong', new_password: 'longenough1' }),
|
|
).rejects.toMatchObject({ status: 401 });
|
|
await expect(
|
|
api.changePassword({ current_password: 'wrong', new_password: 'longenough1' }),
|
|
).rejects.toMatchObject({ status: 401 });
|
|
expect(expiredEvents).toBe(0);
|
|
});
|
|
});
|