Files
zip/adapt_net_http_test.go
T
zeekayandHanzo Dev 090bac90f1 zip.Mount is a proxy, not a mount — the name now says which of the two it is
`Mount` meant two unrelated things across the fleet at once:

  zip/remote.go    func Mount(prefix, addr string, decl ...Declaration) (*App, error)
  112 subsystems   func Mount(app cloud.Router, deps cloud.Deps) error

The second is the fleet's registration contract, stated in hanzoai/cloud's
package doc and checked by cloud.MountFunc. The first is a leftover: Mount was
one of five composition verbs (Listen/Mount/Add/Graft/Use), the other four were
dissolved into Use, and this one survived by being the only one that pointed at
another process. A reader seeing `Mount` in a diff could not tell which was
meant without counting arguments.

Renamed to Proxy, which is what it BUILDS. Every route it registers runs the
one handler this file calls `proxy`, whose whole body is forward() to the
address given; "proxy" is the term of art for a local stand-in whose handlers
hand the request to someone else, so it needs no gloss. It is also a noun,
which a function returning a value should be — composition is Use's job now,
and the doc has said so since the four verbs were dissolved.

NOT Remote: [Remote] already exists here and is the CALLER's side, a client
executing commands against a service elsewhere. This is the SERVER's side, a
definition standing in for that service inside this program's routing table.
Two sides of one wire are two things.

Also renamed the tests that named it, and mount_test.go -> adapt_net_http_test.go
with it: those pin AdaptNetHTTP, a THIRD sense of "mount", and TestMount_*
named none of the three.

Regression set empty: TestServePathAllocsAreChainDepthInvariant fails
identically on the parent commit (an allocation bound, untouched here).

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-04 19:30:42 -07:00

110 lines
4.2 KiB
Go

package zip_test
import (
"io"
"net/http"
"strings"
"testing"
"github.com/zap-proto/zip"
)
// These tests pin the one composition path for fronting a foreign
// net/http.Handler: it mounts as an ordinary wildcard route via
//
// app.Group(prefix).All("/*", zip.AdaptNetHTTP(h))
//
// The load-bearing property is specificity: a static route registered AFTER
// the mount still wins, because the mounted subtree is a normal route subject
// to the zap-proto/fiber fork's most-specific-wins precedence — NOT a separate
// pre-router prefix dispatch.
// call issues an in-memory request through fiber.Test and returns
// (status, body). Empty body sends no request body.
func call(t *testing.T, app *zip.App, method, target, body string) (int, string) {
t.Helper()
var r io.Reader
if body != "" {
r = strings.NewReader(body)
}
req, err := http.NewRequest(method, target, r)
if err != nil {
t.Fatalf("NewRequest(%s %s): %v", method, target, err)
}
resp, err := app.Fiber().Test(req)
if err != nil {
t.Fatalf("Test(%s %s): %v", method, target, err)
}
defer resp.Body.Close()
b, _ := io.ReadAll(resp.Body)
return resp.StatusCode, string(b)
}
// echoHandler is a plain net/http handler that reports the path, query and
// body it received, so tests can prove the request reached it intact.
func echoHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, _ := io.ReadAll(r.Body)
w.Header().Set("Content-Type", "text/plain")
_, _ = io.WriteString(w,
"net/http path="+r.URL.Path+" query="+r.URL.RawQuery+" body="+string(b))
})
}
// TestAdaptNetHTTP_NewForm_Serves proves the recommended form —
// app.All(prefix+"/*", zip.AdaptNetHTTP(h)) — serves the mounted subtree.
func TestAdaptNetHTTP_NewForm_Serves(t *testing.T) {
app := zip.New(zip.Config{DisableStartupMessage: true})
app.All("/api/*", zip.AdaptNetHTTP(echoHandler()))
status, body := call(t, app, "GET", "/api/foo", "")
if status != 200 {
t.Fatalf("GET /api/foo: status %d, want 200 (body=%s)", status, body)
}
if !strings.Contains(body, "net/http path=/api/foo") {
t.Fatalf("GET /api/foo: body=%q, want it served by the mounted net/http handler", body)
}
}
// TestAdaptNetHTTP_StaticRouteWins is the load-bearing proof: a static route
// registered AFTER the wildcard mount still wins for its exact path, while
// every other subpath falls through to the mount. This only holds because
// the mount is an ordinary route participating in specificity precedence —
// registration order is inverted on purpose.
func TestAdaptNetHTTP_StaticRouteWins(t *testing.T) {
app := zip.New(zip.Config{DisableStartupMessage: true})
// Wildcard mount FIRST, static route SECOND. Specificity, not order,
// must decide.
app.All("/v1/commerce/*", zip.AdaptNetHTTP(echoHandler()))
app.Get("/v1/commerce/health", func(c *zip.Ctx) error {
return c.String(200, "static-health")
})
// Exact static path: the later, more-specific static route wins.
if status, body := call(t, app, "GET", "/v1/commerce/health", ""); status != 200 || body != "static-health" {
t.Fatalf("GET /v1/commerce/health: status=%d body=%q, want 200 \"static-health\" (the mount must NOT shadow the later static route)", status, body)
}
// Any other subpath: the mount serves it.
if status, body := call(t, app, "GET", "/v1/commerce/orders", ""); status != 200 || !strings.Contains(body, "net/http path=/v1/commerce/orders") {
t.Fatalf("GET /v1/commerce/orders: status=%d body=%q, want the mount to serve it", status, body)
}
}
// TestAdaptNetHTTP_BodyAndPathIntact proves the adapted handler receives the request
// body and the full subpath + query unchanged through the wildcard mount.
func TestAdaptNetHTTP_BodyAndPathIntact(t *testing.T) {
app := zip.New(zip.Config{DisableStartupMessage: true})
app.All("/api/*", zip.AdaptNetHTTP(echoHandler()))
status, body := call(t, app, "POST", "/api/echo/42?q=hi", "payload-bytes")
if status != 200 {
t.Fatalf("POST /api/echo/42?q=hi: status %d, want 200 (body=%s)", status, body)
}
for _, want := range []string{"path=/api/echo/42", "query=q=hi", "body=payload-bytes"} {
if !strings.Contains(body, want) {
t.Fatalf("POST /api/echo/42?q=hi: response %q missing %q — request did not reach the handler intact", body, want)
}
}
}