Skip to content

feat: passthroughtest, a seam to run the consumed-module passthrough in a test - #42

Merged
AntoineToussaint merged 1 commit into
mainfrom
feat/passthrough-test-seam
Sep 26, 2026
Merged

AntoineToussaint merged 1 commit into
mainfrom
feat/passthrough-test-seam

Conversation

@antoinetoussaint-byte

Copy link
Copy Markdown
Contributor

Why

A consuming solution cannot test its page's calls to a consumed module. The passthrough handler is internal, and Serve resolves the gateway and api.consumes from the composition and registers with the host. So a codefly test suite in a consuming solution cannot run the passthrough against its own Consumes declaration and a fake host, and in particular cannot exercise a server-streaming method.

What

Root package: one shared constructor, one new exported entry point

  • mountPassthrough(mux) is now the only place the passthrough is wired, at PassthroughPathPrefix. serve calls it (the inline block moved there unchanged), and so does the seam.
  • (*Server).PassthroughHandler(PassthroughEnvironment) (http.Handler, error) sets the gateway URL and api.consumes projection from its argument instead of the environment. It then runs the same boot check Serve runs (validatePassthrough) and mounts the handler through mountPassthrough. Nothing else of the solution is served, and nothing registers.
  • PassthroughEnvironment{GatewayURL string; Consumes []manifest.ConsumedAPI}.

These are additions only. No existing exported signature changes.

New package passthroughtest

API
NewHost(t) *Host A fake host gateway. It answers the accounts StartTask mint and reverse-proxies /v1/<as>/* to routed modules. Streams are flushed as they arrive, and a cancelled request is cancelled upstream.
(*Host).Module(as, upstreamURL) *Host Hook: routes a module to the test's own server. Every routed module is also in the api.consumes projection.
(*Host).RefuseMints(func(Mint) *Refusal) *Host Hook: refuses a mint the way accounts does.
(*Host).Mints() []Mint, (*Host).Calls() []Call Every mint (audience, scopes, org, session, bearer, token), and every forwarded call together with the Mint whose capability it presented. This is how a test asserts the authority minted for each method.
(*Host).URL() The gateway base URL.
Start(t, host, consumes...) *Solution / Handler(host, consumes...) (http.Handler, error) The real passthrough, served over httptest, or the bare handler together with the boot refusal Serve would give.
(*Solution).Client(), ClientAs(Viewer), BaseURL(as), .URL A client that sets the bearer plus the x-org-id / x-session-id headers the gateway stamps, and the base URL a generated client takes.
Viewer, DefaultViewer, Mint, Call, Refusal Plain data types.

Docs and gate

  • README: a new "Testing the passthrough: passthroughtest" section.
  • AGENTS.md: now names the second package.
  • The CI formatting gate now runs over the whole tree (gofmt -l . instead of ./*.go). AGENTS.md asks for this in the same PR that adds a subdirectory package.

Tests

passthroughtest/passthroughtest_test.go uses the seam as a consumer would, as an external test package with a dynamically registered placeholder module API (acme.widgets.v1):

  • a unary passthrough, answered as the viewer; the module is presented the minted capability;
  • a server stream that yields three messages in order;
  • per-method authority: Search mints widgets:read and Watch mints its own widgets:watch, asserted from the host's record of which mint each forwarded call presented;
  • the response field mask, on the unary answer and on every streamed message;
  • cancellation on client disconnect: the module's stream handler observes its request context cancelled;
  • a refused mint refuses only its method (permission_denied, with the reason), and the other method is still served;
  • a declaration naming a module the host does not route is refused, as Serve refuses it.

Mutation checks, run locally and then reverted:

  • Detaching the module request from the page's context makes the disconnect test fail in 5s instead of hanging.
  • Forcing module scopes over method scopes fails the per-method authority test.
  • Skipping the mask on streamed messages fails the field-mask test.

Results: go mod verify, go build ./..., go vet ./... and test -z "$(gofmt -l .)" pass. go test ./... -count=1 and go test -race ./... -count=1 -timeout=5m pass for both packages. go mod tidy is a no-op. The repo configures no linter. A local golangci-lint run reports 0 issues in passthroughtest. On the root package it reports 7, none on lines this PR touches.

Classification

Fix. The seam lives in the repository that owns the passthrough and serves the real handler through the one constructor Serve uses. It does not reimplement the passthrough. The one change to runtime code is extracting the existing mount block into mountPassthrough, with no change in behaviour.

Not verified

  • No consuming solution has used the seam yet. It has not been run from a codefly test suite, only from this repo's own go test.
  • The fake host copies the host's contract as this runtime already speaks it: the StartTask Connect JSON shape, the Work Context header, and path-preserving /v1/<as>/* forwarding. It is not checked against a real host or gateway, so a drift there would not show up in a green seam test.
  • Nothing here was run against a real composition, a real accounts mint, or a real module.

🤖 Generated with Claude Code

…in a test

A consuming solution could not exercise its page's calls to a consumed
module in a test: the passthrough handler is internal, and Serve resolves
the gateway and api.consumes from the composition and registers with the
host. A `codefly test` suite therefore could not drive a streamed method at
all.

Server.PassthroughHandler(PassthroughEnvironment) builds the passthrough
exactly as Serve does: the same boot check (validatePassthrough) and the
same mounting code, now one function (mountPassthrough) that serve and the
seam both call, against a gateway URL and api.consumes projection the
caller supplies. Nothing else of the solution is served and nothing
registers.

Package passthroughtest builds on it: a fake host gateway that mints the
viewer's Work Context and forwards /v1/<as>/* to the test's own module
server (streams flushed as they arrive, cancellation propagated), hooks to
route a module and to refuse mints, a record of every mint and of every
forwarded call with the mint it presented, and a client that calls as a
viewer with the identity headers the gateway stamps.

Its own tests drive a unary call, a stream of several messages, per-method
authority, the response field mask on both, and cancellation reaching the
module when the page disconnects, and a refused mint refusing only its
method. The formatting gate now covers the whole tree, not only ./*.go.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@AntoineToussaint
AntoineToussaint merged commit 8da58cd into main Sep 26, 2026
1 check passed
@antoinetoussaint-byte
antoinetoussaint-byte deleted the feat/passthrough-test-seam branch October 25, 2026 17:04
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