
# Estrategia de pruebas

merql utiliza un modelo de pruebas basado en un oráculo. Cada escenario de prueba define un conjunto conocido de entradas (tres estados de la base de datos), un resultado esperado determinista (el oráculo) y compara el resultado real de la fusión con esa expectativa. 32 escenarios cubren fusiones sin conflictos, conflictos, resolución de identidades, tipos de datos, escala y casos límite.

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

Los comandos de esta página se ejecutan desde una copia local del repositorio merql después de
`composer install`. Los scripts de desarrollo de `bin/`, el corpus de
`scenarios/` y las pruebas no forman parte del paquete Composer. Las dependencias de
 desarrollo incluyen PHPUnit 13, que requiere PHP 8.4 o posterior, aunque la
biblioteca funciona con PHP 8.2.

## Pruebas basadas en un oráculo

No existe una herramienta de referencia que realice fusiones de bases de datos de tres vías, por lo que el oráculo se calcula a partir de entradas conocidas en lugar de generarse con una herramienta externa. Cada escenario tiene un resultado esperado determinista que puede verificarse mediante lógica:

1. **Configuración** -- crear tres estados de la base de datos (base, nuestro y suyo) con contenido conocido.
2. **Oráculo** -- calcular el resultado esperado de la fusión a partir de las entradas conocidas.
3. **Resultado real** -- ejecutar la fusión de merql en los tres estados.
4. **Comparación** -- el resultado real debe coincidir exactamente con el resultado esperado.

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

## Ejecutar las pruebas

### Verificación completa

El script `verify-all` es la comprobación final. Ejecuta el análisis estático, las normas de código, las pruebas PHPUnit y la suite de regresión del oráculo:

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

Debe superarse antes de considerar terminado cualquier trabajo.

### Suite de regresión del oráculo

Ejecuta los 32 escenarios:

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

Ejecuta una sola categoría:

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

Las categorías son los nombres de directorio bajo `scenarios/`: `clean`, `conflict`,
`identity`, `types`, `scale` y `edge`. Una categoría desconocida muestra
`No scenarios found` y termina correctamente. Se puede repetir `--category` para ejecutar
varias categorías.

