
# Teststrategie

merql verwendet ein testmodell auf Grundlage von Orakeln. Jedes Testszenario legt bekannte Eingaben (drei Datenbankzustände) und ein deterministisches erwartetes Ergebnis (das Orakel) fest und vergleicht die tatsächliche Merge-Ausgabe damit. 32 Szenarien decken saubere Merges, Konflikte, Identitätsauflösung, Datentypen, Skalierung und Sonderfälle ab.

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

Die Befehle auf dieser Seite werden in einem Checkout des merql-Repositorys nach `composer install` ausgeführt. Die Entwicklungsskripte in `bin/`, das Korpus `scenarios/` und die Tests gehören nicht zum Composer-Paket. Zu den Entwicklungsabhängigkeiten gehört PHPUnit 13, das PHP 8.4 oder höher benötigt, obwohl die Bibliothek selbst mit PHP 8.2 läuft.

## Testen auf Grundlage von Orakeln

Es gibt kein vorhandenes Referenzwerkzeug für Dreiwege-Merges von Datenbanken. Daher wird das Orakel aus bekannten Eingaben berechnet und nicht von einem externen Werkzeug erzeugt. Jedes Szenario hat ein deterministisches erwartetes Ergebnis, das sich anhand der Logik überprüfen lässt:

1. **Einrichtung** -- drei Datenbankzustände (Basis, uns, sie) mit bekannten Inhalten erstellen.
2. **Orakel** -- das erwartete Merge-Ergebnis aus den bekannten Eingaben berechnen.
3. **Tatsächliches Ergebnis** -- den Merge der drei Zustände mit merql ausführen.
4. **Vergleich** -- das tatsächliche Ergebnis muss exakt mit dem erwarteten übereinstimmen.

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

## Tests ausführen

### Vollständige Überprüfung

Das Skript `verify-all` ist die abschließende Prüfstufe. Es führt statische Analyse, Prüfung der Codierungsstandards, PHPUnit-Tests und die Oracle-Regressionstestsuite aus:

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

Dies muss erfolgreich sein, bevor eine Arbeit als abgeschlossen gilt.

### Oracle-Regressionstestsuite

Alle 32 Szenarien ausführen:

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

Eine einzelne Kategorie ausführen:

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

Die Kategorien sind die Verzeichnisnamen unter `scenarios/`: `clean`, `conflict`, `identity`, `types`, `scale` und `edge`. Bei einer unbekannten Kategorie wird `No scenarios found` ausgegeben und das Skript erfolgreich beendet. `--category` kann wiederholt werden, um mehrere Kategorien auszuführen.

