longevity: revisione finale - codice cliente non riusabile, ruoli allineati, middleware verificato
- creaCliente: il codice si deduce dal massimo fra identity E longevity (non piu' solo identity), cosi' un client_code cancellato dall'anagrafica non torna mai disponibile e non si attribuiscono le misure di un vecchio cliente a uno nuovo. Compensazione se la scrittura su longevity fallisce dopo quella su identity. - ROLES unica fonte in auth.ts (Role e isRole derivati); users.astro e users/new.astro usano quella lista invece di array scritti a mano che dimenticavano cliente/trainer. - Commento falso su cosa protegge il middleware, riscritto: rimanda a isProtectedPath. - vitest.config.ts: alias per astro:middleware (stesso bersaglio della pipeline vite di Astro), cosi' src/middleware.ts e' finalmente importabile e testabile in isolamento - prima nessun test lo caricava davvero. - export.test.ts: asserzione posizionale per colonna, non piu' solo intestazioni scritte a mano; intercetta un riordino della SELECT che disallinea etichette e valori. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
// Il difetto più serio di questo ramo era "una funzione di autorizzazione corretta che
|
||||
// nessuno chiama": isProtectedPath e canAccessAdminPath erano giuste, ma finché niente le
|
||||
// collegava dentro src/middleware.ts le pagine sanitarie restavano pubbliche. Nessun altro
|
||||
// test in tutto il repo importa src/middleware.ts: qui lo si carica davvero (vedi l'alias
|
||||
// 'astro:middleware' in vitest.config.ts, necessario perché quel modulo è virtuale e
|
||||
// risolto solo dentro la pipeline vite di Astro) e si esercita la funzione vera, non una
|
||||
// sua reimplementazione.
|
||||
import { describe, it, expect, beforeAll } from 'vitest';
|
||||
|
||||
// getDb() (usato da src/middleware.ts) apre 'data/insanitylab.db' su disco per default:
|
||||
// DB_PATH va puntato a :memory: PRIMA che qualcosa lo chiami (il singleton è pigro).
|
||||
process.env.DB_PATH = ':memory:';
|
||||
|
||||
type FakeContext = {
|
||||
url: URL;
|
||||
cookies: { get: (name: string) => { value: string } | undefined };
|
||||
redirect: (path: string, status?: number) => Response;
|
||||
locals: Record<string, unknown>;
|
||||
};
|
||||
|
||||
function contesto(pathname: string, cookieValue?: string): FakeContext {
|
||||
return {
|
||||
url: new URL(`http://localhost${pathname}`),
|
||||
cookies: { get: () => (cookieValue !== undefined ? { value: cookieValue } : undefined) },
|
||||
redirect: (path, status) => new Response(null, { status: status ?? 302, headers: { Location: path } }),
|
||||
locals: {},
|
||||
};
|
||||
}
|
||||
|
||||
function next() {
|
||||
let chiamata = false;
|
||||
const fn = async () => { chiamata = true; return new Response('PAGINA-SERVITA'); };
|
||||
return { fn, fuChiamata: () => chiamata };
|
||||
}
|
||||
|
||||
describe('src/middleware.ts — protezione reale delle rotte longevity', () => {
|
||||
let onRequest: (context: FakeContext, next: () => Promise<Response>) => Response | Promise<Response>;
|
||||
|
||||
beforeAll(async () => {
|
||||
const mod = await import('../../src/middleware.ts');
|
||||
onRequest = mod.onRequest as typeof onRequest;
|
||||
});
|
||||
|
||||
it('una rotta longevity senza sessione (nessun cookie) redirige al login e non serve la pagina', async () => {
|
||||
const ctx = contesto('/longevity/io');
|
||||
const n = next();
|
||||
const res = await onRequest(ctx, n.fn);
|
||||
|
||||
expect(n.fuChiamata()).toBe(false);
|
||||
expect(res).toBeInstanceOf(Response);
|
||||
expect([301, 302, 303, 307, 308]).toContain((res as Response).status);
|
||||
expect((res as Response).headers.get('Location')).toMatch(/^\/login/);
|
||||
});
|
||||
|
||||
it('una rotta longevity con un token di sessione inesistente redirige comunque al login', async () => {
|
||||
const ctx = contesto('/longevity/gestionale', 'token-che-non-esiste-in-nessuna-sessione');
|
||||
const n = next();
|
||||
const res = await onRequest(ctx, n.fn);
|
||||
|
||||
expect(n.fuChiamata()).toBe(false);
|
||||
expect((res as Response).headers.get('Location')).toMatch(/^\/login/);
|
||||
});
|
||||
|
||||
it('una API longevity senza sessione risponde 401, non serve la richiesta', async () => {
|
||||
const ctx = contesto('/api/longevity/qualcosa');
|
||||
const n = next();
|
||||
const res = await onRequest(ctx, n.fn);
|
||||
|
||||
expect(n.fuChiamata()).toBe(false);
|
||||
expect((res as Response).status).toBe(401);
|
||||
const body = await (res as Response).json();
|
||||
expect(body.error).toBeTruthy();
|
||||
});
|
||||
|
||||
it('una rotta pubblica non passa da nessun controllo: next() viene chiamata', async () => {
|
||||
const ctx = contesto('/blog');
|
||||
const n = next();
|
||||
const res = await onRequest(ctx, n.fn);
|
||||
|
||||
expect(n.fuChiamata()).toBe(true);
|
||||
expect(await (res as Response).text()).toBe('PAGINA-SERVITA');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user