Files
zip/compose_wire_test.go
zeekayandHanzo Dev 5f71d2496b The router is three nouns, and every one of them is concrete
App, Group, Operation. A group is a prefix and what holds under it, an operation
is one typed route declared there, and the verb methods are generic methods —
which Go 1.27 allows on a concrete type and still refuses on an interface. So In
and Out come from the handler and the caller writes no type arguments:

	users := app.Group("/v1/users")
	users.Post("/", api.Create)

	mint := users.Group("/mint").With(middleware.Mint(gate))
	mint.Post("/deposit", api.Deposit)

A Group is a CLOSED HANDLE: two unexported fields, and no method that hands back
the app, the parent or the fiber underneath. A subsystem given the group at its
own prefix cannot reach above it, so confinement is a property of the type
instead of a rule something has to police — which is what a host needs in order
to hand out a bounded surface and know what comes back.

That is bought by giving up the router interface, deliberately. Router and
OpTarget are gone, and with them the ability to implement a route table outside
this package: a decorator is no longer a router, a gate is middleware on a group,
and a bound is a path. The five package-level verb functions go the same way —
a registration is a method on the thing it registers into.

Raw carries what is not a typed operation: a stream, an upgrade, a probe, a
foreign subtree. It names its method, and it is the longer spelling because
opting out of the typed model is the exception and should read like one.



Co-authored-by: Hanzo Dev <[email protected]>
2026-09-17 01:39:17 -07:00

234 lines
8.6 KiB
Go

