Files
InsanityLab-web/apps/platforms/tests/stress-index-sport.test.ts
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

91 lines
3.4 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { durata, dataBreve, dataOra, ultimi, kpiSport, perSettimana, riepilogoAtleti } from '../src/lib/stress-index/sport';
import type { Athlete, Session } from '../src/lib/stress-index/data';
const RIF = '2026-07-12';
const sess = (id: string, athleteId: string, startedAt: string, trimp: number): Session =>
({ id, athleteId, startedAt, durationSec: 1800, sport: null, hrAvg: 120, hrMax: 150, trimp, dfaA1: 1.2, rpe: 5 });
const athletes: Athlete[] = [
{ clientId: 'a', name: 'A. Uno', sport: 'Corsa' },
{ clientId: 'b', name: 'B. Due', sport: null },
];
// Ordinate dalla più recente, come le espone data.ts
const sessions: Session[] = [
sess('s1', 'a', '2026-07-12T09:00', 40),
sess('s2', 'a', '2026-07-08T09:00', 30),
sess('s3', 'a', '2026-07-02T09:00', 60), // settimana precedente
sess('s4', 'b', '2026-06-20T09:00', 20), // oltre i 30 giorni? no: 22 giorni prima
sess('s5', 'b', '2026-04-01T09:00', 15), // fuori da tutte le finestre
];
describe('formattazioni', () => {
it('durata: ore, minuti e secondi', () => {
expect(durata(4860)).toBe('1h 21m');
expect(durata(3275)).toBe('54m 35s');
expect(durata(5)).toBe('5s');
});
it('date in forma breve ed estesa', () => {
expect(dataBreve('2026-07-30T11:27')).toBe('30 lug 2026');
expect(dataOra('2026-07-30T11:27')).toBe('30 lug 2026 alle 11:27');
});
});
describe('finestre temporali', () => {
it('gli ultimi 7 giorni includono il giorno di riferimento ed escludono il settimo indietro', () => {
expect(ultimi(sessions, 7, RIF).map((s) => s.id)).toEqual(['s1', 's2']);
});
it('gli ultimi 30 giorni allargano a chi ha misurato nel mese', () => {
expect(ultimi(sessions, 30, RIF).map((s) => s.id)).toEqual(['s1', 's2', 's3', 's4']);
});
});
describe('kpi', () => {
it('conta sessioni, carico settimanale e atleti attivi nel mese', () => {
expect(kpiSport(sessions, RIF)).toEqual([
{ label: 'Sessioni totali', value: 5 },
{ label: 'Questa settimana', value: 2, note: 'ultimi 7gg' },
{ label: 'TRIMP medio', value: 70, note: 'settimanale' },
{ label: 'Atleti attivi', value: 2, note: 'ultimi 30gg' },
]);
});
});
describe('sessioni per settimana', () => {
const righe = perSettimana(sessions, 4, RIF);
it('produce una riga per settimana, la più recente per ultima', () => {
expect(righe).toHaveLength(4);
// 2026-07-12 è una domenica: la sua settimana parte lunedì 06/07
expect(righe[righe.length - 1].settimana).toBe('06/07');
});
it('conta le sessioni nella settimana giusta', () => {
const perEtichetta = Object.fromEntries(righe.map((r) => [r.settimana, r.sessioni]));
expect(perEtichetta['06/07']).toBe(2); // s1 e s2
expect(perEtichetta['29/06']).toBe(1); // s3
});
});
describe('riepilogo atleti', () => {
const riepilogo = riepilogoAtleti(sessions, athletes, RIF);
it('somma il carico dei 7 giorni e riporta ultima sessione e conteggio mensile', () => {
expect(riepilogo[0]).toEqual({
clientId: 'a', name: 'A. Uno', sport: 'Corsa',
sessioni30: 3, trimp7: 70, ultimoTrimp: 40,
ultimaSessione: '2026-07-12T09:00', trend: 'stabile',
});
});
it('resta stabile quando lo scarto fra le due settimane è minimo', () => {
// b non ha nulla negli ultimi 7 giorni né nei 7 precedenti → nessuno scarto, resta stabile
expect(riepilogo[1].trend).toBe('stabile');
expect(riepilogo[1].sessioni30).toBe(1);
});
});