Files
InsanityLab-web/apps/platforms/tests/accessi.test.ts
T
AdrianoDev 6301f521fd Separazione: le aree riservate diventano un'app propria (apps/platforms)
Campus, Stress Index e Longevity escono dal sito e diventano
apps.insanitylab.it, con utenti, sessioni, login e database propri.
Deciso da Adriano il 03/09/2026: una repo sola, due applicazioni
autosufficienti, cosi' che ognuna resti estraibile.

Misura che ha guidato il taglio: le tre aree condividevano col sito
SOLO l'identita' - ~170 righe fra auth.ts, middleware.ts e safe-next.ts.
Layout, librerie e dati erano gia' disgiunti.

Lato piattaforme:
- ruoli piattaforme/cliente/trainer/admin, senza quelli redazionali
- canAccessPath con default CHIUSO (nel sito era aperto: li' le rotte non
  elencate erano di chiunque fosse loggato, qui ogni sezione e' di qualcuno)
- tutta l'app protetta, si elencano le eccezioni invece dei prefissi protetti
- /api risponde 401/403, non un redirect verso una pagina di login
- tre database distinti: utenti, dati clinici senza nomi, mappatura coi nomi

Lato sito:
- ROLES tornano i tre redazionali; via le regole e il login pubblico
- il login del pannello RESPINGE un account emigrato invece di aprirgli una
  sessione: senza, landingFor lo mandava su /admin, il middleware glielo
  negava, e si formava un ciclo di redirect che sembra un guasto del sito
- l'header non mostra piu' un menu delle piattaforme: un link che esce

Verificato: sito 20 file / 146 test, piattaforme 26 file / 197 test, entrambi
i build passano, e l'app girata davvero in locale - il ruolo piattaforme entra
in Campus e viene rimbalzato da Longevity, il cliente il contrario.

Non ancora in produzione: manca il record DNS di apps.insanitylab.it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EndnceRA5WnA6rvA5iV9WL
2026-09-03 11:21:12 +00:00

106 lines
5.5 KiB
TypeScript

// Chi entra dove, in QUEST'app. Sostituisce tests/auth-piattaforme.test.ts del repo unico:
// là si verificava che il ruolo `piattaforme` non sfondasse dentro il pannello del sito,
// qui il pannello non esiste — le rotte del sito non sono raggiungibili nemmeno sbagliando
// indirizzo, perché stanno su un'altra applicazione, su un altro host, con un altro database.
import { describe, it, expect, beforeEach } from 'vitest';
import type Database from 'better-sqlite3';
import { createDb } from '../src/lib/db';
import { createUser, login, getSessionUser, canAccessPath, landingFor, isRole, isProtectedPath, ROLES } from '../src/lib/auth';
let db: Database.Database;
beforeEach(() => { db = createDb(':memory:'); });
describe('utenti e sessioni di quest\'app', () => {
it('createUser accetta i ruoli dell\'app e getSessionUser li restituisce', () => {
for (const ruolo of ['piattaforme', 'cliente', 'trainer', 'admin'] as const) {
createUser(db, `u-${ruolo}`, 'segreta123', ruolo);
expect(getSessionUser(db, login(db, `u-${ruolo}`, 'segreta123')!)).toMatchObject({ role: ruolo });
}
});
it('i ruoli redazionali del sito qui non esistono', () => {
expect(isRole('superuser')).toBe(false);
expect(isRole('user')).toBe(false);
expect(isRole('campus')).toBe(false);
expect(ROLES).not.toContain('superuser');
expect(ROLES).not.toContain('user');
});
it('isRole accetta esattamente i ruoli di ROLES, nessuno in più o in meno', () => {
// Ereditata da tests/longevity/ruoli.test.ts del repo unico: quel file verificava
// sei ruoli e la tendina di admin/users.astro, che qui non esistono piu'. Questa e'
// la sua asserzione che vale ancora — le due liste devono restare la stessa cosa.
for (const ruolo of ROLES) expect(isRole(ruolo)).toBe(true);
for (const finto of ['campus', 'superuser', 'user', 'Admin', '', 'cliente ']) expect(isRole(finto)).toBe(false);
});
it('landingFor manda ognuno a casa propria, e nessuno su /admin', () => {
expect(landingFor('cliente')).toBe('/longevity/io');
expect(landingFor('trainer')).toBe('/longevity/gestionale');
expect(landingFor('piattaforme')).toBe('/piattaforme');
expect(landingFor('admin')).toBe('/piattaforme');
for (const ruolo of ROLES) expect(landingFor(ruolo)).not.toMatch(/^\/admin/);
});
});
describe('canAccessPath — le tre sezioni', () => {
it('piattaforme: Campus e Stress Index sì, Longevity no', () => {
expect(canAccessPath('piattaforme', '/campus')).toBe(true);
expect(canAccessPath('piattaforme', '/campus/corso-base/lezione-01')).toBe(true);
expect(canAccessPath('piattaforme', '/campus/assets/slide/x.png')).toBe(true);
expect(canAccessPath('piattaforme', '/piattaforme')).toBe(true);
expect(canAccessPath('piattaforme', '/piattaforme/stress-index/clienti')).toBe(true);
// Il ruolo storico non eredita i dati sanitari: sono arrivati dopo, e per altri.
expect(canAccessPath('piattaforme', '/longevity')).toBe(false);
expect(canAccessPath('piattaforme', '/longevity/io')).toBe(false);
expect(canAccessPath('piattaforme', '/api/longevity/questionario')).toBe(false);
});
it('cliente: il proprio spazio sì, il gestionale e le altre piattaforme no', () => {
expect(canAccessPath('cliente', '/longevity')).toBe(true);
expect(canAccessPath('cliente', '/longevity/io')).toBe(true);
expect(canAccessPath('cliente', '/longevity/questionario')).toBe(true);
expect(canAccessPath('cliente', '/api/longevity/questionario')).toBe(true);
expect(canAccessPath('cliente', '/longevity/gestionale')).toBe(false);
expect(canAccessPath('cliente', '/api/longevity/gestionale/misure')).toBe(false);
expect(canAccessPath('cliente', '/campus')).toBe(false);
expect(canAccessPath('cliente', '/piattaforme/stress-index')).toBe(false);
});
it('trainer: entrambe le parti di Longevity, niente Campus né Stress Index', () => {
expect(canAccessPath('trainer', '/longevity/gestionale')).toBe(true);
expect(canAccessPath('trainer', '/longevity/io')).toBe(true);
expect(canAccessPath('trainer', '/api/longevity/gestionale/misure')).toBe(true);
expect(canAccessPath('trainer', '/campus')).toBe(false);
expect(canAccessPath('trainer', '/piattaforme/stress-index')).toBe(false);
});
it('admin passa sempre', () => {
for (const p of ['/campus', '/piattaforme', '/piattaforme/stress-index', '/longevity', '/longevity/gestionale'])
expect(canAccessPath('admin', p)).toBe(true);
});
it('⚠️ il default è CHIUSO: una rotta senza regola non è di nessuno', () => {
// Differenza sostanziale dal sito, dove il default finale era `return true` perché
// blog e upload erano di chiunque fosse loggato. Qui una sezione nuova nasce vietata:
// il difetto si vede provandola, non leggendo un log di accessi.
for (const ruolo of ['piattaforme', 'cliente', 'trainer'])
expect(canAccessPath(ruolo, '/sezione-nuova-senza-regola')).toBe(false);
});
});
describe('isProtectedPath — cosa si apre senza sessione', () => {
it('solo login, logout, 404 e la favicon', () => {
expect(isProtectedPath('/login')).toBe(false);
expect(isProtectedPath('/logout')).toBe(false);
expect(isProtectedPath('/404')).toBe(false);
expect(isProtectedPath('/favicon.svg')).toBe(false);
});
it('tutto il resto è protetto, comprese le pagine che ancora non esistono', () => {
for (const p of ['/', '/campus', '/piattaforme', '/longevity/io', '/api/longevity/questionario',
'/longevity/gestionale', '/qualcosa-che-non-esiste'])
expect(isProtectedPath(p)).toBe(true);
});
});