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; } { 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(); }); });