// 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; }; 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 | Promise; beforeAll(async () => { const mod = await import('../../src/middleware.ts'); // FakeContext è un doppio minimale di APIContext (solo cio' che onRequest usa // davvero: url, cookies, redirect, locals), non un APIContext completo: il cast // diretto viene rifiutato dal typecheck perche' i due tipi non si sovrappongono // abbastanza. Si passa da 'unknown' perche' e' un cast intenzionale, non un bug. onRequest = mod.onRequest as unknown 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(); }); // ⚠️ In quest'app le rotte pubbliche sono l'eccezione, non la regola: /blog non esiste // piu' (e' rimasto sul sito) e la sola pagina raggiungibile senza sessione e' il login. it('il login non passa da nessun controllo: next() viene chiamata', async () => { const ctx = contesto('/login'); const n = next(); const res = await onRequest(ctx, n.fn); expect(n.fuChiamata()).toBe(true); expect(await (res as Response).text()).toBe('PAGINA-SERVITA'); }); });