
# Testavimo strategija

merql naudoja oracle pagrįstą testavimo modelį. Kiekviename testavimo scenarijuje apibrėžiamas žinomas įvesčių rinkinys (trys duomenų bazės būsenos), deterministinis laukiamas rezultatas (oracle), o tikrasis sujungimo rezultatas palyginamas su juo. 32 scenarijai apima sklandžius sujungimus, konfliktus, tapatybės nustatymą, duomenų tipus, mastelį ir kraštinius atvejus.

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

Šiame puslapyje aprašytos komandos vykdomos iš merql saugyklos darbinės kopijos po
`composer install`. Kūrimo scenarijai, esantys `bin/`, `scenarios/`
korpusas ir testai nėra Composer paketo dalis. Kūrimo priklausomybėms reikia PHPUnit 13, kuriam būtina PHP 8.4 arba naujesnė versija, nors pati
biblioteka veikia su PHP 8.2.

## Oracle pagrįstas testavimas

Esamo etaloninio įrankio, atliekančio trijų krypčių duomenų bazės sujungimą, nėra, todėl oracle apskaičiuojamas pagal žinomas įvestis, o ne generuojamas išoriniu įrankiu. Kiekvienas scenarijus turi deterministinį laukiamą rezultatą, kurį galima patikrinti logiškai:

1. **Paruošimas** -- sukurkite tris duomenų bazės būsenas (base, ours, theirs) su žinomu turiniu.
2. **Oracle** -- pagal žinomas įvestis apskaičiuokite laukiamą sujungimo rezultatą.
3. **Faktinis rezultatas** -- atlikite merql trijų būsenų sujungimą.
4. **Palyginimas** -- faktinis rezultatas turi tiksliai sutapti su laukiamu.

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

## Testų vykdymas

### Visas patikrinimas

`verify-all` scenarijus yra galutinė patikra. Jis vykdo statinę analizę, kodavimo standartų patikras, PHPUnit testus ir oracle regresijos testų rinkinį:

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

Kad darbas būtų laikomas baigtu, ši patikra turi būti sėkminga.

### Oracle regresijos testų rinkinys

Paleiskite visus 32 scenarijus:

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

Paleiskite vieną kategoriją:

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

Kategorijos yra katalogų pavadinimai, esantys `scenarios/`: `clean`, `conflict`, `identity`, `types`, `scale` ir `edge`. Nežinoma kategorija išveda `No scenarios found` ir sėkmingai baigia darbą. `--category` galima nurodyti kelis kartus, kad būtų paleistos kelios kategorijos.

