
# Strategi Pengujian

merql menggunakan model pengujian berbasis oracle. Setiap skenario pengujian menentukan sekumpulan input yang diketahui (tiga keadaan basis data), hasil yang diharapkan secara deterministik (oracle), lalu membandingkan keluaran merge aktual dengan hasil tersebut. Ada 32 skenario yang mencakup merge tanpa konflik, konflik, resolusi identitas, tipe data, skala, dan kasus tepi.

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

Perintah di halaman ini dijalankan dari checkout repositori merql setelah
`composer install`. Skrip pengembangan di `bin/`, korpus `scenarios/`,
dan pengujian bukan bagian dari paket Composer. Dependensi pengembangan mencakup
PHPUnit 13, yang memerlukan PHP 8.4 atau lebih baru, meskipun
pustaka itu sendiri berjalan pada PHP 8.2.

## Pengujian berbasis oracle

Tidak ada alat referensi yang tersedia untuk melakukan merge basis data tiga arah, jadi oracle dihitung dari input yang diketahui, bukan dibuat oleh alat eksternal. Setiap skenario memiliki hasil yang diharapkan secara deterministik dan dapat diverifikasi secara logis:

1. **Penyiapan** -- buat tiga keadaan basis data (base, sisi kita, sisi mereka) dengan konten yang diketahui.
2. **Oracle** -- hitung hasil merge yang diharapkan dari input yang diketahui.
3. **Aktual** -- jalankan merge merql pada ketiga keadaan.
4. **Bandingkan** -- hasil aktual harus sama persis dengan hasil yang diharapkan.

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

## Menjalankan pengujian

### Verifikasi lengkap

Skrip `verify-all` adalah pemeriksaan akhir. Skrip ini menjalankan analisis statis, standar pengodean, pengujian PHPUnit, dan rangkaian regresi oracle:

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

Semua pemeriksaan ini harus lolos sebelum pekerjaan dinyatakan selesai.

### Rangkaian regresi oracle

Jalankan semua 32 skenario:

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

Jalankan satu kategori:

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

Kategori adalah nama direktori di bawah `scenarios/`: `clean`, `conflict`,
`identity`, `types`, `scale`, dan `edge`. Kategori yang tidak dikenal akan menampilkan
`No scenarios found` dan keluar dengan sukses. `--category` dapat diulang untuk menjalankan
beberapa kategori.

