Piano dei test
Decisioni (strumenti, livelli, struttura): ADR-0003. Qui: regola, flusso, soglie e matrice requisito → test, sorgente di verità per la copertura. Le spunte [ ] passano a [x] quando il test esiste e passa.
Regola
Nuova funzione, correzione o cambio di comportamento ⇒ test nello stesso commit. Niente test, funzione non finita.
- Una nuova funzione aggiunge righe alla matrice qui sotto (con ID) prima di scrivere il codice.
- Una correzione di bug parte da un test che fallisce e lo lascia come regressione.
- Una modifica che cambia un requisito aggiorna prima il doc, poi la riga della matrice, poi i test, poi il codice.
- Una riga senza test non è copertura: va lasciata
[ ] e conta come debito visibile.
Flusso (estende il doc-first di README.md)
ADR (se serve) → MVP.md (requisito e criterio) → riga in questa matrice → test che falliscono → codice → test verdi → ROADMAP/README/CLAUDE.md.
Livelli
| Livello |
Cosa prova |
Come |
| U — unitario |
Core/ e MainViewModel |
xUnit, finto runner, cartella dati temporanea |
| R — renderer |
MarkdownView |
xUnit + Headless, asserzioni sull'albero visuale |
| G — GUI headless |
MainWindow, template, tastiera, finestre di dialogo |
xUnit + Headless, senza schermo |
| P — processo |
avvio reale di un processo (Claude) |
FakeClaude nel PATH |
| E — end-to-end |
l'eseguibile pubblicato (CLI, 127, exit code) |
publish + processo figlio + FakeClaude |
Struttura prevista
Predisposizioni di testabilità (prerequisito dei test)
Soglie di copertura (la build fallisce sotto soglia)
| Area |
Righe |
Rami |
Core/ |
≥ 95% |
≥ 90% |
Gui/MainViewModel |
≥ 95% |
≥ 90% |
Gui/MarkdownView |
≥ 90% |
≥ 85% |
Gui/MainWindow*, ConfirmDialog (code-behind) |
≥ 85% |
— |
| Totale |
≥ 90% |
— |
Esclusi dal calcolo: XAML generato, Program.cs (tre righe, coperte da E2E), App.axaml.cs. La soglia non sostituisce la matrice: copertura di righe alta con requisiti scoperti non basta.
Matrice requisito → test
Fonte dei requisiti: MVP.md e gli ADR. Livello: vedi sopra. Il nome del test è indicativo.
Avvio dei motori — Core/EngineRunner.cs
| ID |
Requisito |
Liv. |
Test |
|
| CL-01 |
Il prompt è un solo argomento, anche con virgolette e spazi (criterio MVP 4) |
U/P |
Prompt_con_virgolette_arriva_intatto |
[ ] |
| CL-02 |
Primo messaggio: --session-id <guid>; successivi: --resume <guid> |
U |
Sessione_prima_e_ripresa |
[ ] |
| CL-03 |
--model/--effort omessi se vuoti, presenti se valorizzati |
U |
Flag_opzionali |
[ ] |
| CL-04 |
Cartella di lavoro = cartella dell'exe, non la cwd del chiamante (criterio MVP 1) |
U/P |
Cartella_di_lavoro_e_quella_dell_exe |
[ ] |
| CL-05 |
Stdin chiuso subito: nessuna attesa di 3 s |
P |
Stdin_chiuso_non_blocca |
[ ] |
| CL-06 |
CLI: l'exit code di claude è propagato (criterio MVP 3) |
P/E |
ExitCode_propagato |
[ ] |
| CL-07 |
CLI: eseguibile del motore assente ⇒ messaggio chiaro (con il nome dell'eseguibile) ed exit code 127 |
P/E |
Claude_assente_127 |
[ ] |
| CL-08 |
CLI: stdout/stderr ereditati e ritrasmessi (criterio MVP 2: streaming) |
E |
Output_ereditato_in_tempo_reale |
[ ] |
| CL-09 |
GUI: output catturato e ripulito agli estremi; stderr separato |
P |
Output_catturato |
[ ] |
| CL-10 |
GUI: claude assente ⇒ eccezione riconoscibile, non crash |
P |
Claude_assente_in_GUI |
[ ] |
| CL-11 |
Il catalogo modelli contiene gli ID attesi e gli effort previsti |
U |
Catalogo_modelli_ed_effort |
[ ] |
| CL-12 |
Senza argomenti non parte la CLI; con argomenti sì (tutti uniti in un prompt) |
E |
Argomenti_uniti_in_un_prompt |
[ ] |
Codex — Core/Engine.cs (parser e argomenti), Core/EngineRunner.cs
Le righe CL valgono per ogni motore; queste sono specifiche di Codex (ADR-0002). Il parser si prova con output JSONL reali salvati nei test.
| ID |
Requisito |
Liv. |
Test |
|
| CX-01 |
Primo messaggio: exec --json --skip-git-repo-check -s read-only … -- <prompt> |
U |
Codex_primo_messaggio |
[ ] |
| CX-02 |
Ripresa: exec resume --json … -c sandbox_mode="read-only" <id> -- <prompt>, senza -s |
U |
Codex_ripresa |
[ ] |
| CX-03 |
Un prompt che inizia con - arriva intatto (grazie a --) |
U/P |
Codex_prompt_con_trattino |
[ ] |
| CX-04 |
Modello ⇒ -m; effort ⇒ -c model_reasoning_effort=<livello>; vuoti ⇒ omessi |
U |
Codex_modello_ed_effort |
[ ] |
| CX-05 |
Il parser legge thread_id da thread.started |
U |
Codex_thread_id |
[ ] |
| CX-06 |
La risposta è l'ultimo agent_message, anche con più messaggi |
U |
Codex_ultimo_agent_message |
[ ] |
| CX-07 |
turn.failed: messaggio interno di error.message estratto dal JSON annidato; exit ≠ 0 ⇒ errore |
U |
Codex_errore_turno |
[ ] |
| CX-08 |
Eventi item.completed di tipo error (avvisi) non fanno fallire la risposta |
U |
Codex_avvisi_ignorati |
[ ] |
| CX-09 |
Exit 0 senza agent_message ⇒ errore "nessuna risposta" |
U |
Codex_nessuna_risposta |
[ ] |
| CX-10 |
Righe non JSON o eventi sconosciuti sono ignorati senza eccezioni |
U |
Codex_righe_non_json |
[ ] |
| CX-11 |
In modalità CLI (output ereditato) non si usa --json |
U |
Codex_cli_senza_json |
[ ] |
| CX-12 |
Con un FakeClaude configurato come Codex il ciclo primo messaggio + ripresa funziona (id letto e riusato) |
P |
Codex_ciclo_completo_con_finto |
[ ] |
Aggiornamento dei CLI — Core/EngineUpdater.cs, Gui/MainViewModel.cs (ADR-0004)
Si provano con FakeClaude/FakeCodex che rispondono a --version e update; nessun test aggiorna un CLI reale.
| ID |
Requisito |
Liv. |
Test |
|
| UP-01 |
Versione letta da --version (prima riga ripulita); eseguibile assente ⇒ versione nulla |
P |
Versione_letta |
[ ] |
| UP-02 |
Versione cambiata dopo update ⇒ esito "Aggiornato da X a Y" |
P |
Aggiornato |
[ ] |
| UP-03 |
Exit 0 e versione invariata ⇒ "Già aggiornato (X)" |
P |
Gia_aggiornato |
[ ] |
| UP-04 |
Exit ≠ 0 ⇒ errore con le ultime righe dell'output e il comando manuale |
P |
Aggiornamento_fallito |
[ ] |
| UP-05 |
Tempo massimo superato ⇒ processo terminato ed errore chiaro |
P |
Aggiornamento_scaduto |
[ ] |
| UP-06 |
Eseguibile assente ⇒ riga non offerta / errore "non trovato" |
U |
Aggiornamento_senza_eseguibile |
[ ] |
| UP-07 |
Metodo di installazione dedotto dal percorso reale (npm, Homebrew, WinGet, nativo) con collegamenti simbolici risolti |
U |
Metodo_di_installazione |
[ ] |
| UP-08 |
Comando manuale corretto per ogni coppia motore × metodo (tabella dell'ADR-0004) |
U |
Comando_manuale |
[ ] |
| UP-09 |
Un solo aggiornamento alla volta; durante una risposta in corso i pulsanti sono disabilitati e la chat non parte durante l'aggiornamento |
U |
Aggiornamento_esclusivo |
[ ] |
| UP-10 |
Dopo un aggiornamento riuscito le versioni mostrate si rileggono; la chat aperta resta invariata |
U |
Versioni_rilette |
[ ] |
| UP-11 |
Le righe di aggiornamento compaiono solo per i CLI installati |
U/G |
Righe_solo_installati |
[ ] |
| UP-12 |
Il pulsante Aggiorna del riquadro agisce solo sul CLI della sua riga |
G |
Pulsante_per_riga |
[ ] |
| UP-13 |
Nessun aggiornamento parte senza un clic |
U |
Nessun_aggiornamento_automatico |
[ ] |
| UP-14 |
Claude Code: l'ultima versione si legge dal testo di …/claude-code-releases/latest (spazi e a capo ripuliti) |
U |
Ultima_versione_claude |
[ ] |
| UP-15 |
Codex: l'ultima versione si legge da tag_name della release GitHub (rust-v0.162.1 ⇒ 0.162.1); intestazione User-Agent inviata |
U |
Ultima_versione_codex |
[ ] |
| UP-16 |
Confronto: ultima > installata ⇒ disponibile; uguale o più nuova ⇒ aggiornato; versioni con testo attorno (2.1.300 (Claude Code), codex-cli 0.162.1) lette bene |
U |
Confronto_versioni |
[ ] |
| UP-17 |
Rete assente, risposta non valida o timeout ⇒ stato "sconosciuto", nessuna eccezione, pulsante normale |
U |
Controllo_senza_rete |
[ ] |
| UP-18 |
Disponibile ⇒ riga con "Aggiornamento disponibile: X", pulsante evidenziato e puntino sul pulsante Impostazioni; aggiornato ⇒ nessuno dei tre |
U/G |
Evidenziazione_aggiornamento |
[ ] |
| UP-19 |
Il controllo parte all'avvio e all'apertura del riquadro solo se l'ultimo ha più di 5 minuti; dopo un aggiornamento riuscito si ripete; non blocca invio né interfaccia |
U |
Quando_si_controlla |
[ ] |
| UP-20 |
Il controllo non installa nulla e non modifica i CLI |
P |
Controllo_in_sola_lettura |
[ ] |
Cronologia — Core/ChatStore.cs, Core/AppInfo.cs
| ID |
Requisito |
Liv. |
Test |
|
| ST-01 |
Salvataggio e rilettura: id, titolo, data, started, messaggi con ruolo e testo |
U |
Salva_e_rileggi |
[ ] |
| ST-02 |
Una chat senza messaggi non viene salvata |
U |
Chat_vuota_non_salvata |
[ ] |
| ST-03 |
Titolo = inizio del primo messaggio utente: 50 caratteri + "…", a capo sostituiti da spazi |
U |
Titolo_troncato |
[ ] |
| ST-04 |
L'id deve essere un Guid: un id con .. o separatori non tocca il disco fuori dalla cartella |
U |
Id_non_guid_rifiutato |
[ ] |
| ST-05 |
L'elenco è ordinato dalla più recente |
U |
Elenco_ordinato |
[ ] |
| ST-06 |
File JSON corrotto ignorato senza eccezioni |
U |
File_corrotto_ignorato |
[ ] |
| ST-07 |
Eliminazione: rimuove solo il file di quella chat; id inesistente senza errori |
U |
Elimina_chat |
[ ] |
| ST-08 |
Compatibilità del formato: un JSON scritto dalla versione WinForms si legge |
U |
Formato_storico_compatibile |
[ ] |
| ST-09 |
Migrazione: la cartella di un nome precedente (LlmRun) si sposta nella nuova al primo avvio; se la nuova esiste non si tocca nulla |
U |
Migrazione_da_nome_precedente |
[ ] |
| ST-10 |
AppInfo.Name = AssemblyName del csproj |
U |
Nome_da_assembly |
[ ] |
| ST-12 |
Il JSON salva Engine e SessionId; un file senza questi campi si legge come chat di Claude Code con SessionId = id |
U |
Motore_e_sessione_salvati_e_compatibilita |
[ ] |
| ST-11 |
Errore di I/O nel salvataggio non perde la chat a schermo |
U |
vedi VM-12 |
[ ] |
Logica della chat — Gui/MainViewModel.cs
| ID |
Requisito |
Liv. |
Test |
|
| VM-01 |
Senza chat salvate: schermata iniziale (ShowEmpty), elenco vuoto |
U |
Avvio_senza_chat |
[ ] |
| VM-02 |
All'avvio si riapre la chat più recente |
U |
Avvio_riapre_ultima |
[ ] |
| VM-03 |
CanSend falso se il testo è vuoto o solo spazi, o se occupato |
U |
CanSend |
[ ] |
| VM-04 |
Invio: il messaggio utente compare e viene salvato prima della risposta |
U |
Messaggio_salvato_prima_della_risposta |
[ ] |
| VM-05 |
Risposta riuscita: aggiunta come "ai", started diventa vero, il secondo invio usa --resume |
U |
Risposta_e_ripresa |
[ ] |
| VM-06 |
Risposta fallita (exit ≠ 0): messaggio "err" con codice e stderr; started resta falso e il tentativo successivo usa ancora --session-id |
U |
Errore_non_avvia_la_sessione |
[ ] |
| VM-07 |
claude assente: messaggio "'claude' non trovato nel PATH." |
U |
Claude_assente_messaggio |
[ ] |
| VM-08 |
Durante l'attesa: Invia e Nuova chat disabilitati; aprire/eliminare è ignorato |
U |
Occupato_blocca_azioni |
[ ] |
| VM-09 |
Modello: "Predefinito" ⇒ nessun flag; modello scelto ⇒ ID; "Altro ID…" ⇒ testo libero ripulito |
U |
Scelta_modello |
[ ] |
| VM-10 |
Effort: "Predefinito" ⇒ nessun flag; altrimenti il valore |
U |
Scelta_effort |
[ ] |
| VM-11 |
Nuova chat: svuota la vista, nuovo guid, started falso, elenco aggiornato |
U |
Nuova_chat |
[ ] |
| VM-12 |
Errore di I/O nel salvataggio non interrompe l'invio né cancella i messaggi |
U |
Salvataggio_fallito_non_perde_chat |
[ ] |
| VM-13 |
Apertura chat: carica messaggi, started e id; chat inesistente ⇒ elenco ricaricato |
U |
Apri_chat |
[ ] |
| VM-14 |
Eliminazione: con conferma rimuove; senza conferma non fa nulla; se è la chat aperta parte una nuova |
U |
Elimina_con_e_senza_conferma |
[ ] |
| VM-16 |
Barra laterale: il comando commuta SidebarOpen |
U |
Barra_laterale |
[ ] |
| VM-17 |
Copia: il comando passa il testo alla funzione degli appunti |
U |
Copia_negli_appunti |
[ ] |
| VM-18 |
Notifiche di proprietà: HasMessages, ShowEmpty, CanSend, IsCustomModel cambiano quando devono |
U |
Notifiche_proprieta |
[ ] |
| VM-19 |
Il titolo e la data dell'elenco riflettono l'ultimo scambio; la chat aperta è marcata attiva |
U |
Elenco_chat_attiva |
[ ] |
Renderer markdown — Gui/MarkdownView.cs
| ID |
Requisito |
Liv. |
Test |
|
| MD-01 |
Sorgente vuota o nulla ⇒ nessun contenuto, senza errori |
R |
Sorgente_vuota |
[ ] |
| MD-02 |
Titoli h1–h6 con dimensioni decrescenti e peso semi-grassetto |
R |
Titoli |
[ ] |
| MD-03 |
Paragrafi; a capo morbido = spazio, a capo forzato = riga nuova |
R |
A_capo |
[ ] |
| MD-04 |
Grassetto, corsivo, barrato, anche annidati |
R |
Enfasi |
[ ] |
| MD-05 |
Codice inline in carattere monospazio con sfondo |
R |
Codice_inline |
[ ] |
| MD-06 |
Blocco di codice: monospazio, scorrimento orizzontale, pulsante Copia che copia solo il codice |
R/G |
Blocco_codice_e_copia |
[ ] |
| MD-07 |
Liste puntate e numerate (rispetta il numero iniziale), annidate |
R |
Liste |
[ ] |
| MD-08 |
Citazioni con barra laterale, anche annidate |
R |
Citazioni |
[ ] |
| MD-09 |
Tabelle: intestazione in evidenza, allineamenti sinistra/centro/destra, righe irregolari |
R |
Tabelle |
[ ] |
| MD-10 |
Linea orizzontale |
R |
Linea_orizzontale |
[ ] |
| MD-11 |
Link: colore d'accento e sottolineatura; il clic apre solo http(s) |
R |
Link_http_https |
[ ] |
| MD-12 |
Link: nessuna apertura se c'è una selezione in corso; cursore a mano sopra il link |
R |
Link_con_selezione |
[ ] |
| MD-13 |
Posizioni dei link corrette con enfasi, codice inline e a capo prima del link |
R |
Intervalli_link |
[ ] |
| MD-14 |
Immagini: si mostra il testo alternativo, non si scarica nulla |
R |
Immagini_come_testo |
[ ] |
| MD-15 |
HTML grezzo disabilitato: i tag compaiono come testo, non come elementi |
R |
Html_disabilitato |
[ ] |
| MD-16 |
Cambio di tema (chiaro/scuro): i colori si aggiornano ricostruendo la vista |
R |
Cambio_tema |
[ ] |
| MD-17 |
Markdown malformato o blocchi sconosciuti non fanno crashare |
R |
Markdown_malformato |
[ ] |
| MD-18 |
Testo selezionabile |
R |
Testo_selezionabile |
[ ] |
Finestra e interazione — Gui/MainWindow*, Gui/App.axaml, Gui/ConfirmDialog.cs
| ID |
Requisito |
Liv. |
Test |
|
| UI-01 |
La finestra si apre e mostra la schermata iniziale (logo, titolo, sottotitolo, nessun suggerimento) |
G |
Apertura_schermata_iniziale |
[ ] |
| UI-02 |
Proporzioni 16:10, 75% dell'area utile (max 1440), minimo 900x600; senza schermo primario 1280x800 |
G |
Dimensioni_finestra |
[ ] |
| UI-03 |
Invio invia, Maiusc+Invio va a capo, nessun invio a campo vuoto |
G |
Tasti_invio |
[ ] |
| UI-04 |
Il pulsante Invia è disabilitato con campo vuoto o in attesa |
G |
Pulsante_invia |
[ ] |
| UI-05 |
I messaggi hanno aspetto per ruolo: utente, Claude (con Copia), errore |
G |
Template_per_ruolo |
[ ] |
| UI-06 |
La conversazione scorre in fondo a ogni nuovo messaggio |
G |
Scorrimento_in_fondo |
[ ] |
| UI-07 |
Indicatore di attesa visibile solo durante l'elaborazione |
G |
Indicatore_attesa |
[ ] |
| UI-08 |
Elenco chat: voce attiva evidenziata, clic apre, ✕ elimina; "Nessuna chat salvata" se vuoto |
G |
Elenco_chat |
[ ] |
| UI-09 |
Nel riquadro Impostazioni il campo "Altro ID…" compare solo con quella voce del modello |
G |
Campo_modello_personalizzato |
[ ] |
| UI-10 |
Il pulsante a tre linee mostra/nasconde la barra laterale |
G |
Mostra_nascondi_barra |
[ ] |
| UI-11 |
Dialogo di conferma: restituisce vero con Elimina, falso con Annulla o chiusura |
G |
Dialogo_conferma |
[ ] |
| UI-12 |
Accessibilità: ogni pulsante a sola icona e il campo messaggio hanno AutomationProperties.Name |
G |
Nomi_accessibili |
[ ] |
| UI-13 |
Tema: sia il chiaro sia lo scuro risolvono tutti i token usati (nessuna risorsa mancante) |
G |
Token_tema_completi |
[ ] |
| UI-14 |
Focus sul campo messaggio all'apertura, dopo la risposta e dopo "Nuova chat" |
G |
Focus_campo |
[ ] |
| UI-16 |
Il pulsante Impostazioni (in alto a destra) apre il riquadro con Modello ed Effort; Esc lo chiude |
G |
Riquadro_impostazioni |
[ ] |
| UI-17 |
Il composer non contiene selettori di modello ed effort |
G |
Composer_senza_selettori |
[ ] |
| UI-18 |
Le scelte fatte nel riquadro arrivano ai flag --model/--effort del messaggio successivo |
G/U |
Impostazioni_applicate_all_invio |
[ ] |
| UI-15 |
Il nome dell'app compare nel titolo della finestra e nella barra laterale |
G |
Nome_app_visibile |
[ ] |
Eseguibile pubblicato
| ID |
Requisito |
Liv. |
Test |
|
| PUB-01 |
Il publish single-file linux-x64 si compila e la CLI risponde con FakeClaude |
E |
Publish_linux_smoke |
[ ] |
| PUB-02 |
Il publish single-file win-x64 si compila e la CLI risponde con FakeClaude |
E |
Publish_windows_smoke |
[ ] |
| PUB-03 |
Le librerie native (Skia/HarfBuzz) vengono estratte e la GUI si avvia (provata con timeout) |
E |
Gui_si_avvia |
[ ] |
Più motori (ADR-0002) — da compilare quando si implementa la Fase 4
Per ogni motore, la stessa batteria di contratto:
| ID |
Requisito |
Liv. |
|
| EN-01 |
Argomenti del primo messaggio e della ripresa (Claude: CL-02; Codex: CX-01, CX-02) |
U |
[ ] |
| EN-02 |
Rilevamento nel PATH; assente ⇒ disabilitato e messaggio chiaro |
U/P |
[ ] |
| EN-03 |
Lettura dell'id di sessione dall'output (Codex: CX-05) |
U |
[ ] |
| EN-04 |
Parser dell'output → markdown, con output di esempio reali salvati nei test |
U |
[ ] |
| EN-05 |
Modelli ed effort mostrati secondo il motore |
U/G |
[ ] |
| EN-06 |
Il JSON della chat memorizza il motore; cambiare motore apre una nuova chat |
U |
[ ] |
| EN-07 |
Politica dei permessi: nessun flag che salta i controlli senza scelta esplicita |
U |
[ ] |
| EN-08 |
Impostazioni persistite e rilette |
U |
[ ] |
| EN-09 |
Gestione del blocco di agy -p con output rediretto (timeout, errore chiaro) |
P |
[ ] |
| EN-10 |
Engines.Find per id (claude, codex, sconosciuto ⇒ nullo); l'id salvato nei JSON resta stabile |
U |
[ ] |
| EN-11 |
Cambiare CLI nelle Impostazioni apre una nuova chat con quel motore; modello ed effort tornano a "Predefinito"; liste secondo il motore |
U |
[ ] |
| EN-12 |
Motore non installato: etichetta "non installato", non selezionabile; il VM rifiuta la selezione |
U/G |
[ ] |
| EN-13 |
L'interruttore CLI è disabilitato durante l'attesa |
U/G |
[ ] |
| EN-14 |
Aprendo una chat salvata il selettore passa al motore della chat; sessione e started coerenti |
U |
[ ] |
| EN-15 |
Etichetta autore, segnaposto, messaggio d'attesa ed elenco chat mostrano il nome del motore |
U/G |
[ ] |
| EN-16 |
Dopo il primo invio con Codex il SessionId è quello letto dall'output e il secondo invio usa la ripresa; con errore al primo invio resta senza sessione |
U |
[ ] |
| EN-17 |
CLI --engine <id> "prompt"; id sconosciuto o prompt mancante ⇒ uso su stderr ed exit 2; senza --engine ⇒ Claude Code |
E |
[ ] |
| EN-18 |
Rilevamento nel PATH: nome esatto su Linux, .exe su Windows; cartella inesistente nel PATH ignorata |
U |
[ ] |
Come si eseguono (a implementazione avvenuta)
Comandi previsti, da confermare con ADR-0003 → "Da verificare":
- Tutti i test:
dotnet test
- Con copertura e soglie:
dotnet test --collect:"XPlat Code Coverage" e controllo delle soglie della tabella sopra
- Solo una categoria:
dotnet test --filter "Category=Unit" (o Gui, Process, E2E)
Su Linux i test GUI girano senza display (Headless).
Definition of Done di una modifica