test(06-14): regression coverage for surf and bouncer gap closure

This commit is contained in:
Jakub Zych
2026-09-21 19:47:16 +02:00
parent 1d2e00cf59
commit f1218f2c84
5 changed files with 292 additions and 0 deletions

View File

@@ -179,3 +179,91 @@ func TestCompileRouteConflictReturnsError(t *testing.T) {
t.Fatal("expected conflict error")
}
}
type bodyProbePlugin struct {
id string
raw bool
use []string
mw map[string]pact.Middleware
routes func(pact.Router)
}
func (p bodyProbePlugin) ID() string { return p.id }
func (p bodyProbePlugin) Requires() []string { return nil }
func (p bodyProbePlugin) Register(*backpack.App) error { return nil }
func (p bodyProbePlugin) Boot(*backpack.App) error { return nil }
func (p bodyProbePlugin) Middlewares() map[string]pact.Middleware { return p.mw }
func (p bodyProbePlugin) Routes(r pact.Router) error {
open := r.Group
if p.raw {
open = r.GroupRaw
}
open("/", Use(p.use...), func(g pact.Router) {
g.Post("/x", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) })
})
return nil
}
func TestBodyLimitBoundsBodyConsumingMiddleware(t *testing.T) {
type result struct {
n int
err error
}
run := func(t *testing.T, raw bool, use []string, bodyLen int, after func()) (result, *httptest.ResponseRecorder) {
t.Helper()
var res result
reader := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, err := io.ReadAll(r.Body)
res = result{n: len(b), err: err}
if after != nil {
after()
}
next.ServeHTTP(w, r)
})
}
cfg := writeHTTPConfig(t, "body_limits:\n default_bytes: 4\n upload_bytes: 8\n")
h, err := Assemble(backpack.New(cfg), []party.Plugin{bodyProbePlugin{
id: "golem15.probe", raw: raw, use: use,
mw: map[string]pact.Middleware{"reader": reader},
}})
if err != nil {
t.Fatal(err)
}
rec := httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest(http.MethodPost, "/x", strings.NewReader(strings.Repeat("a", bodyLen))))
return res, rec
}
var maxErr *http.MaxBytesError
t.Run("default limit", func(t *testing.T) {
res, _ := run(t, false, []string{"reader"}, 10, nil)
if !errors.As(res.err, &maxErr) || res.n > 4 {
t.Fatalf("read %d bytes, err = %v", res.n, res.err)
}
})
t.Run("body.limit override raises cap", func(t *testing.T) {
res, _ := run(t, false, []string{"reader", "body.limit:6"}, 5, nil)
if res.err != nil || res.n != 5 {
t.Fatalf("read %d bytes, err = %v", res.n, res.err)
}
})
t.Run("body.limit override still bounds", func(t *testing.T) {
res, _ := run(t, false, []string{"reader", "body.limit:6"}, 10, nil)
if !errors.As(res.err, &maxErr) || res.n > 6 {
t.Fatalf("read %d bytes, err = %v", res.n, res.err)
}
})
t.Run("raw route unaffected", func(t *testing.T) {
res, rec := run(t, true, []string{"reader"}, 10, nil)
if res.err != nil || res.n != 10 || rec.Code != http.StatusOK {
t.Fatalf("read %d bytes, err = %v, code %d", res.n, res.err, rec.Code)
}
})
t.Run("panic after read still clean 500", func(t *testing.T) {
_, rec := run(t, false, []string{"reader"}, 10, func() { panic("boom") })
if rec.Code != http.StatusInternalServerError || strings.Contains(rec.Body.String(), "boom") {
t.Fatalf("code %d body %q", rec.Code, rec.Body.String())
}
})
}

View File

