
# Teststrategi

merql bruger en oracle-drevet testmodel. Hvert testscenarie definerer et kendt sæt input (tre databasetilstande), et deterministisk forventet resultat (oracle) og sammenligner det faktiske resultat af sammenfletningen med forventningen. 32 scenarier dækker rene sammenfletninger, konflikter, identitetsafklaring, datatyper, skala og kanttilfælde.

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

Kommandoerne på denne side køres fra en checkout af merql-arkivet efter
`composer install`. Udviklingsscripts i `bin/`, `scenarios/`
testmaterialet og testene er ikke en del af pakken Composer. Udviklingsafhængighederne omfatter PHPUnit 13, som kræver PHP 8.4 eller nyere, selv om selve biblioteket kører på PHP 8.2.

## Oracle-drevet test

Der findes ikke noget eksisterende referenceværktøj, der udfører en trevejs-databasesammenfletning, så oracle-resultatet beregnes ud fra kendte input i stedet for at blive genereret af et eksternt værktøj. Hvert scenarie har et deterministisk forventet resultat, som kan verificeres logisk:

1. **Opsætning** -- opret tre databasetilstande (base, ours, theirs) med kendt indhold.
2. **Oracle** -- beregn det forventede resultat af sammenfletningen ud fra de kendte input.
3. **Faktisk resultat** -- kør merqls sammenfletning på de tre tilstande.
4. **Sammenlign** -- det faktiske resultat skal svare nøjagtigt til det forventede.

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

## Kørsel af test

### Fuld verifikation

Scriptet `verify-all` er den sidste kontrol. Det kører statisk analyse, kodningsstandarder, PHPUnit-test og oracle-regressionspakken:

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

Dette skal bestås, før arbejdet kan betragtes som færdigt.

### Oracle-regressionspakke

Kør alle 32 scenarier:

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

Kør en enkelt kategori:

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

Kategorierne er mappenavnene under `scenarios/`: `clean`, `conflict`,
`identity`, `types`, `scale` og `edge`. En ukendt kategori udskriver
`No scenarios found` og afslutter uden fejl. `--category` kan gentages for at køre
flere kategorier.

