Files
zip/dockey_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

86 lines
3.4 KiB
Go

package zip_test
// Documentation is filed in ONE map for a whole process, and an address is
// unique only within the app that serves it. Two chains in one node both answer
// GET /height, so before the key carried the declaring package, whichever init()
// ran last published its prose for the other's route — in the document and in
// the MCP tool alike. Nothing failed; only the sentence was wrong, which is why
// it survived every test until someone read the output.
import (
"context"
"encoding/json"
"testing"
"github.com/zap-proto/zip"
)
type heightOut struct {
Height uint64 `json:"height"`
}
func aHeight(_ context.Context, _ *struct{}) (*heightOut, error) { return &heightOut{}, nil }
// bHeight is a SECOND handler at the same address, standing in for the second
// chain. Both are declared in this package, so this test cannot show the
// package doing the separating — TestTwoAppsKeepTheirOwnProse does that with
// the real thing. What it shows is the key being read at all.
func bHeight(_ context.Context, _ *struct{}) (*heightOut, error) { return &heightOut{}, nil }
func descriptionOf(t *testing.T, app *zip.App, path string) string {
t.Helper()
raw, err := json.Marshal(app.OpenAPISpec())
if err != nil {
t.Fatal(err)
}
var spec struct {
Paths map[string]map[string]struct {
Description string `json:"description"`
} `json:"paths"`
}
if err := json.Unmarshal(raw, &spec); err != nil {
t.Fatal(err)
}
return spec.Paths[path]["get"].Description
}
// TestProseIsFiledUnderItsPackage: two apps registering the same address each
// publish their OWN sentence, because the key names who declared it.
func TestProseIsFiledUnderItsPackage(t *testing.T) {
const here = "github.com/zap-proto/zip_test"
zip.Describe(zip.DocKey(here, "GET", "/height"), zip.Doc{Description: "The height this package means."})
zip.Describe(zip.DocKey("some/other/chain", "GET", "/height"), zip.Doc{Description: "A different chain's height."})
a := zip.New(zip.Config{AppName: "a", DisableStartupMessage: true})
a.Get("/height", aHeight)
if got := descriptionOf(t, a, "/height"); got != "The height this package means." {
t.Errorf("got %q, want this package's own sentence", got)
}
b := zip.New(zip.Config{AppName: "b", DisableStartupMessage: true})
b.Get("/height", bHeight)
if got := descriptionOf(t, b, "/height"); got != "The height this package means." {
t.Errorf("got %q — a handler declared here must read this package's key", got)
}
}
// TestAnUnqualifiedKeyIsStillRead: a generated file written before the key
// carried a package is still correct for a process serving one app, which is
// most of them. It keeps working, and a qualified key wins where both exist.
func TestAnUnqualifiedKeyIsStillRead(t *testing.T) {
zip.Describe("GET /legacy", zip.Doc{Description: "Filed the old way."})
a := zip.New(zip.Config{AppName: "legacy", DisableStartupMessage: true})
a.Get("/legacy", aHeight)
if got := descriptionOf(t, a, "/legacy"); got != "Filed the old way." {
t.Errorf("got %q, want the unqualified entry", got)
}
zip.Describe(zip.DocKey("github.com/zap-proto/zip_test", "GET", "/legacy"), zip.Doc{Description: "Filed under its package."})
b := zip.New(zip.Config{AppName: "legacy2", DisableStartupMessage: true})
b.Get("/legacy", aHeight)
if got := descriptionOf(t, b, "/legacy"); got != "Filed under its package." {
t.Errorf("got %q, want the qualified entry to win", got)
}
}