Skrip juga menerima `--jobs`, tetapi mengabaikan nilainya dan menjalankan skenario
secara berurutan ([#5](https://github.com/inline0/merql/issues/5)).

Jalankan dengan keluaran minimal (hanya lulus/gagal):

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

### Satu skenario

Uji satu skenario melalui seluruh alur:

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

### Langkah oracle satu per satu

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

### Pengujian unit

```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` hanya menjalankan PHPUnit. Rangkaian `oracle` PHPUnit mengarah ke
`tests/Oracle`, yang berisi kelas pelaksana skenario tetapi tidak memiliki kasus pengujian, jadi
32 skenario hanya dijalankan melalui `composer test:oracle`, `composer verify`, atau
`./bin/verify-all`.

Pengujian integrasi MySQL dilewati kecuali `MERQL_MYSQL_HOST`,
`MERQL_MYSQL_DATABASE`, dan `MERQL_MYSQL_USER` (serta secara opsional
`MERQL_MYSQL_PASSWORD` dan `MERQL_MYSQL_PORT`) mengarah ke basis data pengujian.

### Kualitas kode

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

## Kategori skenario

### clean (8 skenario)

Merge tanpa konflik. Kedua sisi dapat mengubah data, tetapi tidak pernah kolom yang sama pada baris yang sama.

| Skenario | Pengujian |
|---|---|
| `insert-only-theirs` | Sisi mereka menyisipkan baris, sisi kita tidak berubah. |
| `update-only-theirs` | Sisi mereka memperbarui baris, sisi kita tidak berubah. |
| `delete-only-theirs` | Sisi mereka menghapus baris, sisi kita tidak berubah. |
| `insert-only-ours` | Sisi kita menyisipkan baris, sisi mereka tidak berubah. |
| `mixed-no-overlap` | Kedua sisi mengubah tabel atau baris yang berbeda. |
| `both-same-change` | Kedua sisi membuat perubahan identik (tanpa konflik). |
| `column-level-clean` | Kedua sisi mengubah baris yang sama, tetapi kolom berbeda. |
| `multi-table` | Perubahan pada beberapa tabel. |

### conflict (6 skenario)

Skenario yang harus menghasilkan konflik.

| Skenario | Pengujian |
|---|---|
| `both-update-same-column` | Kedua sisi mengubah kolom yang sama menjadi nilai berbeda. |
| `update-vs-delete` | Satu sisi memperbarui, sisi lain menghapus baris yang sama. |
| `delete-vs-update` | Arah sebaliknya untuk pembaruan versus penghapusan. |
| `both-insert-same-pk` | Kedua sisi menyisipkan baris dengan kunci utama yang sama. |
| `multiple-conflicts` | Beberapa konflik dalam satu merge. |
| `partial-conflict` | Beberapa kolom berkonflik, sementara lainnya berhasil di-merge tanpa konflik. |

### identity (4 skenario)

Kasus tepi identitas baris.

| Skenario | Pengujian |
|---|---|
| `auto-increment` | Baris baru memiliki ID berbeda antar cabang. |
| `natural-key` | Cocokkan berdasarkan kolom unik, bukan PK. |
| `composite-key` | Kunci utama dengan beberapa kolom. |
| `no-key` | Tabel tanpa kunci utama (fallback hash konten). |

### types (6 skenario)

Penanganan tipe data untuk semua tipe kolom.

| Skenario | Pengujian |
|---|---|
| `text-columns` | Nilai VARCHAR, TEXT, LONGTEXT. |
| `numeric-columns` | Nilai INT, DECIMAL, FLOAT. |
| `date-columns` | Nilai DATE, DATETIME, TIMESTAMP. |
| `json-columns` | Merge kolom JSON. |
| `blob-columns` | Data biner. |
| `null-handling` | Transisi dari NULL ke nilai, dan dari nilai ke NULL. |

### scale (4 skenario)

Performa saat beban tinggi.

| Skenario | Pengujian |
|---|---|
| `1k-rows` | 1,000 baris. |
| `10k-rows` | 10,000 baris. |
| `100k-rows` | 100,000 baris. |
| `wide-table` | Tabel dengan 50+ kolom. |

### edge (4 skenario)

Kasus tepi dan kondisi batas.

| Skenario | Pengujian |
|---|---|
| `empty-changeset` | Tidak ada perubahan di satu atau kedua sisi. |
| `schema-mismatch` | Kolom ditambahkan atau dihapus antar snapshot. |
| `encoding` | UTF-8, emoji, karakter khusus. |
| `large-text` | Nilai TEXT/LONGTEXT yang sangat besar. |

## Struktur skenario

Setiap skenario adalah direktori di bawah `scenarios/<category>/<name>/` dengan konfigurasi `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"
    }
}
```

Untuk skenario konflik, konflik yang diharapkan ditentukan secara terperinci:

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

## Alur ScenarioRunner

`ScenarioRunner::run()` mengatur satu skenario:

1. `OracleCapture::compute()` membuat tiga snapshot dari data JSON skenario, lalu menjalankan merge.
2. `ScenarioComparator::compare()` memeriksa hasil merge berdasarkan nilai yang diharapkan di `scenario.json`.
3. Mengembalikan status lulus/gagal beserta daftar pesan kegagalan.

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

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

`ScenarioRunner::runAll()` menjalankan setiap skenario secara berurutan:

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

## Menambahkan skenario baru

1. Buat direktori: `scenarios/<category>/<name>/`.
2. Buat `scenario.json` dengan metadata dan ekspektasi skenario.
3. Tentukan tiga keadaan basis data dalam konfigurasi JSON: data base, perubahan di sisi kita, dan perubahan di sisi mereka.
4. Jalankan skenario: `./bin/test-scenario <name>`.
5. Verifikasi seluruh rangkaian: `./bin/verify-all`.

## Standar kualitas kode

merql menerapkan:

- **PHPStan level 10** -- analisis statis ketat tanpa pengecualian baseline.
- **PSR-12** -- standar pengodean yang diperiksa oleh `phpcs`.
- **Regresi oracle** -- semua 32 skenario harus lulus.
- **Pengujian unit** -- pengujian terisolasi untuk setiap komponen.

Skrip `verify-all` menjalankan keempat pemeriksaan secara berurutan. Persetujuan parsial tidak diterima.

## Integrasi berkelanjutan

CI menjalankan pemeriksaan standar pengodean, PHPStan, `composer test`, rangkaian integrasi
pada MySQL 8.4 melalui mysqli, cakupan merge primitif, dan
`composer audit` pada setiap push dan pull request ke `main`. CI tidak menjalankan
korpus regresi oracle ([#6](https://github.com/inline0/merql/issues/6)), jadi
jalankan `composer test:oracle` atau `./bin/verify-all` secara lokal sebelum mengandalkan suatu
perubahan.
