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

114 lines
2.8 KiB
Go

package zip_test
import (
"encoding/json"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// TestProseReachesAServiceInPackageMain runs cmd/zipdoc over a service whose
// handler is in package main, then asks the built program for its OpenAPI
// document. A test binary names package main by its import path, so only a real
// program shows whether the prose zipdoc filed is found at run time.
func TestProseReachesAServiceInPackageMain(t *testing.T) {
if testing.Short() {
t.Skip("compiles a program with the go toolchain")
}
zipDir, err := filepath.Abs(".")
if err != nil {
t.Fatal(err)
}
sum, err := os.ReadFile("go.sum")
if err != nil {
t.Fatal(err)
}
mod, err := os.ReadFile("go.mod")
if err != nil {
t.Fatal(err)
}
directive := ""
for _, line := range strings.Split(string(mod), "\n") {
if strings.HasPrefix(line, "go ") {
directive = line
break
}
}
dir := t.TempDir()
write := func(name, body string) {
if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0o644); err != nil {
t.Fatal(err)
}
}
write("go.mod", "module example.com/prose\n\n"+directive+
"\n\nrequire github.com/zap-proto/zip v1.36.47\n\nreplace github.com/zap-proto/zip => "+zipDir+"\n")
write("go.sum", string(sum))
write("main.go", proseMain)
run := func(args ...string) {
t.Helper()
cmd := exec.Command("go", args...)
cmd.Dir = dir
cmd.Env = append(os.Environ(), "GOFLAGS=-mod=mod", "GOPROXY=off")
out, err := cmd.CombinedOutput()
if err != nil {
if strings.Contains(string(out), "missing go.sum entry") || strings.Contains(string(out), "cannot find module") {
t.Skipf("module cache does not hold zip's dependencies: %s", out)
}
t.Fatalf("go %s: %v\n%s", strings.Join(args, " "), err, out)
}
}
run("run", "github.com/zap-proto/zip/cmd/zipdoc")
run("run", ".", "openapi", "openapi.json")
raw, err := os.ReadFile(filepath.Join(dir, "openapi.json"))
if err != nil {
t.Fatal(err)
}
var doc struct {
Paths map[string]map[string]struct {
Description string `json:"description"`
} `json:"paths"`
}
if err := json.Unmarshal(raw, &doc); err != nil {
t.Fatal(err)
}
if got := doc.Paths["/v1/hello"]["get"].Description; got != "Greets whoever asks." {
t.Errorf("description = %q, want the doc comment zipdoc read", got)
}
}
const proseMain = `package main
import (
"context"
"os"
"github.com/zap-proto/zip"
)
// Greeting is what Hello answers.
type Greeting struct {
Message string ` + "`json:\"message\"`" + `
}
// Empty takes nothing.
type Empty struct{}
// Hello greets whoever asks.
func Hello(context.Context, *Empty) (*Greeting, error) {
return &Greeting{Message: "hello"}, nil
}
func main() {
app := zip.New(zip.Config{AppName: "prose", DisableStartupMessage: true})
app.Get("/v1/hello", Hello)
if done, err := app.Described(); done && err != nil {
os.Exit(1)
}
}
`