
# Teststrategi

merql använder en oracle-baserad testmodell. Varje testscenario definierar en känd uppsättning indata (tre databastillstånd), ett deterministiskt förväntat resultat (oraclet) och jämför det faktiska resultatet av sammanslagningen med förväntningen. 32 scenarier täcker konfliktfria sammanslagningar, konflikter, identitetslösning, datatyper, skala och specialfall.

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

Kommandona på den här sidan körs från en utcheckning av merql-arkivet efter
`composer install`. Utvecklingsskripten i `bin/`, korpusen `scenarios/`
och testerna ingår inte i paketet Composer. Utvecklingsberoendena omfattar
PHPUnit 13, som kräver PHP 8.4 eller senare, även om själva biblioteket
körs med PHP 8.2.

## Oracle-baserad testning

Det finns inget befintligt referensverktyg som utför en trevägssammanslagning av databaser, så oraclet beräknas utifrån kända indata i stället för att genereras av ett externt verktyg. Varje scenario har ett deterministiskt förväntat resultat som kan verifieras logiskt:

1. **Förberedelse** -- skapa tre databastillstånd (base, ours, theirs) med känt innehåll.
2. **Oracle** -- beräkna det förväntade resultatet av sammanslagningen utifrån de kända indata.
3. **Faktiskt resultat** -- kör merql:s sammanslagning av de tre tillstånden.
4. **Jämförelse** -- det faktiska resultatet måste exakt matcha det förväntade resultatet.

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

## Köra tester

### Fullständig verifiering

Skriptet `verify-all` är den slutliga kontrollen. Det kör statisk analys, kodningsstandarder, tester med PHPUnit och oracle-regressionstestsviten:

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

Det här måste godkännas innan något arbete anses vara slutfört.

### Oracle-regressionstestsvit

Kör alla 32 scenarier:

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

Kör en enskild kategori:

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

Kategorierna är katalognamnen under `scenarios/`: `clean`, `conflict`,
`identity`, `types`, `scale` och `edge`. En okänd kategori skriver ut
`No scenarios found` och avslutas utan fel. `--category` kan upprepas för att köra
flera kategorier.