Das Skript akzeptiert auch `--jobs`, ignoriert den Wert jedoch und führt die Szenarien nacheinander aus ([#5](https://github.com/inline0/merql/issues/5)).

Mit minimaler Ausgabe ausführen (nur Erfolg/Fehlschlag):

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

### Einzelnes Szenario

Ein Szenario durch die gesamte Pipeline testen:

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

### Einzelne Orakelschritte

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

### Unit-Tests

```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` führt nur PHPUnit aus. Die PHPUnit-Suite unter `oracle` verweist auf `tests/Oracle`, wo sich die Szenario-Runner-Klassen, aber keine Testfälle befinden. Daher werden die 32 Szenarien nur über `composer test:oracle`, `composer verify` oder `./bin/verify-all` ausgeführt.

Die MySQL-Integrationstests werden übersprungen, sofern `MERQL_MYSQL_HOST`, `MERQL_MYSQL_DATABASE` und `MERQL_MYSQL_USER` (sowie optional `MERQL_MYSQL_PASSWORD` und `MERQL_MYSQL_PORT`) nicht auf eine Testdatenbank verweisen.

### Codequalität

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

## Szenariokategorien

### sauber (8 Szenarien)

Saubere Merges ohne Konflikte. Beide Seiten können Daten ändern, aber niemals dieselbe Spalte derselben Zeile.

| Szenario | Tests |
|---|---|
| `insert-only-theirs` | Sie haben Zeilen eingefügt, wir haben nichts geändert. |
| `update-only-theirs` | Sie haben Zeilen aktualisiert, wir haben nichts geändert. |
| `delete-only-theirs` | Sie haben Zeilen gelöscht, wir haben nichts geändert. |
| `insert-only-ours` | Wir haben Zeilen eingefügt, sie haben nichts geändert. |
| `mixed-no-overlap` | Beide haben unterschiedliche Tabellen oder Zeilen geändert. |
| `both-same-change` | Beide haben identische Änderungen vorgenommen (kein Konflikt). |
| `column-level-clean` | Beide haben dieselbe Zeile, aber unterschiedliche Spalten geändert. |
| `multi-table` | Änderungen an mehreren Tabellen. |

### Konflikt (6 Szenarien)

Szenarien, bei denen Konflikte auftreten müssen.

| Szenario | Tests |
|---|---|
| `both-update-same-column` | Beide haben dieselbe Spalte auf unterschiedliche Werte geändert. |
| `update-vs-delete` | Eine Seite hat dieselbe Zeile aktualisiert, die andere gelöscht. |
| `delete-vs-update` | Umgekehrte Richtung bei Aktualisieren gegenüber Löschen. |
| `both-insert-same-pk` | Beide haben eine Zeile mit demselben Primärschlüssel eingefügt. |
| `multiple-conflicts` | Mehrere Konflikte in einem Merge. |
| `partial-conflict` | Einige Spalten verursachen Konflikte, andere lassen sich problemlos zusammenführen. |

### Identität (4 Szenarien)

Sonderfälle bei der Zeilenidentität.

| Szenario | Tests |
|---|---|
| `auto-increment` | Neue Zeilen haben in den Branches unterschiedliche IDs. |
| `natural-key` | Abgleich anhand eindeutiger Spalten statt des Primärschlüssels. |
| `composite-key` | Zusammengesetzter Primärschlüssel. |
| `no-key` | Tabelle ohne Primärschlüssel (Fallback auf Inhalts-Hash). |

### Typen (6 Szenarien)

Verarbeitung aller Spaltendatentypen.

| Szenario | Tests |
|---|---|
| `text-columns` | VARCHAR-, TEXT- und LONGTEXT-Werte. |
| `numeric-columns` | INT-, DECIMAL- und FLOAT-Werte. |
| `date-columns` | DATE-, DATETIME- und TIMESTAMP-Werte. |
| `json-columns` | Zusammenführen von JSON-Spalten. |
| `blob-columns` | Binärdaten. |
| `null-handling` | Übergänge von NULL zu einem Wert und von einem Wert zu NULL. |

### Skalierung (4 Szenarien)

Leistung unter Last.

| Szenario | Tests |
|---|---|
| `1k-rows` | 1,000 Zeilen. |
| `10k-rows` | 10,000 Zeilen. |
| `100k-rows` | 100,000 Zeilen. |
| `wide-table` | Tabelle mit 50+ Spalten. |

### Randfälle (4 Szenarien)

Randfälle und Grenzbedingungen.

| Szenario | Tests |
|---|---|
| `empty-changeset` | Keine Änderungen auf einer oder beiden Seiten. |
| `schema-mismatch` | Spalte zwischen Snapshots hinzugefügt oder entfernt. |
| `encoding` | UTF-8, Emoji und Sonderzeichen. |
| `large-text` | Sehr große TEXT-/LONGTEXT-Werte. |

## Szenarioaufbau

Jedes Szenario liegt als Verzeichnis unter `scenarios/<category>/<name>/` und enthält eine Konfiguration `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"
    }
}
```

Bei Konfliktszenarien werden die erwarteten Konflikte genau angegeben:

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

## Die ScenarioRunner-Pipeline

`ScenarioRunner::run()` steuert ein einzelnes Szenario:

1. `OracleCapture::compute()` erstellt Snapshots der drei Zustände anhand der Daten in JSON und führt den Merge aus.
2. `ScenarioComparator::compare()` vergleicht das Merge-Ergebnis mit den erwarteten Werten in `scenario.json`.
3. Gibt Erfolg oder Fehlschlag mit einer Liste von Fehlermeldungen zurück.

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

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

`ScenarioRunner::runAll()` führt alle Szenarien der Reihe nach aus:

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

## Neues Szenario hinzufügen

1. Erstelle ein Verzeichnis: `scenarios/<category>/<name>/`.
2. Erstelle `scenario.json` mit den Szenariometadaten und Erwartungen.
3. Definiere die drei Datenbankzustände in der Konfiguration JSON: Basisdaten, unsere Änderungen, ihre Änderungen.
4. Führe das Szenario aus: `./bin/test-scenario <name>`.
5. Überprüfe die gesamte Testsuite: `./bin/verify-all`.

## Codequalitätsstandards

merql setzt Folgendes voraus:

- **PHPStan-Stufe 10** -- strenge statische Analyse ohne Ausnahmen in der Baseline.
- **PSR-12** -- Codierungsstandards, geprüft von `phpcs`.
- **Oracle-Regression** -- alle 32 Szenarien müssen erfolgreich sein.
- **Unit-Tests** -- isolierte Tests für jede Komponente.

Das Skript `verify-all` führt alle vier Prüfungen nacheinander aus. Eine teilweise Freigabe wird nicht akzeptiert.

## Kontinuierliche Integration

CI führt bei jedem Push und Pull Request zu `main` Codierungsstandards, PHPStan, `composer test`, die Integrationssuite mit MySQL 8.4 über mysqli, Tests der Merge-Grundfunktionen und `composer audit` aus. Das Oracle-Regressionstestkorpus wird nicht ausgeführt ([#6](https://github.com/inline0/merql/issues/6)). Führe daher lokal `composer test:oracle` oder `./bin/verify-all` aus, bevor du dich auf eine Änderung verlässt.
