++ fix e controlli
This commit is contained in:
380
HANDOFF.md
380
HANDOFF.md
@@ -4,7 +4,7 @@ Stato del progetto per chi ci mette mano dopo. Per installazione, deploy e uso
|
||||
quotidiano vedi [README.md](README.md); qui c'è il contesto che il README non
|
||||
può dare: perché le cose sono come sono, e cosa resta aperto.
|
||||
|
||||
Ultimo aggiornamento: **29 luglio 2026**.
|
||||
Ultimo aggiornamento: **30 luglio 2026**.
|
||||
|
||||
---
|
||||
|
||||
@@ -22,18 +22,36 @@ redeploy, login, tutte le pagine, code e scheduler.
|
||||
| Sviluppo locale | ✅ stesso compose, profilo `dev` |
|
||||
| Backup / ripristino / azzeramento | ✅ verificati end-to-end in locale |
|
||||
| Ripristino da browser in linea | ⚠️ **limitato a 100 MB da Cloudflare** — vedi [Cosa resta aperto](#cosa-resta-aperto) |
|
||||
| Test automatici | ❌ **assenti** — vedi [Cosa resta aperto](#cosa-resta-aperto) |
|
||||
| Test automatici | ⚠️ **impalcatura pronta, 57 test** — copertura ancora parziale |
|
||||
|
||||
**Punto in cui ci si è fermati (29/07/2026).** Su `develop` è pubblicato tutto:
|
||||
**Punto in cui ci si è fermati (30/07/2026).** Su `develop` è pubblicato tutto:
|
||||
backup completo `.zip`, ripristino, azzeramento, cambio password forzato, barre
|
||||
di avanzamento reali e console di log (ultimo commit `f909791`, deploy
|
||||
verificato confrontando l'impronta degli asset). `MAX_UPLOAD_MB=2048` è
|
||||
impostato **solo** su develop; la produzione non è stata toccata e resta sul suo
|
||||
deploy precedente, senza quella variabile.
|
||||
di avanzamento reali e console di log (commit `f909791`, deploy verificato
|
||||
confrontando l'impronta degli asset). `MAX_UPLOAD_MB=2048` è impostato **solo**
|
||||
su develop; la produzione non è stata toccata e resta sul suo deploy
|
||||
precedente, senza quella variabile.
|
||||
|
||||
Il ripristino dall'interfaccia resta però impraticabile per pacchetti grandi
|
||||
finché non si decide come aggirare Cloudflare: è la prima voce di *Cosa resta
|
||||
aperto*.
|
||||
Il 30/07 sono state fatte due cose.
|
||||
|
||||
**La suite di test, che prima non esisteva**: 79 test eseguibili con un comando,
|
||||
senza dipendere da altri servizi. Sono **verdi** ma coprono ancora una frazione
|
||||
della logica di business — vedi *Cosa resta aperto*. Non fanno parte
|
||||
dell'immagine di produzione e non girano su un deploy: sono uno strumento
|
||||
locale.
|
||||
|
||||
**La pagina "Backup e import" è stata riorganizzata**: cinque zone numerate con
|
||||
un indice che ci salta sopra evidenziandole, un solo sistema di pulsanti (con
|
||||
gli stati di focus, che prima non c'erano), e **una sola console** per tutte le
|
||||
operazioni, agganciata in fondo alla finestra, al posto dei cinque riquadri di
|
||||
log sparsi. L'azzeramento è diventato un componente a sé per poter stare in
|
||||
fondo alla pagina. Tutto verificato lato server dai test; **il comportamento nel
|
||||
browser — apertura automatica della console, scorrimento, lampeggio delle zone —
|
||||
non è stato provato**, perché richiede un browser: è la prima cosa da guardare
|
||||
aprendo la pagina.
|
||||
|
||||
Il ripristino dall'interfaccia resta impraticabile per pacchetti grandi finché
|
||||
non si decide come aggirare Cloudflare: è la prima voce di *Cosa resta aperto*
|
||||
e l'unica che richiede una decisione, non del lavoro.
|
||||
|
||||
---
|
||||
|
||||
@@ -556,6 +574,228 @@ Tre scelte non ovvie:
|
||||
hash generato con `BCRYPT_ROUNDS` diverso) ed evita che Eloquent sovrascriva i
|
||||
timestamp importati. Stessa tecnica in `applyRowMeta()` per le altre entità.
|
||||
|
||||
### La pagina "Backup e import": cinque zone e una sola console
|
||||
|
||||
La pagina era cresciuta per accumulo: sette riquadri senza un ordine dichiarato,
|
||||
la zona pericolosa a metà strada, **cinque** aree di log separate e pulsanti che
|
||||
ogni sezione ridefiniva per conto proprio. È stata rimessa in ordine attorno a
|
||||
tre regole.
|
||||
|
||||
**Cinque zone numerate, in ordine di quanto sono definitive.** `1 Backup`,
|
||||
`2 Ripristino`, `3 Import PDF`, `4 XML`, `5 Zona pericolosa`. Ognuna ha
|
||||
un'ancora (`id="zona-…"`) e l'indice appiccicato in cima ci salta sopra. Un test
|
||||
verifica che le cinque ancore esistano *e che siano in quell'ordine nel
|
||||
sorgente*: l'ordine è la sostanza della sistemazione, non un dettaglio estetico.
|
||||
|
||||
**Il "focus" della pagina.** Premuto un pulsante — o scelta una voce
|
||||
dell'indice — la pagina si sposta sulla zona di riferimento e la fa lampeggiare
|
||||
(`window.tmFocusZona`). Il focus da tastiera si sposta **solo** quando la
|
||||
richiesta è di navigazione: spostarlo anche a ogni azione strapperebbe il
|
||||
cursore da sotto le dita di chi ha appena premuto. Il primo comando di ogni zona
|
||||
è marcato `data-zona-focus` ed è quello che lo riceve.
|
||||
|
||||
**Una sola console, agganciata in fondo alla finestra.** Prima la stessa
|
||||
operazione si raccontava in tre punti diversi: un ripristino cominciava nel
|
||||
riquadro "Browser", proseguiva in quello "Server" e aveva un terzo "Dettaglio"
|
||||
richiudibile nel pannello di avanzamento, in cima. Per capire dove si era
|
||||
fermato bisognava guardarli tutti. Adesso c'è una cronologia sola, in ordine di
|
||||
tempo, con la sorgente accanto a ogni riga, e la finestra è *fissa*: quasi tutti
|
||||
i modi in cui un caricamento muore sono muti, e l'unico indizio è dove si è
|
||||
interrotto il flusso di righe — un log che esce dallo schermo appena si scorre
|
||||
non serve proprio nel momento in cui servirebbe.
|
||||
|
||||
Dettagli non ovvi della console:
|
||||
|
||||
- **Le righe del server viaggiano già scremate.** Il conto di quante ne sono
|
||||
state spedite lo tiene il componente Livewire (`logSpediti`), non il browser:
|
||||
la coda intera dentro ogni risposta significherebbe sessanta righe poco meno
|
||||
di due volte al secondo, e per l'import PDF — la cui coda non ha tetto —
|
||||
l'intera cronologia a ogni poll.
|
||||
|
||||
- **Per ogni operazione c'è un solo componente che la racconta**
|
||||
(`SegueOperazioneBackup::tipiSeguiti()`). Backup e zona pericolosa guardano lo
|
||||
stesso stato condiviso: senza un proprietario dichiarato spedirebbero entrambi
|
||||
le stesse righe, e la console le mostrerebbe due volte.
|
||||
|
||||
- **Il download automatico si ascolta in un posto solo.** `scarica-backup` è un
|
||||
evento su `window`: due componenti che lo ascoltassero entrambi assegnerebbero
|
||||
`window.location.href` due volte, e il pacchetto verrebbe scaricato due volte.
|
||||
L'ascolto sta nella console, che è l'unico elemento sempre presente. Per lo
|
||||
stesso motivo `scaricaOra()` esce subito se non è il proprietario a chiamarlo:
|
||||
il segno "download già inviato" sta nello stato condiviso, quindi chi segue
|
||||
l'operazione di riflesso lo consumerebbe per primo — l'archivio sarebbe pronto
|
||||
e non arriverebbe a nessuno, e per l'azzeramento è pure il passo che sblocca
|
||||
la cancellazione.
|
||||
|
||||
- **Lo scorrimento automatico si sospende se si torna indietro a leggere.**
|
||||
Trascinare in fondo mentre si sta guardando una riga di dieci secondi prima è
|
||||
il modo più rapido di rendere inutile un log che scorre. Compare invece
|
||||
"Torna in fondo".
|
||||
|
||||
- **Il pannello è a posizione fissa, quindi la pagina ha uno spaziatore in
|
||||
fondo.** Senza, coprirebbe l'ultima zona — che è quella pericolosa.
|
||||
|
||||
- **Il JavaScript della console sta in un `<script>`, non dentro `x-data`.** È
|
||||
la via d'uscita dalla trappola documentata più sotto: dentro un `<script>` le
|
||||
virgolette doppie e gli apostrofi non hanno alcuna limitazione. Non sta in
|
||||
`resources/js/app.js` perché quel bundle è un modulo ES, che il browser rimanda
|
||||
a dopo il parsing del documento, mentre Alpine arriva col pacchetto di Livewire
|
||||
in fondo al `<body>` ed è già avviato a quel punto: una `Alpine.data()`
|
||||
registrata dopo l'avvio non viene applicata agli elementi già inizializzati. Un
|
||||
`<script>` classico nella pagina viene eseguito prima, sempre — e c'è un test
|
||||
che verifica proprio che la definizione preceda `livewire.js`, perché è un
|
||||
rapporto che guardando i due file non si vede.
|
||||
|
||||
**Un solo sistema di pulsanti** — vedi la sezione dedicata qui sotto.
|
||||
|
||||
### Un solo pulsante per tutta l'applicazione
|
||||
|
||||
`resources/css/app.css` definisce `.tm-btn`;
|
||||
`resources/views/components/btn.blade.php` è l'unico posto da cui si usa. Nessuna
|
||||
vista scrive quelle classi a mano, e nessuna vista definisce più un pulsante per
|
||||
conto proprio.
|
||||
|
||||
Da dove veniamo: **quattro vocabolari in circolazione insieme** — `style=`
|
||||
inline con i colori scritti a mano (più `onmouseover`/`onmouseout` per simulare
|
||||
l'hover), catene di utility Tailwind ripetute a memoria, `.btn-action*` in sei
|
||||
tinte e `.btn-primary-*` in due. La stessa azione cambiava aspetto da una pagina
|
||||
all'altra, l'ordine di *Salva* e *Annulla* si invertiva fra un form e l'altro, e
|
||||
**nessuno** di quei pulsanti aveva uno stato di focus: chi naviga da tastiera non
|
||||
vedeva dove si trovava.
|
||||
|
||||
Il sistema ha due assi ortogonali, non una tavolozza:
|
||||
|
||||
| Asse | Valori | Regola |
|
||||
|------|--------|--------|
|
||||
| `variante` | `primary` | l'azione principale. Una sola per schermata o zona. |
|
||||
| | `secondary` | tutto il resto: dettaglio, modifica, scarica, filtra. |
|
||||
| | `danger` | **solo** dove si cancellano o si sovrascrivono dati. |
|
||||
| | `ghost` | annulla, chiudi, pulisci. |
|
||||
| `tono` | `solido` | il pieno (default), per le azioni isolate. |
|
||||
| | `tenue` | la tinta chiara, per le righe di elenco. |
|
||||
| | `velato` | per i comandi sopra una superficie colorata: solo la testata. |
|
||||
|
||||
più `dimensione="sm"` per le righe e `icona` per i pulsanti di sola icona.
|
||||
|
||||
Le decisioni non ovvie prese convertendo:
|
||||
|
||||
- **`tenue` esiste per le liste.** In una riga i pulsanti sono tanti quanti i
|
||||
record: cinquanta pulsanti pieni sono rumore, e il rosso pieno smette di
|
||||
significare "attenzione" quando compare cinquanta volte di fila.
|
||||
|
||||
- **Il rosso è tornato a significare una cosa sola.** *Rientra* era rosso pieno
|
||||
quanto un *Elimina*, in tre pagine diverse; anche *Scarica PDF* nella scheda
|
||||
"Da rientrare" della dashboard lo era, solo perché lo è la scheda che lo
|
||||
contiene. Nessuno dei due cancella niente.
|
||||
|
||||
- **Ordine unico: azione principale prima, uscita dopo.** Valeva già in metà dei
|
||||
form, l'opposto nell'altra metà, e nelle modali del Registro c'era la terza
|
||||
variante. Ora è lo stesso ovunque, form e modali comprese.
|
||||
|
||||
- **`icona` richiede `aria-label`.** I pulsanti di sola icona avevano solo un
|
||||
`title`, che i lettori di schermo annunciano in modo inaffidabile: un pulsante
|
||||
senza nome accessibile è un pulsante che non si può usare senza vederlo.
|
||||
|
||||
- **`velato` non è una quinta variante inventata.** La testata ha il suo
|
||||
gradiente, e lì un pulsante bianco o indaco sarebbe una toppa: il colore lo dà
|
||||
lo sfondo. Ha anche un anello di focus bianco, perché quello blu su indaco non
|
||||
si distingue.
|
||||
|
||||
Attenzione a una trappola di Blade: dentro un tag di componente si scrive
|
||||
`:disabled="$condizione"`, **non** `@disabled($condizione)`. Le direttive non
|
||||
vengono compilate lì dentro: il tag `<x-btn>` resta letterale nel PHP generato
|
||||
mentre il `</x-btn>` viene chiuso, e la vista muore con «syntax error, unexpected
|
||||
token endif».
|
||||
|
||||
A tenere insieme tutto questo ci sono due test, perché una convenzione senza
|
||||
guardia dura fino alla prossima pagina scritta copiando una vecchia:
|
||||
|
||||
- `tests/Unit/StileUnificatoTest.php` legge i sorgenti delle viste e rifiuta il
|
||||
ritorno di `.btn-action*`, `.btn-primary-*`, `onmouseover`/`onmouseout`, degli
|
||||
sfondi scritti a mano su `<button>`/`<a>`, e delle classi `tm-btn` messe
|
||||
direttamente in una vista invece che dal componente.
|
||||
- `tests/Feature/PagineApplicativeTest.php` apre **ogni** pagina e verifica che
|
||||
non sia rimasto un `<x-btn` da compilare — perché un componente non compilato
|
||||
non solleva: semplicemente il pulsante non c'è.
|
||||
|
||||
### Perché l'azzeramento è un componente a sé
|
||||
|
||||
`Settings/ZonaPericolosa` è nato da un problema di sola disposizione: la zona
|
||||
pericolosa deve stare **in fondo** alla pagina, mentre backup e ripristino stanno
|
||||
in cima, e i tre erano lo stesso componente. Reso da `BackupManager` l'azzeramento
|
||||
finiva a metà pagina, fra il ripristino e l'import — l'ultimo posto in cui si
|
||||
vuole trovare un pulsante che cancella tutto.
|
||||
|
||||
La logica condivisa non è stata duplicata: sta nel trait
|
||||
`Livewire/Concerns/SegueOperazioneBackup` (posto unico, aggancio all'operazione
|
||||
in corso, download automatico, righe verso la console). Duplicare `postoLibero()`
|
||||
avrebbe significato duplicare la regola su cui si regge il fatto che due
|
||||
operazioni non si sovrascrivano: è l'ultimo posto dove ha senso avere due copie.
|
||||
|
||||
Conseguenza da conoscere: **due componenti pollano lo stesso stato**. Il
|
||||
proprietario a 750ms (5s in attesa-download), l'altro a 3s — non ha righe da
|
||||
raccontare, ma deve accorgersi che il posto si è liberato, altrimenti il suo
|
||||
pulsante resterebbe disabilitato fino al ricaricamento della pagina, con l'aria
|
||||
di essersi rotto. Si avvisano anche a evento (`operazione-avviata`), indirizzato
|
||||
con `->to()` e non gridato: senza, tornerebbe pure al mittente.
|
||||
|
||||
Il secondo tempo dell'azzeramento (verifica del backup, password, conferma)
|
||||
compare adesso **dentro la zona**, dove è stato premuto il primo. Prima usciva
|
||||
nel pannello di avanzamento in cima, a schermate di distanza dal pulsante che lo
|
||||
aveva avviato.
|
||||
|
||||
### La suite di test ha un'immagine sua
|
||||
|
||||
Le dipendenze di sviluppo non stanno nell'immagine di produzione, e non devono
|
||||
cominciare a starci: `--no-dev` è la ragione per cui il runtime non porta con sé
|
||||
PHPUnit, Faker e Ignition. Da qui uno stage `test` nel `Dockerfile`, che
|
||||
condivide con `runtime` lo stage `base` — stesse estensioni, stesso `php.ini` —
|
||||
e si distingue solo per il `vendor/` completo. Un test che passa lì dentro non
|
||||
può passare grazie a un ambiente diverso da quello vero.
|
||||
|
||||
Le scelte che vale la pena conoscere:
|
||||
|
||||
- **`runtime` deve restare l'ultimo stage del file.** `docker build` senza
|
||||
`--target` costruisce l'ultimo, ed è così che lo invocano sia i tre servizi
|
||||
del compose (nessuno dichiara `target`) sia Dokploy. Uno stage aggiunto in
|
||||
fondo diventerebbe **in silenzio** l'immagine di produzione. Per questo `test`
|
||||
sta *prima* di `runtime` pur essendo stato aggiunto dopo. L'avvertenza è anche
|
||||
nel Dockerfile, sopra lo stage.
|
||||
|
||||
- **La suite non dipende da niente.** `phpunit.xml` impone SQLite in memoria,
|
||||
cache `array` e code `sync`, e il servizio compose non ha `depends_on` né
|
||||
`env_file`. Gira a stack spento, non tocca `db_data` né `storage_data`, e
|
||||
soprattutto non può puntare per sbaglio al database di sviluppo: un `.env`
|
||||
ereditato dal container è esattamente il modo in cui una suite di test
|
||||
cancella dati veri.
|
||||
|
||||
Il rovescio: **SQLite non è MariaDB**. Resta un solo punto non verificabile
|
||||
qui, l'ordinamento di `Registro::render()`, che usa `CAST(... AS UNSIGNED)`:
|
||||
va provato sullo stack vero. Gli altri due sono stati tolti di mezzo perché
|
||||
costavano poco e in cambio rendevano testabile una pagina intera — la
|
||||
migrazione `2026_04_08_123500`, il cui `GREATEST(...)` faceva proprio morire
|
||||
la costruzione dello schema, e la media di percorrenza in `Home`, che usava
|
||||
`DATEDIFF`/`YEAR` e teneva fuori dalla suite la pagina più visitata
|
||||
dell'applicazione. Entrambe danno lo stesso risultato di prima.
|
||||
|
||||
- **Il sorgente non è montato, e il `--build` non è facoltativo.** Con i bind
|
||||
mount la stessa suite passa da ~15 secondi a ~2 minuti (ogni bootstrap di
|
||||
Laravel legge una decina di file attraverso il layer di Docker Desktop), e in
|
||||
cambio si guadagna solo di non ricostruire. Il comando canonico è
|
||||
`docker compose --profile test run --build --rm test`: senza `--build` si
|
||||
eseguono i test dell'immagine precedente, che possono essere di due modifiche
|
||||
fa. Stesso genere di trappola del "Rebuild deployment" su Dokploy.
|
||||
|
||||
- **`APP_KEY` di test è fissa e sta in chiaro in `phpunit.xml`.** Nome e cognome
|
||||
dei proclamatori hanno il cast `encrypted`: senza chiave nemmeno un test che
|
||||
non parla di cifratura riesce a scrivere una riga. Non ha alcun rapporto con
|
||||
la chiave di produzione, che vive nell'ambiente e nel volume.
|
||||
|
||||
- **`righeS13()` si chiama in riflessione.** È privata di proposito (non è
|
||||
un'azione Livewire) ma è il calcolo, non il PDF, quello che va protetto:
|
||||
renderla pubblica solo per poterla testare avrebbe allargato la superficie
|
||||
del componente per comodità del test.
|
||||
|
||||
### Un solo container esegue le migrazioni
|
||||
|
||||
Solo `app` migra. `queue-worker` e `scheduler` usano `queue-entrypoint.sh`, che
|
||||
@@ -596,6 +836,10 @@ Contesto utile se ti imbatti in comportamenti che sembrano regressioni.
|
||||
| Cancellazione file senza verifica | «N file cancellati» con i file ancora sul disco (permessi) | `BackupArchive::svuotaDiscoPubblico()` |
|
||||
| Eliminazione definitiva territorio | La riga spariva, mappa e miniatura restavano orfane sul volume per sempre | `Territori/TerritorioCestino` |
|
||||
| Virgolette doppie in `x-data` | Attributo HTML chiuso a metà: il JavaScript finiva a video come testo | `backup-manager.blade.php` |
|
||||
| Warning di `addFile()` al posto del messaggio | Su file illeggibile l'amministratore leggeva `ZipArchive::addFile(): No such file or directory` invece della frase che dice quale file e cosa controllare | `Services/BackupArchive` |
|
||||
| `GREATEST()` in migrazione | Sintassi MySQL: lo schema non si costruiva su SQLite, quindi la suite di test non poteva partire | migrazione `2026_04_08_123500` |
|
||||
| `@disabled` dentro `<x-btn>` | Direttiva non compilata: il tag restava letterale e la pagina moriva con «unexpected token endif» | viste di `settings` |
|
||||
| Segno del download consumato dal componente sbagliato | L'archivio pronto non sarebbe arrivato a nessuno — e per l'azzeramento è il passo che sblocca la cancellazione | `SegueOperazioneBackup::scaricaOra()` |
|
||||
|
||||
---
|
||||
|
||||
@@ -623,39 +867,60 @@ Resta da accertare **quanto pesa davvero il pacchetto** che si vuole
|
||||
ripristinare: sotto i 100 MB il problema non sarebbe la dimensione ma il timeout
|
||||
di 100 secondi di Cloudflare, e la soluzione sarebbe un'altra.
|
||||
|
||||
### 1. Nessun test automatico
|
||||
### 1. Copertura dei test ancora parziale
|
||||
|
||||
`composer.json` richiede PHPUnit ma **la cartella `tests/` non esiste** e non
|
||||
c'è `phpunit.xml`. Ogni verifica è manuale. È il debito più rilevante: le regole
|
||||
di business (anno teocratico, stato territorio, priorità automatica, percentuale
|
||||
campagne) sono esattamente il tipo di logica che va protetta da test — e a
|
||||
maggior ragione backup, ripristino e azzeramento, dove un errore non si vede
|
||||
finché non è troppo tardi.
|
||||
L'impalcatura ora c'è (vedi *La suite di test ha un'immagine sua*) e i tre
|
||||
blocchi che impedivano di scrivere il primo test sono stati rimossi: stage
|
||||
`test` nel Dockerfile con le dipendenze di sviluppo, connessione `sqlite` in
|
||||
`config/database.php`, `phpunit.xml`. Si lancia con:
|
||||
|
||||
Tre blocchi concreti da rimuovere prima di poter scrivere il primo test:
|
||||
```bash
|
||||
docker compose --profile test run --build --rm test
|
||||
```
|
||||
|
||||
1. **PHPUnit non è nell'immagine.** `docker/php/Dockerfile` installa con
|
||||
`--no-dev` (giusto, per la produzione), quindi `vendor/phpunit` nel container
|
||||
non c'è e `docker compose exec app ./vendor/bin/phpunit` non funziona.
|
||||
Servono o un `composer install` locale sull'host, o uno stage `dev` nel
|
||||
Dockerfile che tenga le dipendenze di sviluppo.
|
||||
2. **Manca la connessione SQLite.** `config/database.php` ridefinisce
|
||||
`connections` e lascia solo `mysql`. Per usare SQLite in memoria va
|
||||
riaggiunta la connessione `sqlite`.
|
||||
3. **Manca `phpunit.xml`.** L'autoload `Tests\` in `composer.json` c'è già, il
|
||||
resto no.
|
||||
**Coperto oggi — 233 test, tutti verdi:**
|
||||
|
||||
Dove conviene cominciare, in ordine di rapporto valore/attrito — i primi due
|
||||
non hanno bisogno del database e quindi si sbloccano risolvendo solo il punto 1:
|
||||
| File | Cosa protegge |
|
||||
|------|---------------|
|
||||
| `tests/Unit/BackupArchiveTest.php` | Round-trip crea → riestrai, manifest, avanzamento fino al totale, fallimento su file illeggibile con archivio rimosso, rifiuto di manifest mancante/illeggibile/di formato futuro e di `data.xml` mancante o vuoto, zip slip (percorso assoluto, `..`, unità Windows) e normalizzazione dei percorsi validi, staging ripulito dopo un fallimento, sostituzione totale del disco al ripristino, svuotamento con conteggio |
|
||||
| `tests/Unit/BackupOperationStateTest.php` | Il posto unico: occupazione, rilascio a completamento e a fallimento, puntatore orfano, operazione già chiusa, chiusura tardiva che non deve liberare il posto della nuova, attesa-download che lo tiene occupato, azzeramento della barra al cambio fase, coda dei log limitata |
|
||||
| `tests/Feature/RegistroS13Test.php` | Il ciclo da quattro gruppi del modulo S-13 (1/3/4/5/8/9 assegnazioni), la colonna "Restituito l'ultima volta il" compreso il caso in cui l'ultima del giro precedente è ancora aperta, esclusione di territori disattivati e cestinati, filtro sull'anno di servizio, ordinamento naturale dei numeri |
|
||||
| `tests/Feature/ZonaPericolosaTest.php` | I due tempi dell'azzeramento: frase di conferma, backup preventivo senza cancellazione, rifiuto se il posto è occupato, conferma senza archivio servito, senza password, con password sbagliata, blocco dopo cinque tentativi, annullamento che libera il posto lasciando il backup valido. In più: che ogni operazione abbia un solo componente a raccontarla, e che il segno del download non venga consumato da chi non la possiede |
|
||||
| `tests/Feature/PaginaBackupImportTest.php` | La pagina servita per intero: permessi, le cinque zone con la loro ancora **e nel loro ordine**, la console unica, l'assenza dei vecchi riquadri di log, che nessun tag `<x-btn` sia rimasto da compilare, che i pulsanti usino il sistema unico, e che la console sia definita prima del pacchetto che avvia Alpine |
|
||||
| `tests/Feature/PagineApplicativeTest.php` | **Ogni** pagina dell'applicazione si apre davvero (200) e non ha markup rimasto da compilare. È la rete che serve quando una modifica tocca tutte le viste in una volta: un errore di markup non rompe niente finché qualcuno non apre quella pagina |
|
||||
| `tests/Unit/StileUnificatoTest.php` | Che i vocabolari di stile paralleli non rientrino: `.btn-action*`, `.btn-primary-*`, l'hover simulato con `onmouseover`, gli sfondi scritti a mano sui pulsanti, le classi `tm-btn` messe in una vista invece che dal componente |
|
||||
| `tests/Feature/HomeMediaDurataTest.php` | La "media percorrenza congregazione": unità di misura, esclusione delle assegnazioni aperte, dei rientri nello stesso giorno, delle date impossibili ereditate dalla vecchia applicazione e degli altri anni di servizio |
|
||||
|
||||
- `Services/BackupArchive` con `Storage::fake('public')`: round-trip
|
||||
crea → verifica → estrai, rifiuto dello zip slip, rifiuto del formato futuro,
|
||||
fallimento su archivio incompleto.
|
||||
- `Services/BackupOperationState` con il driver di cache `array`: il posto
|
||||
unico, il rilascio alla chiusura, il puntatore orfano, il non-rilascio da
|
||||
parte di un'operazione vecchia.
|
||||
- `Livewire/Registro::righeS13()`: il ciclo da quattro e la colonna del
|
||||
riporto, con i casi 2/3/4/5/8/9 assegnazioni (questi sì, con database).
|
||||
Il primo giro ha già trovato tre difetti reali, tutti corretti: il warning di
|
||||
`ZipArchive::addFile()` che copriva il messaggio d'errore vero, il `GREATEST()`
|
||||
in migrazione, e `@disabled` dentro un tag di componente (vedi *Bug corretti*).
|
||||
|
||||
Quello che questi test **non** possono coprire: Alpine e il comportamento della
|
||||
console — apertura automatica, scorrimento, evidenziazione delle zone — girano
|
||||
nel browser, e nella suite non c'è un browser. Quella parte va provata a mano.
|
||||
|
||||
**Scoperto ancora, in ordine di quanto costerebbe sbagliarsi:**
|
||||
|
||||
- `Services/BackupArchive::extract()` sui **limiti anti zip-bomb**: numero di
|
||||
entry, dimensione della singola entry, totale decompresso. Sono tre `if` che
|
||||
nessun test tocca, e il caso in cui contano non si presenta mai per sbaglio.
|
||||
- `Jobs/RestoreBackupArchive` e `Jobs/ResetApplication` **nel loro insieme**:
|
||||
l'ordine staging → database → file, il `failed()` che libera il posto, la
|
||||
sopravvivenza dell'amministratore che ha chiesto l'azzeramento, il
|
||||
`password_change_required` acceso su tutte le righe. È la logica il cui
|
||||
errore si scopre solo quando è troppo tardi.
|
||||
- `Livewire/Settings/XmlExchange`: il **round-trip** export → import senza
|
||||
perdite, che è il presidio contro il tipo di bug già visto con `legacy_id`.
|
||||
- Le regole di business: anno teocratico (`AnnoTeocratico::perData` a cavallo
|
||||
del 1º settembre), `Territorio::stato` e la soglia `giorni_per_smarrito`,
|
||||
priorità automatica, percentuale di percorrenza delle campagne.
|
||||
- Il permesso come contratto: che un `assistente` **non** possa modificare o
|
||||
eliminare un'assegnazione, una campagna, un territorio o un proclamatore. È
|
||||
scritto in tre posti (rotta, componente, vista) e un test di richiesta lo
|
||||
verificherebbe tutto insieme.
|
||||
|
||||
Attenzione a non scrivere test su ciò che SQLite non sa eseguire: la dashboard
|
||||
(`Home`) e l'ordinamento di `Registro::render()` usano SQL specifico di MySQL.
|
||||
|
||||
### 2. Race su doppia assegnazione
|
||||
|
||||
@@ -688,6 +953,28 @@ limit. Il controllo d'accesso è solido (confronto in tempo costante su codice a
|
||||
~62 bit, più controlli di scadenza), ma un tentativo di enumerazione non
|
||||
verrebbe né rallentato né registrato. Un `throttle:30,1` costerebbe una riga.
|
||||
|
||||
### 6. Restano da uniformare i colori che non sono pulsanti
|
||||
|
||||
I pulsanti sono a posto in tutta l'applicazione (vedi *Un solo pulsante per
|
||||
tutta l'applicazione*), ma i **colori decorativi** sono ancora scritti a mano,
|
||||
esadecimale per esadecimale, dentro gli attributi `style=`: le piastrelle delle
|
||||
icone e le fasce delle schede nella dashboard, gli sfondi delle intestazioni di
|
||||
tabella, i pannelli delle modali del Registro, i riquadri dei messaggi nel
|
||||
layout.
|
||||
|
||||
Non sono un problema di accessibilità come lo erano i pulsanti — nessuno di
|
||||
questi elementi si può mettere a fuoco — ma sono lo stesso genere di debito: lo
|
||||
stesso verde compare in quattro tonalità leggermente diverse, e cambiare la
|
||||
tinta dell'applicazione oggi significa cercare a mano tutte le occorrenze.
|
||||
|
||||
La via è la stessa già percorsa: dei token in `app.css` (o le variabili di tema
|
||||
di Tailwind 4) e le viste che li citano invece di ripetere i valori. Il test
|
||||
`StileUnificatoTest` è il posto dove aggiungere la guardia, quando si farà.
|
||||
|
||||
Fuori perimetro per scelta: `resources/views/vendor/pagination/*` sono viste
|
||||
pubblicate da Laravel, e `resources/views/pdf/*` le impagina dompdf, che di
|
||||
Tailwind e delle nostre classi non sa niente.
|
||||
|
||||
---
|
||||
|
||||
## Trappole da conoscere
|
||||
@@ -718,7 +1005,11 @@ Nota sul `524`: anche restando sotto i 100 MB, Cloudflare chiude dopo 100
|
||||
secondi se l'origine non ha ancora risposto. Un caricamento lento può quindi
|
||||
fallire pur essendo dentro il limite di dimensione.
|
||||
|
||||
**Il JavaScript di `backup-manager.blade.php` vive dentro un attributo HTML.**
|
||||
**Il JavaScript che vive dentro un attributo HTML.** Vale ancora per i blocchi
|
||||
`x-data` di `backup-manager`, `zona-pericolosa` e della zona Import PDF, che
|
||||
gestiscono i caricamenti. La console, che era il blocco più grosso, è stata
|
||||
portata fuori in un `<script>` proprio per uscirne (vedi *La pagina "Backup e
|
||||
import"*); il resto resta soggetto alla regola.
|
||||
`x-data` finisce alla **prima virgoletta doppia**, comprese quelle dentro un
|
||||
commento JavaScript: un banale `// un generico "errore"` ha chiuso l'attributo a
|
||||
metà e riversato tutto il codice restante a video come testo — è successo due
|
||||
@@ -770,7 +1061,13 @@ cp .env.example .env
|
||||
# SESSION_SECURE_COOKIE=false, e le tre password
|
||||
docker network create dokploy-network
|
||||
|
||||
# 0. La suite passa (non serve nient'altro acceso, ~15 s)
|
||||
docker compose --profile test run --build --rm test
|
||||
|
||||
# 1. L'immagine builda
|
||||
# Senza --target si costruisce l'ultimo stage, che e' `runtime`: se questo
|
||||
# comando producesse un'immagine con phpunit dentro, qualcuno ha aggiunto
|
||||
# uno stage in fondo al Dockerfile.
|
||||
docker build -f docker/php/Dockerfile -t termanager2-check .
|
||||
|
||||
# 2. Stack pulito da zero
|
||||
@@ -795,8 +1092,9 @@ docker compose exec app php artisan tinker --execute="
|
||||
"
|
||||
```
|
||||
|
||||
È lo stesso file che gira in produzione: l'unica differenza è il profilo `dev`
|
||||
(che aggiunge Mailpit) e i valori nel `.env`.
|
||||
È lo stesso file che gira in produzione: le uniche differenze sono i profili
|
||||
`dev` (che aggiunge Mailpit) e `test` (che aggiunge la suite, effimera) e i
|
||||
valori nel `.env`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user