Skriptet godtar även `--jobs`, men ignorerar värdet och kör scenarierna
efter varandra ([#5](https://github.com/inline0/merql/issues/5)).

Kör med minimal utdata (endast godkänt/underkänt):

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

### Enskilt scenario

Testa ett scenario genom hela processen:

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

### Enskilda oracle-steg

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

### Enhetstester

```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ör endast PHPUnit. Testsviten `oracle` PHPUnit pekar på
`tests/Oracle`, som innehåller scenariokörarens klasser men inga testfall, så
de 32 scenarierna körs endast via `composer test:oracle`, `composer verify` eller
`./bin/verify-all`.

MySQL-integrationstesterna hoppas över om inte `MERQL_MYSQL_HOST`,
`MERQL_MYSQL_DATABASE` och `MERQL_MYSQL_USER` (samt eventuellt
`MERQL_MYSQL_PASSWORD` och `MERQL_MYSQL_PORT`) pekar på en testdatabas.

### Kodkvalitet

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

## Scenariokategorier

### clean (8 scenarier)

Konfliktfria sammanslagningar. Båda sidorna kan ändra data, men aldrig samma kolumn i samma rad.

| Scenario | Testar |
|---|---|
| `insert-only-theirs` | Rader som theirs infogade; ours oförändrat. |
| `update-only-theirs` | Rader som theirs uppdaterade; ours oförändrat. |
| `delete-only-theirs` | Rader som theirs tog bort; ours oförändrat. |
| `insert-only-ours` | Rader som ours infogade; theirs oförändrat. |
| `mixed-no-overlap` | Båda ändrade olika tabeller eller rader. |
| `both-same-change` | Båda gjorde identiska ändringar (ingen konflikt). |
| `column-level-clean` | Båda ändrade samma rad, men olika kolumner. |
| `multi-table` | Ändringar i flera tabeller. |

### conflict (6 scenarier)

Scenarier som måste leda till konflikter.

| Scenario | Testar |
|---|---|
| `both-update-same-column` | Båda ändrade samma kolumn till olika värden. |
| `update-vs-delete` | Den ena uppdaterade och den andra tog bort samma rad. |
| `delete-vs-update` | Motsatt riktning för uppdatering kontra borttagning. |
| `both-insert-same-pk` | Båda infogade en rad med samma primärnyckel. |
| `multiple-conflicts` | Flera konflikter i en sammanslagning. |
| `partial-conflict` | Vissa kolumner har konflikter, andra slås samman utan problem. |

### identity (4 scenarier)

Specialfall för radidentitet.

| Scenario | Testar |
|---|---|
| `auto-increment` | Nya rader har olika ID:n i olika grenar. |
| `natural-key` | Matchning via unika kolumner i stället för primärnyckel. |
| `composite-key` | Primärnyckel med flera kolumner. |
| `no-key` | Tabell utan primärnyckel (reservlösning med innehållshash). |

### types (6 scenarier)

Hantering av datatyper för alla kolumntyper.

| Scenario | Testar |
|---|---|
| `text-columns` | Värden av typen VARCHAR, TEXT och LONGTEXT. |
| `numeric-columns` | Värden av typen INT, DECIMAL och FLOAT. |
| `date-columns` | Värden av typen DATE, DATETIME och TIMESTAMP. |
| `json-columns` | Sammanslagning av kolumner av typen JSON. |
| `blob-columns` | Binärdata. |
| `null-handling` | Övergångar från NULL till värde och från värde till NULL. |

### scale (4 scenarier)

Prestanda under belastning.

| Scenario | Testar |
|---|---|
| `1k-rows` | 1,000 rader. |
| `10k-rows` | 10,000 rader. |
| `100k-rows` | 100,000 rader. |
| `wide-table` | Tabell med 50+ kolumner. |

### edge (4 scenarier)

Specialfall och gränsvillkor.

| Scenario | Testar |
|---|---|
| `empty-changeset` | Inga ändringar på ena eller båda sidorna. |
| `schema-mismatch` | Kolumn tillagd eller borttagen mellan ögonblicksbilder. |
| `encoding` | UTF-8, emoji och specialtecken. |
| `large-text` | Mycket stora värden av typen TEXT/LONGTEXT. |

## Scenariostruktur

Varje scenario ligger i en katalog under `scenarios/<category>/<name>/` med en konfiguration av typen `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"
    }
}
```

För konfliktscenarier anges de förväntade konflikterna i detalj:

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

## ScenarioRunner-processen

`ScenarioRunner::run()` samordnar ett enskilt scenario:

1. `OracleCapture::compute()` bygger tre ögonblicksbilder från scenariots JSON-data och kör sammanslagningen.
2. `ScenarioComparator::compare()` kontrollerar sammanslagningens resultat mot de förväntade värdena i `scenario.json`.
3. Returnerar godkänt/underkänt med en lista över felmeddelanden.

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

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

`ScenarioRunner::runAll()` kör alla scenarier i följd:

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

## Lägga till ett nytt scenario

1. Skapa en katalog: `scenarios/<category>/<name>/`.
2. Skapa `scenario.json` med scenariots metadata och förväntningar.
3. Definiera de tre databastillstånden i konfigurationen JSON: basedata, ändringar i ours, ändringar i theirs.
4. Kör scenariot: `./bin/test-scenario <name>`.
5. Verifiera hela testsviten: `./bin/verify-all`.

## Kodkvalitetsstandarder

merql tillämpar:

- **PHPStan nivå 10** -- strikt statisk analys utan undantag från baslinjen.
- **PSR-12** -- kodningsstandarder som kontrolleras av `phpcs`.
- **Oracle-regression** -- alla 32 scenarier måste godkännas.
- **Enhetstester** -- isolerade tester för varje komponent.

Skriptet `verify-all` kör alla fyra kontroller i följd. Delvis godkännande accepteras inte.

## Kontinuerlig integration

CI kör kontroller av kodningsstandarder, PHPStan, `composer test`, integrationssviten
mot MySQL 8.4 via mysqli, täckning av sammanslagningens grundfunktioner och
`composer audit` vid varje push och pull-begäran till `main`. Den kör inte
oracle-regressionskorpusen ([#6](https://github.com/inline0/merql/issues/6)), så
kör `composer test:oracle` eller `./bin/verify-all` lokalt innan du förlitar dig på en
ändring.
