
# Стратегия тестирования

В 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. **Подготовка** -- создайте три состояния базы данных (база, наш вариант, их вариант) с известным содержимым.
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` | Другая сторона добавила строки, наша сторона ничего не меняла. |
| `update-only-theirs` | Другая сторона обновила строки, наша сторона ничего не меняла. |
| `delete-only-theirs` | Другая сторона удалила строки, наша сторона ничего не меняла. |
| `insert-only-ours` | Наша сторона добавила строки, другая сторона ничего не меняла. |
| `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: исходные данные, изменения нашей стороны, изменения другой стороны.
4. Запустите сценарий: `./bin/test-scenario <name>`.
5. Проверьте весь набор тестов: `./bin/verify-all`.

## Стандарты качества кода

merql обеспечивает:

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

Скрипт `verify-all` последовательно выполняет все четыре проверки. Частичное подтверждение не принимается.

## Непрерывная интеграция

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