Skip to content

End-to-end integration test in CI — and the startup bug it caught - #12

Merged
emoss08 merged 2 commits into
mainfrom
claude/custom-cdc-review-zth3ax
Aug 11, 2026
Merged

emoss08 merged 2 commits into
mainfrom
claude/custom-cdc-review-zth3ax

Conversation

@emoss08

@emoss08 emoss08 commented Aug 11, 2026

Copy link
Copy Markdown
Owner

What does this PR do?

Adds a real end-to-end test — the compiled gateway running against a live PostgreSQL (wal_level=logical) and Redis — and fixes the genuine startup bug it caught on its very first run.

The bug: the WAL reader's existence checks for the publication and replication slot used parameterized queries (extended query protocol), which PostgreSQL walsender connections reject with SQLSTATE 08P01. Auto-creation of the slot and publication — the plug-and-play first-run path — never succeeded: the reader retried until CDC_SLOT_RETRY_TIMEOUT and the gateway never became ready on a fresh database. Both checks now run as simple-protocol queries with the names embedded as quoted literals (names come from config; quoting is still applied).

The test (test/integration, behind an integration build tag so go test ./... is unaffected) builds the gateway binary, starts it as a subprocess, and asserts:

  • slot + publication auto-create on first start, /readiness flips to 200
  • rows inserted before startup arrive as backfill READ events on the Redis stream
  • live INSERT/UPDATE/DELETE arrive in per-key order with correct payloads (duplicates collapsed — at-least-once semantics allow them; DELETE carries no new_data)
  • /api/stats agrees with the stream (streaming true, events counted)
  • SIGTERM produces a clean, timely exit

On failure it dumps the gateway's full log into the test output. It cleans up all state it creates (table, publication, slot, stream, backfill resume-state table) so back-to-back runs pass.

CI: a new integration job starts postgres:16 -c wal_level=logical and redis:7 via plain docker run (job services can't override the container command, which the wal_level flag requires) and runs the test on every PR, with service-log dumps on failure.

Type of change

  • Bug fix
  • Refactor / chore

Checklist

  • go test ./... passes (including -race; the integration test ran twice back-to-back locally against real services, ~12s each)
  • gofmt / go vet are clean
  • Tests added or updated for changed behavior (this PR is the test)
  • README.md / CLAUDE.md updated if env vars or behavior changed (CONTRIBUTING.md + CLAUDE.md document the local invocation)
  • If this touches delivery semantics (LSN acknowledgment, sink error handling, TOAST handling): the at-least-once guarantee still holds — the fix only changes the wire protocol used for two read-only catalog checks during startup; LSN acknowledgment and event handling are untouched, and the E2E test now asserts delivery behavior (per-key ordering, duplicate tolerance) on every PR

Notes for the reviewer

  • The protocol bug affected every fresh-database start, so if you've only ever run GTC against databases where the slot/publication already existed (or created them out of band), you'd never have seen it.
  • The test tolerates duplicates by design — asserting exact event counts would make it flaky under redelivery, which at-least-once explicitly permits.

🤖 Generated with Claude Code

https://claude.ai/code/session_019dEj24Ta2o9QxzW9ZrSPtb


Generated by Claude Code

claude added 2 commits August 11, 2026 19:52
…connection

PostgreSQL walsender connections reject the extended (parameterized)
query protocol, so the pg_publication and pg_replication_slots existence
checks failed with SQLSTATE 08P01 on every fresh start — meaning
auto-creation of the slot and publication (the plug-and-play first-run
path) never succeeded and the reader retried until SLOT_RETRY_TIMEOUT.

Run both checks as simple-protocol queries with the name embedded as a
quoted literal (names come from config, quoting is still applied).

Found by the new end-to-end integration test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019dEj24Ta2o9QxzW9ZrSPtb
Runs the compiled gateway as a subprocess against a live PostgreSQL
(wal_level=logical) and Redis, exercising the full pipeline the unit
tests mock at the WAL boundary:

- auto-create of the replication slot and publication on first start
- automatic backfill: READ events for rows that existed before start
- live streaming: INSERT/UPDATE/DELETE arrive on the Redis stream in
  per-key order with the right payloads (duplicates tolerated, as
  at-least-once semantics allow)
- /api/stats agrees with the stream, and SIGTERM shuts down cleanly

Excluded from `go test ./...` by an `integration` build tag; a new CI
job starts postgres:16 and redis:7 with docker run (job services can't
override the container command, and Postgres needs -c wal_level=logical)
and runs it on every PR. CONTRIBUTING.md and CLAUDE.md document how to
run it locally.

The test's first run immediately caught the replication-connection
protocol bug fixed in the previous commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019dEj24Ta2o9QxzW9ZrSPtb
@emoss08
emoss08 merged commit 0b0e533 into main Aug 11, 2026
7 checks passed
@emoss08
emoss08 deleted the claude/custom-cdc-review-zth3ax branch August 11, 2026 20:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants