
# Strategia di test

merql usa un modello di test guidato da un oracolo. Ogni scenario di test definisce un insieme noto di input (tre stati del database), un risultato atteso deterministico (l'oracolo) e confronta l'output effettivo della fusione con quello atteso. 32 scenari coprono fusioni senza conflitti, conflitti, risoluzione dell'identità, tipi di dati, scalabilità e casi limite.

```bash
# Run the full verification suite.
./bin/verify-all
```

I comandi di questa pagina si eseguono dopo `composer install` nella copia locale del repository merql.
Gli script di sviluppo in `bin/`, il corpus `scenarios/` e i test non fanno parte del pacchetto Composer. Le dipendenze di sviluppo includono PHPUnit 13, che richiede PHP 8.4 o successivo, anche se la libreria stessa funziona con PHP 8.2.

## Test guidati da un oracolo

Non esiste uno strumento di riferimento che esegua una fusione di database a tre vie, quindi l'oracolo viene calcolato a partire da input noti anziché essere generato da uno strumento esterno. Ogni scenario ha un risultato atteso deterministico che può essere verificato tramite logica:

1. **Configurazione** -- crea tre stati del database (base, ours, theirs) con contenuti noti.
2. **Oracolo** -- calcola il risultato atteso della fusione a partire dagli input noti.
3. **Effettivo** -- esegue la fusione di merql sui tre stati.
4. **Confronto** -- il risultato effettivo deve corrispondere esattamente a quello atteso.

```
Known inputs (base, ours, theirs)
         |                    |
    Deterministic          merql merge
    computation
         |                    |
    Expected result       Actual result
         |                    |
         -----> Compare <-----
                  |
            Pass or Fail
```

## Esecuzione dei test

### Verifica completa

Lo script `verify-all` è il controllo finale. Esegue l'analisi statica, verifica gli standard di codifica, i test PHPUnit e la suite di regressione dell'oracolo:

```bash
./bin/verify-all
```

Tutto questo deve essere superato prima di considerare completo qualsiasi lavoro.

### Suite di regressione dell'oracolo

Eseguire tutti i 32 scenari:

```bash
./bin/test-regression
```

Eseguire una singola categoria:

```bash
./bin/test-regression --category clean
./bin/test-regression --category conflict
./bin/test-regression --category identity
```

Le categorie corrispondono ai nomi delle directory in `scenarios/`: `clean`, `conflict`,
`identity`, `types`, `scale` e `edge`. Una categoria sconosciuta stampa
`No scenarios found` e termina con successo. È possibile ripetere `--category` per eseguire
più categorie.

Lo script accetta anche `--jobs`, ma ignora il valore ed esegue gli scenari
uno dopo l'altro ([#5](https://github.com/inline0/merql/issues/5)).

Eseguire con output minimo (solo esito positivo/negativo):

```bash
./bin/test-regression --fast
```

### Singolo scenario

Testare uno scenario attraverso l'intera pipeline:

```bash
./bin/test-scenario column-level-clean
```

### Singoli passaggi dell'oracolo

```bash
./bin/oracle column-level-clean    # Compute expected result
./bin/actual column-level-clean    # Run merql, capture output
./bin/compare column-level-clean   # Diff oracle vs actual
```

### Test unitari

```bash
composer test:unit         # Isolated component tests
composer test:integration  # Integration tests (SQLite, plus MySQL when configured)
composer test              # All PHPUnit suites
composer test:oracle       # Oracle regression corpus (bin/test-regression)
composer verify            # analyse + cs + test + test:oracle
```

`composer test` esegue solo PHPUnit. La suite `oracle` PHPUnit punta a
`tests/Oracle`, che contiene le classi del runner degli scenari ma nessun caso di test, quindi
i 32 scenari vengono eseguiti solo tramite `composer test:oracle`, `composer verify` o
`./bin/verify-all`.

I test di integrazione MySQL vengono esclusi a meno che `MERQL_MYSQL_HOST`,
`MERQL_MYSQL_DATABASE` e `MERQL_MYSQL_USER` (ed eventualmente anche
`MERQL_MYSQL_PASSWORD` e `MERQL_MYSQL_PORT`) non puntino a un database di test.

### Qualità del codice

```bash
composer cs            # PSR-12 coding standards check
composer cs:fix        # Auto-fix coding standards
composer analyse       # PHPStan level 10 static analysis
```

## Categorie di scenari

### clean (8 scenari)

Fusioni senza conflitti. Entrambi i lati possono modificare i dati, ma mai la stessa colonna della stessa riga.

| Scenario | Test |
|---|---|
| `insert-only-theirs` | theirs ha inserito righe, ours non ha apportato modifiche. |
| `update-only-theirs` | theirs ha aggiornato righe, ours non ha apportato modifiche. |
| `delete-only-theirs` | theirs ha eliminato righe, ours non ha apportato modifiche. |
| `insert-only-ours` | ours ha inserito righe, theirs non ha apportato modifiche. |
| `mixed-no-overlap` | Entrambi hanno modificato tabelle o righe diverse. |
| `both-same-change` | Entrambi hanno apportato modifiche identiche (nessun conflitto). |
| `column-level-clean` | Entrambi hanno modificato la stessa riga, ma colonne diverse. |
| `multi-table` | Modifiche in più tabelle. |

### conflict (6 scenari)

Scenari che devono generare conflitti.

| Scenario | Test |
|---|---|
| `both-update-same-column` | Entrambi hanno modificato la stessa colonna assegnandole valori diversi. |
| `update-vs-delete` | Un lato ha aggiornato la riga e l'altro l'ha eliminata. |
| `delete-vs-update` | Direzione inversa del conflitto aggiornamento/eliminazione. |
| `both-insert-same-pk` | Entrambi hanno inserito una riga con la stessa chiave primaria. |
| `multiple-conflicts` | Più conflitti in una fusione. |
| `partial-conflict` | Alcune colonne sono in conflitto, altre si fondono senza conflitti. |

### identity (4 scenari)

Casi limite relativi all'identità delle righe.

| Scenario | Test |
|---|---|
| `auto-increment` | Le nuove righe hanno ID diversi tra i rami. |
| `natural-key` | Abbinamento tramite colonne univoche anziché PK. |
| `composite-key` | Chiave primaria composta da più colonne. |
| `no-key` | Tabella senza chiave primaria (alternativa basata sull'hash dei contenuti). |

### types (6 scenari)

Gestione dei tipi di dati per tutti i tipi di colonna.

| Scenario | Test |
|---|---|
| `text-columns` | Valori VARCHAR, TEXT, LONGTEXT. |
| `numeric-columns` | Valori INT, DECIMAL, FLOAT. |
| `date-columns` | Valori DATE, DATETIME, TIMESTAMP. |
| `json-columns` | Fusione della colonna JSON. |
| `blob-columns` | Dati binari. |
| `null-handling` | Transizioni da NULL a un valore e da un valore a NULL. |

### scale (4 scenari)

Prestazioni sotto carico.

| Scenario | Test |
|---|---|
| `1k-rows` | 1,000 righe. |
| `10k-rows` | 10,000 righe. |
| `100k-rows` | 100,000 righe. |
| `wide-table` | Tabella con oltre 50 colonne. |

### edge (4 scenari)

Casi limite e condizioni al confine.

| Scenario | Test |
|---|---|
| `empty-changeset` | Nessuna modifica su uno o entrambi i lati. |
| `schema-mismatch` | Colonna aggiunta o rimossa tra gli snapshot. |
| `encoding` | UTF-8, emoji, caratteri speciali. |
| `large-text` | Valori TEXT/LONGTEXT molto grandi. |

## Struttura degli scenari

Ogni scenario è una directory in `scenarios/<category>/<name>/` con una configurazione `scenario.json`:

```json
{
    "name": "column-level-clean",
    "category": "clean",
    "description": "Both sides modify same row but different columns",
    "tables": ["test_posts"],
    "expectations": {
        "conflicts": 0,
        "operations": 3,
        "changeset_match": "exact",
        "merge_match": "exact",
        "sql_match": "semantic"
    }
}
```

Per gli scenari con conflitti, i conflitti attesi sono specificati nel dettaglio:

```json
{
    "expectations": {
        "conflicts": 1,
        "conflict_details": [
            {
                "table": "test_posts",
                "primary_key": {"id": 42},
                "column": "title",
                "ours_value": "Welcome",
                "theirs_value": "Greetings"
            }
        ]
    }
}
```

## La pipeline ScenarioRunner

`ScenarioRunner::run()` orchestra un singolo scenario:

1. `OracleCapture::compute()` crea tre snapshot dai dati JSON dello scenario ed esegue la fusione.
2. `ScenarioComparator::compare()` verifica il risultato della fusione rispetto ai valori attesi in `scenario.json`.
3. Restituisce l'esito positivo/negativo con un elenco di messaggi di errore.

```php
use Merql\Tests\Oracle\ScenarioRunner;

$result = ScenarioRunner::run($scenario);
// ['name' => 'column-level-clean', 'pass' => true, 'failures' => []]
```

`ScenarioRunner::runAll()` esegue tutti gli scenari in sequenza:

```php
$results = ScenarioRunner::runAll($scenarios);
```

## Aggiungere un nuovo scenario

1. Create una directory: `scenarios/<category>/<name>/`.
2. Create `scenario.json` con i metadati e le aspettative dello scenario.
3. Definite i tre stati del database nella configurazione JSON: dati base, modifiche di ours, modifiche di theirs.
4. Eseguite lo scenario: `./bin/test-scenario <name>`.
5. Verificate la suite completa: `./bin/verify-all`.

## Standard di qualità del codice

merql applica:

- **PHPStan livello 10** -- analisi statica rigorosa senza eccezioni nella baseline.
- **PSR-12** -- standard di codifica verificati da `phpcs`.
- **Regressione dell'oracolo** -- tutti i 32 scenari devono essere superati.
- **Test unitari** -- test isolati per ogni componente.

Lo script `verify-all` esegue tutti e quattro i controlli in sequenza. Non sono ammesse approvazioni parziali.

## Integrazione continua

L'integrazione continua esegue gli standard di codifica, PHPStan, `composer test`, la suite di integrazione
con MySQL 8.4 tramite mysqli, la copertura delle primitive di fusione e
`composer audit` a ogni push e richiesta di pull verso `main`. Non esegue il
corpus di regressione dell'oracolo ([#6](https://github.com/inline0/merql/issues/6)), quindi
eseguite `composer test:oracle` o `./bin/verify-all` in locale prima di affidarvi a una
modifica.
