
# Strategija testiranja

merql uporablja model testiranja, ki temelji na referenčnih rezultatih. Vsak testni scenarij določa znan nabor vhodnih podatkov (tri stanja zbirke podatkov), determinističen pričakovani rezultat (referenčni rezultat) in primerja dejanski rezultat združevanja s pričakovanim. 32 scenarijev pokriva čista združevanja, spore, razreševanje identitete, podatkovne tipe, obseg in robne primere.

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

Ukazi na tej strani se izvajajo v kopiji repozitorija merql po
`composer install`. Razvojni skripti v `bin/`, korpus `scenarios/`
in testi niso del paketa Composer. Razvojne odvisnosti vključujejo PHPUnit 13, za katerega je potreben PHP 8.4 ali novejši, čeprav knjižnica sama deluje z PHP 8.2.

## Testiranje na podlagi referenčnih rezultatov

Ni obstoječega referenčnega orodja, ki bi izvajalo tristransko združevanje zbirk podatkov, zato se referenčni rezultat izračuna iz znanih vhodnih podatkov, namesto da bi ga ustvarilo zunanje orodje. Vsak scenarij ima determinističen pričakovani rezultat, ki ga je mogoče preveriti z logiko:

1. **Priprava** -- ustvarite tri stanja zbirke podatkov (osnovno stanje, naša kopija, njihova kopija) z znano vsebino.
2. **Referenčni rezultat** -- iz znanih vhodnih podatkov izračunajte pričakovani rezultat združevanja.
3. **Dejanski rezultat** -- izvedite združevanje treh stanj z merql.
4. **Primerjava** -- dejanski rezultat se mora natančno ujemati s pričakovanim.

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

## Zagon testov

### Celovito preverjanje

Skript `verify-all` je končno preverjanje. Izvede statično analizo, preverjanje standardov kodiranja, teste PHPUnit in regresijsko zbirko referenčnih testov:

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

To mora uspeti, preden je katero koli delo mogoče šteti za dokončano.

### Regresijska zbirka referenčnih testov

Zaženite vseh 32 scenarijev:

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

Zaženite eno kategorijo:

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

Kategorije so imena imenikov pod `scenarios/`: `clean`, `conflict`,
`identity`, `types`, `scale` in `edge`. Neznana kategorija izpiše
`No scenarios found` in se uspešno konča. `--category` lahko ponovite, da zaženete
več kategorij.

