
# Teststrategie

merql gebruikt een oraclegestuurd testmodel. Elk testscenario definieert een bekende set invoer (drie databasetoestanden), een deterministisch verwacht resultaat (de oracle) en vergelijkt de werkelijke uitvoer van de merge met die verwachting. 32 scenario's bestrijken schone merges, conflicten, identiteitsbepaling, gegevenstypen, schaal en uitzonderingsgevallen.

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

De opdrachten op deze pagina worden uitgevoerd vanuit een checkout van de merql-repository na
`composer install`. De ontwikkelscripts in `bin/`, het corpus van `scenarios/`
en de tests maken geen deel uit van het Composer-pakket. De ontwikkelafhankelijkheden omvatten
PHPUnit 13, waarvoor PHP 8.4 of hoger vereist is, hoewel de
bibliotheek zelf draait op PHP 8.2.

## Oraclegestuurd testen

Er bestaat geen referentietool voor het uitvoeren van een drieweg-databasemerge. Daarom wordt de oracle berekend op basis van bekende invoer, niet gegenereerd door een externe tool. Elk scenario heeft een deterministisch verwacht resultaat dat aan de hand van logica kan worden gecontroleerd:

1. **Setup** -- maak drie databasetoestanden (base, ours, theirs) met bekende inhoud.
2. **Oracle** -- bereken het verwachte mergeresultaat op basis van de bekende invoer.
3. **Werkelijke uitvoering** -- voer de merge van merql uit op de drie toestanden.
4. **Vergelijking** -- het werkelijke resultaat moet exact overeenkomen met het verwachte resultaat.

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

## Tests uitvoeren

### Volledige verificatie

Het `verify-all`-script is de laatste controle. Het voert statische analyse, coderingsstandaarden, PHPUnit-tests en de oracle-regressiesuite uit:

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

Dit moet slagen voordat werk als voltooid wordt beschouwd.

### Oracle-regressiesuite

Voer alle 32 scenario's uit:

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

Voer één categorie uit:

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

Categorieën zijn de mapnamen onder `scenarios/`: `clean`, `conflict`,
`identity`, `types`, `scale` en `edge`. Bij een onbekende categorie wordt `No scenarios found` weergegeven en eindigt het script succesvol. `--category` kan worden herhaald om
meerdere categorieën uit te voeren.

