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!=anon è 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 espone | a cosa serve |
|---|---|
chiamate | quante richieste attraverserebbero la rete, e di che tipo |
scritture | ogni 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-serviceper 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 restituisceferie: 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: undata-servicespento 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/menon 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, madata-servicesi autentica da solo: quando una richiesta gli arriva senzax-user-id, il middlewareuserContextchiama/auth/validateper sapere chi sia l'utente, passandox-forwarded-uriex-forwarded-prefixcon 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 unDENYsu un percorso/data-service/**che nessuno ha mai chiamato dal browser — ed è il posto dove nessuno pensa di guardare. È il motivo per cui la098concedeva 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 GET — viewer,
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 riviste —
091_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:
095 | toglie al ruolo dipendente il permesso di lettura generalizzato |
096 | collega un dipendente a un account (employees.user_id) |
097 | email del dipendente, sorgente dell'accesso |
098 | ridà al ruolo dipendente il minimo per vedere la propria scheda |
099 | storico del costo orario, con intervalli che non possono sovrapporsi |
100 | ruolo, macro-livello e job level separati, con i cataloghi seminati |
101 | i livelli veri del CCNL Commercio al posto dei segnaposto |
102 | catalogo sedi e storico dell'assegnazione |
103 | la sede di Milano, e l'assegnazione di chi non ne aveva una |
104 | linea di riporto storicizzata, con il trigger anti-ciclo |
105 | sotto-organizzazioni come dimensione, con lo storico |
106 | le tre societa di Italbiotec |
107 | il flag di collaboratore sulla persona, non sulla societa |
108 | le due tabelle delle ferie, con i giorni goduti ricostruiti dalle presenze |
109 | al ruolo dipendente i due percorsi del self-service ferie |
110 | il ruolo di dipendente a chi e in anagrafica e non ce l'ha, amministratori compresi |
111 | il piano ferie concesso anche sul lato data-service della catena |
112 | i tre macro-livelli, e la regola che li ricava dal job level |
113 | il tetto di ore per risorsa, un attributo di ogni progetto |
114 | le tariffe standard per ente e macro-livello, storicizzate |
115 | la sede opzionale del progetto, riferita al catalogo sedi |
116 | freeze_config: la colonna che mancava a una funzione gia costruita |
091, 093 | non 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.