mediaDrop/tests/session.test.ts
Nicolas 8a3fe1407f fix(session): session glissante prolongée par l'activité (#12)
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>
2026-10-14 07:36:16 +02:00

92 lines
3.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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