Het script accepteert ook `--jobs`, maar negeert de waarde en voert scenario's
na elkaar uit ([#5](https://github.com/inline0/merql/issues/5)).

Uitvoeren met minimale uitvoer (alleen geslaagd/mislukt):

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

### Eén scenario

Test één scenario via de volledige pipeline:

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

### Afzonderlijke oracle-stappen

```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
```

### Unittests

```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` voert alleen PHPUnit uit. De `oracle`-suite van PHPUnit verwijst naar
`tests/Oracle`, met de klassen van de scenariorunner maar zonder testgevallen. Daarom worden de 32 scenario's alleen uitgevoerd via `composer test:oracle`, `composer verify` of
`./bin/verify-all`.

De MySQL-integratietests worden overgeslagen tenzij `MERQL_MYSQL_HOST`,
`MERQL_MYSQL_DATABASE` en `MERQL_MYSQL_USER` (en eventueel ook
`MERQL_MYSQL_PASSWORD` en `MERQL_MYSQL_PORT`) naar een testdatabase verwijzen.

### Codekwaliteit

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

## Scenariocategorieën

### clean (8 scenario's)

Schone merges zonder conflicten. Beide zijden kunnen gegevens wijzigen, maar nooit dezelfde kolom van dezelfde rij.

| Scenario | Tests |
|---|---|
| `insert-only-theirs` | Theirs heeft rijen ingevoegd, ours is ongewijzigd. |
| `update-only-theirs` | Theirs heeft rijen bijgewerkt, ours is ongewijzigd. |
| `delete-only-theirs` | Theirs heeft rijen verwijderd, ours is ongewijzigd. |
| `insert-only-ours` | Ours heeft rijen ingevoegd, theirs is ongewijzigd. |
| `mixed-no-overlap` | Beide hebben verschillende tabellen of rijen gewijzigd. |
| `both-same-change` | Beide hebben identieke wijzigingen aangebracht (geen conflict). |
| `column-level-clean` | Beide hebben dezelfde rij gewijzigd, maar verschillende kolommen. |
| `multi-table` | Wijzigingen in meerdere tabellen. |

### conflict (6 scenario's)

Scenario's die conflicten moeten opleveren.

| Scenario | Tests |
|---|---|
| `both-update-same-column` | Beide hebben dezelfde kolom gewijzigd in verschillende waarden. |
| `update-vs-delete` | De ene heeft dezelfde rij bijgewerkt en de andere verwijderd. |
| `delete-vs-update` | Omgekeerde richting van update versus verwijdering. |
| `both-insert-same-pk` | Beide hebben een rij met dezelfde primaire sleutel ingevoegd. |
| `multiple-conflicts` | Meerdere conflicten in één merge. |
| `partial-conflict` | Sommige kolommen conflicteren, andere worden zonder conflict samengevoegd. |

### identity (4 scenario's)

Randgevallen rond rij-identiteit.

| Scenario | Tests |
|---|---|
| `auto-increment` | Nieuwe rijen hebben verschillende ID's in verschillende branches. |
| `natural-key` | Koppelen op basis van unieke kolommen in plaats van PK. |
| `composite-key` | Primaire sleutel met meerdere kolommen. |
| `no-key` | Tabel zonder primaire sleutel (terugvaloptie op basis van inhoudshash). |

### types (6 scenario's)

Verwerking van gegevenstypen voor alle kolomtypen.

| Scenario | Tests |
|---|---|
| `text-columns` | Waarden van het type VARCHAR, TEXT en LONGTEXT. |
| `numeric-columns` | Waarden van het type INT, DECIMAL en FLOAT. |
| `date-columns` | Waarden van het type DATE, DATETIME en TIMESTAMP. |
| `json-columns` | JSON-kolommerge. |
| `blob-columns` | Binaire gegevens. |
| `null-handling` | Overgangen van NULL naar een waarde en van een waarde naar NULL. |

### scale (4 scenario's)

Prestaties onder belasting.

| Scenario | Tests |
|---|---|
| `1k-rows` | 1,000 rijen. |
| `10k-rows` | 10,000 rijen. |
| `100k-rows` | 100,000 rijen. |
| `wide-table` | Tabel met 50+ kolommen. |

### edge (4 scenario's)

Uitzonderingsgevallen en randvoorwaarden.

| Scenario | Tests |
|---|---|
| `empty-changeset` | Geen wijzigingen aan één of beide zijden. |
| `schema-mismatch` | Kolom toegevoegd of verwijderd tussen snapshots. |
| `encoding` | UTF-8, emoji en speciale tekens. |
| `large-text` | Zeer grote TEXT/LONGTEXT-waarden. |

## Scenariostructuur

Elk scenario is een map onder `scenarios/<category>/<name>/` met een configuratie `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"
    }
}
```

Voor conflictscenario's worden de verwachte conflicten gedetailleerd opgegeven:

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

## De ScenarioRunner-pipeline

`ScenarioRunner::run()` coördineert één scenario:

1. `OracleCapture::compute()` bouwt drie snapshots op basis van de gegevens van JSON en voert de merge uit.
2. `ScenarioComparator::compare()` controleert het mergeresultaat aan de hand van de verwachte waarden in `scenario.json`.
3. Retourneert geslaagd/mislukt met een lijst foutmeldingen.

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

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

`ScenarioRunner::runAll()` voert elk scenario achtereenvolgens uit:

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

## Een nieuw scenario toevoegen

1. Maak een map: `scenarios/<category>/<name>/`.
2. Maak `scenario.json` met de scenariometagegevens en verwachtingen.
3. Definieer de drie databasestatussen in de configuratie van JSON: basisgegevens, wijzigingen van ours en wijzigingen van theirs.
4. Voer het scenario uit: `./bin/test-scenario <name>`.
5. Controleer de volledige suite: `./bin/verify-all`.

## Codekwaliteitsnormen

merql hanteert:

- **PHPStan niveau 10** -- strikte statische analyse zonder uitzonderingen op basis van een baseline.
- **PSR-12** -- coderingsnormen gecontroleerd door `phpcs`.
- **Oracle-regressie** -- alle 32 scenario's moeten slagen.
- **Unit-tests** -- geïsoleerde tests voor elk onderdeel.

Het script `verify-all` voert alle vier controles achter elkaar uit. Gedeeltelijke goedkeuring wordt niet geaccepteerd.

## Continue integratie

CI voert bij elke push en pull request naar `main` de controles voor coderingsnormen, PHPStan, `composer test`, de integratiesuite met MySQL 8.4 via mysqli, de dekking van merge-primitieven en `composer audit` uit. De oracle-regressiecorpus wordt niet uitgevoerd ([#6](https://github.com/inline0/merql/issues/6)); voer daarom lokaal `composer test:oracle` of `./bin/verify-all` uit voordat u op een wijziging vertrouwt.
