// Special thanks to @codemicro for moving this to fiber core // Original middleware: github.com/codemicro/fiber-cache package cache import ( "bytes" "fmt" "io/ioutil" "net/http" "net/http/httptest" "sync" "testing" "time" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/utils" "github.com/valyala/fasthttp" ) func Test_Cache_CacheControl(t *testing.T) { app := fiber.New() app.Use(New(Config{ CacheControl: true, Expiration: 10 * time.Second, })) app.Get("/", func(c *fiber.Ctx) error { return c.SendString("Hello, World!") }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) utils.AssertEqual(t, nil, err) resp, err = app.Test(httptest.NewRequest("GET", "/", nil)) utils.AssertEqual(t, nil, err) utils.AssertEqual(t, "public, max-age=10", resp.Header.Get(fiber.HeaderCacheControl)) } func Test_Cache_Expired(t *testing.T) { app := fiber.New() expiration := 1 * time.Second app.Use(New(Config{ Expiration: expiration, })) app.Get("/", func(c *fiber.Ctx) error { return c.SendString(fmt.Sprintf("%d", time.Now().UnixNano())) }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) utils.AssertEqual(t, nil, err) body, err := ioutil.ReadAll(resp.Body) utils.AssertEqual(t, nil, err) // Sleep until the cache is expired time.Sleep(expiration) respCached, err := app.Test(httptest.NewRequest("GET", "/", nil)) utils.AssertEqual(t, nil, err) bodyCached, err := ioutil.ReadAll(respCached.Body) utils.AssertEqual(t, nil, err) if bytes.Equal(body, bodyCached) { t.Errorf("Cache should have expired: %s, %s", body, bodyCached) } } func Test_Cache(t *testing.T) { app := fiber.New() app.Use(New()) app.Get("/", func(c *fiber.Ctx) error { now := fmt.Sprintf("%d", time.Now().UnixNano()) return c.SendString(now) }) req := httptest.NewRequest("GET", "/", nil) resp, err := app.Test(req) utils.AssertEqual(t, nil, err) cachedReq := httptest.NewRequest("GET", "/", nil) cachedResp, err := app.Test(cachedReq) utils.AssertEqual(t, nil, err) body, err := ioutil.ReadAll(resp.Body) utils.AssertEqual(t, nil, err) cachedBody, err := ioutil.ReadAll(cachedResp.Body) utils.AssertEqual(t, nil, err) utils.AssertEqual(t, cachedBody, body) } // go test -run Test_Cache_Concurrency_Store -race -v func Test_Cache_Concurrency_Store(t *testing.T) { // Test concurrency using a custom store app := fiber.New() app.Use(New(Config{ Store: testStore{stmap: map[string][]byte{}, mutex: &sync.RWMutex{}}, })) app.Get("/", func(c *fiber.Ctx) error { return c.SendString("Hello tester!") }) var wg sync.WaitGroup singleRequest := func(wg *sync.WaitGroup) { defer wg.Done() resp, err := app.Test(httptest.NewRequest(http.MethodGet, "/", nil)) utils.AssertEqual(t, nil, err) utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode) body, err := ioutil.ReadAll(resp.Body) utils.AssertEqual(t, nil, err) utils.AssertEqual(t, "Hello tester!", string(body)) } for i := 0; i <= 49; i++ { wg.Add(1) go singleRequest(&wg) } wg.Wait() req := httptest.NewRequest("GET", "/", nil) resp, err := app.Test(req) utils.AssertEqual(t, nil, err) cachedReq := httptest.NewRequest("GET", "/", nil) cachedResp, err := app.Test(cachedReq) utils.AssertEqual(t, nil, err) body, err := ioutil.ReadAll(resp.Body) utils.AssertEqual(t, nil, err) cachedBody, err := ioutil.ReadAll(cachedResp.Body) utils.AssertEqual(t, nil, err) utils.AssertEqual(t, cachedBody, body) } func Test_Cache_Invalid_Expiration(t *testing.T) { app := fiber.New() cache := New(Config{Expiration: 0 * time.Second}) app.Use(cache) app.Get("/", func(c *fiber.Ctx) error { now := fmt.Sprintf("%d", time.Now().UnixNano()) return c.SendString(now) }) req := httptest.NewRequest("GET", "/", nil) resp, err := app.Test(req) utils.AssertEqual(t, nil, err) cachedReq := httptest.NewRequest("GET", "/", nil) cachedResp, err := app.Test(cachedReq) utils.AssertEqual(t, nil, err) body, err := ioutil.ReadAll(resp.Body) utils.AssertEqual(t, nil, err) cachedBody, err := ioutil.ReadAll(cachedResp.Body) utils.AssertEqual(t, nil, err) utils.AssertEqual(t, cachedBody, body) } func Test_Cache_Invalid_Method(t *testing.T) { app := fiber.New() app.Use(New()) app.Post("/", func(c *fiber.Ctx) error { return c.SendString(c.Query("cache")) }) app.Get("/get", func(c *fiber.Ctx) error { return c.SendString(c.Query("cache")) }) resp, err := app.Test(httptest.NewRequest("POST", "/?cache=123", nil)) utils.AssertEqual(t, nil, err) body, err := ioutil.ReadAll(resp.Body) utils.AssertEqual(t, nil, err) utils.AssertEqual(t, "123", string(body)) resp, err = app.Test(httptest.NewRequest("POST", "/?cache=12345", nil)) utils.AssertEqual(t, nil, err) body, err = ioutil.ReadAll(resp.Body) utils.AssertEqual(t, nil, err) utils.AssertEqual(t, "12345", string(body)) resp, err = app.Test(httptest.NewRequest("GET", "/get?cache=123", nil)) utils.AssertEqual(t, nil, err) body, err = ioutil.ReadAll(resp.Body) utils.AssertEqual(t, nil, err) utils.AssertEqual(t, "123", string(body)) resp, err = app.Test(httptest.NewRequest("GET", "/get?cache=12345", nil)) utils.AssertEqual(t, nil, err) body, err = ioutil.ReadAll(resp.Body) utils.AssertEqual(t, nil, err) utils.AssertEqual(t, "123", string(body)) } func Test_Cache_NothingToCache(t *testing.T) { app := fiber.New() app.Use(New(Config{Expiration: -(time.Second * 1)})) app.Get("/", func(c *fiber.Ctx) error { return c.SendString(time.Now().String()) }) resp, err := app.Test(httptest.NewRequest("GET", "/", nil)) utils.AssertEqual(t, nil, err) body, err := ioutil.ReadAll(resp.Body) utils.AssertEqual(t, nil, err) time.Sleep(500 * time.Millisecond) respCached, err := app.Test(httptest.NewRequest("GET", "/", nil)) utils.AssertEqual(t, nil, err) bodyCached, err := ioutil.ReadAll(respCached.Body) utils.AssertEqual(t, nil, err) if bytes.Equal(body, bodyCached) { t.Errorf("Cache should have expired: %s, %s", body, bodyCached) } } // go test -v -run=^$ -bench=Benchmark_Cache -benchmem -count=4 func Benchmark_Cache(b *testing.B) { app := fiber.New() app.Use(New()) app.Get("/", func(c *fiber.Ctx) error { data, err := ioutil.ReadFile("../../.github/README.md") utils.AssertEqual(b, nil, err) return c.Send(data) }) h := app.Handler() fctx := &fasthttp.RequestCtx{} fctx.Request.Header.SetMethod("GET") fctx.Request.SetRequestURI("/") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { h(fctx) } utils.AssertEqual(b, fiber.StatusOK, fctx.Response.Header.StatusCode()) } // go test -v -run=^$ -bench=Benchmark_Cache_Store -benchmem -count=4 func Benchmark_Cache_Store(b *testing.B) { app := fiber.New() app.Use(New(Config{ Store: testStore{stmap: map[string][]byte{}, mutex: &sync.RWMutex{}}, })) app.Get("/", func(c *fiber.Ctx) error { data, err := ioutil.ReadFile("../../.github/README.md") utils.AssertEqual(b, nil, err) return c.Send(data) }) h := app.Handler() fctx := &fasthttp.RequestCtx{} fctx.Request.Header.SetMethod("GET") fctx.Request.SetRequestURI("/") b.ReportAllocs() b.ResetTimer() for n := 0; n < b.N; n++ { h(fctx) } utils.AssertEqual(b, fiber.StatusOK, fctx.Response.Header.StatusCode()) } // testStore is used for testing custom stores type testStore struct { stmap map[string][]byte mutex *sync.RWMutex } func (s testStore) Get(id string) ([]byte, error) { s.mutex.RLock() val, ok := s.stmap[id] s.mutex.RUnlock() if !ok { return []byte{}, nil } else { return val, nil } } func (s testStore) Set(id string, val []byte, _ time.Duration) error { s.mutex.Lock() s.stmap[id] = val s.mutex.Unlock() return nil } func (s testStore) Clear() error { s.stmap = map[string][]byte{} return nil } func (s testStore) Delete(id string) error { s.mutex.Lock() delete(s.stmap, id) s.mutex.Unlock() return nil }