Scenarijus taip pat priima `--jobs`, tačiau nepaiso reikšmės ir paleidžia scenarijus vieną po kito ([#5](https://github.com/inline0/merql/issues/5)).

Paleisti rodant minimalią išvestį (tik sėkmė arba nesėkmė):

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

### Vienas scenarijus

Patikrinkite vieną scenarijų per visą apdorojimo grandinę:

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

### Atskiri oracle žingsniai

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

### Vienetiniai testai

```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` paleidžia tik PHPUnit. `oracle` PHPUnit rinkinys nurodo į `tests/Oracle`, kuriame yra scenarijų vykdyklės klasės, bet nėra testavimo atvejų, todėl 32 scenarijai paleidžiami tik per `composer test:oracle`, `composer verify` arba `./bin/verify-all`.

MySQL integravimo testai praleidžiami, nebent `MERQL_MYSQL_HOST`, `MERQL_MYSQL_DATABASE` ir `MERQL_MYSQL_USER` (taip pat pasirinktinai `MERQL_MYSQL_PASSWORD` ir `MERQL_MYSQL_PORT`) nurodo testų duomenų bazę.

### Kodo kokybė

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

## Scenarijų kategorijos

### clean (8 scenarijai)

Švarūs sujungimai be konfliktų. Abi pusės gali keisti duomenis, bet niekada nekeičia to paties to paties įrašo stulpelio.

| Scenarijus | Testas |
|---|---|
| `insert-only-theirs` | Jų pusė įterpė įrašų, mūsų pusė jų nepakeitė. |
| `update-only-theirs` | Jų pusė atnaujino įrašų, mūsų pusė jų nepakeitė. |
| `delete-only-theirs` | Jų pusė ištrynė įrašų, mūsų pusė jų nepakeitė. |
| `insert-only-ours` | Mūsų pusė įterpė įrašų, jų pusė jų nepakeitė. |
| `mixed-no-overlap` | Abi pusės pakeitė skirtingas lenteles arba įrašus. |
| `both-same-change` | Abi pusės atliko vienodus pakeitimus (konflikto nėra). |
| `column-level-clean` | Abi pusės pakeitė tą patį įrašą, bet skirtingus stulpelius. |
| `multi-table` | Pakeitimai keliose lentelėse. |

### conflict (6 scenarijai)

Scenarijai, kuriuose turi kilti konfliktų.

| Scenarijus | Testas |
|---|---|
| `both-update-same-column` | Abi pusės pakeitė tą patį stulpelį į skirtingas reikšmes. |
| `update-vs-delete` | Viena pusė atnaujino, kita ištrynė tą patį įrašą. |
| `delete-vs-update` | Atvirkštinis atnaujinimo ir ištrynimo atvejis. |
| `both-insert-same-pk` | Abi pusės įterpė įrašą su tuo pačiu pirminiu raktu. |
| `multiple-conflicts` | Keli konfliktai viename sujungime. |
| `partial-conflict` | Kai kurie stulpeliai konfliktuoja, kiti sujungiami be konfliktų. |

### identity (4 scenarijai)

Įrašų tapatybės kraštiniai atvejai.

| Scenarijus | Testas |
|---|---|
| `auto-increment` | Naujų įrašų ID skirtingose šakose skiriasi. |
| `natural-key` | Atitikmenys nustatomi pagal unikalius stulpelius, o ne pagal PK. |
| `composite-key` | Kelių stulpelių pirminis raktas. |
| `no-key` | Lentelė be pirminio rakto (atsarginis turinio maišos variantas). |

### types (6 scenarijai)

Duomenų tipų apdorojimas visiems stulpelių tipams.

| Scenarijus | Testas |
|---|---|
| `text-columns` | VARCHAR, TEXT, LONGTEXT reikšmės. |
| `numeric-columns` | INT, DECIMAL, FLOAT reikšmės. |
| `date-columns` | DATE, DATETIME, TIMESTAMP reikšmės. |
| `json-columns` | JSON stulpelio sujungimas. |
| `blob-columns` | Dvejetainiai duomenys. |
| `null-handling` | Perėjimai iš NULL į reikšmę ir iš reikšmės į NULL. |

### scale (4 scenarijai)

Našumas esant apkrovai.

| Scenarijus | Testas |
|---|---|
| `1k-rows` | 1,000 įrašų. |
| `10k-rows` | 10,000 įrašų. |
| `100k-rows` | 100,000 įrašų. |
| `wide-table` | Lentelė su 50+ stulpelių. |

### edge (4 scenarijai)

Kraštiniai atvejai ir ribinės sąlygos.

| Scenarijus | Testas |
|---|---|
| `empty-changeset` | Pakeitimų nėra vienoje arba abiejose pusėse. |
| `schema-mismatch` | Tarp momentinių kopijų pridėtas arba pašalintas stulpelis. |
| `encoding` | UTF-8, jaustukai, specialieji simboliai. |
| `large-text` | Labai didelės TEXT/LONGTEXT reikšmės. |

## Scenarijaus struktūra

Kiekvienas scenarijus yra katalogas, esantis `scenarios/<category>/<name>/`, su `scenario.json` konfigūracija:

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

Konfliktų scenarijuose tikėtini konfliktai išsamiai nurodomi:

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

## ScenarioRunner apdorojimo grandinė

`ScenarioRunner::run()` koordinuoja vieną scenarijų:

1. `OracleCapture::compute()` iš scenarijaus JSON duomenų sukuria tris momentines kopijas ir paleidžia sujungimą.
2. `ScenarioComparator::compare()` palygina sujungimo rezultatą su `scenario.json` nurodytomis tikėtinomis reikšmėmis.
3. Grąžina sėkmės arba nesėkmės būseną ir nesėkmės pranešimų sąrašą.

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

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

`ScenarioRunner::runAll()` iš eilės paleidžia kiekvieną scenarijų:

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

## Naujo scenarijaus pridėjimas

1. Sukurkite katalogą: `scenarios/<category>/<name>/`.
2. Sukurkite `scenario.json` su scenarijaus metaduomenimis ir tikėtinomis reikšmėmis.
3. JSON konfigūracijoje apibrėžkite tris duomenų bazės būsenas: bazinius duomenis, mūsų pakeitimus ir jų pakeitimus.
4. Paleiskite scenarijų: `./bin/test-scenario <name>`.
5. Patikrinkite visą rinkinį: `./bin/verify-all`.

## Kodo kokybės standartai

merql taiko:

- **PHPStan 10 lygį** -- griežtą statinę analizę be bazinės linijos išimčių.
- **PSR-12** -- kodavimo standartus tikrina `phpcs`.
- **Oracle regresija** -- turi būti sėkmingi visi 32 scenarijai.
- **Vienetiniai testai** -- atskiri kiekvieno komponento testai.

`verify-all` scenarijus paeiliui atlieka visas keturias patikras. Dalinis patvirtinimas nepriimamas.

## Nuolatinė integracija

CI kiekvieno siuntimo ir ištraukimo užklausos į `main` metu tikrina kodavimo standartus, PHPStan, `composer test`, integravimo testų rinkinį su MySQL 8.4 per mysqli, sujungimo primityvų aprėptį ir `composer audit`. Ji nevykdo oracle regresijos korpuso ([#6](https://github.com/inline0/merql/issues/6)), todėl prieš pasikliaudami pakeitimu paleiskite `composer test:oracle` arba `./bin/verify-all` vietoje.
