Skip to main content

Eseguire, verificare, rilasciare

Questa pagina raccoglie la parte operativa: come far girare i test, come verificare che siano vivi, cosa ricostruire dopo uno spostamento. Sono le cose che costano più tempo a riscoprire di quanto costi scriverle.

Eseguire i test

cd rendicontazione/services/rendicontazione-service && node --test
cd backbone-core/services/data-service && node --test

Il problema delle dipendenze

npm install nel servizio verticale fallisce:

npm error 401 Unauthorized - GET https://npm.pkg.github.com/@vinovalab%2fbackbone-shared

Il pacchetto @vinovalab/backbone-shared sta su un registry privato GitHub e richiede un token. Senza quello, node_modules resta assente e i test che importano express, exceljs o axios falliscono al caricamento.

I test di dominio non ne hanno bisogno — il client dei dati è iniettabile e le fixture sono in memoria — ma alcuni sì: l'esportazione Excel usa exceljs, e caricare il router intero richiede express e multer.

Aggirarlo senza token, installando le sole dipendenze pubbliche altrove:

mkdir -p /tmp/deps && cd /tmp/deps
npm install --registry=https://registry.npmjs.org \
exceljs axios express multer redis
cd -
NODE_PATH=/tmp/deps/node_modules node --test

Il test dell'esportazione dichiara lo skip quando exceljs manca, invece di sparire in silenzio: uno skip dichiarato è un'informazione, un test che non compare è un'assenza che nessuno nota.

Verificare che i test siano vivi

È il passo che ha trovato qualcosa in ogni onda. Il procedimento è meccanico: si rompe il codice di proposito e si guarda cosa fallisce.

cp modules/projects/projectQueries.js /tmp/backup.js

# togliere la riga che si vuole mettere alla prova
python3 -c "
p='modules/projects/projectQueries.js'; s=open(p).read(); a=s
s=s.replace('LA RIGA DA TOGLIERE','')
assert s!=a, 'NESSUNA SOSTITUZIONE' # senza questo si crede di aver perturbato
open(p,'w').write(s)"

node --test 2>&1 | grep '^not ok'
cp /tmp/backup.js modules/projects/projectQueries.js

Due avvertenze imparate sul campo:

  • assert s!=a non è opzionale. Una sostituzione che non aggancia lascia il codice intatto, i test verdi, e la falsa conclusione che sia coperto. È successo, ed è per questo che c'è.
  • Se fallisce solo il [pinning] e nessun [invariante], la regola è registrata ma non dichiarata. Va aggiunto un invariante, oppure la fixture non contiene il caso — che è stato il problema in tre onde su quattro.

Il client finto dei test

test/golden/harness.js nel verticale espone clientDaFixture(fixture), che riproduce il contratto di data-service leggendo e scrivendo su un oggetto in memoria.

cosa esponea cosa serve
chiamatequante richieste attraverserebbero la rete, e di che tipo
scrittureogni create, update e delete, in ordine

Le scritture vengono applicate alla fixture, non solo registrate. Serve ai flussi che rileggono ciò che hanno appena scritto — la riconciliazione decide se cancellare il doppione dopo averne spostato le righe — dove un finto di sola registrazione darebbe risposte che in produzione non accadono.

Riproduce anche ciò che data-service non fa: sort_by e sort_dir in una lettura multipla vengono ignorati. Un finto più generoso del servizio vero renderebbe verde codice che in produzione non lo è.

Ricostruire dopo uno spostamento

Ogni onda tocca entrambi i lati, quindi vanno ricostruiti entrambi. data-service per primo: il verticale chiama endpoint che devono già esistere.

cd backbone-core
docker compose -f docker/compose/docker-compose.dev.yml \
--env-file docker/env/.env.dev up -d --build data-service

cd ../rendicontazione
docker compose --env-file docker/env/.env.dev \
-f docker/compose/docker-compose.dev.yml up -d --build rendicontazione-service

Il frontend non serve ricostruirlo: il contratto delle risposte non cambia.

Una lettura in più può rompere schede che non la usano

