c8a6b318cb
Chiesto da Adriano: si entra e si sceglie dove andare, anche quando la scelta e' una sola. Prima ogni ruolo saltava dritto nella sua sezione. Non basta cambiare landingFor: /piattaforme era riservato ai ruoli admin e piattaforme, quindi mandarci un cliente avrebbe prodotto un CICLO di redirect - il middleware nega la pagina, rimanda a landingFor, landingFor rimanda dove il middleware nega. L'indice diventa la casa comune (aperto a ogni ruolo), mentre Stress Index, che sta sotto lo stesso percorso, resta riservato: la sua regola viene prima, perche' vince la prima che combacia. L'indice mostra a ognuno solo le sezioni che puo' aprire, quindi la scelta non espone mai una porta chiusa. Un test nuovo presidia l'invariante che rende impossibile il ciclo: la destinazione di landingFor deve essere accessibile a TUTTI i ruoli. Verificato in produzione: il cliente demo atterra sulla scelta e vede la sola card Longevity, adriano vede Campus e Stress Index, e da li' il percorso prosegue fino al referto. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EndnceRA5WnA6rvA5iV9WL
116 lines
6.3 KiB
TypeScript
116 lines
6.3 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('dopo il login si atterra sulla scelta della piattaforma, qualunque sia il ruolo', () => {
|
|
// Deciso da Adriano il 03/09/2026: si entra e si sceglie, anche quando la scelta e' una
|
|
// sola. Prima ogni ruolo saltava direttamente nella sua sezione.
|
|
for (const ruolo of ROLES) expect(landingFor(ruolo)).toBe('/piattaforme');
|
|
for (const ruolo of ROLES) expect(landingFor(ruolo)).not.toMatch(/^\/admin/);
|
|
});
|
|
|
|
it('⚠️ la pagina di atterraggio è aperta a TUTTI i ruoli, o è un ciclo di redirect', () => {
|
|
// landingFor e' anche il "torna a casa" del middleware quando un ruolo apre una sezione
|
|
// che non gli spetta: se la sua destinazione fosse a sua volta negata, il middleware
|
|
// rimanderebbe li' all'infinito. E' il guasto che il sito ha rischiato lo stesso giorno
|
|
// con gli account emigrati, in un punto diverso del codice.
|
|
for (const ruolo of ROLES) expect(canAccessPath(ruolo, landingFor(ruolo))).toBe(true);
|
|
});
|
|
});
|
|
|
|
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);
|
|
// ...ma l'indice sì: è la pagina di scelta, ed è la sua casa dopo il login.
|
|
expect(canAccessPath('cliente', '/piattaforme')).toBe(true);
|
|
});
|
|
|
|
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);
|
|
expect(canAccessPath('trainer', '/piattaforme')).toBe(true);
|
|
});
|
|
|
|
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);
|
|
});
|
|
});
|