Skript sprejme tudi `--jobs`, vendar vrednost prezre in scenarije zažene
zaporedno ([#5](https://github.com/inline0/merql/issues/5)).

Zaženite z najmanj izpisa (samo uspeh/neuspeh):

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

### Posamezen scenarij

Preizkusite en scenarij skozi celoten postopek:

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

### Posamezni koraki referenčnega preizkusa

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

### Enotski testi

```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` zažene samo PHPUnit. Zbirka `oracle` PHPUnit kaže na
`tests/Oracle`, v katerem so razredi za izvajanje scenarijev, vendar ni testnih primerov, zato
se 32 scenarijev zažene samo prek `composer test:oracle`, `composer verify` ali
`./bin/verify-all`.

Integracijski testi MySQL so preskočeni, razen če `MERQL_MYSQL_HOST`,
`MERQL_MYSQL_DATABASE` in `MERQL_MYSQL_USER` (ter po želji
`MERQL_MYSQL_PASSWORD` in `MERQL_MYSQL_PORT`) kažejo na testno zbirko podatkov.

### Kakovost kode

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

## Kategorije scenarijev

### čisti (8 scenarijev)

Čista združevanja brez sporov. Obe strani lahko spreminjata podatke, vendar nikoli istega stolpca v isti vrstici.

| Scenarij | Preizkuša |
|---|---|
| `insert-only-theirs` | Njihova kopija je vstavila vrstice, naša je nespremenjena. |
| `update-only-theirs` | Njihova kopija je posodobila vrstice, naša je nespremenjena. |
| `delete-only-theirs` | Njihova kopija je izbrisala vrstice, naša je nespremenjena. |
| `insert-only-ours` | Naša kopija je vstavila vrstice, njihova je nespremenjena. |
| `mixed-no-overlap` | Obe strani sta spremenili različne tabele ali vrstice. |
| `both-same-change` | Obe strani sta izvedli enake spremembe (brez spora). |
| `column-level-clean` | Obe strani sta spremenili isto vrstico, vendar različne stolpce. |
| `multi-table` | Spremembe v več tabelah. |

### spori (6 scenarijev)

Scenariji, pri katerih mora priti do sporov.

| Scenarij | Preizkuša |
|---|---|
| `both-update-same-column` | Obe strani sta isti stolpec spremenili na različni vrednosti. |
| `update-vs-delete` | Ena stran je posodobila, druga pa izbrisala isto vrstico. |
| `delete-vs-update` | Obratna smer posodobitve v primerjavi z izbrisom. |
| `both-insert-same-pk` | Obe strani sta vstavili vrstico z istim primarnim ključem. |
| `multiple-conflicts` | Več sporov v enem združevanju. |
| `partial-conflict` | Nekateri stolpci so v sporu, drugi se združijo brez težav. |

### identiteta (4 scenariji)

Robni primeri identitete vrstic.

| Scenarij | Preizkuša |
|---|---|
| `auto-increment` | Nove vrstice imajo med vejami različne ID-je. |
| `natural-key` | Ujemanje po enoličnih stolpcih namesto po primarnem ključu. |
| `composite-key` | Večstolpčni primarni ključ. |
| `no-key` | Tabela brez primarnega ključa (nadomestno ujemanje zgoščene vsebine). |

### tipi (6 scenarijev)

Obravnava podatkovnih tipov pri vseh tipih stolpcev.

| Scenarij | Preizkuša |
|---|---|
| `text-columns` | Vrednosti VARCHAR, TEXT in LONGTEXT. |
| `numeric-columns` | Vrednosti INT, DECIMAL in FLOAT. |
| `date-columns` | Vrednosti DATE, DATETIME in TIMESTAMP. |
| `json-columns` | Združevanje stolpca JSON. |
| `blob-columns` | Binarni podatki. |
| `null-handling` | Prehodi iz NULL v vrednost in iz vrednosti v NULL. |

### obseg (4 scenariji)

Zmogljivost pri obremenitvi.

| Scenarij | Preizkuša |
|---|---|
| `1k-rows` | 1,000 vrstic. |
| `10k-rows` | 10,000 vrstic. |
| `100k-rows` | 100,000 vrstic. |
| `wide-table` | Tabelo s 50+ stolpci. |

### robni primeri (4 scenariji)

Robni primeri in mejni pogoji.

| Scenarij | Preizkuša |
|---|---|
| `empty-changeset` | Brez sprememb na eni ali obeh straneh. |
| `schema-mismatch` | Stolpec, dodan ali odstranjen med posnetki. |
| `encoding` | UTF-8, emodžiji, posebni znaki. |
| `large-text` | Zelo velike vrednosti TEXT/LONGTEXT. |

## Struktura scenarija

Vsak scenarij je imenik pod `scenarios/<category>/<name>/` s konfiguracijo `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"
    }
}
```

Pri scenarijih s spori so pričakovani spori podrobno določeni:

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

## Postopek ScenarioRunner

`ScenarioRunner::run()` usklajuje en sam scenarij:

1. `OracleCapture::compute()` iz podatkov JSON scenarija ustvari tri posnetke in izvede združevanje.
2. `ScenarioComparator::compare()` preveri rezultat združevanja glede na pričakovane vrednosti v `scenario.json`.
3. Vrne rezultat uspeh/neuspeh in seznam sporočil o napakah.

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

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

`ScenarioRunner::runAll()` zaporedno zažene vsak scenarij:

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

## Dodajanje novega scenarija

1. Ustvarite imenik: `scenarios/<category>/<name>/`.
2. Ustvarite `scenario.json` z metapodatki scenarija in pričakovanji.
3. V konfiguraciji JSON določite tri stanja zbirke podatkov: osnovne podatke, spremembe v naši kopiji in spremembe v njihovi kopiji.
4. Zaženite scenarij: `./bin/test-scenario <name>`.
5. Preverite celotno zbirko: `./bin/verify-all`.

## Standardi kakovosti kode

merql uveljavlja:

- **PHPStan stopnje 10** -- strogo statično analizo brez izjem na podlagi osnovne konfiguracije.
- **PSR-12** -- standarde kodiranja preverja `phpcs`.
- **Regresijski preizkus na podlagi referenčnih rezultatov** -- vseh 32 scenarijev mora uspeti.
- **Enotski testi** -- izolirani testi za vsako komponento.

Skript `verify-all` zaporedno izvede vsa štiri preverjanja. Delna potrditev ni sprejemljiva.

## Neprekinjena integracija

CI ob vsakem potisku in zahtevku za združitev v `main` izvede preverjanje standardov kodiranja, PHPStan, `composer test`, integracijsko zbirko za MySQL 8.4 prek mysqli, preverjanje primitivov za združevanje in `composer audit`. Ne zažene regresijskega korpusa referenčnih testov ([#6](https://github.com/inline0/merql/issues/6)), zato pred zaupanjem v spremembo lokalno zaženite `composer test:oracle` ali `./bin/verify-all`.
