Chaque requête authentifiée repousse l'expiration (au plus une fois par minute), côté jeton et côté cookie. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
92 lines
3.4 KiB
TypeScript
92 lines
3.4 KiB
TypeScript
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||
import { describe, expect, it, vi } from 'vitest';
|
||
|
||
import { UnauthorizedError } from '../src/errors.js';
|
||
import {
|
||
createSessionRefresher,
|
||
getSessionUser,
|
||
requireSession,
|
||
type SessionUser,
|
||
} from '../src/session.js';
|
||
|
||
function requestWithSessionUser(user: SessionUser | undefined): FastifyRequest {
|
||
return {
|
||
session: { get: (key: string) => (key === 'user' ? user : undefined) },
|
||
} as unknown as FastifyRequest;
|
||
}
|
||
|
||
const reply = {} as FastifyReply;
|
||
|
||
describe('getSessionUser', () => {
|
||
it('renvoie null quand il n’y a pas de session', () => {
|
||
expect(getSessionUser(requestWithSessionUser(undefined))).toBeNull();
|
||
});
|
||
|
||
it('renvoie null quand la session est expirée', () => {
|
||
const user: SessionUser = { sub: 'u1', name: 'Alice', expiresAt: Date.now() - 1000 };
|
||
expect(getSessionUser(requestWithSessionUser(user))).toBeNull();
|
||
});
|
||
|
||
it('renvoie l’utilisateur quand la session est valide', () => {
|
||
const user: SessionUser = { sub: 'u1', name: 'Alice', expiresAt: Date.now() + 60_000 };
|
||
expect(getSessionUser(requestWithSessionUser(user))).toEqual(user);
|
||
});
|
||
});
|
||
|
||
describe('createSessionRefresher (session glissante)', () => {
|
||
const MAX_AGE_S = 3600;
|
||
|
||
function requestRecordingSet(user: SessionUser | undefined): {
|
||
request: FastifyRequest;
|
||
set: ReturnType<typeof vi.fn>;
|
||
} {
|
||
const set = vi.fn();
|
||
const request = {
|
||
session: { get: (key: string) => (key === 'user' ? user : undefined), set },
|
||
} as unknown as FastifyRequest;
|
||
return { request, set };
|
||
}
|
||
|
||
it('prolonge une session utilisée depuis plus d’une minute', () => {
|
||
const user: SessionUser = { sub: 'u1', name: 'Alice', expiresAt: Date.now() + 1_800_000 };
|
||
const { request, set } = requestRecordingSet(user);
|
||
createSessionRefresher(MAX_AGE_S)(request);
|
||
expect(set).toHaveBeenCalledTimes(1);
|
||
const [key, value] = set.mock.calls[0] as [string, SessionUser];
|
||
expect(key).toBe('user');
|
||
expect(value.sub).toBe('u1');
|
||
expect(value.expiresAt).toBeGreaterThan(Date.now() + 3_500_000);
|
||
});
|
||
|
||
it('ne réécrit pas la session à chaque requête (moins d’une minute)', () => {
|
||
const user: SessionUser = { sub: 'u1', name: 'Alice', expiresAt: Date.now() + 3_590_000 };
|
||
const { request, set } = requestRecordingSet(user);
|
||
createSessionRefresher(MAX_AGE_S)(request);
|
||
expect(set).not.toHaveBeenCalled();
|
||
});
|
||
|
||
it('ne ressuscite pas une session expirée ni une requête anonyme', () => {
|
||
const expired: SessionUser = { sub: 'u1', name: 'Alice', expiresAt: Date.now() - 1000 };
|
||
for (const user of [expired, undefined]) {
|
||
const { request, set } = requestRecordingSet(user);
|
||
createSessionRefresher(MAX_AGE_S)(request);
|
||
expect(set).not.toHaveBeenCalled();
|
||
}
|
||
});
|
||
});
|
||
|
||
describe('requireSession', () => {
|
||
it('appelle done(UnauthorizedError) sans session', () => {
|
||
const done = vi.fn();
|
||
requireSession(requestWithSessionUser(undefined), reply, done);
|
||
expect(done).toHaveBeenCalledTimes(1);
|
||
expect(done.mock.calls[0]?.[0]).toBeInstanceOf(UnauthorizedError);
|
||
});
|
||
|
||
it('appelle done() avec une session valide', () => {
|
||
const done = vi.fn();
|
||
const user: SessionUser = { sub: 'u1', name: 'Alice', expiresAt: Date.now() + 60_000 };
|
||
requireSession(requestWithSessionUser(user), reply, done);
|
||
expect(done).toHaveBeenCalledWith();
|
||
});
|
||
});
|