mirror of
https://github.com/zap-proto/zip.git
synced 2026-10-19 15:38:57 +00:00
Three things a consumer migration surfaced. HEAD shadows. Declaration filtered ALL HEAD and OPTIONS, which also deleted an EXPLICITLY declared HEAD — a real door a host would then answer 405 on, which is the analytics-outage class this type exists to prevent. The doctrine was right and the mechanism was wrong: a declaration now projects the PROGRAM, not the materialised router, so shadows never appear (nothing wrote them) and declared HEAD does. App.All is the one entry that is genuinely many doors, so it expands. Consumers enumerating routes should read Declaration, not Fiber().GetRoutes — fiber's own method cannot apply a doctrine zip owns. Fiber() escapes. Fiber() hands out the CURRENT generation's router, and a generation is a projection, so app.Fiber().Use(cors.New(...)) was silently discarded by the next build — no error, no CORS headers. A doc line is weak medicine for a lost CORS policy, so the generation records its route count and a rebuild that finds more refuses, naming the count and pointing at app.Use. Listen. Serve is the one serving mechanism and Listen is now literally Serve followed by Wait — sugar, not a second path. The no-handle shape is deliberate and documented: a fixed program wants the terminal form, anything that loads at run time wants the handle. And a bug of my own, caught by TestLoad_EmbeddedBinary: the first cut of Serve left h.errc nil, so Wait returned immediately and Listen stopped serving at all. The test I wrote for Listen passed against that, so it has been strengthened to dial the socket and assert Listen has NOT returned — verified to fail without the fix. Co-authored-by: Hanzo Dev <[email protected]>
67 lines
2.1 KiB
Go
67 lines
2.1 KiB
Go
package zip
|
|
|
|
import (
|
|
"context"
|
|
|
|
fiber "github.com/zap-proto/fiber/v3"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// The footgun a consumer migration found, guarded.
|
|
//
|
|
// hanzoai/playground had s.Router.Fiber().Use(cors.New(...)). Fiber() returns
|
|
// the CURRENT generation's router, and a generation is a projection — so the
|
|
// next registration materialised a fresh one and the CORS middleware was
|
|
// silently discarded. No error, no panic, no CORS headers on any response.
|
|
func TestFiberEscape_ForeignRegistrationIsRefusedNotDiscarded(t *testing.T) {
|
|
app := quiet("host")
|
|
app.Get("/live", func(c *Ctx) error { return c.String(200, "live") })
|
|
h, err := host2(app)
|
|
if err != nil {
|
|
t.Fatalf("host: %v", err)
|
|
}
|
|
|
|
// The mistake: registering on the value Fiber() handed out.
|
|
app.Fiber().Get("/smuggled", func(fc fiber.Ctx) error { return fc.SendString("smuggled") })
|
|
|
|
// The next composition must REFUSE rather than silently drop it.
|
|
defer func() {
|
|
r := recover()
|
|
if r == nil {
|
|
t.Fatal("a rebuild discarded a route registered on Fiber() without saying so")
|
|
}
|
|
msg, _ := r.(string)
|
|
for _, want := range []string{"App.Fiber()", "discard", "app.Use(mw)"} {
|
|
if !strings.Contains(msg, want) {
|
|
t.Errorf("refusal does not mention %q: %s", want, msg)
|
|
}
|
|
}
|
|
}()
|
|
later := quiet("later")
|
|
later.Get("/later", func(c *Ctx) error { return nil })
|
|
_ = h.Include(later)
|
|
}
|
|
|
|
// The guard must not fire on zip's own work: control routes, plugin mounts and
|
|
// ordinary composition all rebuild without tripping it.
|
|
func TestFiberEscape_OrdinaryCompositionDoesNotTripTheGuard(t *testing.T) {
|
|
app := quiet("host")
|
|
Get(app, "/v1/thing", func(ctx1 context.Context, in *invoiceIn) (*invoiceOut, error) { return &invoiceOut{}, nil },
|
|
WithOperationID("thing"))
|
|
h, err := host2(app)
|
|
if err != nil {
|
|
t.Fatalf("host: %v", err)
|
|
}
|
|
for i := 0; i < 3; i++ {
|
|
p := quiet("p" + itoa(i))
|
|
p.Get("/p"+itoa(i), func(c *Ctx) error { return nil })
|
|
if err := h.Include(p); err != nil {
|
|
t.Fatalf("Include %d: %v", i, err)
|
|
}
|
|
}
|
|
if n := h.Generation(); n != 3 {
|
|
t.Errorf("generation = %d, want 3", n)
|
|
}
|
|
}
|