El script también acepta `--jobs`, pero ignora su valor y ejecuta los escenarios
uno tras otro ([#5](https://github.com/inline0/merql/issues/5)).

Ejecuta con una salida mínima (solo aprobado/reprobado):

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

### Escenario individual

Prueba un escenario mediante el flujo completo:

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

### Pasos individuales del 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
```

### Pruebas unitarias

```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` ejecuta solo PHPUnit. La suite `oracle` PHPUnit apunta a
`tests/Oracle`, que contiene las clases del ejecutor de escenarios, pero ningún caso de prueba, por lo que
los 32 escenarios solo se ejecutan mediante `composer test:oracle`, `composer verify` o
`./bin/verify-all`.

Las pruebas de integración MySQL se omiten salvo que `MERQL_MYSQL_HOST`,
`MERQL_MYSQL_DATABASE` y `MERQL_MYSQL_USER` (además de, opcionalmente,
`MERQL_MYSQL_PASSWORD` y `MERQL_MYSQL_PORT`) apunten a una base de datos de prueba.

### Calidad del código

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

## Categorías de escenarios

### clean (8 escenarios)

Fusiones sin conflictos. Ambos lados pueden cambiar datos, pero nunca la misma columna de la misma fila.

| Escenario | Pruebas |
|---|---|
| `insert-only-theirs` | theirs insertó filas; ours no cambió. |
| `update-only-theirs` | theirs actualizó filas; ours no cambió. |
| `delete-only-theirs` | theirs eliminó filas; ours no cambió. |
| `insert-only-ours` | ours insertó filas; theirs no cambió. |
| `mixed-no-overlap` | Ambos cambiaron tablas o filas distintas. |
| `both-same-change` | Ambos hicieron los mismos cambios (sin conflicto). |
| `column-level-clean` | Ambos cambiaron la misma fila, pero distintas columnas. |
| `multi-table` | Cambios en varias tablas. |

### conflict (6 escenarios)

Escenarios que deben generar conflictos.

| Escenario | Pruebas |
|---|---|
| `both-update-same-column` | Ambos cambiaron la misma columna a valores distintos. |
| `update-vs-delete` | Uno actualizó y el otro eliminó la misma fila. |
| `delete-vs-update` | Dirección inversa de actualización frente a eliminación. |
| `both-insert-same-pk` | Ambos insertaron una fila con la misma clave principal. |
| `multiple-conflicts` | Varios conflictos en una fusión. |
| `partial-conflict` | Algunas columnas entran en conflicto y otras se fusionan sin problemas. |

### identity (4 escenarios)

Casos límite de identidad de filas.

| Escenario | Pruebas |
|---|---|
| `auto-increment` | Las filas nuevas tienen distintos ID entre ramas. |
| `natural-key` | Coincidencia mediante columnas únicas en lugar de la PK. |
| `composite-key` | Clave principal de varias columnas. |
| `no-key` | Tabla sin clave principal (alternativa de hash del contenido). |

### types (6 escenarios)

Gestión de tipos de datos en todos los tipos de columna.

| Escenario | Pruebas |
|---|---|
| `text-columns` | Valores VARCHAR, TEXT y LONGTEXT. |
| `numeric-columns` | Valores INT, DECIMAL y FLOAT. |
| `date-columns` | Valores DATE, DATETIME y TIMESTAMP. |
| `json-columns` | Fusión de columnas JSON. |
| `blob-columns` | Datos binarios. |
| `null-handling` | Transiciones de NULL a un valor y de un valor a NULL. |

### scale (4 escenarios)

Rendimiento con carga.

| Escenario | Pruebas |
|---|---|
| `1k-rows` | 1,000 filas. |
| `10k-rows` | 10,000 filas. |
| `100k-rows` | 100,000 filas. |
| `wide-table` | Tabla con más de 50 columnas. |

### edge (4 escenarios)

Casos límite y condiciones de borde.

| Escenario | Pruebas |
|---|---|
| `empty-changeset` | Sin cambios en uno o ambos lados. |
| `schema-mismatch` | Columna añadida o eliminada entre instantáneas. |
| `encoding` | UTF-8, emojis y caracteres especiales. |
| `large-text` | Valores TEXT/LONGTEXT muy grandes. |

## Estructura de los escenarios

Cada escenario es un directorio bajo `scenarios/<category>/<name>/` con una configuración `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"
    }
}
```

En los escenarios con conflictos, los conflictos esperados se especifican en detalle:

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

## Flujo de ScenarioRunner

`ScenarioRunner::run()` coordina un escenario individual:

1. `OracleCapture::compute()` crea tres instantáneas a partir de los datos JSON del escenario y ejecuta la fusión.
2. `ScenarioComparator::compare()` comprueba el resultado de la fusión frente a los valores esperados en `scenario.json`.
3. Devuelve aprobado/reprobado junto con una lista de mensajes de error.

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

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

`ScenarioRunner::runAll()` ejecuta todos los escenarios en secuencia:

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

## Añadir un escenario nuevo

1. Crea un directorio: `scenarios/<category>/<name>/`.
2. Crea `scenario.json` con los metadatos y las expectativas del escenario.
3. Define los tres estados de la base de datos en la configuración JSON: datos base, modificaciones nuestras y modificaciones suyas.
4. Ejecuta el escenario: `./bin/test-scenario <name>`.
5. Verifica la suite completa: `./bin/verify-all`.

## Estándares de calidad del código

merql aplica:

- **PHPStan nivel 10** -- análisis estático estricto sin excepciones de referencia.
- **PSR-12** -- normas de código verificadas por `phpcs`.
- **Regresión del oráculo** -- deben superarse los 32 escenarios.
- **Pruebas unitarias** -- pruebas aisladas para cada componente.

El script `verify-all` ejecuta las cuatro comprobaciones en secuencia. No se acepta una aprobación parcial.

## Integración continua

CI ejecuta las normas de código, PHPStan, `composer test`, la suite de integración
con MySQL 8.4 mediante mysqli, la cobertura de primitivas de fusión y
`composer audit` en cada envío y solicitud de incorporación a `main`. No ejecuta el
corpus de regresión del oráculo ([#6](https://github.com/inline0/merql/issues/6)), así que
antes de confiar en un cambio, ejecuta localmente `composer test:oracle` o `./bin/verify-all`.
