
# Estratégia de testes

merql usa um modelo de testes orientado por oráculo. Cada cenário de teste define um conjunto conhecido de entradas (três estados do banco de dados), um resultado esperado determinístico (o oráculo) e compara a saída real da mesclagem com essa expectativa. 32 cenários abrangem mesclagens sem conflitos, conflitos, resolução de identidade, tipos de dados, escala e casos extremos.

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

Os comandos desta página são executados a partir de um checkout do repositório merql após `composer install`. Os scripts de desenvolvimento em `bin/`, o corpus de `scenarios/` e os testes não fazem parte do pacote Composer. As dependências de desenvolvimento incluem PHPUnit 13, que requer PHP 8.4 ou posterior, embora a biblioteca em si seja executada em PHP 8.2.

## Testes orientados por oráculo

Não existe uma ferramenta de referência que faça mesclagem de banco de dados em três vias, portanto o oráculo é calculado a partir de entradas conhecidas, em vez de ser gerado por uma ferramenta externa. Cada cenário tem um resultado esperado determinístico que pode ser verificado por lógica:

1. **Configuração** -- crie três estados do banco de dados (base, ours, theirs) com conteúdo conhecido.
2. **Oráculo** -- calcule o resultado esperado da mesclagem a partir das entradas conhecidas.
3. **Resultado real** -- execute a mesclagem do merql nos três estados.
4. **Comparação** -- o resultado real deve corresponder exatamente ao resultado esperado.

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

## Executar testes

### Verificação completa

O script `verify-all` é a etapa final de validação. Ele executa análise estática, padrões de código, testes PHPUnit e o conjunto de regressão do oráculo:

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

Isso deve passar antes que qualquer trabalho seja considerado concluído.

### Conjunto de regressão do oráculo

Execute todos os 32 cenários:

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

Execute uma única categoria:

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

As categorias são os nomes dos diretórios em `scenarios/`: `clean`, `conflict`, `identity`, `types`, `scale` e `edge`. Uma categoria desconhecida exibe `No scenarios found` e termina com sucesso. `--category` pode ser repetido para executar várias categorias.

