
# Strategia testowania

merql korzysta z modelu testowania sterowanego wyrocznią. Każdy scenariusz testowy określa znany zestaw danych wejściowych (trzy stany bazy danych), deterministyczny oczekiwany wynik (wyrocznię) i porównuje go z rzeczywistym wynikiem scalania. 32 scenariusze obejmują poprawne scalania, konflikty, rozpoznawanie tożsamości, typy danych, skalę i przypadki brzegowe.

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

Polecenia na tej stronie uruchamia się w kopii repozytorium merql po
`composer install`. Skrypty programistyczne w `bin/`, korpus `scenarios/`
i testy nie należą do pakietu Composer. Zależności programistyczne obejmują PHPUnit 13, który wymaga PHP 8.4 lub nowszego, choć sama
biblioteka działa na PHP 8.2.

## Testowanie sterowane wyrocznią

Nie istnieje narzędzie referencyjne wykonujące trójdrożne scalanie baz danych, dlatego wyrocznię oblicza się na podstawie znanych danych wejściowych, a nie generuje za pomocą zewnętrznego narzędzia. Każdy scenariusz ma deterministyczny oczekiwany wynik, który można zweryfikować logicznie:

1. **Przygotowanie** -- utwórz trzy stany bazy danych (base, ours, theirs) o znanej zawartości.
2. **Wyrocznia** -- oblicz oczekiwany wynik scalania na podstawie znanych danych wejściowych.
3. **Wynik rzeczywisty** -- uruchom scalanie merql na trzech stanach.
4. **Porównanie** -- wynik rzeczywisty musi dokładnie odpowiadać oczekiwanemu.

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

## Uruchamianie testów

### Pełna weryfikacja

Skrypt `verify-all` jest ostatnią bramką. Uruchamia analizę statyczną, kontrolę standardów kodowania, testy PHPUnit i zestaw testów regresyjnych oracle:

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

Wszystkie te kontrole muszą przejść, zanim jakiekolwiek prace zostaną uznane za zakończone.

### Zestaw testów regresyjnych oracle

Uruchom wszystkie 32 scenariusze:

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

Uruchom jedną kategorię:

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

Kategorie to nazwy katalogów w `scenarios/`: `clean`, `conflict`,
`identity`, `types`, `scale` i `edge`. Nieznana kategoria wypisuje
`No scenarios found` i kończy działanie powodzeniem. Opcję `--category` można powtarzać, aby uruchomić
kilka kategorii.