Successo il 18 agosto 2026, in DEV. Aggiungendo le ferie alla lettura multipla di /employees/:id/storico, le schede Gerarchia e Storico hanno smesso di aprirsi con un 400: data-service non era ancora stato ricostruito, non conosceva employee_leave_balances, e /query è atomica — una tabella rifiutata fa fallire tutte le letture della richiesta, comprese quelle che funzionavano.

Non è un caso raro: succede per tutta la durata di ogni rilascio in cui il verticale è avanti rispetto a data-service o alla migrazione. Da lì due regole:

  • ricostruire data-service per primo — vale già per gli spostamenti, vale anche per una tabella nuova;
  • una lettura nuova dentro una richiesta esistente va resa non fatale. storico() ora ritenta senza le ferie e restituisce ferie: null, che la scheda mostra come «non leggibili in questo ambiente» — diverso da [], che direbbe «non ha mai preso ferie». Se cade anche il secondo tentativo, l'errore originale passa: un data-service spento non deve travestirsi da funzionalità mancante.

Una rotta nuova non basta scriverla: va concessa

Successo il 18 agosto 2026 con PUT /rendicontazione-service/employees/me/ferie/piano, che rispondeva 403 con il codice a posto da entrambi i lati.

Il permesso non è nel servizio. Traefik chiama /auth/validate, che confronta il percorso del browser con backbone_identity.role_permissions usando minimatch, e il metodo deve combaciare. Una rotta che non compare lì non esiste per chi la chiama, per quanto sia scritta bene.

