
# Стратегія тестування

merql використовує модель тестування на основі оракула. У кожному тестовому сценарії визначено відомий набір вхідних даних (три стани бази даних), детермінований очікуваний результат (оракул), з яким порівнюється фактичний результат злиття. 32 сценарії охоплюють злиття без конфліктів, конфлікти, визначення ідентичності, типи даних, масштаб і крайові випадки.

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

Команди на цій сторінці виконуються з копії репозиторію merql після
`composer install`. Скрипти розробки в `bin/`, корпусі `scenarios/`
та тести не входять до пакета Composer. Серед залежностей для розробки є PHPUnit 13, якому потрібен PHP 8.4 або новіший, хоча сама бібліотека працює на PHP 8.2.

## Тестування на основі оракула

Немає готового еталонного інструмента для злиття трьох станів бази даних, тому оракул обчислюється на основі відомих вхідних даних, а не генерується зовнішнім інструментом. Кожен сценарій має детермінований очікуваний результат, який можна перевірити логічно:

1. **Підготовка** -- створіть три стани бази даних (base, ours, theirs) із відомим вмістом.
2. **Оракул** -- обчисліть очікуваний результат злиття на основі відомих вхідних даних.
3. **Фактичний результат** -- виконайте злиття merql для трьох станів.
4. **Порівняння** -- фактичний результат має точно збігатися з очікуваним.

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

## Запуск тестів

### Повна перевірка

Скрипт `verify-all` — це завершальний етап перевірки. Він запускає статичний аналіз, перевірку стандартів кодування, тести PHPUnit і набір регресійних перевірок за оракулом:

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

Ця перевірка має пройти, перш ніж роботу можна буде вважати завершеною.

### Набір регресійних перевірок за оракулом

Запустіть усі 32 сценарії:

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

Запустіть одну категорію:

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

Назви категорій відповідають назвам каталогів у `scenarios/`: `clean`, `conflict`,
`identity`, `types`, `scale` і `edge`. Якщо вказати невідому категорію, буде виведено `No scenarios found`, а виконання завершиться успішно. `--category` можна повторювати, щоб запустити кілька категорій.

