Skip to content

test(db): add live Postgres integration test via testcontainers #222

Description

@ajianaz

Problem

CI hanya menjalankan cargo test --workspace dengan SQLite sebagai backend live. Path Postgres — yang diiklankan sebagai fitur core sejak v0.1.2 — tidak pernah diuji end-to-end di CI.

Bukti:

  • .github/workflows/ci.yml tidak punya service postgres block.
  • Tidak ada testcontainers dev-dependency di crates/trapfall-db/Cargo.toml.
  • Semua integration test pakai sqlite::open(":memory:") atau file temp.

Ini sudah dua kali bikin masalah:

  1. fix: Docker image dan binary release tidak support Postgres #207 — binary release tidak support Postgres (compile feature flag salah).
  2. security(api): mask DSN in dashboard-facing Project schema #213main.rs crash saat startup dengan postgres:// karena always-call sqlite_pool(). Bug ini hanya terlihat saat user coba pakai Postgres di production, tidak ketahuan di CI. Fix ada di PR fix: postgres startup + DSN leak + MCP schema sync + minor cleanups #217, tapi regression serupa bisa terjadi lagi tanpa guard.

Saat ini paritas SQLite ↔ Postgres hanya di-guard oleh unit test shared (issue #169) yang mock kedua backend via trait, bukan test SQL sungguhan.

Proposed solution

Pakai testcontainers crate untuk spin up Postgres container ephemeral di setiap test run yang butuh live DB.

Arsitektur

```
crates/trapfall-db/
tests/
common/mod.rs ← helper: async fn spawn_postgres() -> (Container, Database)
postgres_integration.rs ← test yang hanya jalan kalau TEST_POSTGRES=1 (atau selalu, kalau container tersedia)
```

Test yang perlu ada:

  1. Migration parity: jalankan run_postgres_migrations() di container baru → verifikasi semua tabel + index + constraint created identik dengan SQLite.
  2. CRUD paritas: insert project → insert issue → insert event → verify select returns expected shape. Bandingkan dengan hasil SQLite.
  3. Transaction rollback: delete_project gagal di tengah → verify no orphaned rows (regression test untuk fix(db): wrap delete_project in transaction + atomic upsert_issue #209).
  4. Concurrent upsert: 2 event untuk issue yang sama datang bersamaan → verify count = 2, bukan 1 (regression test untuk fix(db): wrap delete_project in transaction + atomic upsert_issue #209).
  5. Connection pool: drop DB lalu reconnect → verify pool recovery.

CI setup

Tambah job baru di .github/workflows/ci.yml:

```yaml
test-postgres:
name: Test (Postgres via testcontainers)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Run Postgres integration tests
run: cargo test --features trapfall_db/postgres --test postgres_integration
```

testcontainers akan pull image postgres:16-alpine otomatis (Docker sudah tersedia di GitHub Actions runner).

Acceptance criteria

Notes

  • Prioritas: medium-high. TrapFall mengiklankan Postgres sebagai backend utama (sanding SQLite). Tidak punya integration test untuk itu adalah blind spot.
  • Pertimbangan image size: postgres:16-alpine ~80MB, pull cepat di GHA runner. Tidak akan memperlambat CI signifikan (<1 menit untuk full suite).
  • Out of scope: SQLite integration test (sudah implicit lewat unit tests yang pakai :memory:).
  • Out of scope: performance benchmark Postgres vs SQLite.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions