test(06-14): regression coverage for surf and bouncer gap closure
This commit is contained in:
@@ -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())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user