github.com/gofiber/fiber/v2@v2.47.0/middleware/cache/cache_test.go (about)

     1  // Special thanks to @codemicro for moving this to fiber core
     2  // Original middleware: github.com/codemicro/fiber-cache
     3  package cache
     4  
     5  import (
     6  	"bytes"
     7  	"fmt"
     8  	"io"
     9  	"math"
    10  	"net/http/httptest"
    11  	"os"
    12  	"strconv"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/gofiber/fiber/v2"
    17  	"github.com/gofiber/fiber/v2/internal/storage/memory"
    18  	"github.com/gofiber/fiber/v2/middleware/etag"
    19  	"github.com/gofiber/fiber/v2/utils"
    20  
    21  	"github.com/valyala/fasthttp"
    22  )
    23  
    24  func Test_Cache_CacheControl(t *testing.T) {
    25  	t.Parallel()
    26  
    27  	app := fiber.New()
    28  
    29  	app.Use(New(Config{
    30  		CacheControl: true,
    31  		Expiration:   10 * time.Second,
    32  	}))
    33  
    34  	app.Get("/", func(c *fiber.Ctx) error {
    35  		return c.SendString("Hello, World!")
    36  	})
    37  
    38  	_, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
    39  	utils.AssertEqual(t, nil, err)
    40  
    41  	resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
    42  	utils.AssertEqual(t, nil, err)
    43  	utils.AssertEqual(t, "public, max-age=10", resp.Header.Get(fiber.HeaderCacheControl))
    44  }
    45  
    46  func Test_Cache_Expired(t *testing.T) {
    47  	t.Parallel()
    48  
    49  	app := fiber.New()
    50  	app.Use(New(Config{Expiration: 2 * time.Second}))
    51  
    52  	app.Get("/", func(c *fiber.Ctx) error {
    53  		return c.SendString(fmt.Sprintf("%d", time.Now().UnixNano()))
    54  	})
    55  
    56  	resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
    57  	utils.AssertEqual(t, nil, err)
    58  	body, err := io.ReadAll(resp.Body)
    59  	utils.AssertEqual(t, nil, err)
    60  
    61  	// Sleep until the cache is expired
    62  	time.Sleep(3 * time.Second)
    63  
    64  	respCached, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
    65  	utils.AssertEqual(t, nil, err)
    66  	bodyCached, err := io.ReadAll(respCached.Body)
    67  	utils.AssertEqual(t, nil, err)
    68  
    69  	if bytes.Equal(body, bodyCached) {
    70  		t.Errorf("Cache should have expired: %s, %s", body, bodyCached)
    71  	}
    72  
    73  	// Next response should be also cached
    74  	respCachedNextRound, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
    75  	utils.AssertEqual(t, nil, err)
    76  	bodyCachedNextRound, err := io.ReadAll(respCachedNextRound.Body)
    77  	utils.AssertEqual(t, nil, err)
    78  
    79  	if !bytes.Equal(bodyCachedNextRound, bodyCached) {
    80  		t.Errorf("Cache should not have expired: %s, %s", bodyCached, bodyCachedNextRound)
    81  	}
    82  }
    83  
    84  func Test_Cache(t *testing.T) {
    85  	t.Parallel()
    86  
    87  	app := fiber.New()
    88  	app.Use(New())
    89  
    90  	app.Get("/", func(c *fiber.Ctx) error {
    91  		now := fmt.Sprintf("%d", time.Now().UnixNano())
    92  		return c.SendString(now)
    93  	})
    94  
    95  	req := httptest.NewRequest(fiber.MethodGet, "/", nil)
    96  	resp, err := app.Test(req)
    97  	utils.AssertEqual(t, nil, err)
    98  
    99  	cachedReq := httptest.NewRequest(fiber.MethodGet, "/", nil)
   100  	cachedResp, err := app.Test(cachedReq)
   101  	utils.AssertEqual(t, nil, err)
   102  
   103  	body, err := io.ReadAll(resp.Body)
   104  	utils.AssertEqual(t, nil, err)
   105  	cachedBody, err := io.ReadAll(cachedResp.Body)
   106  	utils.AssertEqual(t, nil, err)
   107  
   108  	utils.AssertEqual(t, cachedBody, body)
   109  }
   110  
   111  // go test -run Test_Cache_WithNoCacheRequestDirective
   112  func Test_Cache_WithNoCacheRequestDirective(t *testing.T) {
   113  	t.Parallel()
   114  
   115  	app := fiber.New()
   116  	app.Use(New())
   117  
   118  	app.Get("/", func(c *fiber.Ctx) error {
   119  		return c.SendString(c.Query("id", "1"))
   120  	})
   121  
   122  	// Request id = 1
   123  	req := httptest.NewRequest(fiber.MethodGet, "/", nil)
   124  	resp, err := app.Test(req)
   125  	utils.AssertEqual(t, nil, err)
   126  	body, err := io.ReadAll(resp.Body)
   127  	utils.AssertEqual(t, nil, err)
   128  	utils.AssertEqual(t, cacheMiss, resp.Header.Get("X-Cache"))
   129  	utils.AssertEqual(t, []byte("1"), body)
   130  	// Response cached, entry id = 1
   131  
   132  	// Request id = 2 without Cache-Control: no-cache
   133  	cachedReq := httptest.NewRequest(fiber.MethodGet, "/?id=2", nil)
   134  	cachedResp, err := app.Test(cachedReq)
   135  	utils.AssertEqual(t, nil, err)
   136  	cachedBody, err := io.ReadAll(cachedResp.Body)
   137  	utils.AssertEqual(t, nil, err)
   138  	utils.AssertEqual(t, cacheHit, cachedResp.Header.Get("X-Cache"))
   139  	utils.AssertEqual(t, []byte("1"), cachedBody)
   140  	// Response not cached, returns cached response, entry id = 1
   141  
   142  	// Request id = 2 with Cache-Control: no-cache
   143  	noCacheReq := httptest.NewRequest(fiber.MethodGet, "/?id=2", nil)
   144  	noCacheReq.Header.Set(fiber.HeaderCacheControl, noCache)
   145  	noCacheResp, err := app.Test(noCacheReq)
   146  	utils.AssertEqual(t, nil, err)
   147  	noCacheBody, err := io.ReadAll(noCacheResp.Body)
   148  	utils.AssertEqual(t, nil, err)
   149  	utils.AssertEqual(t, cacheMiss, noCacheResp.Header.Get("X-Cache"))
   150  	utils.AssertEqual(t, []byte("2"), noCacheBody)
   151  	// Response cached, returns updated response, entry = 2
   152  
   153  	/* Check Test_Cache_WithETagAndNoCacheRequestDirective */
   154  	// Request id = 2 with Cache-Control: no-cache again
   155  	noCacheReq1 := httptest.NewRequest(fiber.MethodGet, "/?id=2", nil)
   156  	noCacheReq1.Header.Set(fiber.HeaderCacheControl, noCache)
   157  	noCacheResp1, err := app.Test(noCacheReq1)
   158  	utils.AssertEqual(t, nil, err)
   159  	noCacheBody1, err := io.ReadAll(noCacheResp1.Body)
   160  	utils.AssertEqual(t, nil, err)
   161  	utils.AssertEqual(t, cacheMiss, noCacheResp1.Header.Get("X-Cache"))
   162  	utils.AssertEqual(t, []byte("2"), noCacheBody1)
   163  	// Response cached, returns updated response, entry = 2
   164  
   165  	// Request id = 1 without Cache-Control: no-cache
   166  	cachedReq1 := httptest.NewRequest(fiber.MethodGet, "/", nil)
   167  	cachedResp1, err := app.Test(cachedReq1)
   168  	utils.AssertEqual(t, nil, err)
   169  	cachedBody1, err := io.ReadAll(cachedResp1.Body)
   170  	utils.AssertEqual(t, nil, err)
   171  	utils.AssertEqual(t, cacheHit, cachedResp1.Header.Get("X-Cache"))
   172  	utils.AssertEqual(t, []byte("2"), cachedBody1)
   173  	// Response not cached, returns cached response, entry id = 2
   174  }
   175  
   176  // go test -run Test_Cache_WithETagAndNoCacheRequestDirective
   177  func Test_Cache_WithETagAndNoCacheRequestDirective(t *testing.T) {
   178  	t.Parallel()
   179  
   180  	app := fiber.New()
   181  	app.Use(
   182  		etag.New(),
   183  		New(),
   184  	)
   185  
   186  	app.Get("/", func(c *fiber.Ctx) error {
   187  		return c.SendString(c.Query("id", "1"))
   188  	})
   189  
   190  	// Request id = 1
   191  	req := httptest.NewRequest(fiber.MethodGet, "/", nil)
   192  	resp, err := app.Test(req)
   193  	utils.AssertEqual(t, nil, err)
   194  	utils.AssertEqual(t, cacheMiss, resp.Header.Get("X-Cache"))
   195  	utils.AssertEqual(t, fiber.StatusOK, resp.StatusCode)
   196  	// Response cached, entry id = 1
   197  
   198  	// If response status 200
   199  	etagToken := resp.Header.Get("Etag")
   200  
   201  	// Request id = 2 with ETag but without Cache-Control: no-cache
   202  	cachedReq := httptest.NewRequest(fiber.MethodGet, "/?id=2", nil)
   203  	cachedReq.Header.Set(fiber.HeaderIfNoneMatch, etagToken)
   204  	cachedResp, err := app.Test(cachedReq)
   205  	utils.AssertEqual(t, nil, err)
   206  	utils.AssertEqual(t, cacheHit, cachedResp.Header.Get("X-Cache"))
   207  	utils.AssertEqual(t, fiber.StatusNotModified, cachedResp.StatusCode)
   208  	// Response not cached, returns cached response, entry id = 1, status not modified
   209  
   210  	// Request id = 2 with ETag and Cache-Control: no-cache
   211  	noCacheReq := httptest.NewRequest(fiber.MethodGet, "/?id=2", nil)
   212  	noCacheReq.Header.Set(fiber.HeaderCacheControl, noCache)
   213  	noCacheReq.Header.Set(fiber.HeaderIfNoneMatch, etagToken)
   214  	noCacheResp, err := app.Test(noCacheReq)
   215  	utils.AssertEqual(t, nil, err)
   216  	utils.AssertEqual(t, cacheMiss, noCacheResp.Header.Get("X-Cache"))
   217  	utils.AssertEqual(t, fiber.StatusOK, noCacheResp.StatusCode)
   218  	// Response cached, returns updated response, entry id = 2
   219  
   220  	// If response status 200
   221  	etagToken = noCacheResp.Header.Get("Etag")
   222  
   223  	// Request id = 2 with ETag and Cache-Control: no-cache again
   224  	noCacheReq1 := httptest.NewRequest(fiber.MethodGet, "/?id=2", nil)
   225  	noCacheReq1.Header.Set(fiber.HeaderCacheControl, noCache)
   226  	noCacheReq1.Header.Set(fiber.HeaderIfNoneMatch, etagToken)
   227  	noCacheResp1, err := app.Test(noCacheReq1)
   228  	utils.AssertEqual(t, nil, err)
   229  	utils.AssertEqual(t, cacheMiss, noCacheResp1.Header.Get("X-Cache"))
   230  	utils.AssertEqual(t, fiber.StatusNotModified, noCacheResp1.StatusCode)
   231  	// Response cached, returns updated response, entry id = 2, status not modified
   232  
   233  	// Request id = 1 without ETag and Cache-Control: no-cache
   234  	cachedReq1 := httptest.NewRequest(fiber.MethodGet, "/", nil)
   235  	cachedResp1, err := app.Test(cachedReq1)
   236  	utils.AssertEqual(t, nil, err)
   237  	utils.AssertEqual(t, cacheHit, cachedResp1.Header.Get("X-Cache"))
   238  	utils.AssertEqual(t, fiber.StatusOK, cachedResp1.StatusCode)
   239  	// Response not cached, returns cached response, entry id = 2
   240  }
   241  
   242  // go test -run Test_Cache_WithNoStoreRequestDirective
   243  func Test_Cache_WithNoStoreRequestDirective(t *testing.T) {
   244  	t.Parallel()
   245  
   246  	app := fiber.New()
   247  	app.Use(New())
   248  
   249  	app.Get("/", func(c *fiber.Ctx) error {
   250  		return c.SendString(c.Query("id", "1"))
   251  	})
   252  
   253  	// Request id = 2
   254  	noStoreReq := httptest.NewRequest(fiber.MethodGet, "/?id=2", nil)
   255  	noStoreReq.Header.Set(fiber.HeaderCacheControl, noStore)
   256  	noStoreResp, err := app.Test(noStoreReq)
   257  	utils.AssertEqual(t, nil, err)
   258  	noStoreBody, err := io.ReadAll(noStoreResp.Body)
   259  	utils.AssertEqual(t, nil, err)
   260  	utils.AssertEqual(t, []byte("2"), noStoreBody)
   261  	// Response not cached, returns updated response
   262  }
   263  
   264  func Test_Cache_WithSeveralRequests(t *testing.T) {
   265  	t.Parallel()
   266  
   267  	app := fiber.New()
   268  
   269  	app.Use(New(Config{
   270  		CacheControl: true,
   271  		Expiration:   10 * time.Second,
   272  	}))
   273  
   274  	app.Get("/:id", func(c *fiber.Ctx) error {
   275  		return c.SendString(c.Params("id"))
   276  	})
   277  
   278  	for runs := 0; runs < 10; runs++ {
   279  		for i := 0; i < 10; i++ {
   280  			func(id int) {
   281  				rsp, err := app.Test(httptest.NewRequest(fiber.MethodGet, fmt.Sprintf("/%d", id), nil))
   282  				utils.AssertEqual(t, nil, err)
   283  
   284  				defer func(body io.ReadCloser) {
   285  					err := body.Close()
   286  					utils.AssertEqual(t, nil, err)
   287  				}(rsp.Body)
   288  
   289  				idFromServ, err := io.ReadAll(rsp.Body)
   290  				utils.AssertEqual(t, nil, err)
   291  
   292  				a, err := strconv.Atoi(string(idFromServ))
   293  				utils.AssertEqual(t, nil, err)
   294  
   295  				// SomeTimes,The id is not equal with a
   296  				utils.AssertEqual(t, id, a)
   297  			}(i)
   298  		}
   299  	}
   300  }
   301  
   302  func Test_Cache_Invalid_Expiration(t *testing.T) {
   303  	t.Parallel()
   304  
   305  	app := fiber.New()
   306  	cache := New(Config{Expiration: 0 * time.Second})
   307  	app.Use(cache)
   308  
   309  	app.Get("/", func(c *fiber.Ctx) error {
   310  		now := fmt.Sprintf("%d", time.Now().UnixNano())
   311  		return c.SendString(now)
   312  	})
   313  
   314  	req := httptest.NewRequest(fiber.MethodGet, "/", nil)
   315  	resp, err := app.Test(req)
   316  	utils.AssertEqual(t, nil, err)
   317  
   318  	cachedReq := httptest.NewRequest(fiber.MethodGet, "/", nil)
   319  	cachedResp, err := app.Test(cachedReq)
   320  	utils.AssertEqual(t, nil, err)
   321  
   322  	body, err := io.ReadAll(resp.Body)
   323  	utils.AssertEqual(t, nil, err)
   324  	cachedBody, err := io.ReadAll(cachedResp.Body)
   325  	utils.AssertEqual(t, nil, err)
   326  
   327  	utils.AssertEqual(t, cachedBody, body)
   328  }
   329  
   330  func Test_Cache_Get(t *testing.T) {
   331  	t.Parallel()
   332  
   333  	app := fiber.New()
   334  
   335  	app.Use(New())
   336  
   337  	app.Post("/", func(c *fiber.Ctx) error {
   338  		return c.SendString(c.Query("cache"))
   339  	})
   340  
   341  	app.Get("/get", func(c *fiber.Ctx) error {
   342  		return c.SendString(c.Query("cache"))
   343  	})
   344  
   345  	resp, err := app.Test(httptest.NewRequest(fiber.MethodPost, "/?cache=123", nil))
   346  	utils.AssertEqual(t, nil, err)
   347  	body, err := io.ReadAll(resp.Body)
   348  	utils.AssertEqual(t, nil, err)
   349  	utils.AssertEqual(t, "123", string(body))
   350  
   351  	resp, err = app.Test(httptest.NewRequest(fiber.MethodPost, "/?cache=12345", nil))
   352  	utils.AssertEqual(t, nil, err)
   353  	body, err = io.ReadAll(resp.Body)
   354  	utils.AssertEqual(t, nil, err)
   355  	utils.AssertEqual(t, "12345", string(body))
   356  
   357  	resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/get?cache=123", nil))
   358  	utils.AssertEqual(t, nil, err)
   359  	body, err = io.ReadAll(resp.Body)
   360  	utils.AssertEqual(t, nil, err)
   361  	utils.AssertEqual(t, "123", string(body))
   362  
   363  	resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/get?cache=12345", nil))
   364  	utils.AssertEqual(t, nil, err)
   365  	body, err = io.ReadAll(resp.Body)
   366  	utils.AssertEqual(t, nil, err)
   367  	utils.AssertEqual(t, "123", string(body))
   368  }
   369  
   370  func Test_Cache_Post(t *testing.T) {
   371  	t.Parallel()
   372  
   373  	app := fiber.New()
   374  
   375  	app.Use(New(Config{
   376  		Methods: []string{fiber.MethodPost},
   377  	}))
   378  
   379  	app.Post("/", func(c *fiber.Ctx) error {
   380  		return c.SendString(c.Query("cache"))
   381  	})
   382  
   383  	app.Get("/get", func(c *fiber.Ctx) error {
   384  		return c.SendString(c.Query("cache"))
   385  	})
   386  
   387  	resp, err := app.Test(httptest.NewRequest(fiber.MethodPost, "/?cache=123", nil))
   388  	utils.AssertEqual(t, nil, err)
   389  	body, err := io.ReadAll(resp.Body)
   390  	utils.AssertEqual(t, nil, err)
   391  	utils.AssertEqual(t, "123", string(body))
   392  
   393  	resp, err = app.Test(httptest.NewRequest(fiber.MethodPost, "/?cache=12345", nil))
   394  	utils.AssertEqual(t, nil, err)
   395  	body, err = io.ReadAll(resp.Body)
   396  	utils.AssertEqual(t, nil, err)
   397  	utils.AssertEqual(t, "123", string(body))
   398  
   399  	resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/get?cache=123", nil))
   400  	utils.AssertEqual(t, nil, err)
   401  	body, err = io.ReadAll(resp.Body)
   402  	utils.AssertEqual(t, nil, err)
   403  	utils.AssertEqual(t, "123", string(body))
   404  
   405  	resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/get?cache=12345", nil))
   406  	utils.AssertEqual(t, nil, err)
   407  	body, err = io.ReadAll(resp.Body)
   408  	utils.AssertEqual(t, nil, err)
   409  	utils.AssertEqual(t, "12345", string(body))
   410  }
   411  
   412  func Test_Cache_NothingToCache(t *testing.T) {
   413  	t.Parallel()
   414  
   415  	app := fiber.New()
   416  
   417  	app.Use(New(Config{Expiration: -(time.Second * 1)}))
   418  
   419  	app.Get("/", func(c *fiber.Ctx) error {
   420  		return c.SendString(time.Now().String())
   421  	})
   422  
   423  	resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
   424  	utils.AssertEqual(t, nil, err)
   425  	body, err := io.ReadAll(resp.Body)
   426  	utils.AssertEqual(t, nil, err)
   427  
   428  	time.Sleep(500 * time.Millisecond)
   429  
   430  	respCached, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
   431  	utils.AssertEqual(t, nil, err)
   432  	bodyCached, err := io.ReadAll(respCached.Body)
   433  	utils.AssertEqual(t, nil, err)
   434  
   435  	if bytes.Equal(body, bodyCached) {
   436  		t.Errorf("Cache should have expired: %s, %s", body, bodyCached)
   437  	}
   438  }
   439  
   440  func Test_Cache_CustomNext(t *testing.T) {
   441  	t.Parallel()
   442  
   443  	app := fiber.New()
   444  
   445  	app.Use(New(Config{
   446  		Next: func(c *fiber.Ctx) bool {
   447  			return c.Response().StatusCode() != fiber.StatusOK
   448  		},
   449  		CacheControl: true,
   450  	}))
   451  
   452  	app.Get("/", func(c *fiber.Ctx) error {
   453  		return c.SendString(time.Now().String())
   454  	})
   455  
   456  	app.Get("/error", func(c *fiber.Ctx) error {
   457  		return c.Status(fiber.StatusInternalServerError).SendString(time.Now().String())
   458  	})
   459  
   460  	resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
   461  	utils.AssertEqual(t, nil, err)
   462  	body, err := io.ReadAll(resp.Body)
   463  	utils.AssertEqual(t, nil, err)
   464  
   465  	respCached, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
   466  	utils.AssertEqual(t, nil, err)
   467  	bodyCached, err := io.ReadAll(respCached.Body)
   468  	utils.AssertEqual(t, nil, err)
   469  	utils.AssertEqual(t, true, bytes.Equal(body, bodyCached))
   470  	utils.AssertEqual(t, true, respCached.Header.Get(fiber.HeaderCacheControl) != "")
   471  
   472  	_, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/error", nil))
   473  	utils.AssertEqual(t, nil, err)
   474  
   475  	errRespCached, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/error", nil))
   476  	utils.AssertEqual(t, nil, err)
   477  	utils.AssertEqual(t, true, errRespCached.Header.Get(fiber.HeaderCacheControl) == "")
   478  }
   479  
   480  func Test_CustomKey(t *testing.T) {
   481  	t.Parallel()
   482  
   483  	app := fiber.New()
   484  	var called bool
   485  	app.Use(New(Config{KeyGenerator: func(c *fiber.Ctx) string {
   486  		called = true
   487  		return utils.CopyString(c.Path())
   488  	}}))
   489  
   490  	app.Get("/", func(c *fiber.Ctx) error {
   491  		return c.SendString("hi")
   492  	})
   493  
   494  	req := httptest.NewRequest(fiber.MethodGet, "/", nil)
   495  	_, err := app.Test(req)
   496  	utils.AssertEqual(t, nil, err)
   497  	utils.AssertEqual(t, true, called)
   498  }
   499  
   500  func Test_CustomExpiration(t *testing.T) {
   501  	t.Parallel()
   502  
   503  	app := fiber.New()
   504  	var called bool
   505  	var newCacheTime int
   506  	app.Use(New(Config{ExpirationGenerator: func(c *fiber.Ctx, cfg *Config) time.Duration {
   507  		called = true
   508  		var err error
   509  		newCacheTime, err = strconv.Atoi(c.GetRespHeader("Cache-Time", "600"))
   510  		utils.AssertEqual(t, nil, err)
   511  		return time.Second * time.Duration(newCacheTime)
   512  	}}))
   513  
   514  	app.Get("/", func(c *fiber.Ctx) error {
   515  		c.Response().Header.Add("Cache-Time", "1")
   516  		now := fmt.Sprintf("%d", time.Now().UnixNano())
   517  		return c.SendString(now)
   518  	})
   519  
   520  	resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
   521  	utils.AssertEqual(t, nil, err)
   522  	utils.AssertEqual(t, true, called)
   523  	utils.AssertEqual(t, 1, newCacheTime)
   524  
   525  	// Sleep until the cache is expired
   526  	time.Sleep(1 * time.Second)
   527  
   528  	cachedResp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
   529  	utils.AssertEqual(t, nil, err)
   530  
   531  	body, err := io.ReadAll(resp.Body)
   532  	utils.AssertEqual(t, nil, err)
   533  	cachedBody, err := io.ReadAll(cachedResp.Body)
   534  	utils.AssertEqual(t, nil, err)
   535  
   536  	if bytes.Equal(body, cachedBody) {
   537  		t.Errorf("Cache should have expired: %s, %s", body, cachedBody)
   538  	}
   539  
   540  	// Next response should be cached
   541  	cachedRespNextRound, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
   542  	utils.AssertEqual(t, nil, err)
   543  	cachedBodyNextRound, err := io.ReadAll(cachedRespNextRound.Body)
   544  	utils.AssertEqual(t, nil, err)
   545  
   546  	if !bytes.Equal(cachedBodyNextRound, cachedBody) {
   547  		t.Errorf("Cache should not have expired: %s, %s", cachedBodyNextRound, cachedBody)
   548  	}
   549  }
   550  
   551  func Test_AdditionalE2EResponseHeaders(t *testing.T) {
   552  	t.Parallel()
   553  
   554  	app := fiber.New()
   555  	app.Use(New(Config{
   556  		StoreResponseHeaders: true,
   557  	}))
   558  
   559  	app.Get("/", func(c *fiber.Ctx) error {
   560  		c.Response().Header.Add("X-Foobar", "foobar")
   561  		return c.SendString("hi")
   562  	})
   563  
   564  	req := httptest.NewRequest(fiber.MethodGet, "/", nil)
   565  	resp, err := app.Test(req)
   566  	utils.AssertEqual(t, nil, err)
   567  	utils.AssertEqual(t, "foobar", resp.Header.Get("X-Foobar"))
   568  
   569  	req = httptest.NewRequest(fiber.MethodGet, "/", nil)
   570  	resp, err = app.Test(req)
   571  	utils.AssertEqual(t, nil, err)
   572  	utils.AssertEqual(t, "foobar", resp.Header.Get("X-Foobar"))
   573  }
   574  
   575  func Test_CacheHeader(t *testing.T) {
   576  	t.Parallel()
   577  
   578  	app := fiber.New()
   579  
   580  	app.Use(New(Config{
   581  		Expiration: 10 * time.Second,
   582  		Next: func(c *fiber.Ctx) bool {
   583  			return c.Response().StatusCode() != fiber.StatusOK
   584  		},
   585  	}))
   586  
   587  	app.Get("/", func(c *fiber.Ctx) error {
   588  		return c.SendString("Hello, World!")
   589  	})
   590  
   591  	app.Post("/", func(c *fiber.Ctx) error {
   592  		return c.SendString(c.Query("cache"))
   593  	})
   594  
   595  	app.Get("/error", func(c *fiber.Ctx) error {
   596  		return c.Status(fiber.StatusInternalServerError).SendString(time.Now().String())
   597  	})
   598  
   599  	resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
   600  	utils.AssertEqual(t, nil, err)
   601  	utils.AssertEqual(t, cacheMiss, resp.Header.Get("X-Cache"))
   602  
   603  	resp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
   604  	utils.AssertEqual(t, nil, err)
   605  	utils.AssertEqual(t, cacheHit, resp.Header.Get("X-Cache"))
   606  
   607  	resp, err = app.Test(httptest.NewRequest(fiber.MethodPost, "/?cache=12345", nil))
   608  	utils.AssertEqual(t, nil, err)
   609  	utils.AssertEqual(t, cacheUnreachable, resp.Header.Get("X-Cache"))
   610  
   611  	errRespCached, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/error", nil))
   612  	utils.AssertEqual(t, nil, err)
   613  	utils.AssertEqual(t, cacheUnreachable, errRespCached.Header.Get("X-Cache"))
   614  }
   615  
   616  func Test_Cache_WithHead(t *testing.T) {
   617  	t.Parallel()
   618  
   619  	app := fiber.New()
   620  	app.Use(New())
   621  
   622  	app.Get("/", func(c *fiber.Ctx) error {
   623  		now := fmt.Sprintf("%d", time.Now().UnixNano())
   624  		return c.SendString(now)
   625  	})
   626  
   627  	req := httptest.NewRequest(fiber.MethodHead, "/", nil)
   628  	resp, err := app.Test(req)
   629  	utils.AssertEqual(t, nil, err)
   630  	utils.AssertEqual(t, cacheMiss, resp.Header.Get("X-Cache"))
   631  
   632  	cachedReq := httptest.NewRequest(fiber.MethodHead, "/", nil)
   633  	cachedResp, err := app.Test(cachedReq)
   634  	utils.AssertEqual(t, nil, err)
   635  	utils.AssertEqual(t, cacheHit, cachedResp.Header.Get("X-Cache"))
   636  
   637  	body, err := io.ReadAll(resp.Body)
   638  	utils.AssertEqual(t, nil, err)
   639  	cachedBody, err := io.ReadAll(cachedResp.Body)
   640  	utils.AssertEqual(t, nil, err)
   641  
   642  	utils.AssertEqual(t, cachedBody, body)
   643  }
   644  
   645  func Test_Cache_WithHeadThenGet(t *testing.T) {
   646  	t.Parallel()
   647  
   648  	app := fiber.New()
   649  	app.Use(New())
   650  	app.Get("/", func(c *fiber.Ctx) error {
   651  		return c.SendString(c.Query("cache"))
   652  	})
   653  
   654  	headResp, err := app.Test(httptest.NewRequest(fiber.MethodHead, "/?cache=123", nil))
   655  	utils.AssertEqual(t, nil, err)
   656  	headBody, err := io.ReadAll(headResp.Body)
   657  	utils.AssertEqual(t, nil, err)
   658  	utils.AssertEqual(t, "", string(headBody))
   659  	utils.AssertEqual(t, cacheMiss, headResp.Header.Get("X-Cache"))
   660  
   661  	headResp, err = app.Test(httptest.NewRequest(fiber.MethodHead, "/?cache=123", nil))
   662  	utils.AssertEqual(t, nil, err)
   663  	headBody, err = io.ReadAll(headResp.Body)
   664  	utils.AssertEqual(t, nil, err)
   665  	utils.AssertEqual(t, "", string(headBody))
   666  	utils.AssertEqual(t, cacheHit, headResp.Header.Get("X-Cache"))
   667  
   668  	getResp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/?cache=123", nil))
   669  	utils.AssertEqual(t, nil, err)
   670  	getBody, err := io.ReadAll(getResp.Body)
   671  	utils.AssertEqual(t, nil, err)
   672  	utils.AssertEqual(t, "123", string(getBody))
   673  	utils.AssertEqual(t, cacheMiss, getResp.Header.Get("X-Cache"))
   674  
   675  	getResp, err = app.Test(httptest.NewRequest(fiber.MethodGet, "/?cache=123", nil))
   676  	utils.AssertEqual(t, nil, err)
   677  	getBody, err = io.ReadAll(getResp.Body)
   678  	utils.AssertEqual(t, nil, err)
   679  	utils.AssertEqual(t, "123", string(getBody))
   680  	utils.AssertEqual(t, cacheHit, getResp.Header.Get("X-Cache"))
   681  }
   682  
   683  func Test_CustomCacheHeader(t *testing.T) {
   684  	t.Parallel()
   685  
   686  	app := fiber.New()
   687  
   688  	app.Use(New(Config{
   689  		CacheHeader: "Cache-Status",
   690  	}))
   691  
   692  	app.Get("/", func(c *fiber.Ctx) error {
   693  		return c.SendString("Hello, World!")
   694  	})
   695  
   696  	resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
   697  	utils.AssertEqual(t, nil, err)
   698  	utils.AssertEqual(t, cacheMiss, resp.Header.Get("Cache-Status"))
   699  }
   700  
   701  // Because time points are updated once every X milliseconds, entries in tests can often have
   702  // equal expiration times and thus be in an random order. This closure hands out increasing
   703  // time intervals to maintain strong ascending order of expiration
   704  func stableAscendingExpiration() func(c1 *fiber.Ctx, c2 *Config) time.Duration {
   705  	i := 0
   706  	return func(c1 *fiber.Ctx, c2 *Config) time.Duration {
   707  		i++
   708  		return time.Hour * time.Duration(i)
   709  	}
   710  }
   711  
   712  func Test_Cache_MaxBytesOrder(t *testing.T) {
   713  	t.Parallel()
   714  
   715  	app := fiber.New()
   716  	app.Use(New(Config{
   717  		MaxBytes:            2,
   718  		ExpirationGenerator: stableAscendingExpiration(),
   719  	}))
   720  
   721  	app.Get("/*", func(c *fiber.Ctx) error {
   722  		return c.SendString("1")
   723  	})
   724  
   725  	cases := [][]string{
   726  		// Insert a, b into cache of size 2 bytes (responses are 1 byte)
   727  		{"/a", cacheMiss},
   728  		{"/b", cacheMiss},
   729  		{"/a", cacheHit},
   730  		{"/b", cacheHit},
   731  		// Add c -> a evicted
   732  		{"/c", cacheMiss},
   733  		{"/b", cacheHit},
   734  		// Add a again -> b evicted
   735  		{"/a", cacheMiss},
   736  		{"/c", cacheHit},
   737  		// Add b -> c evicted
   738  		{"/b", cacheMiss},
   739  		{"/c", cacheMiss},
   740  	}
   741  
   742  	for idx, tcase := range cases {
   743  		rsp, err := app.Test(httptest.NewRequest(fiber.MethodGet, tcase[0], nil))
   744  		utils.AssertEqual(t, nil, err)
   745  		utils.AssertEqual(t, tcase[1], rsp.Header.Get("X-Cache"), fmt.Sprintf("Case %v", idx))
   746  	}
   747  }
   748  
   749  func Test_Cache_MaxBytesSizes(t *testing.T) {
   750  	t.Parallel()
   751  
   752  	app := fiber.New()
   753  
   754  	app.Use(New(Config{
   755  		MaxBytes:            7,
   756  		ExpirationGenerator: stableAscendingExpiration(),
   757  	}))
   758  
   759  	app.Get("/*", func(c *fiber.Ctx) error {
   760  		path := c.Context().URI().LastPathSegment()
   761  		size, err := strconv.Atoi(string(path))
   762  		utils.AssertEqual(t, nil, err)
   763  		return c.Send(make([]byte, size))
   764  	})
   765  
   766  	cases := [][]string{
   767  		{"/1", cacheMiss},
   768  		{"/2", cacheMiss},
   769  		{"/3", cacheMiss},
   770  		{"/4", cacheMiss}, // 1+2+3+4 > 7 => 1,2 are evicted now
   771  		{"/3", cacheHit},
   772  		{"/1", cacheMiss},
   773  		{"/2", cacheMiss},
   774  		{"/8", cacheUnreachable}, // too big to cache -> unreachable
   775  	}
   776  
   777  	for idx, tcase := range cases {
   778  		rsp, err := app.Test(httptest.NewRequest(fiber.MethodGet, tcase[0], nil))
   779  		utils.AssertEqual(t, nil, err)
   780  		utils.AssertEqual(t, tcase[1], rsp.Header.Get("X-Cache"), fmt.Sprintf("Case %v", idx))
   781  	}
   782  }
   783  
   784  // go test -v -run=^$ -bench=Benchmark_Cache -benchmem -count=4
   785  func Benchmark_Cache(b *testing.B) {
   786  	app := fiber.New()
   787  
   788  	app.Use(New())
   789  
   790  	app.Get("/demo", func(c *fiber.Ctx) error {
   791  		data, _ := os.ReadFile("../../.github/README.md") //nolint:errcheck // We're inside a benchmark
   792  		return c.Status(fiber.StatusTeapot).Send(data)
   793  	})
   794  
   795  	h := app.Handler()
   796  
   797  	fctx := &fasthttp.RequestCtx{}
   798  	fctx.Request.Header.SetMethod(fiber.MethodGet)
   799  	fctx.Request.SetRequestURI("/demo")
   800  
   801  	b.ReportAllocs()
   802  	b.ResetTimer()
   803  
   804  	for n := 0; n < b.N; n++ {
   805  		h(fctx)
   806  	}
   807  
   808  	utils.AssertEqual(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode())
   809  	utils.AssertEqual(b, true, len(fctx.Response.Body()) > 30000)
   810  }
   811  
   812  // go test -v -run=^$ -bench=Benchmark_Cache_Storage -benchmem -count=4
   813  func Benchmark_Cache_Storage(b *testing.B) {
   814  	app := fiber.New()
   815  
   816  	app.Use(New(Config{
   817  		Storage: memory.New(),
   818  	}))
   819  
   820  	app.Get("/demo", func(c *fiber.Ctx) error {
   821  		data, _ := os.ReadFile("../../.github/README.md") //nolint:errcheck // We're inside a benchmark
   822  		return c.Status(fiber.StatusTeapot).Send(data)
   823  	})
   824  
   825  	h := app.Handler()
   826  
   827  	fctx := &fasthttp.RequestCtx{}
   828  	fctx.Request.Header.SetMethod(fiber.MethodGet)
   829  	fctx.Request.SetRequestURI("/demo")
   830  
   831  	b.ReportAllocs()
   832  	b.ResetTimer()
   833  
   834  	for n := 0; n < b.N; n++ {
   835  		h(fctx)
   836  	}
   837  
   838  	utils.AssertEqual(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode())
   839  	utils.AssertEqual(b, true, len(fctx.Response.Body()) > 30000)
   840  }
   841  
   842  func Benchmark_Cache_AdditionalHeaders(b *testing.B) {
   843  	app := fiber.New()
   844  	app.Use(New(Config{
   845  		StoreResponseHeaders: true,
   846  	}))
   847  
   848  	app.Get("/demo", func(c *fiber.Ctx) error {
   849  		c.Response().Header.Add("X-Foobar", "foobar")
   850  		return c.SendStatus(418)
   851  	})
   852  
   853  	h := app.Handler()
   854  
   855  	fctx := &fasthttp.RequestCtx{}
   856  	fctx.Request.Header.SetMethod(fiber.MethodGet)
   857  	fctx.Request.SetRequestURI("/demo")
   858  
   859  	b.ReportAllocs()
   860  	b.ResetTimer()
   861  
   862  	for n := 0; n < b.N; n++ {
   863  		h(fctx)
   864  	}
   865  
   866  	utils.AssertEqual(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode())
   867  	utils.AssertEqual(b, []byte("foobar"), fctx.Response.Header.Peek("X-Foobar"))
   868  }
   869  
   870  func Benchmark_Cache_MaxSize(b *testing.B) {
   871  	// The benchmark is run with three different MaxSize parameters
   872  	// 1) 0:        Tracking is disabled = no overhead
   873  	// 2) MaxInt32: Enough to store all entries = no removals
   874  	// 3) 100:      Small size = constant insertions and removals
   875  	cases := []uint{0, math.MaxUint32, 100}
   876  	names := []string{"Disabled", "Unlim", "LowBounded"}
   877  	for i, size := range cases {
   878  		b.Run(names[i], func(b *testing.B) {
   879  			app := fiber.New()
   880  			app.Use(New(Config{MaxBytes: size}))
   881  
   882  			app.Get("/*", func(c *fiber.Ctx) error {
   883  				return c.Status(fiber.StatusTeapot).SendString("1")
   884  			})
   885  
   886  			h := app.Handler()
   887  			fctx := &fasthttp.RequestCtx{}
   888  			fctx.Request.Header.SetMethod(fiber.MethodGet)
   889  
   890  			b.ReportAllocs()
   891  			b.ResetTimer()
   892  
   893  			for n := 0; n < b.N; n++ {
   894  				fctx.Request.SetRequestURI(fmt.Sprintf("/%v", n))
   895  				h(fctx)
   896  			}
   897  
   898  			utils.AssertEqual(b, fiber.StatusTeapot, fctx.Response.Header.StatusCode())
   899  		})
   900  	}
   901  }