Tre cose da sapere prima di cercare nel posto sbagliato:

  • i pattern non sono prefissi. /rendicontazione-service/employees/me non copre /employees/me/ferie: serve /employees/me/**, oppure il percorso esatto;
  • va concessa su entrambe le superfici, e la seconda non è il gateway. Il salto verticale → data-service è interno alla rete Docker, ma data-service si autentica da solo: quando una richiesta gli arriva senza x-user-id, il middleware userContext chiama /auth/validate per sapere chi sia l'utente, passando x-forwarded-uri e x-forwarded-prefix con default /data-service. Auth-service ricompone /data-service/rendicontazione/... e autorizza, perché autenticare e autorizzare sono la stessa chiamata. Concedere solo /rendicontazione-service/** lascia quindi un DENY su un percorso /data-service/** che nessuno ha mai chiamato dal browser — ed è il posto dove nessuno pensa di guardare. È il motivo per cui la 098 concedeva i permessi «sui due lati della catena». Chi scrive ci ha sbagliato due volte: prima deducendo che il secondo controllo non esistesse, poi attribuendolo al gateway. Il codice che lo fa è services/data-service/middleware/userContext.js;
  • i superadmin non se ne accorgono, perché saltano il controllo. Una rotta nuova provata solo da un superadmin sembra funzionare per tutti.

Il log di auth-service dice esattamente quale percorso e quale metodo sono stati negati, e va letto prima di ipotizzare:

docker logs backbone-core-dev-auth-service --since 30m 2>&1 | grep DENY

I ruoli gestionali hanno /rendicontazione-service/** con metodo ANY, quindi per loro una rotta nuova funziona subito. Chi ha solo GETviewer, organization_admin — e il employee, che ha tre percorsi stretti, no. Il ruolo con cui si prova cambia il risultato: provare una scrittura come HR manager e dedurne che funziona per il dipendente è l'errore facile.

Un 502 su una scrittura è quasi sempre una colonna che manca

Successo il 18 agosto 2026 su PUT /projects/:id, dopo aver aggiunto sede e tetto ore al payload senza aver ancora applicato le migrazioni.

La catena: il verticale scrive con __strict_columns, datahub rifiuta la colonna che non conosce con un 400 e un messaggio preciso — «Unknown column(s) "work_location_id" …» — e data-service lo traduceva in 502 "Servizio dati non raggiungibile". Che è falso: il servizio ha risposto, e benissimo.

Corretto lo stesso giorno: un 4xx di datahub ora passa come 400 con la spiegazione che datahub ha scritto. Restano 502 i guasti veri, e non esce mai err.message di axios — quello può contenere host e indirizzi interni, ed è la ragione per cui la regola generale era stata scritta al contrario.

Quando succede, la cura è sempre la stessa: applicare la migrazione e — per le colonne — riavviare datahub.

Il sintomo del disallineamento è un 404 su una rotta del verticale che esiste. Significa che il verticale chiama un endpoint che nel container a valle non c'è ancora. Il corpo della risposta lo dice: il client aggiunge sempre il percorso a valle al messaggio, per esempio ... [data-service /query].

In sospeso

Cose vere e non fatte, elencate perché non si perdano.

Due migrazioni entrate in git senza essere state riviste091_add_dev_server_origins.sql e 093_rendicontazione_vinova_lab_access.sql. Erano state segnalate come non versionate e da rivedere prima di uscire da DEV; sono poi finite in un commit come effetto collaterale di un git add -A sulla cartella, senza che quella revisione avvenisse. Essere tracciate non le rende approvate: la 091 contiene id di applicazione fissi e origini di sviluppo, la 093 dà accesso a Vinova Lab senza distinguere l'ambiente. Vanno lette prima di applicarle a TEST.

Migrazioni in coda per TEST, applicate solo in DEV:

095toglie al ruolo dipendente il permesso di lettura generalizzato
096collega un dipendente a un account (employees.user_id)
097email del dipendente, sorgente dell'accesso
098ridà al ruolo dipendente il minimo per vedere la propria scheda
099storico del costo orario, con intervalli che non possono sovrapporsi
100ruolo, macro-livello e job level separati, con i cataloghi seminati
101i livelli veri del CCNL Commercio al posto dei segnaposto
102catalogo sedi e storico dell'assegnazione
103la sede di Milano, e l'assegnazione di chi non ne aveva una
104linea di riporto storicizzata, con il trigger anti-ciclo
105sotto-organizzazioni come dimensione, con lo storico
106le tre societa di Italbiotec
107il flag di collaboratore sulla persona, non sulla societa
108le due tabelle delle ferie, con i giorni goduti ricostruiti dalle presenze
109al ruolo dipendente i due percorsi del self-service ferie
110il ruolo di dipendente a chi e in anagrafica e non ce l'ha, amministratori compresi
111il piano ferie concesso anche sul lato data-service della catena
112i tre macro-livelli, e la regola che li ricava dal job level
113il tetto di ore per risorsa, un attributo di ogni progetto
114le tariffe standard per ente e macro-livello, storicizzate
115la sede opzionale del progetto, riferita al catalogo sedi
116freeze_config: la colonna che mancava a una funzione gia costruita
091, 093non tracciate in git, vedi sopra

Dopo ogni migrazione serve un refresh dello schema di datahub, e i due casi non si comportano allo stesso modo.

Colonne nuove su una tabella esistente: servì un riavvio. Il 16 agosto 2026 il POST /api/refresh non bastò. Il sintomo di uno schema non aggiornato è il peggiore possibile — la scrittura risponde 200 e il dato non viene salvato, perché datahub scarta in silenzio le colonne che non conosce. È costato tre giri: email, livello, città. Da lì viene __strict_columns, che fa rifiutare la scrittura invece di farla riuscire a vuoto.

Tabelle nuove: basta il refresh. Verificato leggendo il codice il 18 agosto 2026 per la 108: /api/table/:nome risolve il router a ogni richiesta dalla mappa che refreshSchema sostituisce per intero, quindi una tabella che prima non c'era compare subito. Il sintomo qui è diverso e meno insidioso — un 404 che sembra una migrazione mancante.

Le due osservazioni convivono male: leggendo il codice di oggi anche le colonne dovrebbero aggiornarsi da sole, e il messaggio d'errore in dynamicRouterGenerator.js che consiglia il riavvio parla di mounted routers che quel montaggio non sembra avere. Nel dubbio, per le colonne vale l'osservazione sul campo, non la lettura del codice: chi tocca colonne riavvii, chi aggiunge tabelle faccia il refresh.

La baseline di latenza non esiste, per scelta. ADR-002 prevedeva una soglia p95 concordata prima del rimpatrio; all'approvazione si è deciso di non misurarla. Conseguenza accettata: se emergerà un rallentamento non ci sarà un riferimento pre-migrazione con cui confrontarlo.

Il gate P0 in produzione non è stato eseguito — pre-flight, backup, migrazioni, invarianti. Non urgente finché non esiste una produzione vera, ma resta aperto.