Skrypt przyjmuje też `--jobs`, ale ignoruje jego wartość i uruchamia scenariusze
jeden po drugim ([#5](https://github.com/inline0/merql/issues/5)).

Uruchom z minimalnym wyjściem (tylko powodzenie/niepowodzenie):

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

### Pojedynczy scenariusz

Przetestuj jeden scenariusz w całym potoku:

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

### Poszczególne kroki oracle

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

### Testy jednostkowe

```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` uruchamia tylko PHPUnit. Zestaw `oracle` PHPUnit wskazuje na
`tests/Oracle`, który zawiera klasy uruchamiające scenariusze, ale nie zawiera przypadków testowych, więc
32 scenariusze są uruchamiane tylko przez `composer test:oracle`, `composer verify` lub
`./bin/verify-all`.

Testy integracyjne MySQL są pomijane, chyba że `MERQL_MYSQL_HOST`,
`MERQL_MYSQL_DATABASE` i `MERQL_MYSQL_USER` (oraz opcjonalnie
`MERQL_MYSQL_PASSWORD` i `MERQL_MYSQL_PORT`) wskazują bazę danych do testów.

### Jakość kodu

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

## Kategorie scenariuszy

### clean (8 scenariuszy)

Scenariusze czystego scalania bez konfliktów. Obie strony mogą zmieniać dane, ale nigdy tę samą kolumnę w tym samym wierszu.

| Scenariusz | Testy |
|---|---|
| `insert-only-theirs` | Ich strona wstawiła wiersze, nasza strona bez zmian. |
| `update-only-theirs` | Ich strona zaktualizowała wiersze, nasza strona bez zmian. |
| `delete-only-theirs` | Ich strona usunęła wiersze, nasza strona bez zmian. |
| `insert-only-ours` | Nasza strona wstawiła wiersze, ich strona bez zmian. |
| `mixed-no-overlap` | Obie strony zmieniły różne tabele lub wiersze. |
| `both-same-change` | Obie strony wprowadziły identyczne zmiany (brak konfliktu). |
| `column-level-clean` | Obie strony zmieniły ten sam wiersz, ale różne kolumny. |
| `multi-table` | Zmiany w wielu tabelach. |

### conflict (6 scenariuszy)

Scenariusze, które muszą powodować konflikty.

| Scenariusz | Testy |
|---|---|
| `both-update-same-column` | Obie strony zmieniły tę samą kolumnę na różne wartości. |
| `update-vs-delete` | Jedna strona zaktualizowała wiersz, a druga usunęła ten sam wiersz. |
| `delete-vs-update` | Odwrotna kolejność aktualizacji i usunięcia. |
| `both-insert-same-pk` | Obie strony wstawiły wiersz z tym samym kluczem głównym. |
| `multiple-conflicts` | Kilka konfliktów w jednym scalaniu. |
| `partial-conflict` | Konflikty w niektórych kolumnach, pozostałe scalają się bez konfliktów. |

### identity (4 scenariusze)

Przypadki brzegowe tożsamości wierszy.

| Scenariusz | Testy |
|---|---|
| `auto-increment` | Nowe wiersze mają różne identyfikatory w poszczególnych gałęziach. |
| `natural-key` | Dopasowanie według unikatowych kolumn zamiast PK. |
| `composite-key` | Klucz główny złożony z wielu kolumn. |
| `no-key` | Tabela bez klucza głównego (zapasowe użycie skrótu treści). |

### types (6 scenariuszy)

Obsługa typów danych we wszystkich typach kolumn.

| Scenariusz | Testy |
|---|---|
| `text-columns` | Wartości VARCHAR, TEXT, LONGTEXT. |
| `numeric-columns` | Wartości INT, DECIMAL, FLOAT. |
| `date-columns` | Wartości DATE, DATETIME, TIMESTAMP. |
| `json-columns` | Scalanie kolumny JSON. |
| `blob-columns` | Dane binarne. |
| `null-handling` | Przejścia z NULL na wartość i z wartości na NULL. |

### scale (4 scenariusze)

Wydajność pod obciążeniem.

| Scenariusz | Testy |
|---|---|
| `1k-rows` | 1,000 wierszy. |
| `10k-rows` | 10,000 wierszy. |
| `100k-rows` | 100,000 wierszy. |
| `wide-table` | Tabela z ponad 50 kolumnami. |

### edge (4 scenariusze)

Przypadki brzegowe i graniczne warunki.

| Scenariusz | Testy |
|---|---|
| `empty-changeset` | Brak zmian po jednej lub obu stronach. |
| `schema-mismatch` | Kolumna dodana lub usunięta między migawkami. |
| `encoding` | UTF-8, emoji, znaki specjalne. |
| `large-text` | Bardzo duże wartości TEXT/LONGTEXT. |

## Struktura scenariusza

Każdy scenariusz to katalog w `scenarios/<category>/<name>/` z konfiguracją `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"
    }
}
```

W przypadku scenariuszy konfliktowych oczekiwane konflikty są szczegółowo określone:

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

## Potok ScenarioRunner

`ScenarioRunner::run()` koordynuje wykonanie pojedynczego scenariusza:

1. `OracleCapture::compute()` tworzy trzy migawki na podstawie danych JSON scenariusza i uruchamia scalanie.
2. `ScenarioComparator::compare()` sprawdza wynik scalania, porównując go z oczekiwanymi wartościami w `scenario.json`.
3. Zwraca wynik powodzenia lub niepowodzenia wraz z listą komunikatów o błędach.

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

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

`ScenarioRunner::runAll()` uruchamia kolejno wszystkie scenariusze:

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

## Dodawanie nowego scenariusza

1. Utwórz katalog: `scenarios/<category>/<name>/`.
2. Utwórz `scenario.json` z metadanymi scenariusza i oczekiwanymi wynikami.
3. Zdefiniuj trzy stany bazy danych w konfiguracji JSON: dane bazowe, zmiany po naszej stronie i zmiany po ich stronie.
4. Uruchom scenariusz: `./bin/test-scenario <name>`.
5. Sprawdź cały zestaw testów: `./bin/verify-all`.

## Standardy jakości kodu

merql wymusza:

- **PHPStan poziom 10** -- rygorystyczna analiza statyczna bez wyjątków od wartości bazowych.
- **PSR-12** -- standardy kodowania sprawdzane przez `phpcs`.
- **Testy regresyjne oracle** -- wszystkie 32 scenariusze muszą przejść.
- **Testy jednostkowe** -- izolowane testy poszczególnych komponentów.

Skrypt `verify-all` uruchamia kolejno wszystkie cztery kontrole. Nie dopuszcza się częściowego zatwierdzenia.

## Integracja ciągła

CI uruchamia kontrolę standardów kodowania, PHPStan, `composer test`, zestaw testów integracyjnych
z MySQL 8.4 przez mysqli, testy pokrycia prymitywów scalania oraz
`composer audit` przy każdym wypchnięciu zmian i żądaniu ściągnięcia do `main`. Nie uruchamia
zestawu testów regresyjnych oracle ([#6](https://github.com/inline0/merql/issues/6)), dlatego
przed poleganiem na zmianie uruchom lokalnie `composer test:oracle` lub `./bin/verify-all`.