O script também aceita `--jobs`, mas ignora o valor e executa os cenários um após o outro ([#5](https://github.com/inline0/merql/issues/5)).

Execute com saída mínima (somente aprovação/reprovação):

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

### Cenário único

Teste um cenário por meio do pipeline completo:

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

### Etapas individuais do oráculo

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

### Testes unitários

```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` executa somente PHPUnit. O conjunto `oracle` PHPUnit aponta para `tests/Oracle`, que contém as classes do executor de cenários, mas nenhum caso de teste; por isso, os 32 cenários são executados apenas por meio de `composer test:oracle`, `composer verify` ou `./bin/verify-all`.

Os testes de integração de MySQL são ignorados, a menos que `MERQL_MYSQL_HOST`, `MERQL_MYSQL_DATABASE` e `MERQL_MYSQL_USER` (e, opcionalmente, `MERQL_MYSQL_PASSWORD` e `MERQL_MYSQL_PORT`) apontem para um banco de dados de teste.

### Qualidade do código

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

## Categorias de cenários

### clean (8 cenários)

Mesclagens sem conflitos. Ambos os lados podem alterar dados, mas nunca a mesma coluna da mesma linha.

| Cenário | Testes |
|---|---|
| `insert-only-theirs` | theirs inseriu linhas; ours não mudou. |
| `update-only-theirs` | theirs atualizou linhas; ours não mudou. |
| `delete-only-theirs` | theirs excluiu linhas; ours não mudou. |
| `insert-only-ours` | ours inseriu linhas; theirs não mudou. |
| `mixed-no-overlap` | Ambos alteraram tabelas ou linhas diferentes. |
| `both-same-change` | Ambos fizeram alterações idênticas (sem conflito). |
| `column-level-clean` | Ambos alteraram a mesma linha, em colunas diferentes. |
| `multi-table` | Alterações em várias tabelas. |

### conflict (6 cenários)

Cenários que devem gerar conflitos.

| Cenário | Testes |
|---|---|
| `both-update-same-column` | Ambos alteraram a mesma coluna para valores diferentes. |
| `update-vs-delete` | Um atualizou e o outro excluiu a mesma linha. |
| `delete-vs-update` | Direção inversa da atualização versus exclusão. |
| `both-insert-same-pk` | Ambos inseriram uma linha com a mesma chave primária. |
| `multiple-conflicts` | Vários conflitos em uma mesclagem. |
| `partial-conflict` | Algumas colunas entram em conflito; outras são mescladas sem problemas. |

### identity (4 cenários)

Casos extremos de identidade de linha.

| Cenário | Testes |
|---|---|
| `auto-increment` | Novas linhas têm IDs diferentes entre ramificações. |
| `natural-key` | Associação por colunas únicas em vez de PK. |
| `composite-key` | Chave primária com várias colunas. |
| `no-key` | Tabela sem chave primária (alternativa de hash de conteúdo). |

### types (6 cenários)

Tratamento de tipos de dados em todos os tipos de coluna.

| Cenário | Testes |
|---|---|
| `text-columns` | Valores VARCHAR, TEXT e LONGTEXT. |
| `numeric-columns` | Valores INT, DECIMAL e FLOAT. |
| `date-columns` | Valores DATE, DATETIME e TIMESTAMP. |
| `json-columns` | Mesclagem da coluna JSON. |
| `blob-columns` | Dados binários. |
| `null-handling` | Transições de NULL para valor e de valor para NULL. |

### scale (4 cenários)

Desempenho sob carga.

| Cenário | Testes |
|---|---|
| `1k-rows` | 1,000 linhas. |
| `10k-rows` | 10,000 linhas. |
| `100k-rows` | 100,000 linhas. |
| `wide-table` | Tabela com mais de 50 colunas. |

### edge (4 cenários)

Casos extremos e condições de limite.

| Cenário | Testes |
|---|---|
| `empty-changeset` | Sem alterações em um ou nos dois lados. |
| `schema-mismatch` | Coluna adicionada ou removida entre snapshots. |
| `encoding` | UTF-8, emojis e caracteres especiais. |
| `large-text` | Valores muito grandes de TEXT/LONGTEXT. |

## Estrutura dos cenários

Cada cenário é um diretório em `scenarios/<category>/<name>/` com uma configuração `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"
    }
}
```

Para cenários de conflito, os conflitos esperados são especificados em detalhes:

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

## Pipeline do ScenarioRunner

`ScenarioRunner::run()` coordena um único cenário:

1. `OracleCapture::compute()` cria três snapshots a partir dos dados JSON do cenário e executa a mesclagem.
2. `ScenarioComparator::compare()` verifica o resultado da mesclagem em relação aos valores esperados em `scenario.json`.
3. Retorna aprovação/reprovação com uma lista de mensagens de falha.

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

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

`ScenarioRunner::runAll()` executa todos os cenários em sequência:

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

## Adicionar um novo cenário

1. Crie um diretório: `scenarios/<category>/<name>/`.
2. Crie `scenario.json` com os metadados e as expectativas do cenário.
3. Defina os três estados do banco de dados na configuração JSON: dados base, alterações de ours e alterações de theirs.
4. Execute o cenário: `./bin/test-scenario <name>`.
5. Verifique o conjunto completo: `./bin/verify-all`.

## Padrões de qualidade do código

merql exige:

- **PHPStan nível 10** -- análise estática rigorosa, sem exceções de baseline.
- **PSR-12** -- padrões de código verificados por `phpcs`.
- **Regressão do oráculo** -- todos os 32 cenários devem passar.
- **Testes unitários** -- testes isolados para cada componente.

O script `verify-all` executa as quatro verificações em sequência. Não se aceita aprovação parcial.

## Integração contínua

A CI executa a verificação de padrões de código, PHPStan, `composer test`, o conjunto de testes de integração com MySQL 8.4 via mysqli, a cobertura das primitivas de mesclagem e `composer audit` em cada push e pull request para `main`. Ela não executa o corpus de regressão do oráculo ([#6](https://github.com/inline0/merql/issues/6)), então execute `composer test:oracle` ou `./bin/verify-all` localmente antes de confiar em uma alteração.
