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

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. Наборът PHPUnit на `oracle` сочи към
`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
```

## Категории сценарии

### чисто (8 сценария)

Сливания без конфликти. Двете страни може да променят данни, но никога една и съща колона на един и същ ред.

| Сценарий | Проверява |
|---|---|
| `insert-only-theirs` | В тяхната версия са вмъкнати редове, нашата не е променена. |
| `update-only-theirs` | В тяхната версия са обновени редове, нашата не е променена. |
| `delete-only-theirs` | В тяхната версия са изтрити редове, нашата не е променена. |
| `insert-only-ours` | В нашата версия са вмъкнати редове, тяхната не е променена. |
| `mixed-no-overlap` | Двете страни са променили различни таблици или редове. |
| `both-same-change` | Двете страни са направили еднакви промени (без конфликт). |
| `column-level-clean` | Двете страни са променили един и същ ред, но различни колони. |
| `multi-table` | Промени в няколко таблици. |

### конфликти (6 сценария)

Сценарии, които трябва да доведат до конфликти.

| Сценарий | Проверява |
|---|---|
| `both-update-same-column` | Двете страни са променили една и съща колона на различни стойности. |
| `update-vs-delete` | Едната страна е обновила реда, а другата го е изтрила. |
| `delete-vs-update` | Обратна посока на конфликта при обновяване и изтриване. |
| `both-insert-same-pk` | Двете страни са вмъкнали ред със същия първичен ключ. |
| `multiple-conflicts` | Няколко конфликта в едно сливане. |
| `partial-conflict` | Някои колони са в конфликт, а други се сливат без проблем. |

### идентичност (4 сценария)

Крайни случаи при идентичността на редовете.

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

### типове (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. |

### мащаб (4 сценария)

Производителност при натоварване.

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

### крайни случаи (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`, преди да разчитате на промяна.