Скрипт також приймає `--jobs`, але ігнорує значення й запускає сценарії послідовно ([#5](https://github.com/inline0/merql/issues/5)).

Запустіть із мінімальним виведенням (лише результат: пройдено/не пройдено):

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

### Окремий сценарій

Протестуйте один сценарій через повний конвеєр:

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

### Окремі кроки оракула

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

### Модульні тести

```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` запускає лише PHPUnit. Набір тестів `oracle` PHPUnit вказує на `tests/Oracle`, де містяться класи запуску сценаріїв, але немає тестових випадків, тож 32 сценарії запускаються лише через `composer test:oracle`, `composer verify` або `./bin/verify-all`.

Інтеграційні тести MySQL пропускаються, якщо `MERQL_MYSQL_HOST`, `MERQL_MYSQL_DATABASE` і `MERQL_MYSQL_USER` (а також, за потреби, `MERQL_MYSQL_PASSWORD` і `MERQL_MYSQL_PORT`) не вказують на тестову базу даних.

### Якість коду

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

## Категорії сценаріїв

### clean (8 сценаріїв)

Злиття без конфліктів. Обидві сторони можуть змінювати дані, але ніколи не змінюють той самий стовпець того самого рядка.

| Сценарій | Перевірки |
|---|---|
| `insert-only-theirs` | theirs додала рядки, ours їх не змінювала. |
| `update-only-theirs` | theirs оновила рядки, ours їх не змінювала. |
| `delete-only-theirs` | theirs видалила рядки, ours їх не змінювала. |
| `insert-only-ours` | ours додала рядки, theirs їх не змінювала. |
| `mixed-no-overlap` | Обидві сторони змінили різні таблиці або рядки. |
| `both-same-change` | Обидві сторони внесли однакові зміни (без конфлікту). |
| `column-level-clean` | Обидві сторони змінили різні стовпці того самого рядка. |
| `multi-table` | Зміни в кількох таблицях. |

### conflict (6 сценаріїв)

Сценарії, у яких мають виникати конфлікти.

| Сценарій | Перевірки |
|---|---|
| `both-update-same-column` | Обидві сторони змінили той самий стовпець на різні значення. |
| `update-vs-delete` | Одна сторона оновила рядок, інша видалила його. |
| `delete-vs-update` | Зворотний напрямок конфлікту «оновлення проти видалення». |
| `both-insert-same-pk` | Обидві сторони додали рядок з однаковим первинним ключем. |
| `multiple-conflicts` | Кілька конфліктів в одному злитті. |
| `partial-conflict` | У частині стовпців конфлікти, решта зливається без них. |

### identity (4 сценарії)

Крайові випадки ідентичності рядків.

| Сценарій | Перевірки |
|---|---|
| `auto-increment` | Нові рядки мають різні ID у різних гілках. |
| `natural-key` | Зіставлення за унікальними стовпцями замість PK. |
| `composite-key` | Первинний ключ із кількох стовпців. |
| `no-key` | Таблиця без первинного ключа (резервний варіант із хешем вмісту). |

### types (6 сценаріїв)

Обробка всіх типів даних стовпців.

| Сценарій | Перевірки |
|---|---|
| `text-columns` | Значення VARCHAR, TEXT, LONGTEXT. |
| `numeric-columns` | Значення INT, DECIMAL, FLOAT. |
| `date-columns` | Значення DATE, DATETIME, TIMESTAMP. |
| `json-columns` | Злиття стовпця JSON. |
| `blob-columns` | Двійкові дані. |
| `null-handling` | Перехід від NULL до значення і від значення до NULL. |

### scale (4 сценарії)

Продуктивність під навантаженням.

| Сценарій | Перевірки |
|---|---|
| `1k-rows` | 1,000 рядків. |
| `10k-rows` | 10,000 рядків. |
| `100k-rows` | 100,000 рядків. |
| `wide-table` | Таблиця з понад 50 стовпцями. |

### edge (4 сценарії)

Крайові випадки та граничні умови.

| Сценарій | Перевірки |
|---|---|
| `empty-changeset` | Змін немає з одного або обох боків. |
| `schema-mismatch` | Стовпець додано або вилучено між знімками. |
| `encoding` | UTF-8, емодзі, спеціальні символи. |
| `large-text` | Дуже великі значення TEXT/LONGTEXT. |

## Структура сценарію

Кожен сценарій — це каталог у `scenarios/<category>/<name>/` із конфігурацією `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"
    }
}
```

Для сценаріїв із конфліктами очікувані конфлікти вказуються докладно:

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

## Конвеєр ScenarioRunner

`ScenarioRunner::run()` координує виконання окремого сценарію:

1. `OracleCapture::compute()` створює три знімки з даних JSON сценарію та виконує злиття.
2. `ScenarioComparator::compare()` звіряє результат злиття з очікуваними значеннями в `scenario.json`.
3. Повертає результат «пройдено/не пройдено» зі списком повідомлень про помилки.

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

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

`ScenarioRunner::runAll()` запускає кожен сценарій послідовно:

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

## Додавання сценарію

1. Створіть каталог: `scenarios/<category>/<name>/`.
2. Створіть `scenario.json` з метаданими та очікуваними результатами сценарію.
3. Визначте три стани бази даних у конфігурації JSON: базові дані, зміни ours і зміни theirs.
4. Запустіть сценарій: `./bin/test-scenario <name>`.
5. Перевірте весь набір: `./bin/verify-all`.

## Стандарти якості коду

merql забезпечує дотримання таких вимог:

- **PHPStan рівня 10** -- суворий статичний аналіз без винятків у базовій конфігурації.
- **PSR-12** -- стандарти кодування перевіряються за допомогою `phpcs`.
- **Регресійні перевірки за оракулом** -- усі 32 сценарії мають пройти.
- **Модульні тести** -- ізольовані тести для кожного компонента.

Скрипт `verify-all` послідовно виконує всі чотири перевірки. Часткове схвалення не приймається.

## Безперервна інтеграція

Під час кожного push і pull request до `main` CI перевіряє стандарти кодування, запускає PHPStan, `composer test`, набір інтеграційних тестів через mysqli на MySQL 8.4, перевірку базових операцій злиття та `composer audit`. Корпус регресійних перевірок за оракулом не запускається ([#6](https://github.com/inline0/merql/issues/6)), тому перед тим, як покладатися на зміну, локально запустіть `composer test:oracle` або `./bin/verify-all`.
