Skip to content

Commit 26150d5

Browse files
committed
Consolidate idempotency tests
1 parent 670fbd5 commit 26150d5

File tree

2 files changed

+313
-5
lines changed

2 files changed

+313
-5
lines changed

middleware/idempotency/idempotency_test.go

Lines changed: 251 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
package idempotency_test
1+
package idempotency
22

33
import (
44
"errors"
5+
"fmt"
56
"io"
7+
"net/http"
68
"net/http/httptest"
79
"strconv"
810
"sync"
@@ -11,7 +13,6 @@ import (
1113
"time"
1214

1315
"github.com/gofiber/fiber/v3"
14-
"github.com/gofiber/fiber/v3/middleware/idempotency"
1516
"github.com/valyala/fasthttp"
1617

1718
"github.com/stretchr/testify/assert"
@@ -29,7 +30,7 @@ func Test_Idempotency(t *testing.T) {
2930
}
3031

3132
isMethodSafe := fiber.IsMethodSafe(c.Method())
32-
isIdempotent := idempotency.IsFromCache(c) || idempotency.WasPutToCache(c)
33+
isIdempotent := IsFromCache(c) || WasPutToCache(c)
3334
hasReqHeader := c.Get("X-Idempotency-Key") != ""
3435

3536
if isMethodSafe {
@@ -53,7 +54,7 @@ func Test_Idempotency(t *testing.T) {
5354
// Needs to be at least a second as the memory storage doesn't support shorter durations.
5455
const lifetime = 2 * time.Second
5556

56-
app.Use(idempotency.New(idempotency.Config{
57+
app.Use(New(Config{
5758
Lifetime: lifetime,
5859
}))
5960

@@ -136,7 +137,7 @@ func Benchmark_Idempotency(b *testing.B) {
136137
// Needs to be at least a second as the memory storage doesn't support shorter durations.
137138
const lifetime = 1 * time.Second
138139

139-
app.Use(idempotency.New(idempotency.Config{
140+
app.Use(New(Config{
140141
Lifetime: lifetime,
141142
}))
142143

@@ -169,3 +170,248 @@ func Benchmark_Idempotency(b *testing.B) {
169170
}
170171
})
171172
}
173+
174+
// ---------- additional tests (moved from config_extra_test.go and idempotency_additional_test.go) ----------
175+
176+
const validKey = "00000000-0000-0000-0000-000000000000"
177+
178+
func Test_configDefault_defaults(t *testing.T) {
179+
t.Parallel()
180+
181+
cfg := configDefault()
182+
require.NotNil(t, cfg.Lock)
183+
require.NotNil(t, cfg.Storage)
184+
require.Equal(t, ConfigDefault.Lifetime, cfg.Lifetime)
185+
require.Equal(t, ConfigDefault.KeyHeader, cfg.KeyHeader)
186+
require.Nil(t, cfg.KeepResponseHeaders)
187+
188+
app := fiber.New()
189+
190+
fctx := &fasthttp.RequestCtx{}
191+
fctx.Request.Header.SetMethod(fiber.MethodGet)
192+
ctx := app.AcquireCtx(fctx)
193+
assert.True(t, cfg.Next(ctx))
194+
app.ReleaseCtx(ctx)
195+
196+
fctx = &fasthttp.RequestCtx{}
197+
fctx.Request.Header.SetMethod(fiber.MethodPost)
198+
ctx = app.AcquireCtx(fctx)
199+
assert.False(t, cfg.Next(ctx))
200+
app.ReleaseCtx(ctx)
201+
202+
assert.NoError(t, cfg.KeyHeaderValidate(validKey))
203+
assert.Error(t, cfg.KeyHeaderValidate("short"))
204+
}
205+
206+
func Test_configDefault_override(t *testing.T) {
207+
t.Parallel()
208+
209+
l := &stubLock{}
210+
s := &stubStorage{}
211+
212+
cfg := configDefault(Config{
213+
Lifetime: 42 * time.Second,
214+
KeyHeader: "Foo",
215+
KeepResponseHeaders: []string{},
216+
Lock: l,
217+
Storage: s,
218+
})
219+
220+
require.Equal(t, 42*time.Second, cfg.Lifetime)
221+
require.Equal(t, "Foo", cfg.KeyHeader)
222+
require.Nil(t, cfg.KeepResponseHeaders)
223+
require.Equal(t, l, cfg.Lock)
224+
require.Equal(t, s, cfg.Storage)
225+
require.NotNil(t, cfg.Next)
226+
require.NotNil(t, cfg.KeyHeaderValidate)
227+
}
228+
229+
// helper to perform request
230+
func do(app *fiber.App, req *http.Request) (*http.Response, string) {
231+
resp, err := app.Test(req, fiber.TestConfig{Timeout: 5 * time.Second})
232+
if err != nil {
233+
panic(err)
234+
}
235+
body, _ := io.ReadAll(resp.Body)
236+
return resp, string(body)
237+
}
238+
239+
func Test_New_NextSkip(t *testing.T) {
240+
t.Parallel()
241+
app := fiber.New()
242+
var count int
243+
244+
app.Use(New(Config{Next: func(c fiber.Ctx) bool { return true }}))
245+
246+
app.Post("/", func(c fiber.Ctx) error {
247+
count++
248+
return c.SendString(fmt.Sprintf("%d", count))
249+
})
250+
251+
req := httptest.NewRequest(http.MethodPost, "/", nil)
252+
req.Header.Set(ConfigDefault.KeyHeader, validKey)
253+
_, body1 := do(app, req)
254+
255+
req2 := httptest.NewRequest(http.MethodPost, "/", nil)
256+
req2.Header.Set(ConfigDefault.KeyHeader, validKey)
257+
_, body2 := do(app, req2)
258+
259+
require.Equal(t, "1", body1)
260+
require.Equal(t, "2", body2)
261+
}
262+
263+
func Test_New_InvalidKey(t *testing.T) {
264+
t.Parallel()
265+
app := fiber.New()
266+
app.Use(New())
267+
app.Post("/", func(c fiber.Ctx) error { return nil })
268+
269+
req := httptest.NewRequest(http.MethodPost, "/", nil)
270+
req.Header.Set(ConfigDefault.KeyHeader, "bad")
271+
resp, body := do(app, req)
272+
273+
require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode)
274+
assert.Contains(t, body, "invalid length")
275+
}
276+
277+
func Test_New_StorageGetError(t *testing.T) {
278+
t.Parallel()
279+
app := fiber.New()
280+
s := &stubStorage{getErr: errors.New("boom")}
281+
app.Use(New(Config{Storage: s, Lock: &stubLock{}}))
282+
app.Post("/", func(c fiber.Ctx) error { return c.SendString("ok") })
283+
284+
req := httptest.NewRequest(http.MethodPost, "/", nil)
285+
req.Header.Set(ConfigDefault.KeyHeader, validKey)
286+
resp, body := do(app, req)
287+
288+
require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode)
289+
assert.Contains(t, body, "failed to write cached response at fastpath")
290+
}
291+
292+
func Test_New_UnmarshalError(t *testing.T) {
293+
t.Parallel()
294+
app := fiber.New()
295+
s := &stubStorage{data: map[string][]byte{validKey: []byte("bad")}}
296+
app.Use(New(Config{Storage: s, Lock: &stubLock{}}))
297+
app.Post("/", func(c fiber.Ctx) error { return c.SendString("ok") })
298+
299+
req := httptest.NewRequest(http.MethodPost, "/", nil)
300+
req.Header.Set(ConfigDefault.KeyHeader, validKey)
301+
resp, body := do(app, req)
302+
303+
require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode)
304+
assert.Contains(t, body, "failed to write cached response at fastpath")
305+
}
306+
307+
func Test_New_StoreRetrieve_FilterHeaders(t *testing.T) {
308+
t.Parallel()
309+
app := fiber.New()
310+
s := &stubStorage{}
311+
app.Use(New(Config{
312+
Storage: s,
313+
Lock: &stubLock{},
314+
KeepResponseHeaders: []string{"Foo"},
315+
}))
316+
317+
var count int
318+
app.Post("/", func(c fiber.Ctx) error {
319+
count++
320+
c.Set("Foo", "foo")
321+
c.Set("Bar", "bar")
322+
return c.SendString(fmt.Sprintf("resp%d", count))
323+
})
324+
325+
req := httptest.NewRequest(http.MethodPost, "/", nil)
326+
req.Header.Set(ConfigDefault.KeyHeader, validKey)
327+
resp, body := do(app, req)
328+
require.Equal(t, "resp1", body)
329+
require.Equal(t, "foo", resp.Header.Get("Foo"))
330+
require.Equal(t, "bar", resp.Header.Get("Bar"))
331+
332+
req2 := httptest.NewRequest(http.MethodPost, "/", nil)
333+
req2.Header.Set(ConfigDefault.KeyHeader, validKey)
334+
resp2, body2 := do(app, req2)
335+
require.Equal(t, "resp1", body2)
336+
require.Equal(t, "foo", resp2.Header.Get("Foo"))
337+
require.Empty(t, resp2.Header.Get("Bar"))
338+
require.Equal(t, 1, count)
339+
require.Equal(t, 1, s.setCount)
340+
}
341+
342+
func Test_New_HandlerError(t *testing.T) {
343+
t.Parallel()
344+
app := fiber.New()
345+
s := &stubStorage{}
346+
app.Use(New(Config{Storage: s, Lock: &stubLock{}}))
347+
app.Post("/", func(c fiber.Ctx) error { return errors.New("boom") })
348+
349+
req := httptest.NewRequest(http.MethodPost, "/", nil)
350+
req.Header.Set(ConfigDefault.KeyHeader, validKey)
351+
resp, body := do(app, req)
352+
require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode)
353+
require.Equal(t, "boom", body)
354+
require.Equal(t, 0, s.setCount)
355+
356+
resp2, body2 := do(app, req)
357+
require.Equal(t, fiber.StatusInternalServerError, resp2.StatusCode)
358+
require.Equal(t, "boom", body2)
359+
require.Equal(t, 0, s.setCount)
360+
}
361+
362+
func Test_New_LockError(t *testing.T) {
363+
t.Parallel()
364+
app := fiber.New()
365+
l := &stubLock{lockErr: errors.New("fail")}
366+
app.Use(New(Config{Lock: l, Storage: &stubStorage{}}))
367+
app.Post("/", func(c fiber.Ctx) error { return c.SendString("ok") })
368+
369+
req := httptest.NewRequest(http.MethodPost, "/", nil)
370+
req.Header.Set(ConfigDefault.KeyHeader, validKey)
371+
resp, body := do(app, req)
372+
require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode)
373+
assert.Contains(t, body, "failed to lock")
374+
}
375+
376+
func Test_New_StorageSetError(t *testing.T) {
377+
t.Parallel()
378+
app := fiber.New()
379+
s := &stubStorage{setErr: errors.New("nope")}
380+
app.Use(New(Config{Storage: s, Lock: &stubLock{}}))
381+
app.Post("/", func(c fiber.Ctx) error { return c.SendString("ok") })
382+
383+
req := httptest.NewRequest(http.MethodPost, "/", nil)
384+
req.Header.Set(ConfigDefault.KeyHeader, validKey)
385+
resp, body := do(app, req)
386+
require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode)
387+
assert.Contains(t, body, "failed to save response")
388+
}
389+
390+
func Test_New_UnlockError(t *testing.T) {
391+
t.Parallel()
392+
app := fiber.New()
393+
l := &stubLock{unlockErr: errors.New("u")}
394+
app.Use(New(Config{Lock: l, Storage: &stubStorage{}}))
395+
app.Post("/", func(c fiber.Ctx) error { return c.SendString("ok") })
396+
397+
req := httptest.NewRequest(http.MethodPost, "/", nil)
398+
req.Header.Set(ConfigDefault.KeyHeader, validKey)
399+
resp, body := do(app, req)
400+
require.Equal(t, fiber.StatusOK, resp.StatusCode)
401+
require.Equal(t, "ok", body)
402+
}
403+
404+
func Test_New_SecondPassReadError(t *testing.T) {
405+
t.Parallel()
406+
app := fiber.New()
407+
s := &stubStorage{}
408+
l := &stubLock{afterLock: func() { s.getErr = errors.New("g") }}
409+
app.Use(New(Config{Lock: l, Storage: s}))
410+
app.Post("/", func(c fiber.Ctx) error { return c.SendString("ok") })
411+
412+
req := httptest.NewRequest(http.MethodPost, "/", nil)
413+
req.Header.Set(ConfigDefault.KeyHeader, validKey)
414+
resp, body := do(app, req)
415+
require.Equal(t, fiber.StatusInternalServerError, resp.StatusCode)
416+
assert.Contains(t, body, "failed to write cached response while locked")
417+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package idempotency
2+
3+
import "time"
4+
5+
// stubLock implements Locker for testing purposes.
6+
type stubLock struct {
7+
lockErr error
8+
unlockErr error
9+
afterLock func()
10+
}
11+
12+
func (s *stubLock) Lock(string) error {
13+
if s.afterLock != nil {
14+
s.afterLock()
15+
}
16+
return s.lockErr
17+
}
18+
func (s *stubLock) Unlock(string) error { return s.unlockErr }
19+
20+
// stubStorage implements fiber.Storage for testing.
21+
type stubStorage struct {
22+
data map[string][]byte
23+
getErr error
24+
setErr error
25+
setCount int
26+
}
27+
28+
func (s *stubStorage) Get(key string) ([]byte, error) {
29+
if s.getErr != nil {
30+
return nil, s.getErr
31+
}
32+
if s.data == nil {
33+
return nil, nil
34+
}
35+
return s.data[key], nil
36+
}
37+
38+
func (s *stubStorage) Set(key string, val []byte, _ time.Duration) error {
39+
if s.setErr != nil {
40+
return s.setErr
41+
}
42+
if s.data == nil {
43+
s.data = make(map[string][]byte)
44+
}
45+
s.data[key] = val
46+
s.setCount++
47+
return nil
48+
}
49+
50+
func (s *stubStorage) Delete(key string) error {
51+
if s.data != nil {
52+
delete(s.data, key)
53+
}
54+
return nil
55+
}
56+
57+
func (s *stubStorage) Reset() error {
58+
s.data = make(map[string][]byte)
59+
return nil
60+
}
61+
62+
func (s *stubStorage) Close() error { return nil }

0 commit comments

Comments
 (0)