@@ -403,3 +403,71 @@ func TestRegisterBucketRejectsInvalidDefinitions(t *testing.T) {
t.Fatal("overflowing minutes accepted")
}
}
func TestRegisterBucketRejectsInvalid(t *testing.T) {
key := func(*http.Request) string { return "k" }
cases := []struct {
name string
store Store
b Bucket
}{
{"nil key", NewMemoryStore(time.Minute), Bucket{Max: 1, Decay: time.Minute}},
{"max zero", NewMemoryStore(time.Minute), Bucket{Max: 0, Decay: time.Minute, Key: key}},
{"max negative", NewMemoryStore(time.Minute), Bucket{Max: -1, Decay: time.Minute, Key: key}},
{"decay zero", NewMemoryStore(time.Minute), Bucket{Max: 1, Key: key}},
{"decay negative", NewMemoryStore(time.Minute), Bucket{Max: 1, Decay: -time.Second, Key: key}},
{"nil store", nil, Bucket{Max: 1, Decay: time.Minute, Key: key}},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
l := NewFixedWindowLimiter(tc.store, nil)
err := l.RegisterBucket("golem15.p", "bkt", tc.b)
if err == nil || !strings.Contains(err.Error(), "golem15.p") || !strings.Contains(err.Error(), "bkt") {
t.Fatalf("err = %v", err)
}
})
}
}
func TestValidateThrottleRejectsOverflowAndNilStore(t *testing.T) {
l := NewFixedWindowLimiter(NewMemoryStore(time.Minute), nil)
for _, p := range []string{"1,9223372036854775807", "0,1", "1,0", "-1,1", "x,y", "nope"} {
if err := l.ValidateThrottle(p); err == nil {
t.Errorf("%q accepted", p)
}
}
if err := l.ValidateThrottle("5,1"); err != nil {
t.Fatal(err)
}
if err := NewFixedWindowLimiter(nil, nil).ValidateThrottle("5,1"); err == nil {
t.Fatal("nil store accepted")
}
var nilLim *FixedWindowLimiter
if err := nilLim.ValidateThrottle("5,1"); err == nil {
t.Fatal("nil limiter accepted")
}
}
func TestMiddlewareFailsClosed(t *testing.T) {
var nilLim *FixedWindowLimiter
cases := map[string]*FixedWindowLimiter{
"unknown bucket": NewFixedWindowLimiter(NewMemoryStore(time.Minute), nil),
"nil store": NewFixedWindowLimiter(nil, nil),
"nil limiter": nilLim,
}
params := map[string]string{"unknown bucket": "missing", "nil store": "5,1", "nil limiter": "5,1"}
for name, l := range cases {
t.Run(name, func(t *testing.T) {
called := false
h := l.Middleware(params[name])(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
called = true
w.WriteHeader(http.StatusNoContent)
}))
rec := httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil))
if called || rec.Code != http.StatusInternalServerError {
t.Fatalf("called=%v code=%d", called, rec.Code)
}
})
}
}

View File

@@ -480,3 +480,55 @@ func TestDuplicateMiddlewareFactoryNamesPluginAndName(t *testing.T) {
t.Fatalf("want plugin and factory name in error, got %v", err)
}
}
func TestBuildRouterFailsOnMissingBodyConfig(t *testing.T) {
bad := map[string]string{
"missing default": "body_limits:\n upload_bytes: 8\n",
"missing upload": "body_limits:\n default_bytes: 8\n",
"zero": "body_limits:\n default_bytes: 0\n upload_bytes: 8\n",
"negative": "body_limits:\n default_bytes: 4\n upload_bytes: -1\n",
"non-numeric": "body_limits:\n default_bytes: lots\n upload_bytes: 8\n",
}
for name, y := range bad {
t.Run(name, func(t *testing.T) {
if _, err := BuildRouter(backpack.New(writeHTTPConfig(t, y)), nil); err == nil {
t.Fatal("expected error")
}
})
}
if _, err := BuildRouter(backpack.New(writeHTTPConfig(t, "body_limits:\n default_bytes: 4\n upload_bytes: 8\n")), nil); err != nil {
t.Fatal(err)
}
}
type factoryCountPlugin struct{ calls *map[string]int }
func (p factoryCountPlugin) ID() string { return "golem15.count" }
func (p factoryCountPlugin) Requires() []string { return nil }
func (p factoryCountPlugin) Register(*backpack.App) error { return nil }
func (p factoryCountPlugin) Boot(*backpack.App) error { return nil }
func (p factoryCountPlugin) MiddlewareFactories() map[string]func(string) pact.Middleware {
return map[string]func(string) pact.Middleware{"count": func(param string) pact.Middleware {
(*p.calls)[param]++
return func(next http.Handler) http.Handler { return next }
}}
}
func (p factoryCountPlugin) Routes(r pact.Router) error {
r.Group("/", Use("count:a"), func(g pact.Router) {
g.Get("/one", func(http.ResponseWriter, *http.Request) {})
g.Get("/two", func(http.ResponseWriter, *http.Request) {})
g.Get("/three", func(http.ResponseWriter, *http.Request) {}, "count:b")
})
return nil
}
func TestFactoriesBuiltOncePerName(t *testing.T) {
calls := map[string]int{}
cfg := writeHTTPConfig(t, "body_limits:\n default_bytes: 4\n upload_bytes: 8\n")
if _, err := Assemble(backpack.New(cfg), []party.Plugin{factoryCountPlugin{calls: &calls}}); err != nil {
t.Fatal(err)
}
if calls["a"] != 1 || calls["b"] != 1 {
t.Fatalf("factory invocations = %v, want each name:param built once", calls)
}
}