package zip
import (
"context"
"io"
"net/http/httptest"
"strings"
"testing"
)
// The walk says what a program MEANS. These say what it DOES — the same
// programs, driven over the wire, because a projection that is right on paper
// and wrong at the socket is wrong.
func wireGET(t *testing.T, a *App, path string, headers ...string) (int, string) {
t.Helper()
req := httptest.NewRequest("GET", path, nil)
for i := 0; i+1 < len(headers); i += 2 {
req.Header.Set(headers[i], headers[i+1])
}
resp, err := a.Fiber().Test(req)
if err != nil {
t.Fatalf("GET %s: %v", path, err)
}
defer func() { _ = resp.Body.Close() }()
body, _ := io.ReadAll(resp.Body)
return resp.StatusCode, string(body)
}
// guarded is a definition whose entire authentication is a pathless
// app.Use — the shape that made route-copying unsafe and that the old
// delegating verb existed to protect.
func guarded(name string) *App {
a := quiet(name)
a.Use(H(func(c *Ctx) error {
if c.Header("Authorization") == "" {
return Errorf(401, "no credentials")
}
return c.Continue()
}))
a.Raw("GET", "/thing", func(c *Ctx) error { return c.String(200, name+" thing") })
return a
}
// TestWire_IncludedMiddlewareStaysInsideItsDefinition is the property the whole
// composition model is judged on, and inclusion by reference has to earn it the
// hard way: there is no delegation boundary at run time to hide behind, so the
// definition's middleware has to be composed onto the definition's OWN routes
// and nothing else.
//
// The failure this prevents is not hypothetical: a child's pathless Use, placed
// on the host's router, gates every route in the host binary.
func TestWire_IncludedMiddlewareStaysInsideItsDefinition(t *testing.T) {
root := quiet("host")
root.Raw("GET", "/before", func(c *Ctx) error { return c.String(200, "before") })
root.Use(guarded("iam"))
root.Raw("GET", "/after", func(c *Ctx) error { return c.String(200, "after") })
// The guard DID come across: the definition's own route is gated.
if code, _ := wireGET(t, root, "/thing"); code != 401 {
t.Errorf("the definition's route answered %d without credentials — its guard was dropped", code)
}
if code, b := wireGET(t, root, "/thing", "Authorization", "yes"); code != 200 || b != "iam thing" {
t.Errorf("the definition's route with credentials: %d %q", code, b)
}
// And it did NOT bleed — in EITHER direction. Routes written before the
// inclusion and routes written after it are both untouched.
if code, b := wireGET(t, root, "/before"); code != 200 || b != "before" {
t.Errorf("host route written BEFORE the inclusion answered %d %q — the guard bled backwards", code, b)
}
if code, b := wireGET(t, root, "/after"); code != 200 || b != "after" {
t.Errorf("host route written AFTER the inclusion answered %d %q — the guard bled forwards", code, b)
}
}
// TestWire_GroupIsLexicalScope: middleware declared in a group applies to that
// group and to nothing else, including nothing that comes after it.
func TestWire_GroupIsLexicalScope(t *testing.T) {
root := quiet("host")
v1 := root.Group("/v1")
v1.Use(H(func(c *Ctx) error {
if c.Header("X-Key") == "" {
return Errorf(401, "no key")
}
return c.Continue()
}))
v1.Raw("GET", "/x", func(c *Ctx) error { return c.String(200, "v1 x") })
root.Raw("GET", "/open", func(c *Ctx) error { return c.String(200, "open") })
if code, _ := wireGET(t, root, "/v1/x"); code != 401 {
t.Errorf("group route answered %d without the key — the group's Use did not apply", code)
}
if code, b := wireGET(t, root, "/v1/x", "X-Key", "k"); code != 200 || b != "v1 x" {
t.Errorf("group route with the key: %d %q", code, b)
}
if code, b := wireGET(t, root, "/open"); code != 200 || b != "open" {
t.Errorf("a route outside the group answered %d %q — the group's Use escaped its scope", code, b)
}
}
// TestWire_DiamondServesBothOccurrences: one definition, two prefixes, and BOTH
// answer. The router keys on the occurrence, so two inclusions are two route
// sets, not one that the second inclusion silently lost.
func TestWire_DiamondServesBothOccurrences(t *testing.T) {
billing := unnamedApp() // no declared id, so it may occur twice
root := quiet("cloud")
root.Group("/v1").Use(billing)
root.Group("/admin").Use(billing)
for _, p := range []string{"/v1/invoices/i-1", "/admin/invoices/i-1"} {
code, body := wireGET(t, root, p)
if code != 200 || !strings.Contains(body, "i-1") {
t.Errorf("%s answered %d %q", p, code, body)
}
}
}
// TestWire_DifferentGuardsPerOccurrence. The same definition legitimately runs
// under one guard in one place and another guard elsewhere; that is the
// semantics of an occurrence, not an approximation of it.
func TestWire_DifferentGuardsPerOccurrence(t *testing.T) {
shared := quiet("shared")
shared.Raw("GET", "/who", func(c *Ctx) error { return c.String(200, "shared") })
need := func(h string) Handler {
return func(c *Ctx) error {
if c.Header(h) == "" {
return Errorf(401, "want %s", h)
}
return c.Continue()
}
}
root := quiet("host")
pub := root.Group("/pub")
pub.Use(need("X-User"))
pub.Use(shared)
adm := root.Group("/adm")
adm.Use(need("X-Admin"))
adm.Use(shared)
if code, _ := wireGET(t, root, "/pub/who", "X-User", "u"); code != 200 {
t.Errorf("/pub/who with a user credential answered %d", code)
}
if code, _ := wireGET(t, root, "/pub/who", "X-Admin", "a"); code != 401 {
t.Errorf("/pub/who accepted the ADMIN credential (%d) — the occurrences share a guard", code)
}
if code, _ := wireGET(t, root, "/adm/who", "X-Admin", "a"); code != 200 {
t.Errorf("/adm/who with an admin credential answered %d", code)
}
if code, _ := wireGET(t, root, "/adm/who", "X-User", "u"); code != 401 {
t.Errorf("/adm/who accepted the USER credential (%d) — the occurrences share a guard", code)
}
}
// TestWire_UndeclaredPathIsNotSwallowed. Inclusion registers exactly the
// addresses the definition declares, so a path nobody declares is a 404 from
// the host rather than being swallowed by a wildcard.
func TestWire_UndeclaredPathIsNotSwallowed(t *testing.T) {
root := quiet("host")
root.Group("/v1").Use(billingApp())
if code, _ := wireGET(t, root, "/v1/nobody-declares-this"); code != 404 {
t.Errorf("undeclared path answered %d, want 404 — the inclusion is swallowing", code)
}
}
// TestWire_OneCtxAcrossAnInclusionBoundary. A definition's middleware and the
// host's middleware run in one request, so they must be handed ONE *Ctx —
// enrichment written by either has to be visible to the other. Binding a
// handler to the definition that declared it instead of the app that serves it
// breaks exactly this.
func TestWire_OneCtxAcrossAnInclusionBoundary(t *testing.T) {
var seen []*Ctx
inner := quiet("inner")
inner.Use(H(func(c *Ctx) error { seen = append(seen, c); return c.Continue() }))
inner.Raw("GET", "/deep", func(c *Ctx) error {
seen = append(seen, c)
return c.NoContent(204)
})
root := quiet("host")
root.Use(H(func(c *Ctx) error { seen = append(seen, c); return c.Continue() }))
root.Group("/v1").Use(inner)
if code, _ := wireGET(t, root, "/v1/deep"); code != 204 {
t.Fatalf("/v1/deep answered %d", code)
}
if len(seen) != 3 {
t.Fatalf("ran %d handlers, want 3", len(seen))
}
for i, c := range seen {
if c != seen[0] {
t.Fatalf("handler %d got a different *Ctx — handlers were bound to their definition, not to the server", i)
}
}
}
// TestWire_IncludedControlPlaneIsNotAdopted. zip's own projections belong to
// the process serving them. Inclusion reads a definition's whole program, so
// without an explicit rule a definition that had ever rendered its own document
// would hand the host ITS document at the host's well-known path — and the
// composition would publish the part as if it were the whole.
func TestWire_IncludedControlPlaneIsNotAdopted(t *testing.T) {
child := billingApp()
// the definition renders its own control plane first
if err := child.Build(); err != nil {
t.Fatalf("child Build: %v", err)
}
host := quiet("host")
host.Get("/own", func(context.Context, *invoiceIn) (*invoiceOut, error) { return &invoiceOut{}, nil }, WithOperationID("hostOwn"))
host.Group("/v1").Use(child)
if err := host.Build(); err != nil {
t.Fatalf("Build: %v", err)
}
code, body := wireGET(t, host, SpecPath)
if code != 200 {
t.Fatalf("%s answered %d", SpecPath, code)
}
if !strings.Contains(body, `"title":"host"`) {
t.Errorf("the included definition took the host's document: %s", firstN(body, 200))
}
if !strings.Contains(body, "/v1/invoices/{id}") {
t.Errorf("the host's served document is missing the included op: %s", firstN(body, 400))
}
}
func firstN(s string, n int) string {
if len(s) <= n {
return s
}
return s[:n]
}