Files
zip/claim_test.go
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

91 lines
3.1 KiB
Go

package zip_test
import (
"strings"
"testing"
"github.com/zap-proto/zip"
)
// Two definitions cannot own one address. That rule used to live in a private
// prefix-claim ledger (App.claim/release) that only Proxy and Load consulted;
// it now lives where every OTHER address collision is already decided — the
// walk, at build. One mechanism, so there is no second answer to disagree with
// the first, and the report is strictly better: every collision at once, with
// both claimants and their call sites named.
//
// The defect the old ledger closed is still closed, and these prove it.
func plugin(t *testing.T, name, prefix string) *zip.App {
t.Helper()
p, err := zip.Load(zip.Plugin{Name: name, Addr: "/run/zip/" + name + ".sock"}, prefix)
if err != nil {
t.Fatalf("Load(%s): %v", name, err)
}
return p
}
// A second definition claiming a held prefix is refused. Before, the second
// claim returned nil and the first registration kept answering, so the losing
// plugin's whole surface served the winner's 404 and nothing said so.
func TestASecondClaimOfAPrefixIsRefused(t *testing.T) {
app := zip.New(zip.Config{AppName: "host", DisableStartupMessage: true})
app.Use(plugin(t, "a", "/v1/x"), plugin(t, "b", "/v1/x"))
err := app.Build()
if err == nil {
t.Fatal("two definitions claiming /v1/x were both accepted")
}
for _, want := range []string{"/v1/x", `"a"`, `"b"`} {
if !strings.Contains(err.Error(), want) {
t.Errorf("refusal does not name %s: %v", want, err)
}
}
}
// A refused composition changes nothing: the live generation is whatever was
// valid, and a half-applied plugin is not a state that exists.
func TestARefusedCompositionIsRolledBack(t *testing.T) {
app := zip.New(zip.Config{AppName: "host", DisableStartupMessage: true})
app.Use(plugin(t, "first", "/v1/a"))
// Serve IS the build — generation 0. Include is a HOST verb.
h, err := zip.Serve(app, "127.0.0.1:0")
if err != nil {
t.Fatalf("Serve: %v", err)
}
defer func() { _ = h.Close() }()
if err := h.Include(plugin(t, "second", "/v1/a")); err == nil {
t.Fatal("a colliding plugin was included")
}
if n, _ := app.Generation(); n != 0 {
t.Errorf("a refused Include advanced the generation to %d", n)
}
// The plugin table still holds exactly the one that was valid.
names := []string{}
for _, p := range app.Plugins() {
names = append(names, p.Name)
}
if len(names) != 1 || names[0] != "first" {
t.Errorf("plugins = %v, want [first]", names)
}
}
// Proxy and Load are the same kind of thing now — definition constructors — so
// they collide with each other exactly as two Loads do. There is no separate
// register for either.
func TestProxyAndLoadCollideThroughOneMechanism(t *testing.T) {
remote, err := zip.Proxy("/v1/y", "/run/zip/y.sock")
if err != nil {
t.Fatalf("Proxy: %v", err)
}
app := zip.New(zip.Config{AppName: "host", DisableStartupMessage: true})
app.Use(remote, plugin(t, "z", "/v1/y"))
if err := app.Build(); err == nil {
t.Fatal("a Proxy and a Load claiming /v1/y were both accepted")
} else if !strings.Contains(err.Error(), "/v1/y") {
t.Errorf("refusal does not name the address: %v", err)
}
}