test(runway): end-to-end coverage for the merge queue round trip - #540
Open
behinddwalls wants to merge 2 commits into
Open
test(runway): end-to-end coverage for the merge queue round trip#540behinddwalls wants to merge 2 commits into
behinddwalls wants to merge 2 commits into
Conversation
## Summary
### Why?
Runway had no integration or e2e coverage at all — `test/integration/` has no `runway/` subtree, and `test/e2e/` covered only `submitqueue` and `stovepipe`. Everything Runway does was proven in isolation: `git_merger_test.go` drives a real `git` binary against a real bare remote across every merge strategy, and the controllers are covered with mocks.
What nothing proved is the service. A client publishes a `MergeRequest` to a real queue and a `MergeResult` has to come back on the signal topic under the client's correlation id. Topic-registry wiring, the protojson round trip over the wire, partition-key propagation, the ack-and-publish-FAILED path for expected outcomes, and DLQ reconciliation were all untested. The submitqueue e2e does boot the Runway container, but with the noop merger and only as a dependency of submitqueue's own flow.
### What?
`test/e2e/runway/` is a compose-backed suite (queue MySQL + the Runway service) that drives Runway the way its real client does. Runway is consumer-only, so the suite publishes to an inbound merge topic and listens on the corresponding signal topic. Five cases:
- **Merge happy path** — SUCCEEDED on `merge-signal` with the id echoed, every step attributable by step id in application order, a produced revision per step, and the partition key carried from request to result.
- **Conflict-check happy path** — SUCCEEDED on `merge-conflict-check-signal` with no outputs, since a dry run commits nothing.
- **Terminal failures** — conflict and invalid request, on both topic pairs: FAILED with a reason, answered by the controller rather than dead-lettered.
- **DLQ reconcile** — an unexpected fault is rejected to the dead-letter topic and the reconciler resolves the correlation id, so the client never waits forever.
- **Undecodable payload** — bytes that carry no correlation id signal nothing. Asserted with a same-partition sentinel rather than a sleep.
Outcomes have to be steerable from the payload, so the service runs a new `runway/extension/merger/fake` whose result is driven by an `sq-fake=<token>` marker in a change URI (`merge-conflict`, `merge-invalid`, `merge-error`), matching the convention the submitqueue fakes already use. It is selected by `MERGER=fake`; the git and noop paths are untouched. What a merge *does* stays out of the e2e — that is the git merger's own test.
The marker helper moved from `submitqueue/core/fakemarker` to `platform/fakemarker`. Runway needs it and `{domain}/core/` is domain-internal; four importers were updated.
One assertion is worth calling out. Checking that a request landed on the dead-letter topic races the queue's message GC, which reclaims the row once the reconciler acks it. The suite discriminates on the `dead-lettered: ` reason prefix instead — deterministic, and also the only thing on the wire that tells a client which component answered.
## Test Plan
- ✅ `bazel test //test/e2e/runway:go_default_test` — 5 tests, ~13s. Verified with `-test.v` that every case and subtest actually runs.
- ✅ `bazel test //test/e2e/submitqueue:go_default_test` and `//test/e2e/stovepipe:go_default_test` — confirms the `fakemarker` move.
- ✅ `bazel test //runway/...` — including the new fake's unit tests.
- ✅ `make test`, `make fmt`, license headers, gazelle/tidy idempotency.
`//submitqueue/orchestrator/controller/{batch,cancel}:go_default_test` fail, but they fail identically on unmodified `main` and `bazel query` shows neither depends on anything this touches.
behinddwalls
marked this pull request as ready for review
August 7, 2026 00:40
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Why?
Runway had no integration or e2e coverage at all —
test/integration/has norunway/subtree, andtest/e2e/covered onlysubmitqueueandstovepipe. Everything Runway does was proven in isolation:git_merger_test.godrives a realgitbinary against a real bare remote across every merge strategy, and the controllers are covered with mocks.What nothing proved is the service. A client publishes a
MergeRequestto a real queue and aMergeResulthas to come back on the signal topic under the client's correlation id. Topic-registry wiring, the protojson round trip over the wire, partition-key propagation, the ack-and-publish-FAILED path for expected outcomes, and DLQ reconciliation were all untested. The submitqueue e2e does boot the Runway container, but with the noop merger and only as a dependency of submitqueue's own flow.What?
test/e2e/runway/is a compose-backed suite (queue MySQL + the Runway service) that drives Runway the way its real client does. Runway is consumer-only, so the suite publishes to an inbound merge topic and listens on the corresponding signal topic. Five cases:merge-signalwith the id echoed, every step attributable by step id in application order, a produced revision per step, and the partition key carried from request to result.merge-conflict-check-signalwith no outputs, since a dry run commits nothing.Outcomes have to be steerable from the payload, so the service runs a new
runway/extension/merger/fakewhose result is driven by ansq-fake=<token>marker in a change URI (merge-conflict,merge-invalid,merge-error), matching the convention the submitqueue fakes already use. It is selected byMERGER=fake; the git and noop paths are untouched. What a merge does stays out of the e2e — that is the git merger's own test.The marker helper moved from
submitqueue/core/fakemarkertoplatform/fakemarker. Runway needs it and{domain}/core/is domain-internal; four importers were updated.One assertion is worth calling out. Checking that a request landed on the dead-letter topic races the queue's message GC, which reclaims the row once the reconciler acks it. The suite discriminates on the
dead-lettered:reason prefix instead — deterministic, and also the only thing on the wire that tells a client which component answered.Test Plan
bazel test //test/e2e/runway:go_default_test— 5 tests, ~13s. Verified with-test.vthat every case and subtest actually runs.bazel test //test/e2e/submitqueue:go_default_testand//test/e2e/stovepipe:go_default_test— confirms thefakemarkermove.bazel test //runway/...— including the new fake's unit tests.make test,make fmt, license headers, gazelle/tidy idempotency.//submitqueue/orchestrator/controller/{batch,cancel}:go_default_testfail, but they fail identically on unmodifiedmainandbazel queryshows neither depends on anything this touches.