Scriptet accepterer også `--jobs`, men ignorerer værdien og kører scenarierne
ét ad gangen ([#5](https://github.com/inline0/merql/issues/5)).

Kør med minimal output (kun bestået/ikke bestået):

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

### Enkelt scenarie

Test ét scenarie gennem hele forløbet:

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

### Individuelle oracle-trin

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

### Enhedstest

```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` kører kun PHPUnit. `oracle`-pakken PHPUnit peger på
`tests/Oracle`, som indeholder scenarierunner-klasserne, men ingen testtilfælde, så
de 32 scenarier køres kun via `composer test:oracle`, `composer verify` eller
`./bin/verify-all`.

MySQL-integrationstestene springes over, medmindre `MERQL_MYSQL_HOST`,
`MERQL_MYSQL_DATABASE` og `MERQL_MYSQL_USER` (samt eventuelt
`MERQL_MYSQL_PASSWORD` og `MERQL_MYSQL_PORT`) peger på en testdatabase.

### Kodekvalitet

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

## Scenariekategorier

### clean (8 scenarier)

Rene sammenfletninger uden konflikter. Begge sider kan ændre data, men aldrig den samme kolonne i den samme række.

| Scenarie | Test |
|---|---|
| `insert-only-theirs` | De indsatte rækker hos dem, vores uændrede. |
| `update-only-theirs` | De opdaterede rækker hos dem, vores uændrede. |
| `delete-only-theirs` | De slettede rækker hos dem, vores uændrede. |
| `insert-only-ours` | Vores indsatte rækker, deres uændrede. |
| `mixed-no-overlap` | Begge ændrede forskellige tabeller eller rækker. |
| `both-same-change` | Begge foretog identiske ændringer (ingen konflikt). |
| `column-level-clean` | Begge ændrede samme række, men forskellige kolonner. |
| `multi-table` | Ændringer på tværs af flere tabeller. |

### conflict (6 scenarier)

Scenarier, der skal skabe konflikter.

| Scenarie | Test |
|---|---|
| `both-update-same-column` | Begge ændrede samme kolonne til forskellige værdier. |
| `update-vs-delete` | Den ene opdaterede, den anden slettede samme række. |
| `delete-vs-update` | Omvendt retning af opdatering kontra sletning. |
| `both-insert-same-pk` | Begge indsatte en række med samme primærnøgle. |
| `multiple-conflicts` | Flere konflikter i én sammenfletning. |
| `partial-conflict` | Nogle kolonner er i konflikt, andre sammenflettes uden konflikt. |

### identity (4 scenarier)

Kanttilfælde for rækkeidentitet.

| Scenarie | Test |
|---|---|
| `auto-increment` | Nye rækker har forskellige ID'er på tværs af grene. |
| `natural-key` | Match via unikke kolonner i stedet for PK. |
| `composite-key` | Primærnøgle med flere kolonner. |
| `no-key` | Tabel uden primærnøgle (fallback til indholdshash). |

### types (6 scenarier)

Håndtering af datatyper for alle kolonnetyper.

| Scenarie | Test |
|---|---|
| `text-columns` | VARCHAR-, TEXT- og LONGTEXT-værdier. |
| `numeric-columns` | INT-, DECIMAL- og FLOAT-værdier. |
| `date-columns` | DATE-, DATETIME- og TIMESTAMP-værdier. |
| `json-columns` | JSON-kolonnesammenfletning. |
| `blob-columns` | Binære data. |
| `null-handling` | Overgange fra NULL til værdi og fra værdi til NULL. |

### scale (4 scenarier)

Ydeevne under belastning.

| Scenarie | Test |
|---|---|
| `1k-rows` | 1,000 rækker. |
| `10k-rows` | 10,000 rækker. |
| `100k-rows` | 100,000 rækker. |
| `wide-table` | Tabel med over 50 kolonner. |

### edge (4 scenarier)

Kanttilfælde og grænsebetingelser.

| Scenarie | Test |
|---|---|
| `empty-changeset` | Ingen ændringer på den ene eller begge sider. |
| `schema-mismatch` | Kolonne tilføjet eller fjernet mellem snapshots. |
| `encoding` | UTF-8, emoji, specialtegn. |
| `large-text` | Meget store TEXT/LONGTEXT-værdier. |

## Scenariestruktur

Hvert scenarie er en mappe under `scenarios/<category>/<name>/` med en `scenario.json`-konfiguration:

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

For konfliktscenarier angives de forventede konflikter i detaljer:

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

## ScenarioRunner-forløbet

`ScenarioRunner::run()` styrer et enkelt scenarie:

1. `OracleCapture::compute()` opbygger tre snapshots ud fra scenariets data i JSON og kører sammenfletningen.
2. `ScenarioComparator::compare()` kontrollerer sammenfletningsresultatet mod de forventede værdier i `scenario.json`.
3. Returnerer bestået/ikke bestået med en liste over fejlmeddelelser.

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

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

`ScenarioRunner::runAll()` kører alle scenarier efter hinanden:

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

## Tilføjelse af et nyt scenarie

1. Opret en mappe: `scenarios/<category>/<name>/`.
2. Opret `scenario.json` med scenariemetadata og forventninger.
3. Definér de tre databasetilstande i JSON-konfigurationen: basedata, vores ændringer, deres ændringer.
4. Kør scenariet: `./bin/test-scenario <name>`.
5. Verificér hele pakken: `./bin/verify-all`.

## Standarder for kodekvalitet

merql håndhæver:

- **PHPStan niveau 10** -- streng statisk analyse uden undtagelser fra baseline.
- **PSR-12** -- kodningsstandarder kontrolleret af `phpcs`.
- **Oracle-regression** -- alle 32 scenarier skal bestås.
- **Enhedstest** -- isolerede test af hver komponent.

Scriptet `verify-all` kører alle fire kontroller efter hinanden. Delvis godkendelse accepteres ikke.

## Kontinuerlig integration

CI kører kodningsstandarder, PHPStan, `composer test`, integrationspakken
mod MySQL 8.4 via mysqli, dækning af sammenfletningsprimitive og
`composer audit` ved hver push og pull request til `main`. Den kører ikke
oracle-regressionspakken ([#6](https://github.com/inline0/merql/issues/6)), så
kør `composer test:oracle` eller `./bin/verify-all` lokalt, før du stoler på en
ændring.
