// ⚡️ Fiber is an Express inspired web framework written in Go with ☕️
// 🤖 Github Repository: https://github.com/gofiber/fiber
// 📌 API Documentation: https://docs.gofiber.io

//nolint:bodyclose // Much easier to just ignore memory leaks in tests
package fiber

import (
	"bufio"
	"bytes"
	"compress/gzip"
	"compress/zlib"
	"context"
	"crypto/tls"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"mime/multipart"
	"net"
	"net/http/httptest"
	"net/url"
	"os"
	"path/filepath"
	"reflect"
	"strconv"
	"strings"
	"testing"
	"text/template"
	"time"

	"github.com/gofiber/fiber/v2/internal/storage/memory"
	"github.com/gofiber/fiber/v2/utils"

	"github.com/valyala/bytebufferpool"
	"github.com/valyala/fasthttp"
)

// go test -run Test_Ctx_Accepts
func Test_Ctx_Accepts(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderAccept, "text/html,application/xhtml+xml,application/xml;q=0.9")
	utils.AssertEqual(t, "", c.Accepts(""))
	utils.AssertEqual(t, "", c.Accepts())
	utils.AssertEqual(t, ".xml", c.Accepts(".xml"))
	utils.AssertEqual(t, "", c.Accepts(".john"))
	utils.AssertEqual(t, "application/xhtml+xml", c.Accepts("application/xml", "application/xml+rss", "application/yaml", "application/xhtml+xml"), "must use client-preferred mime type")

	c.Request().Header.Set(HeaderAccept, "application/json, text/plain, */*;q=0")
	utils.AssertEqual(t, "", c.Accepts("html"), "must treat */*;q=0 as not acceptable")

	c.Request().Header.Set(HeaderAccept, "text/*, application/json")
	utils.AssertEqual(t, "html", c.Accepts("html"))
	utils.AssertEqual(t, "text/html", c.Accepts("text/html"))
	utils.AssertEqual(t, "json", c.Accepts("json", "text"))
	utils.AssertEqual(t, "application/json", c.Accepts("application/json"))
	utils.AssertEqual(t, "", c.Accepts("image/png"))
	utils.AssertEqual(t, "", c.Accepts("png"))

	c.Request().Header.Set(HeaderAccept, "text/html, application/json")
	utils.AssertEqual(t, "text/*", c.Accepts("text/*"))

	c.Request().Header.Set(HeaderAccept, "*/*")
	utils.AssertEqual(t, "html", c.Accepts("html"))
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Accepts -benchmem -count=4
func Benchmark_Ctx_Accepts(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	acceptHeader := "text/html,application/xhtml+xml,application/xml;q=0.9"
	c.Request().Header.Set("Accept", acceptHeader)
	acceptValues := [][]string{
		{".xml"},
		{"json", "xml"},
		{"application/json", "application/xml"},
	}
	expectedResults := []string{".xml", "xml", "application/xml"}

	for i := 0; i < len(acceptValues); i++ {
		b.Run(fmt.Sprintf("run-%#v", acceptValues[i]), func(bb *testing.B) {
			var res string
			bb.ReportAllocs()
			bb.ResetTimer()

			for n := 0; n < bb.N; n++ {
				res = c.Accepts(acceptValues[i]...)
			}
			utils.AssertEqual(bb, expectedResults[i], res)
		})
	}
}

// go test -run Test_Ctx_Accepts_EmptyAccept
func Test_Ctx_Accepts_EmptyAccept(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	utils.AssertEqual(t, ".forwarded", c.Accepts(".forwarded"))
}

// go test -run Test_Ctx_Accepts_Wildcard
func Test_Ctx_Accepts_Wildcard(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderAccept, "*/*;q=0.9")
	utils.AssertEqual(t, "html", c.Accepts("html"))
	utils.AssertEqual(t, "foo", c.Accepts("foo"))
	utils.AssertEqual(t, ".bar", c.Accepts(".bar"))
	c.Request().Header.Set(HeaderAccept, "text/html,application/*;q=0.9")
	utils.AssertEqual(t, "xml", c.Accepts("xml"))
}

// go test -run Test_Ctx_AcceptsCharsets
func Test_Ctx_AcceptsCharsets(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderAcceptCharset, "utf-8, iso-8859-1;q=0.5")
	utils.AssertEqual(t, "utf-8", c.AcceptsCharsets("utf-8"))
}

// go test -v -run=^$ -bench=Benchmark_Ctx_AcceptsCharsets -benchmem -count=4
func Benchmark_Ctx_AcceptsCharsets(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set("Accept-Charset", "utf-8, iso-8859-1;q=0.5")
	var res string
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		res = c.AcceptsCharsets("utf-8")
	}
	utils.AssertEqual(b, "utf-8", res)
}

// go test -run Test_Ctx_AcceptsEncodings
func Test_Ctx_AcceptsEncodings(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderAcceptEncoding, "deflate, gzip;q=1.0, *;q=0.5")
	utils.AssertEqual(t, "gzip", c.AcceptsEncodings("gzip"))
	utils.AssertEqual(t, "abc", c.AcceptsEncodings("abc"))
}

// go test -v -run=^$ -bench=Benchmark_Ctx_AcceptsEncodings -benchmem -count=4
func Benchmark_Ctx_AcceptsEncodings(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderAcceptEncoding, "deflate, gzip;q=1.0, *;q=0.5")
	var res string
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		res = c.AcceptsEncodings("gzip")
	}
	utils.AssertEqual(b, "gzip", res)
}

// go test -run Test_Ctx_AcceptsLanguages
func Test_Ctx_AcceptsLanguages(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderAcceptLanguage, "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5")
	utils.AssertEqual(t, "fr", c.AcceptsLanguages("fr"))
}

// go test -v -run=^$ -bench=Benchmark_Ctx_AcceptsLanguages -benchmem -count=4
func Benchmark_Ctx_AcceptsLanguages(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderAcceptLanguage, "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5")
	var res string
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		res = c.AcceptsLanguages("fr")
	}
	utils.AssertEqual(b, "fr", res)
}

// go test -run Test_Ctx_App
func Test_Ctx_App(t *testing.T) {
	t.Parallel()
	app := New()
	app.config.BodyLimit = 1000
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	utils.AssertEqual(t, 1000, c.App().config.BodyLimit)
}

// go test -run Test_Ctx_Append
func Test_Ctx_Append(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Append("X-Test", "Hello")
	c.Append("X-Test", "World")
	c.Append("X-Test", "Hello", "World")
	// similar value in the middle
	c.Append("X2-Test", "World")
	c.Append("X2-Test", "XHello")
	c.Append("X2-Test", "Hello", "World")
	// similar value at the start
	c.Append("X3-Test", "XHello")
	c.Append("X3-Test", "World")
	c.Append("X3-Test", "Hello", "World")
	// try it with multiple similar values
	c.Append("X4-Test", "XHello")
	c.Append("X4-Test", "Hello")
	c.Append("X4-Test", "HelloZ")
	c.Append("X4-Test", "YHello")
	c.Append("X4-Test", "Hello")
	c.Append("X4-Test", "YHello")
	c.Append("X4-Test", "HelloZ")
	c.Append("X4-Test", "XHello")
	// without append value
	c.Append("X-Custom-Header")

	utils.AssertEqual(t, "Hello, World", string(c.Response().Header.Peek("X-Test")))
	utils.AssertEqual(t, "World, XHello, Hello", string(c.Response().Header.Peek("X2-Test")))
	utils.AssertEqual(t, "XHello, World, Hello", string(c.Response().Header.Peek("X3-Test")))
	utils.AssertEqual(t, "XHello, Hello, HelloZ, YHello", string(c.Response().Header.Peek("X4-Test")))
	utils.AssertEqual(t, "", string(c.Response().Header.Peek("x-custom-header")))
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Append -benchmem -count=4
func Benchmark_Ctx_Append(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		c.Append("X-Custom-Header", "Hello")
		c.Append("X-Custom-Header", "World")
		c.Append("X-Custom-Header", "Hello")
	}
	utils.AssertEqual(b, "Hello, World", app.getString(c.Response().Header.Peek("X-Custom-Header")))
}

// go test -run Test_Ctx_Attachment
func Test_Ctx_Attachment(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	// empty
	c.Attachment()
	utils.AssertEqual(t, `attachment`, string(c.Response().Header.Peek(HeaderContentDisposition)))
	// real filename
	c.Attachment("./static/img/logo.png")
	utils.AssertEqual(t, `attachment; filename="logo.png"`, string(c.Response().Header.Peek(HeaderContentDisposition)))
	utils.AssertEqual(t, "image/png", string(c.Response().Header.Peek(HeaderContentType)))
	// check quoting
	c.Attachment("another document.pdf\"\r\nBla: \"fasel")
	utils.AssertEqual(t, `attachment; filename="another+document.pdf%22%0D%0ABla%3A+%22fasel"`, string(c.Response().Header.Peek(HeaderContentDisposition)))
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Attachment -benchmem -count=4
func Benchmark_Ctx_Attachment(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		// example with quote params
		c.Attachment("another document.pdf\"\r\nBla: \"fasel")
	}
	utils.AssertEqual(b, `attachment; filename="another+document.pdf%22%0D%0ABla%3A+%22fasel"`, string(c.Response().Header.Peek(HeaderContentDisposition)))
}

// go test -run Test_Ctx_BaseURL
func Test_Ctx_BaseURL(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().SetRequestURI("http://google.com/test")
	utils.AssertEqual(t, "http://google.com", c.BaseURL())
	// Check cache
	utils.AssertEqual(t, "http://google.com", c.BaseURL())
}

// go test -v -run=^$ -bench=Benchmark_Ctx_BaseURL -benchmem
func Benchmark_Ctx_BaseURL(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().SetHost("google.com:1337")
	c.Request().URI().SetPath("/haha/oke/lol")
	var res string
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		res = c.BaseURL()
	}
	utils.AssertEqual(b, "http://google.com:1337", res)
}

// go test -run Test_Ctx_Body
func Test_Ctx_Body(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().SetBody([]byte("john=doe"))
	utils.AssertEqual(t, []byte("john=doe"), c.Body())
}

func Benchmark_Ctx_Body(b *testing.B) {
	const input = "john=doe"

	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	c.Request().SetBody([]byte(input))
	for i := 0; i < b.N; i++ {
		_ = c.Body()
	}

	utils.AssertEqual(b, []byte(input), c.Body())
}

// go test -run Test_Ctx_Body_With_Compression
func Test_Ctx_Body_With_Compression(t *testing.T) {
	t.Parallel()
	tests := []struct {
		name            string
		contentEncoding string
		body            []byte
		expectedBody    []byte
	}{
		{
			name:            "gzip",
			contentEncoding: "gzip",
			body:            []byte("john=doe"),
			expectedBody:    []byte("john=doe"),
		},
		{
			name:            "unsupported_encoding",
			contentEncoding: "undefined",
			body:            []byte("keeps_ORIGINAL"),
			expectedBody:    []byte("keeps_ORIGINAL"),
		},
		{
			name:            "gzip then unsupported",
			contentEncoding: "gzip, undefined",
			body:            []byte("Go, be gzipped"),
			expectedBody:    []byte("Go, be gzipped"),
		},
		{
			name:            "invalid_deflate",
			contentEncoding: "gzip,deflate",
			body:            []byte("I'm not correctly compressed"),
			expectedBody:    []byte(zlib.ErrHeader.Error()),
		},
	}

	for _, testObject := range tests {
		tCase := testObject // Duplicate object to ensure it will be unique across all runs
		t.Run(tCase.name, func(t *testing.T) {
			app := New()
			c := app.AcquireCtx(&fasthttp.RequestCtx{})
			defer app.ReleaseCtx(c)
			c.Request().Header.Set("Content-Encoding", tCase.contentEncoding)

			if strings.Contains(tCase.contentEncoding, "gzip") {
				var b bytes.Buffer
				gz := gzip.NewWriter(&b)

				_, err := gz.Write(tCase.body)
				utils.AssertEqual(t, nil, err)

				err = gz.Flush()
				utils.AssertEqual(t, nil, err)

				err = gz.Close()
				utils.AssertEqual(t, nil, err)
				tCase.body = b.Bytes()
			}

			c.Request().SetBody(tCase.body)
			body := c.Body()
			utils.AssertEqual(t, tCase.expectedBody, body)

			// Check if body raw is the same as previous before decompression
			utils.AssertEqual(
				t, tCase.body, c.Request().Body(),
				"Body raw must be the same as set before",
			)
		})
	}
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Body_With_Compression -benchmem -count=4
func Benchmark_Ctx_Body_With_Compression(b *testing.B) {
	encodingErr := errors.New("failed to encoding data")

	var (
		compressGzip = func(data []byte) ([]byte, error) {
			var buf bytes.Buffer
			writer := gzip.NewWriter(&buf)
			if _, err := writer.Write(data); err != nil {
				return nil, encodingErr
			}
			if err := writer.Flush(); err != nil {
				return nil, encodingErr
			}
			if err := writer.Close(); err != nil {
				return nil, encodingErr
			}
			return buf.Bytes(), nil
		}
		compressDeflate = func(data []byte) ([]byte, error) {
			var buf bytes.Buffer
			writer := zlib.NewWriter(&buf)
			if _, err := writer.Write(data); err != nil {
				return nil, encodingErr
			}
			if err := writer.Flush(); err != nil {
				return nil, encodingErr
			}
			if err := writer.Close(); err != nil {
				return nil, encodingErr
			}
			return buf.Bytes(), nil
		}
	)
	compressionTests := []struct {
		contentEncoding string
		compressWriter  func([]byte) ([]byte, error)
	}{
		{
			contentEncoding: "gzip",
			compressWriter:  compressGzip,
		},
		{
			contentEncoding: "gzip,invalid",
			compressWriter:  compressGzip,
		},
		{
			contentEncoding: "deflate",
			compressWriter:  compressDeflate,
		},
		{
			contentEncoding: "gzip,deflate",
			compressWriter: func(data []byte) ([]byte, error) {
				var (
					buf    bytes.Buffer
					writer interface {
						io.WriteCloser
						Flush() error
					}
					err error
				)

				// deflate
				{
					writer = zlib.NewWriter(&buf)
					if _, err = writer.Write(data); err != nil {
						return nil, encodingErr
					}
					if err = writer.Flush(); err != nil {
						return nil, encodingErr
					}
					if err = writer.Close(); err != nil {
						return nil, encodingErr
					}
				}

				data = make([]byte, buf.Len())
				copy(data, buf.Bytes())
				buf.Reset()

				// gzip
				{
					writer = gzip.NewWriter(&buf)
					if _, err = writer.Write(data); err != nil {
						return nil, encodingErr
					}
					if err = writer.Flush(); err != nil {
						return nil, encodingErr
					}
					if err = writer.Close(); err != nil {
						return nil, encodingErr
					}
				}

				return buf.Bytes(), nil
			},
		},
	}

	for _, ct := range compressionTests {
		b.Run(ct.contentEncoding, func(b *testing.B) {
			app := New()
			const input = "john=doe"
			c := app.AcquireCtx(&fasthttp.RequestCtx{})
			defer app.ReleaseCtx(c)

			c.Request().Header.Set("Content-Encoding", ct.contentEncoding)
			compressedBody, err := ct.compressWriter([]byte(input))
			utils.AssertEqual(b, nil, err)

			c.Request().SetBody(compressedBody)
			for i := 0; i < b.N; i++ {
				_ = c.Body()
			}

			utils.AssertEqual(b, []byte(input), c.Body())
		})
	}
}

// go test -run Test_Ctx_BodyParser
func Test_Ctx_BodyParser(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	type Demo struct {
		Name string `json:"name" xml:"name" form:"name" query:"name"`
	}

	{
		var gzipJSON bytes.Buffer
		w := gzip.NewWriter(&gzipJSON)
		_, err := w.Write([]byte(`{"name":"john"}`))
		utils.AssertEqual(t, nil, err)
		err = w.Close()
		utils.AssertEqual(t, nil, err)

		c.Request().Header.SetContentType(MIMEApplicationJSON)
		c.Request().Header.Set(HeaderContentEncoding, "gzip")
		c.Request().SetBody(gzipJSON.Bytes())
		c.Request().Header.SetContentLength(len(gzipJSON.Bytes()))
		d := new(Demo)
		utils.AssertEqual(t, nil, c.BodyParser(d))
		utils.AssertEqual(t, "john", d.Name)
		c.Request().Header.Del(HeaderContentEncoding)
	}

	testDecodeParser := func(contentType, body string) {
		c.Request().Header.SetContentType(contentType)
		c.Request().SetBody([]byte(body))
		c.Request().Header.SetContentLength(len(body))
		d := new(Demo)
		utils.AssertEqual(t, nil, c.BodyParser(d))
		utils.AssertEqual(t, "john", d.Name)
	}

	testDecodeParser(MIMEApplicationJSON, `{"name":"john"}`)
	testDecodeParser(MIMEApplicationXML, `<Demo><name>john</name></Demo>`)
	testDecodeParser(MIMEApplicationForm, "name=john")
	testDecodeParser(MIMEMultipartForm+`;boundary="b"`, "--b\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\njohn\r\n--b--")

	// Ensure JSON extension MIME type gets parsed as JSON
	testDecodeParser("application/problem+json", `{"name":"john"}`)

	testDecodeParserError := func(contentType, body string) {
		c.Request().Header.SetContentType(contentType)
		c.Request().SetBody([]byte(body))
		c.Request().Header.SetContentLength(len(body))
		utils.AssertEqual(t, false, c.BodyParser(nil) == nil)
	}

	testDecodeParserError("invalid-content-type", "")
	testDecodeParserError(MIMEMultipartForm+`;boundary="b"`, "--b")

	type CollectionQuery struct {
		Data []Demo `query:"data"`
	}

	c.Request().Reset()
	c.Request().Header.SetContentType(MIMEApplicationForm)
	c.Request().SetBody([]byte("data[0][name]=john&data[1][name]=doe"))
	c.Request().Header.SetContentLength(len(c.Body()))
	cq := new(CollectionQuery)
	utils.AssertEqual(t, nil, c.BodyParser(cq))
	utils.AssertEqual(t, 2, len(cq.Data))
	utils.AssertEqual(t, "john", cq.Data[0].Name)
	utils.AssertEqual(t, "doe", cq.Data[1].Name)

	c.Request().Reset()
	c.Request().Header.SetContentType(MIMEApplicationForm)
	c.Request().SetBody([]byte("data.0.name=john&data.1.name=doe"))
	c.Request().Header.SetContentLength(len(c.Body()))
	cq = new(CollectionQuery)
	utils.AssertEqual(t, nil, c.BodyParser(cq))
	utils.AssertEqual(t, 2, len(cq.Data))
	utils.AssertEqual(t, "john", cq.Data[0].Name)
	utils.AssertEqual(t, "doe", cq.Data[1].Name)

	t.Run("MultipartCollectionQueryDotNotation", func(t *testing.T) {
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		c.Request().Reset()

		buf := &bytes.Buffer{}
		writer := multipart.NewWriter(buf)
		utils.AssertEqual(t, nil, writer.WriteField("data.0.name", "john"))
		utils.AssertEqual(t, nil, writer.WriteField("data.1.name", "doe"))
		utils.AssertEqual(t, nil, writer.Close())

		c.Request().Header.SetContentType(writer.FormDataContentType())
		c.Request().SetBody(buf.Bytes())
		c.Request().Header.SetContentLength(len(c.Body()))

		cq := new(CollectionQuery)
		utils.AssertEqual(t, nil, c.BodyParser(cq))
		utils.AssertEqual(t, len(cq.Data), 2)
		utils.AssertEqual(t, "john", cq.Data[0].Name)
		utils.AssertEqual(t, "doe", cq.Data[1].Name)
	})

	t.Run("MultipartCollectionQuerySquareBrackets", func(t *testing.T) {
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		c.Request().Reset()

		buf := &bytes.Buffer{}
		writer := multipart.NewWriter(buf)
		utils.AssertEqual(t, nil, writer.WriteField("data[0][name]", "john"))
		utils.AssertEqual(t, nil, writer.WriteField("data[1][name]", "doe"))
		utils.AssertEqual(t, nil, writer.Close())

		c.Request().Header.SetContentType(writer.FormDataContentType())
		c.Request().SetBody(buf.Bytes())
		c.Request().Header.SetContentLength(len(c.Body()))

		cq := new(CollectionQuery)
		utils.AssertEqual(t, nil, c.BodyParser(cq))
		utils.AssertEqual(t, len(cq.Data), 2)
		utils.AssertEqual(t, "john", cq.Data[0].Name)
		utils.AssertEqual(t, "doe", cq.Data[1].Name)
	})
}

func Test_Ctx_BodyParser_InvalidRequestData(t *testing.T) {
	t.Parallel()

	type RequestBody struct {
		NestedContent []*struct {
			Value string `form:"value"`
		} `form:"nested-content"`
	}
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	c.Request().Reset()
	c.Request().Header.SetContentType(MIMEApplicationForm)
	// Test with invalid form data
	c.Request().SetBody([]byte("nested-content[-1].value=Foo&nested-content[0].value=Bar&nested-content[1].value=FooBar"))
	c.Request().Header.SetContentLength(len(c.Body()))

	subject := new(RequestBody)
	err := c.BodyParser(subject)

	utils.AssertEqual(t, true, nil != err)
	utils.AssertEqual(t, "failed to decode: schema: panic while decoding: reflect: slice index out of range", fmt.Sprintf("%v", err))
}

func Test_Ctx_BodyParser_IndexTooLarge(t *testing.T) {
	defer SetParserDecoder(ParserConfig{IgnoreUnknownKeys: true})
	SetParserDecoder(ParserConfig{IgnoreUnknownKeys: false})
	type RequestBody struct {
		NestedContent []*struct {
			Value string `form:"value"`
		} `form:"nested-content"`
	}
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	c.Request().Reset()
	c.Request().Header.SetContentType(MIMEApplicationForm)
	c.Request().SetBody([]byte("nested-content.1001.value=Foo"))
	c.Request().Header.SetContentLength(len(c.Body()))

	subject := new(RequestBody)
	err := c.BodyParser(subject)

	utils.AssertEqual(t, true, nil != err)
	utils.AssertEqual(t, "failed to decode: schema: index exceeds parser limit", fmt.Sprintf("%v", err))
}

func Test_Ctx_ParamParser(t *testing.T) {
	t.Parallel()
	app := New()
	app.Get("/test1/:userId/role/:roleId", func(ctx *Ctx) error {
		type Demo struct {
			UserID uint `params:"userId"`
			RoleID uint `params:"roleId"`
		}
		d := new(Demo)

		utils.AssertEqual(t, nil, ctx.ParamsParser(d))
		utils.AssertEqual(t, uint(111), d.UserID)
		utils.AssertEqual(t, uint(222), d.RoleID)
		return nil
	})
	_, err := app.Test(httptest.NewRequest(MethodGet, "/test1/111/role/222", nil))
	utils.AssertEqual(t, nil, err)

	_, err = app.Test(httptest.NewRequest(MethodGet, "/test2/111/role/222", nil))
	utils.AssertEqual(t, nil, err)
}

// go test -run Test_Ctx_BodyParser_WithSetParserDecoder
func Test_Ctx_BodyParser_WithSetParserDecoder(t *testing.T) {
	type CustomTime time.Time

	timeConverter := func(value string) reflect.Value {
		if v, err := time.Parse("2006-01-02", value); err == nil {
			return reflect.ValueOf(v)
		}
		return reflect.Value{}
	}

	customTime := ParserType{
		Customtype: CustomTime{},
		Converter:  timeConverter,
	}

	SetParserDecoder(ParserConfig{
		IgnoreUnknownKeys: true,
		ParserType:        []ParserType{customTime},
		ZeroEmpty:         true,
		SetAliasTag:       "form",
	})

	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	type Demo struct {
		Date  CustomTime `form:"date"`
		Title string     `form:"title"`
		Body  string     `form:"body"`
	}

	testDecodeParser := func(contentType, body string) {
		c.Request().Header.SetContentType(contentType)
		c.Request().SetBody([]byte(body))
		c.Request().Header.SetContentLength(len(body))
		d := Demo{
			Title: "Existing title",
			Body:  "Existing Body",
		}
		utils.AssertEqual(t, nil, c.BodyParser(&d))
		date := fmt.Sprintf("%v", d.Date)
		utils.AssertEqual(t, "{0 63743587200 <nil>}", date)
		utils.AssertEqual(t, "", d.Title)
		utils.AssertEqual(t, "New Body", d.Body)
	}

	testDecodeParser(MIMEApplicationForm, "date=2020-12-15&title=&body=New Body")
	testDecodeParser(MIMEMultipartForm+`; boundary="b"`, "--b\r\nContent-Disposition: form-data; name=\"date\"\r\n\r\n2020-12-15\r\n--b\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\n\r\n--b\r\nContent-Disposition: form-data; name=\"body\"\r\n\r\nNew Body\r\n--b--")
}

// go test -v -run=^$ -bench=Benchmark_Ctx_BodyParser_JSON -benchmem -count=4
func Benchmark_Ctx_BodyParser_JSON(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type Demo struct {
		Name string `json:"name"`
	}
	body := []byte(`{"name":"john"}`)
	c.Request().SetBody(body)
	c.Request().Header.SetContentType(MIMEApplicationJSON)
	c.Request().Header.SetContentLength(len(body))
	d := new(Demo)

	b.ReportAllocs()
	b.ResetTimer()

	for n := 0; n < b.N; n++ {
		_ = c.BodyParser(d) //nolint:errcheck // It is fine to ignore the error here as we check it once further below
	}
	utils.AssertEqual(b, nil, c.BodyParser(d))
	utils.AssertEqual(b, "john", d.Name)
}

// go test -v -run=^$ -bench=Benchmark_Ctx_BodyParser_JSON_Extension -benchmem -count=4
func Benchmark_Ctx_BodyParser_JSON_Extension(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type Demo struct {
		Name string `json:"name"`
	}
	body := []byte(`{"name":"john"}`)
	c.Request().SetBody(body)
	c.Request().Header.SetContentType("application/problem+json")
	c.Request().Header.SetContentLength(len(body))
	d := new(Demo)

	b.ReportAllocs()
	b.ResetTimer()

	for n := 0; n < b.N; n++ {
		_ = c.BodyParser(d) //nolint:errcheck // It is fine to ignore the error here as we check it once further below
	}
	utils.AssertEqual(b, nil, c.BodyParser(d))
	utils.AssertEqual(b, "john", d.Name)
}

// go test -v -run=^$ -bench=Benchmark_Ctx_BodyParser_XML -benchmem -count=4
func Benchmark_Ctx_BodyParser_XML(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type Demo struct {
		Name string `xml:"name"`
	}
	body := []byte("<Demo><name>john</name></Demo>")
	c.Request().SetBody(body)
	c.Request().Header.SetContentType(MIMEApplicationXML)
	c.Request().Header.SetContentLength(len(body))
	d := new(Demo)

	b.ReportAllocs()
	b.ResetTimer()

	for n := 0; n < b.N; n++ {
		_ = c.BodyParser(d) //nolint:errcheck // It is fine to ignore the error here as we check it once further below
	}
	utils.AssertEqual(b, nil, c.BodyParser(d))
	utils.AssertEqual(b, "john", d.Name)
}

// go test -v -run=^$ -bench=Benchmark_Ctx_BodyParser_Form -benchmem -count=4
func Benchmark_Ctx_BodyParser_Form(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type Demo struct {
		Name string `form:"name"`
	}
	body := []byte("name=john")
	c.Request().SetBody(body)
	c.Request().Header.SetContentType(MIMEApplicationForm)
	c.Request().Header.SetContentLength(len(body))
	d := new(Demo)

	b.ReportAllocs()
	b.ResetTimer()

	for n := 0; n < b.N; n++ {
		_ = c.BodyParser(d) //nolint:errcheck // It is fine to ignore the error here as we check it once further below
	}
	utils.AssertEqual(b, nil, c.BodyParser(d))
	utils.AssertEqual(b, "john", d.Name)
}

// go test -v -run=^$ -bench=Benchmark_Ctx_BodyParser_MultipartForm -benchmem -count=4
func Benchmark_Ctx_BodyParser_MultipartForm(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type Demo struct {
		Name string `form:"name"`
	}

	body := []byte("--b\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\njohn\r\n--b--")
	c.Request().SetBody(body)
	c.Request().Header.SetContentType(MIMEMultipartForm + `;boundary="b"`)
	c.Request().Header.SetContentLength(len(body))
	d := new(Demo)

	b.ReportAllocs()
	b.ResetTimer()

	for n := 0; n < b.N; n++ {
		_ = c.BodyParser(d) //nolint:errcheck // It is fine to ignore the error here as we check it once further below
	}
	utils.AssertEqual(b, nil, c.BodyParser(d))
	utils.AssertEqual(b, "john", d.Name)
}

// go test -run Test_Ctx_Context
func Test_Ctx_Context(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	utils.AssertEqual(t, "*fasthttp.RequestCtx", fmt.Sprintf("%T", c.Context()))
}

// go test -run Test_Ctx_UserContext
func Test_Ctx_UserContext(t *testing.T) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	t.Run("Nil_Context", func(t *testing.T) {
		ctx := c.UserContext()
		utils.AssertEqual(t, ctx, context.Background())
	})
	t.Run("ValueContext", func(t *testing.T) {
		testKey := struct{}{}
		testValue := "Test Value"
		ctx := context.WithValue(context.Background(), testKey, testValue)
		utils.AssertEqual(t, testValue, ctx.Value(testKey))
	})
}

// go test -run Test_Ctx_SetUserContext
func Test_Ctx_SetUserContext(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	testKey := struct{}{}
	testValue := "Test Value"
	ctx := context.WithValue(context.Background(), testKey, testValue)
	c.SetUserContext(ctx)
	utils.AssertEqual(t, testValue, c.UserContext().Value(testKey))
}

// go test -run Test_Ctx_UserContext_Multiple_Requests
func Test_Ctx_UserContext_Multiple_Requests(t *testing.T) {
	t.Parallel()
	testKey := struct{}{}
	testValue := "foobar-value"

	app := New()
	app.Get("/", func(c *Ctx) error {
		ctx := c.UserContext()

		if ctx.Value(testKey) != nil {
			return c.SendStatus(StatusInternalServerError)
		}

		input := utils.CopyString(c.Query("input", "NO_VALUE"))
		ctx = context.WithValue(ctx, testKey, fmt.Sprintf("%s_%s", testValue, input))
		c.SetUserContext(ctx)

		return c.Status(StatusOK).SendString(fmt.Sprintf("resp_%s_returned", input))
	})

	// Consecutive Requests
	for i := 1; i <= 10; i++ {
		t.Run(fmt.Sprintf("request_%d", i), func(t *testing.T) {
			resp, err := app.Test(httptest.NewRequest(MethodGet, fmt.Sprintf("/?input=%d", i), nil))

			utils.AssertEqual(t, nil, err, "Unexpected error from response")
			utils.AssertEqual(t, StatusOK, resp.StatusCode, "context.Context returned from c.UserContext() is reused")

			b, err := io.ReadAll(resp.Body)
			utils.AssertEqual(t, nil, err, "Unexpected error from reading response body")
			utils.AssertEqual(t, fmt.Sprintf("resp_%d_returned", i), string(b), "response text incorrect")
		})
	}
}

// go test -run Test_Ctx_Cookie
func Test_Ctx_Cookie(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	expire := time.Now().Add(24 * time.Hour)
	var dst []byte
	dst = expire.In(time.UTC).AppendFormat(dst, time.RFC1123)
	httpdate := strings.ReplaceAll(string(dst), "UTC", "GMT")
	cookie := &Cookie{
		Name:    "username",
		Value:   "john",
		Expires: expire,
		// SameSite: CookieSameSiteStrictMode, // default is "lax"
	}
	c.Cookie(cookie)
	expect := "username=john; expires=" + httpdate + "; path=/; SameSite=Lax"
	utils.AssertEqual(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))

	expect = "username=john; expires=" + httpdate + "; path=/"
	cookie.SameSite = CookieSameSiteDisabled
	c.Cookie(cookie)
	utils.AssertEqual(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))

	expect = "username=john; expires=" + httpdate + "; path=/; SameSite=Strict"
	cookie.SameSite = CookieSameSiteStrictMode
	c.Cookie(cookie)
	utils.AssertEqual(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))

	expect = "username=john; expires=" + httpdate + "; path=/; secure; SameSite=None"
	cookie.Secure = true
	cookie.SameSite = CookieSameSiteNoneMode
	c.Cookie(cookie)
	utils.AssertEqual(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))

	expect = "username=john; path=/; secure; SameSite=None"
	// should remove expires and max-age headers
	cookie.SessionOnly = true
	cookie.Expires = expire
	cookie.MaxAge = 10000
	c.Cookie(cookie)
	utils.AssertEqual(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))

	expect = "username=john; path=/; secure; SameSite=None"
	// should remove expires and max-age headers when no expire and no MaxAge (default time)
	cookie.SessionOnly = false
	cookie.Expires = time.Time{}
	cookie.MaxAge = 0
	c.Cookie(cookie)
	utils.AssertEqual(t, expect, string(c.Response().Header.Peek(HeaderSetCookie)))
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Cookie -benchmem -count=4
func Benchmark_Ctx_Cookie(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		c.Cookie(&Cookie{
			Name:  "John",
			Value: "Doe",
		})
	}
	utils.AssertEqual(b, "John=Doe; path=/; SameSite=Lax", app.getString(c.Response().Header.Peek("Set-Cookie")))
}

// go test -run Test_Ctx_CookieParser -v
func Test_Ctx_CookieParser(t *testing.T) {
	t.Parallel()
	app := New(Config{EnableSplittingOnParsers: true})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type Cookie struct {
		Name    string
		Class   int
		Courses []string
	}
	c.Request().Header.Set("Cookie", "name=doe")
	c.Request().Header.Set("Cookie", "class=100")
	c.Request().Header.Set("Cookie", "courses=maths,english")
	cookie := new(Cookie)

	// correct test cases
	utils.AssertEqual(t, nil, c.CookieParser(cookie))
	utils.AssertEqual(t, "doe", cookie.Name)
	utils.AssertEqual(t, 100, cookie.Class)
	utils.AssertEqual(t, 2, len(cookie.Courses))

	// wrong test cases
	empty := new(Cookie)
	c.Request().Header.Set("Cookie", "name")
	c.Request().Header.Set("Cookie", "class")
	c.Request().Header.Set("Cookie", "courses")
	utils.AssertEqual(t, nil, c.CookieParser(cookie))
	utils.AssertEqual(t, "", empty.Name)
	utils.AssertEqual(t, 0, empty.Class)
	utils.AssertEqual(t, 0, len(empty.Courses))
}

// go test -run Test_Ctx_CookieParserUsingTag -v
func Test_Ctx_CookieParserUsingTag(t *testing.T) {
	t.Parallel()
	app := New(Config{EnableSplittingOnParsers: true})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type Cook struct {
		ID       int      `cookie:"id"`
		Name     string   `cookie:"name"`
		Courses  []string `cookie:"courses"`
		Enrolled bool     `cookie:"student"`
		Fees     float32  `cookie:"fee"`
		Grades   []uint8  `cookie:"score"`
	}
	cookie1 := new(Cook)
	cookie1.Name = "Joseph"
	utils.AssertEqual(t, "Joseph", cookie1.Name)

	c.Request().Header.Set("Cookie", "id=1")
	c.Request().Header.Set("Cookie", "name=Joey")
	c.Request().Header.Set("Cookie", "courses=maths,english, chemistry, physics")
	c.Request().Header.Set("Cookie", "student=true")
	c.Request().Header.Set("Cookie", "fee=45.78")
	c.Request().Header.Set("Cookie", "score=7,6,10")
	utils.AssertEqual(t, nil, c.CookieParser(cookie1))
	utils.AssertEqual(t, "Joey", cookie1.Name)
	utils.AssertEqual(t, true, cookie1.Enrolled)
	utils.AssertEqual(t, float32(45.78), cookie1.Fees)
	utils.AssertEqual(t, []uint8{7, 6, 10}, cookie1.Grades)

	type RequiredCookie struct {
		House string `cookie:"house,required"`
	}
	rc := new(RequiredCookie)
	utils.AssertEqual(t, "failed to decode: house is empty", c.CookieParser(rc).Error())

	type ArrayCookie struct {
		Dates []int
	}

	ac := new(ArrayCookie)
	c.Request().Header.Set("Cookie", "dates[]=7,6,10")
	utils.AssertEqual(t, nil, c.CookieParser(ac))
	utils.AssertEqual(t, 3, len(ac.Dates))
}

// go test -run Test_Ctx_CookieParserSchema -v
func Test_Ctx_CookieParser_Schema(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type result struct {
		Maths   int `cookie:"maths"`
		English int `cookie:"english"`
	}
	type resStruct struct {
		Name   string `cookie:"name"`
		Age    int    `cookie:"age"`
		Result result `cookie:"result"`
	}
	res := &resStruct{
		Name: "Joseph",
		Age:  10,
		Result: result{
			Maths:   10,
			English: 10,
		},
	}

	// set cookie
	c.Request().Header.Set("Cookie", "name=Joseph")
	c.Request().Header.Set("Cookie", "age=10")
	c.Request().Header.Set("Cookie", "result.maths=10")
	c.Request().Header.Set("Cookie", "result.english=10")
	hR := new(resStruct)
	r := c.CookieParser(hR)

	utils.AssertEqual(t, nil, r)
	utils.AssertEqual(t, *res, *hR)
}

// go test -run Benchmark_Ctx_CookieParser -v
func Benchmark_Ctx_CookieParser(b *testing.B) {
	app := New(Config{EnableSplittingOnParsers: true})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type Cook struct {
		ID       int      `cookie:"id"`
		Name     string   `cookie:"name"`
		Courses  []string `cookie:"courses"`
		Enrolled bool     `cookie:"student"`
		Fees     float32  `cookie:"fee"`
		Grades   []uint8  `cookie:"score"`
	}
	cookie1 := new(Cook)
	cookie1.Name = "Joseph"

	c.Request().Header.Set("Cookie", "id=1")
	c.Request().Header.Set("Cookie", "name=Joey")
	c.Request().Header.Set("Cookie", "courses=maths,english, chemistry, physics")
	c.Request().Header.Set("Cookie", "student=true")
	c.Request().Header.Set("Cookie", "fee=45.78")
	c.Request().Header.Set("Cookie", "score=7,6,10")

	var err error
	// Run the function b.N times
	for i := 0; i < b.N; i++ {
		err = c.CookieParser(cookie1)
	}
	utils.AssertEqual(b, nil, err)
}

// go test -run Test_Ctx_Cookies
func Test_Ctx_Cookies(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set("Cookie", "john=doe")
	utils.AssertEqual(t, "doe", c.Cookies("john"))
	utils.AssertEqual(t, "default", c.Cookies("unknown", "default"))
}

// go test -run Test_Ctx_Format
func Test_Ctx_Format(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderAccept, MIMETextPlain)
	err := c.Format([]byte("Hello, World!"))
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "Hello, World!", string(c.Response().Body()))

	c.Request().Header.Set(HeaderAccept, MIMETextHTML)
	err = c.Format("Hello, World!")
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "<p>Hello, World!</p>", string(c.Response().Body()))

	c.Request().Header.Set(HeaderAccept, MIMETextHTML)
	err = c.Format("<script>alert(1)</script>")
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "<p>&lt;script&gt;alert(1)&lt;/script&gt;</p>", string(c.Response().Body()))

	c.Request().Header.Set(HeaderAccept, MIMEApplicationJSON)
	err = c.Format("Hello, World!")
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, `"Hello, World!"`, string(c.Response().Body()))

	c.Request().Header.Set(HeaderAccept, MIMEApplicationJSON)
	err = c.Format("<script>alert(1)</script>")
	utils.AssertEqual(t, nil, err)
	jsonBody := append([]byte(nil), c.Response().Body()...)
	err = c.JSON("<script>alert(1)</script>")
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, string(jsonBody), string(c.Response().Body()))

	c.Request().Header.Set(HeaderAccept, MIMETextPlain)
	err = c.Format(complex(1, 1))
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "(1+1i)", string(c.Response().Body()))

	c.Request().Header.Set(HeaderAccept, MIMEApplicationXML)
	err = c.Format("Hello, World!")
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, `<string>Hello, World!</string>`, string(c.Response().Body()))

	c.Request().Header.Set(HeaderAccept, MIMEApplicationXML)
	err = c.Format("<script>alert(1)</script>")
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, `<string>&lt;script&gt;alert(1)&lt;/script&gt;</string>`, string(c.Response().Body()))

	err = c.Format(complex(1, 1))
	utils.AssertEqual(t, true, err != nil)

	c.Request().Header.Set(HeaderAccept, MIMETextPlain)
	err = c.Format(Map{})
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "map[]", string(c.Response().Body()))

	type broken string
	c.Request().Header.Set(HeaderAccept, "broken/accept")
	err = c.Format(broken("Hello, World!"))
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, `Hello, World!`, string(c.Response().Body()))
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Format -benchmem -count=4
func Benchmark_Ctx_Format(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set("Accept", "text/plain")
	b.ReportAllocs()
	b.ResetTimer()

	var err error
	for n := 0; n < b.N; n++ {
		err = c.Format("Hello, World!")
	}

	utils.AssertEqual(b, nil, err)
	utils.AssertEqual(b, `Hello, World!`, string(c.Response().Body()))
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Format_HTML -benchmem -count=4
func Benchmark_Ctx_Format_HTML(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set("Accept", "text/html")
	b.ReportAllocs()
	b.ResetTimer()

	var err error
	for n := 0; n < b.N; n++ {
		err = c.Format("Hello, World!")
	}

	utils.AssertEqual(b, nil, err)
	utils.AssertEqual(b, "<p>Hello, World!</p>", string(c.Response().Body()))
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Format_JSON -benchmem -count=4
func Benchmark_Ctx_Format_JSON(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set("Accept", "application/json")
	b.ReportAllocs()
	b.ResetTimer()

	var err error
	for n := 0; n < b.N; n++ {
		err = c.Format("Hello, World!")
	}

	utils.AssertEqual(b, nil, err)
	utils.AssertEqual(b, `"Hello, World!"`, string(c.Response().Body()))
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Format_XML -benchmem -count=4
func Benchmark_Ctx_Format_XML(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set("Accept", "application/xml")
	b.ReportAllocs()
	b.ResetTimer()

	var err error
	for n := 0; n < b.N; n++ {
		err = c.Format("Hello, World!")
	}

	utils.AssertEqual(b, nil, err)
	utils.AssertEqual(b, `<string>Hello, World!</string>`, string(c.Response().Body()))
}

// go test -run Test_Ctx_FormFile
func Test_Ctx_FormFile(t *testing.T) {
	// TODO: We should clean this up
	t.Parallel()
	app := New()

	app.Post("/test", func(c *Ctx) error {
		fh, err := c.FormFile("file")
		utils.AssertEqual(t, nil, err)
		utils.AssertEqual(t, "test", fh.Filename)

		f, err := fh.Open()
		utils.AssertEqual(t, nil, err)
		defer func() {
			utils.AssertEqual(t, nil, f.Close())
		}()

		b := new(bytes.Buffer)
		_, err = io.Copy(b, f)
		utils.AssertEqual(t, nil, err)
		utils.AssertEqual(t, "hello world", b.String())
		return nil
	})

	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	ioWriter, err := writer.CreateFormFile("file", "test")
	utils.AssertEqual(t, nil, err)

	_, err = ioWriter.Write([]byte("hello world"))
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, nil, writer.Close())

	req := httptest.NewRequest(MethodPost, "/test", body)
	req.Header.Set(HeaderContentType, writer.FormDataContentType())
	req.Header.Set(HeaderContentLength, strconv.Itoa(len(body.Bytes())))

	resp, err := app.Test(req)
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
}

// go test -run Test_Ctx_FormValue
func Test_Ctx_FormValue(t *testing.T) {
	t.Parallel()
	app := New()

	app.Post("/test", func(c *Ctx) error {
		utils.AssertEqual(t, "john", c.FormValue("name"))
		return nil
	})

	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	utils.AssertEqual(t, nil, writer.WriteField("name", "john"))
	utils.AssertEqual(t, nil, writer.Close())

	req := httptest.NewRequest(MethodPost, "/test", body)
	req.Header.Set("Content-Type", fmt.Sprintf("multipart/form-data; boundary=%s", writer.Boundary()))
	req.Header.Set("Content-Length", strconv.Itoa(len(body.Bytes())))

	resp, err := app.Test(req)
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Fresh_StaleEtag -benchmem -count=4
func Benchmark_Ctx_Fresh_StaleEtag(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	for n := 0; n < b.N; n++ {
		c.Request().Header.Set(HeaderIfNoneMatch, "a, b, c, d")
		c.Request().Header.Set(HeaderCacheControl, "c")
		c.Fresh()

		c.Request().Header.Set(HeaderIfNoneMatch, "a, b, c, d")
		c.Request().Header.Set(HeaderCacheControl, "e")
		c.Fresh()
	}
}

// go test -run Test_Ctx_Fresh
func Test_Ctx_Fresh(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	utils.AssertEqual(t, false, c.Fresh())

	c.Request().Header.Set(HeaderIfNoneMatch, "*")
	c.Request().Header.Set(HeaderCacheControl, "no-cache")
	utils.AssertEqual(t, false, c.Fresh())

	c.Request().Header.Set(HeaderIfNoneMatch, "*")
	c.Request().Header.Set(HeaderCacheControl, ",no-cache,")
	utils.AssertEqual(t, false, c.Fresh())

	c.Request().Header.Set(HeaderIfNoneMatch, "*")
	c.Request().Header.Set(HeaderCacheControl, "aa,no-cache,")
	utils.AssertEqual(t, false, c.Fresh())

	c.Request().Header.Set(HeaderIfNoneMatch, "*")
	c.Request().Header.Set(HeaderCacheControl, ",no-cache,bb")
	utils.AssertEqual(t, false, c.Fresh())

	c.Request().Header.Set(HeaderIfNoneMatch, "675af34563dc-tr34")
	c.Request().Header.Set(HeaderCacheControl, "public")
	utils.AssertEqual(t, false, c.Fresh())

	c.Request().Header.Set(HeaderIfNoneMatch, "a, b")
	c.Response().Header.Set(HeaderETag, "c")
	utils.AssertEqual(t, false, c.Fresh())

	c.Response().Header.Set(HeaderETag, "a")
	utils.AssertEqual(t, true, c.Fresh())

	c.Request().Header.Set(HeaderIfModifiedSince, "xxWed, 21 Oct 2015 07:28:00 GMT")
	c.Response().Header.Set(HeaderLastModified, "xxWed, 21 Oct 2015 07:28:00 GMT")
	utils.AssertEqual(t, false, c.Fresh())

	c.Response().Header.Set(HeaderLastModified, "Wed, 21 Oct 2015 07:28:00 GMT")
	utils.AssertEqual(t, false, c.Fresh())

	c.Request().Header.Set(HeaderIfModifiedSince, "Wed, 21 Oct 2015 07:28:00 GMT")
	utils.AssertEqual(t, false, c.Fresh())
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Fresh_WithNoCache -benchmem -count=4
func Benchmark_Ctx_Fresh_WithNoCache(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	c.Request().Header.Set(HeaderIfNoneMatch, "*")
	c.Request().Header.Set(HeaderCacheControl, "no-cache")
	for n := 0; n < b.N; n++ {
		c.Fresh()
	}
}

// go test -run Test_Ctx_Parsers -v
func Test_Ctx_Parsers(t *testing.T) {
	t.Parallel()
	// setup
	app := New()

	type TestStruct struct {
		Name             string
		Class            int
		NameWithDefault  string `json:"name2" xml:"Name2" form:"name2" cookie:"name2" query:"name2" params:"name2" reqHeader:"name2"`
		ClassWithDefault int    `json:"class2" xml:"Class2" form:"class2" cookie:"class2" query:"class2" params:"class2" reqHeader:"class2"`
	}

	withValues := func(t *testing.T, actionFn func(c *Ctx, testStruct *TestStruct) error) {
		t.Helper()
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		defer app.ReleaseCtx(c)
		testStruct := new(TestStruct)

		utils.AssertEqual(t, nil, actionFn(c, testStruct))
		utils.AssertEqual(t, "foo", testStruct.Name)
		utils.AssertEqual(t, 111, testStruct.Class)
		utils.AssertEqual(t, "bar", testStruct.NameWithDefault)
		utils.AssertEqual(t, 222, testStruct.ClassWithDefault)
	}

	t.Run("BodyParser:xml", func(t *testing.T) {
		t.Parallel()
		withValues(t, func(c *Ctx, testStruct *TestStruct) error {
			c.Request().Header.SetContentType(MIMEApplicationXML)
			c.Request().SetBody([]byte(`<TestStruct><Name>foo</Name><Class>111</Class><Name2>bar</Name2><Class2>222</Class2></TestStruct>`))
			return c.BodyParser(testStruct)
		})
	})
	t.Run("BodyParser:form", func(t *testing.T) {
		t.Parallel()
		withValues(t, func(c *Ctx, testStruct *TestStruct) error {
			c.Request().Header.SetContentType(MIMEApplicationForm)
			c.Request().SetBody([]byte(`name=foo&class=111&name2=bar&class2=222`))
			return c.BodyParser(testStruct)
		})
	})
	t.Run("BodyParser:json", func(t *testing.T) {
		t.Parallel()
		withValues(t, func(c *Ctx, testStruct *TestStruct) error {
			c.Request().Header.SetContentType(MIMEApplicationJSON)
			c.Request().SetBody([]byte(`{"name":"foo","class":111,"name2":"bar","class2":222}`))
			return c.BodyParser(testStruct)
		})
	})
	t.Run("BodyParser:multiform", func(t *testing.T) {
		t.Parallel()
		withValues(t, func(c *Ctx, testStruct *TestStruct) error {
			body := []byte("--b\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\nfoo\r\n--b\r\nContent-Disposition: form-data; name=\"class\"\r\n\r\n111\r\n--b\r\nContent-Disposition: form-data; name=\"name2\"\r\n\r\nbar\r\n--b\r\nContent-Disposition: form-data; name=\"class2\"\r\n\r\n222\r\n--b--")
			c.Request().SetBody(body)
			c.Request().Header.SetContentType(MIMEMultipartForm + `;boundary="b"`)
			c.Request().Header.SetContentLength(len(body))
			return c.BodyParser(testStruct)
		})
	})
	t.Run("CookieParser", func(t *testing.T) {
		t.Parallel()
		withValues(t, func(c *Ctx, testStruct *TestStruct) error {
			c.Request().Header.Set("Cookie", "name=foo;name2=bar;class=111;class2=222")
			return c.CookieParser(testStruct)
		})
	})
	t.Run("QueryParser", func(t *testing.T) {
		t.Parallel()
		withValues(t, func(c *Ctx, testStruct *TestStruct) error {
			c.Request().URI().SetQueryString("name=foo&name2=bar&class=111&class2=222")
			return c.QueryParser(testStruct)
		})
	})
	t.Run("ParamsParser", func(t *testing.T) {
		t.Parallel()
		withValues(t, func(c *Ctx, testStruct *TestStruct) error {
			c.route = &Route{Params: []string{"name", "name2", "class", "class2"}}
			c.values = [30]string{"foo", "bar", "111", "222"}
			return c.ParamsParser(testStruct)
		})
	})
	t.Run("ReqHeaderParser", func(t *testing.T) {
		t.Parallel()
		withValues(t, func(c *Ctx, testStruct *TestStruct) error {
			c.Request().Header.Add("name", "foo")
			c.Request().Header.Add("name2", "bar")
			c.Request().Header.Add("class", "111")
			c.Request().Header.Add("class2", "222")
			return c.ReqHeaderParser(testStruct)
		})
	})
}

// go test -run Test_Ctx_Get
func Test_Ctx_Get(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderAcceptCharset, "utf-8, iso-8859-1;q=0.5")
	c.Request().Header.Set(HeaderReferer, "Monster")
	utils.AssertEqual(t, "utf-8, iso-8859-1;q=0.5", c.Get(HeaderAcceptCharset))
	utils.AssertEqual(t, "Monster", c.Get(HeaderReferer))
	utils.AssertEqual(t, "default", c.Get("unknown", "default"))
}

// go test -run Test_Ctx_IsProxyTrusted
func Test_Ctx_IsProxyTrusted(t *testing.T) {
	t.Parallel()

	{
		app := New()
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		defer app.ReleaseCtx(c)
		utils.AssertEqual(t, true, c.IsProxyTrusted())
	}
	{
		app := New(Config{
			EnableTrustedProxyCheck: false,
		})
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		defer app.ReleaseCtx(c)
		utils.AssertEqual(t, true, c.IsProxyTrusted())
	}

	{
		app := New(Config{
			EnableTrustedProxyCheck: true,
		})
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		defer app.ReleaseCtx(c)
		utils.AssertEqual(t, false, c.IsProxyTrusted())
	}
	{
		app := New(Config{
			EnableTrustedProxyCheck: true,

			TrustedProxies: []string{},
		})
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		defer app.ReleaseCtx(c)
		utils.AssertEqual(t, false, c.IsProxyTrusted())
	}
	{
		app := New(Config{
			EnableTrustedProxyCheck: true,

			TrustedProxies: []string{
				"127.0.0.1",
			},
		})
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		defer app.ReleaseCtx(c)
		utils.AssertEqual(t, false, c.IsProxyTrusted())
	}
	{
		app := New(Config{
			EnableTrustedProxyCheck: true,

			TrustedProxies: []string{
				"127.0.0.1/8",
			},
		})
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		defer app.ReleaseCtx(c)
		utils.AssertEqual(t, false, c.IsProxyTrusted())
	}
	{
		app := New(Config{
			EnableTrustedProxyCheck: true,

			TrustedProxies: []string{
				"0.0.0.0",
			},
		})
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		defer app.ReleaseCtx(c)
		utils.AssertEqual(t, true, c.IsProxyTrusted())
	}
	{
		app := New(Config{
			EnableTrustedProxyCheck: true,

			TrustedProxies: []string{
				"0.0.0.1/31",
			},
		})
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		defer app.ReleaseCtx(c)
		utils.AssertEqual(t, true, c.IsProxyTrusted())
	}
	{
		app := New(Config{
			EnableTrustedProxyCheck: true,

			TrustedProxies: []string{
				"0.0.0.1/31junk",
			},
		})
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		defer app.ReleaseCtx(c)
		utils.AssertEqual(t, false, c.IsProxyTrusted())
	}
}

// go test -run Test_Ctx_Hostname
func Test_Ctx_Hostname(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().SetRequestURI("http://google.com/test")
	utils.AssertEqual(t, "google.com", c.Hostname())
}

// go test -run Test_Ctx_Hostname_Untrusted
func Test_Ctx_Hostname_UntrustedProxy(t *testing.T) {
	t.Parallel()
	// Don't trust any proxy
	{
		app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{}})
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		c.Request().SetRequestURI("http://google.com/test")
		c.Request().Header.Set(HeaderXForwardedHost, "google1.com")
		utils.AssertEqual(t, "google.com", c.Hostname())
		app.ReleaseCtx(c)
	}
	// Trust to specific proxy list
	{
		app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.8.0.0", "0.8.0.1"}})
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		c.Request().SetRequestURI("http://google.com/test")
		c.Request().Header.Set(HeaderXForwardedHost, "google1.com")
		utils.AssertEqual(t, "google.com", c.Hostname())
		app.ReleaseCtx(c)
	}
}

// go test -run Test_Ctx_Hostname_Trusted
func Test_Ctx_Hostname_TrustedProxy(t *testing.T) {
	t.Parallel()
	{
		app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0", "0.8.0.1"}})
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		c.Request().SetRequestURI("http://google.com/test")
		c.Request().Header.Set(HeaderXForwardedHost, "google1.com")
		utils.AssertEqual(t, "google1.com", c.Hostname())
		app.ReleaseCtx(c)
	}
}

// go test -run Test_Ctx_Hostname_Trusted_Multiple
func Test_Ctx_Hostname_TrustedProxy_Multiple(t *testing.T) {
	t.Parallel()
	{
		app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0", "0.8.0.1"}})
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		c.Request().SetRequestURI("http://google.com/test")
		c.Request().Header.Set(HeaderXForwardedHost, "google1.com, google2.com")
		utils.AssertEqual(t, "google1.com", c.Hostname())
		app.ReleaseCtx(c)
	}
}

// go test -run Test_Ctx_Hostname_UntrustedProxyRange
func Test_Ctx_Hostname_TrustedProxyRange(t *testing.T) {
	t.Parallel()

	app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0/30"}})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	c.Request().SetRequestURI("http://google.com/test")
	c.Request().Header.Set(HeaderXForwardedHost, "google1.com")
	utils.AssertEqual(t, "google1.com", c.Hostname())
	app.ReleaseCtx(c)
}

// go test -run Test_Ctx_Hostname_UntrustedProxyRange
func Test_Ctx_Hostname_UntrustedProxyRange(t *testing.T) {
	t.Parallel()

	app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"1.0.0.0/30"}})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	c.Request().SetRequestURI("http://google.com/test")
	c.Request().Header.Set(HeaderXForwardedHost, "google1.com")
	utils.AssertEqual(t, "google.com", c.Hostname())
	app.ReleaseCtx(c)
}

// go test -run Test_Ctx_Port
func Test_Ctx_Port(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	utils.AssertEqual(t, "0", c.Port())
}

// go test -run Test_Ctx_PortInHandler
func Test_Ctx_PortInHandler(t *testing.T) {
	t.Parallel()
	app := New()

	app.Get("/port", func(c *Ctx) error {
		return c.SendString(c.Port())
	})

	resp, err := app.Test(httptest.NewRequest(MethodGet, "/port", nil))
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")

	body, err := io.ReadAll(resp.Body)
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "0", string(body))
}

// go test -run Test_Ctx_IP
func Test_Ctx_IP(t *testing.T) {
	t.Parallel()

	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	// default behavior will return the remote IP from the stack
	utils.AssertEqual(t, "0.0.0.0", c.IP())

	// X-Forwarded-For is set, but it is ignored because proxyHeader is not set
	c.Request().Header.Set(HeaderXForwardedFor, "0.0.0.1")
	utils.AssertEqual(t, "0.0.0.0", c.IP())
}

// go test -run Test_Ctx_IP_ProxyHeader
func Test_Ctx_IP_ProxyHeader(t *testing.T) {
	t.Parallel()

	// make sure that the same behavior exists for different proxy header names
	proxyHeaderNames := []string{"Real-Ip", HeaderXForwardedFor}

	for _, proxyHeaderName := range proxyHeaderNames {
		app := New(Config{ProxyHeader: proxyHeaderName})
		c := app.AcquireCtx(&fasthttp.RequestCtx{})

		c.Request().Header.Set(proxyHeaderName, "0.0.0.1")
		utils.AssertEqual(t, "0.0.0.1", c.IP())

		// without IP validation we return the full string
		c.Request().Header.Set(proxyHeaderName, "0.0.0.1, 0.0.0.2")
		utils.AssertEqual(t, "0.0.0.1, 0.0.0.2", c.IP())

		// without IP validation we return invalid IPs
		c.Request().Header.Set(proxyHeaderName, "invalid, 0.0.0.2, 0.0.0.3")
		utils.AssertEqual(t, "invalid, 0.0.0.2, 0.0.0.3", c.IP())

		// when proxy header is enabled but the value is empty, without IP validation we return an empty string
		c.Request().Header.Set(proxyHeaderName, "")
		utils.AssertEqual(t, "", c.IP())

		// without IP validation we return an invalid IP
		c.Request().Header.Set(proxyHeaderName, "not-valid-ip")
		utils.AssertEqual(t, "not-valid-ip", c.IP())

		app.ReleaseCtx(c)
	}
}

// go test -run Test_Ctx_IP_ProxyHeader
func Test_Ctx_IP_ProxyHeader_With_IP_Validation(t *testing.T) {
	t.Parallel()

	// make sure that the same behavior exists for different proxy header names
	proxyHeaderNames := []string{"Real-Ip", HeaderXForwardedFor}

	for _, proxyHeaderName := range proxyHeaderNames {
		app := New(Config{EnableIPValidation: true, ProxyHeader: proxyHeaderName})
		c := app.AcquireCtx(&fasthttp.RequestCtx{})

		// when proxy header & validation is enabled and the value is a valid IP, we return it
		c.Request().Header.Set(proxyHeaderName, "0.0.0.1")
		utils.AssertEqual(t, "0.0.0.1", c.IP())

		// when proxy header & validation is enabled and the value is a list of IPs, we return the first valid IP
		c.Request().Header.Set(proxyHeaderName, "0.0.0.1, 0.0.0.2")
		utils.AssertEqual(t, "0.0.0.1", c.IP())

		c.Request().Header.Set(proxyHeaderName, "invalid, 0.0.0.2, 0.0.0.3")
		utils.AssertEqual(t, "0.0.0.2", c.IP())

		// when proxy header & validation is enabled but the value is empty, we will ignore the header
		c.Request().Header.Set(proxyHeaderName, "")
		utils.AssertEqual(t, "0.0.0.0", c.IP())

		// when proxy header & validation is enabled but the value is not an IP, we will ignore the header
		// and return the IP of the caller
		c.Request().Header.Set(proxyHeaderName, "not-valid-ip")
		utils.AssertEqual(t, "0.0.0.0", c.IP())

		app.ReleaseCtx(c)
	}
}

// go test -run Test_Ctx_IP_UntrustedProxy
func Test_Ctx_IP_UntrustedProxy(t *testing.T) {
	t.Parallel()
	app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.8.0.1"}, ProxyHeader: HeaderXForwardedFor})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	c.Request().Header.Set(HeaderXForwardedFor, "0.0.0.1")
	defer app.ReleaseCtx(c)
	utils.AssertEqual(t, "0.0.0.0", c.IP())
}

// go test -run Test_Ctx_IP_TrustedProxy
func Test_Ctx_IP_TrustedProxy(t *testing.T) {
	t.Parallel()
	app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0"}, ProxyHeader: HeaderXForwardedFor})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	c.Request().Header.Set(HeaderXForwardedFor, "0.0.0.1")
	defer app.ReleaseCtx(c)
	utils.AssertEqual(t, "0.0.0.1", c.IP())
}

// go test -run Test_Ctx_IPs  -parallel
func Test_Ctx_IPs(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	// normal happy path test case
	c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1, 127.0.0.2, 127.0.0.3")
	utils.AssertEqual(t, []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, c.IPs())

	// inconsistent space formatting
	c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1,127.0.0.2  ,127.0.0.3")
	utils.AssertEqual(t, []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, c.IPs())

	// invalid IPs are allowed to be returned
	c.Request().Header.Set(HeaderXForwardedFor, "invalid, 127.0.0.1, 127.0.0.2")
	utils.AssertEqual(t, []string{"invalid", "127.0.0.1", "127.0.0.2"}, c.IPs())
	c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1, invalid, 127.0.0.2")
	utils.AssertEqual(t, []string{"127.0.0.1", "invalid", "127.0.0.2"}, c.IPs())

	// ensure that the ordering of IPs in the header is maintained
	c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.3, 127.0.0.1, 127.0.0.2")
	utils.AssertEqual(t, []string{"127.0.0.3", "127.0.0.1", "127.0.0.2"}, c.IPs())

	// ensure for IPv6
	c.Request().Header.Set(HeaderXForwardedFor, "9396:9549:b4f7:8ed0:4791:1330:8c06:e62d, invalid, 2345:0425:2CA1::0567:5673:23b5")
	utils.AssertEqual(t, []string{"9396:9549:b4f7:8ed0:4791:1330:8c06:e62d", "invalid", "2345:0425:2CA1::0567:5673:23b5"}, c.IPs())

	// empty header
	c.Request().Header.Set(HeaderXForwardedFor, "")
	utils.AssertEqual(t, 0, len(c.IPs()))

	// missing header
	c.Request()
	utils.AssertEqual(t, 0, len(c.IPs()))
}

func Test_Ctx_IPs_With_IP_Validation(t *testing.T) {
	t.Parallel()
	app := New(Config{EnableIPValidation: true})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	// normal happy path test case
	c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1, 127.0.0.2, 127.0.0.3")
	utils.AssertEqual(t, []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, c.IPs())

	// inconsistent space formatting
	c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1,127.0.0.2  ,127.0.0.3")
	utils.AssertEqual(t, []string{"127.0.0.1", "127.0.0.2", "127.0.0.3"}, c.IPs())

	// invalid IPs are in the header
	c.Request().Header.Set(HeaderXForwardedFor, "invalid, 127.0.0.1, 127.0.0.2")
	utils.AssertEqual(t, []string{"127.0.0.1", "127.0.0.2"}, c.IPs())
	c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1, invalid, 127.0.0.2")
	utils.AssertEqual(t, []string{"127.0.0.1", "127.0.0.2"}, c.IPs())

	// ensure that the ordering of IPs in the header is maintained
	c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.3, 127.0.0.1, 127.0.0.2")
	utils.AssertEqual(t, []string{"127.0.0.3", "127.0.0.1", "127.0.0.2"}, c.IPs())

	// ensure for IPv6
	c.Request().Header.Set(HeaderXForwardedFor, "f037:825e:eadb:1b7b:1667:6f0a:5356:f604, invalid, 9396:9549:b4f7:8ed0:4791:1330:8c06:e62d")
	utils.AssertEqual(t, []string{"f037:825e:eadb:1b7b:1667:6f0a:5356:f604", "9396:9549:b4f7:8ed0:4791:1330:8c06:e62d"}, c.IPs())

	// empty header
	c.Request().Header.Set(HeaderXForwardedFor, "")
	utils.AssertEqual(t, 0, len(c.IPs()))

	// missing header
	c.Request()
	utils.AssertEqual(t, 0, len(c.IPs()))
}

// go test -v -run=^$ -bench=Benchmark_Ctx_IPs -benchmem -count=4
func Benchmark_Ctx_IPs(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1, invalid, 127.0.0.1")
	var res []string
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		res = c.IPs()
	}
	utils.AssertEqual(b, []string{"127.0.0.1", "invalid", "127.0.0.1"}, res)
}

func Benchmark_Ctx_IPs_v6(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderXForwardedFor, "f037:825e:eadb:1b7b:1667:6f0a:5356:f604, invalid, 2345:0425:2CA1::0567:5673:23b5")
	var res []string
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		res = c.IPs()
	}
	utils.AssertEqual(b, []string{"f037:825e:eadb:1b7b:1667:6f0a:5356:f604", "invalid", "2345:0425:2CA1::0567:5673:23b5"}, res)
}

func Benchmark_Ctx_IPs_With_IP_Validation(b *testing.B) {
	app := New(Config{EnableIPValidation: true})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1, invalid, 127.0.0.1")
	var res []string
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		res = c.IPs()
	}
	utils.AssertEqual(b, []string{"127.0.0.1", "127.0.0.1"}, res)
}

func Benchmark_Ctx_IPs_v6_With_IP_Validation(b *testing.B) {
	app := New(Config{EnableIPValidation: true})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderXForwardedFor, "2345:0425:2CA1:0000:0000:0567:5673:23b5, invalid, 2345:0425:2CA1::0567:5673:23b5")
	var res []string
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		res = c.IPs()
	}
	utils.AssertEqual(b, []string{"2345:0425:2CA1:0000:0000:0567:5673:23b5", "2345:0425:2CA1::0567:5673:23b5"}, res)
}

func Benchmark_Ctx_IP_With_ProxyHeader(b *testing.B) {
	app := New(Config{ProxyHeader: HeaderXForwardedFor})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1")
	var res string
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		res = c.IP()
	}
	utils.AssertEqual(b, "127.0.0.1", res)
}

func Benchmark_Ctx_IP_With_ProxyHeader_and_IP_Validation(b *testing.B) {
	app := New(Config{ProxyHeader: HeaderXForwardedFor, EnableIPValidation: true})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1")
	var res string
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		res = c.IP()
	}
	utils.AssertEqual(b, "127.0.0.1", res)
}

func Benchmark_Ctx_IP(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request()
	var res string
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		res = c.IP()
	}
	utils.AssertEqual(b, "0.0.0.0", res)
}

// go test -run Test_Ctx_Is
func Test_Ctx_Is(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderContentType, MIMETextHTML+"; boundary=something")
	utils.AssertEqual(t, true, c.Is(".html"))
	utils.AssertEqual(t, true, c.Is("html"))
	utils.AssertEqual(t, false, c.Is("json"))
	utils.AssertEqual(t, false, c.Is(".json"))
	utils.AssertEqual(t, false, c.Is(""))
	utils.AssertEqual(t, false, c.Is(".foooo"))

	c.Request().Header.Set(HeaderContentType, MIMEApplicationJSONCharsetUTF8)
	utils.AssertEqual(t, false, c.Is("html"))
	utils.AssertEqual(t, true, c.Is("json"))
	utils.AssertEqual(t, true, c.Is(".json"))

	c.Request().Header.Set(HeaderContentType, " application/json;charset=UTF-8")
	utils.AssertEqual(t, false, c.Is("html"))
	utils.AssertEqual(t, true, c.Is("json"))
	utils.AssertEqual(t, true, c.Is(".json"))

	c.Request().Header.Set(HeaderContentType, MIMEApplicationXMLCharsetUTF8)
	utils.AssertEqual(t, false, c.Is("html"))
	utils.AssertEqual(t, true, c.Is("xml"))
	utils.AssertEqual(t, true, c.Is(".xml"))

	c.Request().Header.Set(HeaderContentType, MIMETextPlain)
	utils.AssertEqual(t, false, c.Is("html"))
	utils.AssertEqual(t, true, c.Is("txt"))
	utils.AssertEqual(t, true, c.Is(".txt"))

	// case-insensitive and trimmed
	c.Request().Header.Set(HeaderContentType, "APPLICATION/JSON; charset=utf-8")
	utils.AssertEqual(t, true, c.Is("json"))
	utils.AssertEqual(t, true, c.Is(".json"))

	// mismatched subtype should not match
	c.Request().Header.Set(HeaderContentType, "application/json+xml")
	utils.AssertEqual(t, false, c.Is("json"))
	utils.AssertEqual(t, false, c.Is(".json"))
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Is -benchmem -count=4
func Benchmark_Ctx_Is(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderContentType, MIMEApplicationJSON)
	var res bool
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		_ = c.Is(".json")
		res = c.Is("json")
	}
	utils.AssertEqual(b, true, res)
}

// go test -run Test_Ctx_Locals
func Test_Ctx_Locals(t *testing.T) {
	t.Parallel()
	app := New()
	app.Use(func(c *Ctx) error {
		c.Locals("john", "doe")
		return c.Next()
	})
	app.Get("/test", func(c *Ctx) error {
		utils.AssertEqual(t, "doe", c.Locals("john"))
		return nil
	})
	resp, err := app.Test(httptest.NewRequest(MethodGet, "/test", nil))
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
}

// go test -run Test_Ctx_Method
func Test_Ctx_Method(t *testing.T) {
	t.Parallel()
	fctx := &fasthttp.RequestCtx{}
	fctx.Request.Header.SetMethod(MethodGet)
	app := New()
	c := app.AcquireCtx(fctx)
	defer app.ReleaseCtx(c)
	utils.AssertEqual(t, MethodGet, c.Method())
	c.Method(MethodPost)
	utils.AssertEqual(t, MethodPost, c.Method())

	c.Method("MethodInvalid")
	utils.AssertEqual(t, MethodPost, c.Method())
}

// go test -run Test_Ctx_ClientHelloInfo
func Test_Ctx_ClientHelloInfo(t *testing.T) {
	t.Parallel()
	app := New()
	app.Get("/ServerName", func(c *Ctx) error {
		result := c.ClientHelloInfo()
		if result != nil {
			return c.SendString(result.ServerName)
		}

		return c.SendString("ClientHelloInfo is nil")
	})
	app.Get("/SignatureSchemes", func(c *Ctx) error {
		result := c.ClientHelloInfo()
		if result != nil {
			return c.JSON(result.SignatureSchemes)
		}

		return c.SendString("ClientHelloInfo is nil")
	})
	app.Get("/SupportedVersions", func(c *Ctx) error {
		result := c.ClientHelloInfo()
		if result != nil {
			return c.JSON(result.SupportedVersions)
		}

		return c.SendString("ClientHelloInfo is nil")
	})

	// Test without TLS handler
	resp, err := app.Test(httptest.NewRequest(MethodGet, "/ServerName", nil))
	utils.AssertEqual(t, nil, err)
	body, err := io.ReadAll(resp.Body)
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, []byte("ClientHelloInfo is nil"), body)

	// Test with TLS Handler
	const (
		pssWithSHA256 = 0x0804
		versionTLS13  = 0x0304
	)
	app.tlsHandler = &TLSHandler{clientHelloInfo: &tls.ClientHelloInfo{
		ServerName:        "example.golang",
		SignatureSchemes:  []tls.SignatureScheme{pssWithSHA256},
		SupportedVersions: []uint16{versionTLS13},
	}}

	// Test ServerName
	resp, err = app.Test(httptest.NewRequest(MethodGet, "/ServerName", nil))
	utils.AssertEqual(t, nil, err)
	body, err = io.ReadAll(resp.Body)
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, []byte("example.golang"), body)

	// Test SignatureSchemes
	resp, err = app.Test(httptest.NewRequest(MethodGet, "/SignatureSchemes", nil))
	utils.AssertEqual(t, nil, err)
	body, err = io.ReadAll(resp.Body)
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "["+strconv.Itoa(pssWithSHA256)+"]", string(body))

	// Test SupportedVersions
	resp, err = app.Test(httptest.NewRequest(MethodGet, "/SupportedVersions", nil))
	utils.AssertEqual(t, nil, err)
	body, err = io.ReadAll(resp.Body)
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "["+strconv.Itoa(versionTLS13)+"]", string(body))
}

// go test -run Test_Ctx_InvalidMethod
func Test_Ctx_InvalidMethod(t *testing.T) {
	t.Parallel()
	app := New()
	app.Get("/", func(c *Ctx) error {
		return nil
	})

	fctx := &fasthttp.RequestCtx{}
	fctx.Request.Header.SetMethod("InvalidMethod")
	fctx.Request.SetRequestURI("/")

	app.Handler()(fctx)

	utils.AssertEqual(t, 400, fctx.Response.StatusCode())
	utils.AssertEqual(t, []byte("Invalid http method"), fctx.Response.Body())
}

// go test -run Test_Ctx_MultipartForm
func Test_Ctx_MultipartForm(t *testing.T) {
	t.Parallel()
	app := New()

	app.Post("/test", func(c *Ctx) error {
		result, err := c.MultipartForm()
		utils.AssertEqual(t, nil, err)
		utils.AssertEqual(t, "john", result.Value["name"][0])
		return nil
	})

	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)
	utils.AssertEqual(t, nil, writer.WriteField("name", "john"))
	utils.AssertEqual(t, nil, writer.Close())

	req := httptest.NewRequest(MethodPost, "/test", body)
	req.Header.Set(HeaderContentType, fmt.Sprintf("multipart/form-data; boundary=%s", writer.Boundary()))
	req.Header.Set(HeaderContentLength, strconv.Itoa(len(body.Bytes())))

	resp, err := app.Test(req)
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
}

// go test -v -run=^$ -bench=Benchmark_Ctx_MultipartForm -benchmem -count=4
func Benchmark_Ctx_MultipartForm(b *testing.B) {
	app := New()

	app.Post("/", func(c *Ctx) error {
		_, err := c.MultipartForm()
		return err
	})

	c := &fasthttp.RequestCtx{}

	body := []byte("--b\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\njohn\r\n--b--")
	c.Request.SetBody(body)
	c.Request.Header.SetContentType(MIMEMultipartForm + `;boundary="b"`)
	c.Request.Header.SetContentLength(len(body))

	h := app.Handler()

	b.ReportAllocs()
	b.ResetTimer()

	for n := 0; n < b.N; n++ {
		h(c)
	}
}

// go test -run Test_Ctx_OriginalURL
func Test_Ctx_OriginalURL(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.SetRequestURI("http://google.com/test?search=demo")
	utils.AssertEqual(t, "http://google.com/test?search=demo", c.OriginalURL())
}

// go test -race -run Test_Ctx_Params
func Test_Ctx_Params(t *testing.T) {
	t.Parallel()
	app := New()
	app.Get("/test/:user", func(c *Ctx) error {
		utils.AssertEqual(t, "john", c.Params("user"))
		return nil
	})
	app.Get("/test2/*", func(c *Ctx) error {
		utils.AssertEqual(t, "im/a/cookie", c.Params("*"))
		return nil
	})
	app.Get("/test3/*/blafasel/*", func(c *Ctx) error {
		utils.AssertEqual(t, "1111", c.Params("*1"))
		utils.AssertEqual(t, "2222", c.Params("*2"))
		utils.AssertEqual(t, "1111", c.Params("*"))
		return nil
	})
	app.Get("/test4/:optional?", func(c *Ctx) error {
		utils.AssertEqual(t, "", c.Params("optional"))
		return nil
	})
	app.Get("/test5/:id/:Id", func(c *Ctx) error {
		utils.AssertEqual(t, "first", c.Params("id"))
		utils.AssertEqual(t, "first", c.Params("Id"))
		return nil
	})
	resp, err := app.Test(httptest.NewRequest(MethodGet, "/test/john", nil))
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")

	resp, err = app.Test(httptest.NewRequest(MethodGet, "/test2/im/a/cookie", nil))
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")

	resp, err = app.Test(httptest.NewRequest(MethodGet, "/test3/1111/blafasel/2222", nil))
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")

	resp, err = app.Test(httptest.NewRequest(MethodGet, "/test4", nil))
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")

	resp, err = app.Test(httptest.NewRequest(MethodGet, "/test5/first/second", nil))
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
}

func Test_Ctx_Params_Case_Sensitive(t *testing.T) {
	t.Parallel()
	app := New(Config{CaseSensitive: true})
	app.Get("/test/:User", func(c *Ctx) error {
		utils.AssertEqual(t, "john", c.Params("User"))
		utils.AssertEqual(t, "", c.Params("user"))
		return nil
	})
	app.Get("/test2/:id/:Id", func(c *Ctx) error {
		utils.AssertEqual(t, "first", c.Params("id"))
		utils.AssertEqual(t, "second", c.Params("Id"))
		return nil
	})
	resp, err := app.Test(httptest.NewRequest(MethodGet, "/test/john", nil))
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")

	resp, err = app.Test(httptest.NewRequest(MethodGet, "/test2/first/second", nil))
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
}

// go test -race -run Test_Ctx_AllParams
func Test_Ctx_AllParams(t *testing.T) {
	t.Parallel()
	app := New()
	app.Get("/test/:user", func(c *Ctx) error {
		utils.AssertEqual(t, map[string]string{"user": "john"}, c.AllParams())
		return nil
	})
	app.Get("/test2/*", func(c *Ctx) error {
		utils.AssertEqual(t, map[string]string{"*1": "im/a/cookie"}, c.AllParams())
		return nil
	})
	app.Get("/test3/*/blafasel/*", func(c *Ctx) error {
		utils.AssertEqual(t, map[string]string{"*1": "1111", "*2": "2222"}, c.AllParams())
		return nil
	})
	app.Get("/test4/:optional?", func(c *Ctx) error {
		utils.AssertEqual(t, map[string]string{"optional": ""}, c.AllParams())
		return nil
	})

	resp, err := app.Test(httptest.NewRequest(MethodGet, "/test/john", nil))
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")

	resp, err = app.Test(httptest.NewRequest(MethodGet, "/test2/im/a/cookie", nil))
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")

	resp, err = app.Test(httptest.NewRequest(MethodGet, "/test3/1111/blafasel/2222", nil))
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")

	resp, err = app.Test(httptest.NewRequest(MethodGet, "/test4", nil))
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Params -benchmem -count=4
func Benchmark_Ctx_Params(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.route = &Route{
		Params: []string{
			"param1", "param2", "param3", "param4",
		},
	}
	c.values = [maxParams]string{
		"john", "doe", "is", "awesome",
	}
	var res string
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		_ = c.Params("param1")
		_ = c.Params("param2")
		_ = c.Params("param3")
		res = c.Params("param4")
	}
	utils.AssertEqual(b, "awesome", res)
}

// go test -v -run=^$ -bench=Benchmark_Ctx_AllParams -benchmem -count=4
func Benchmark_Ctx_AllParams(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.route = &Route{
		Params: []string{
			"param1", "param2", "param3", "param4",
		},
	}
	c.values = [maxParams]string{
		"john", "doe", "is", "awesome",
	}
	var res map[string]string
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		res = c.AllParams()
	}
	utils.AssertEqual(
		b,
		map[string]string{
			"param1": "john",
			"param2": "doe",
			"param3": "is",
			"param4": "awesome",
		},
		res,
	)
}

// go test -v -run=^$ -bench=Benchmark_Ctx_ParamsParse -benchmem -count=4
func Benchmark_Ctx_ParamsParse(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.route = &Route{
		Params: []string{
			"param1", "param2", "param3", "param4",
		},
	}
	c.values = [maxParams]string{
		"john", "doe", "is", "awesome",
	}
	var res struct {
		Param1 string `params:"param1"`
		Param2 string `params:"param2"`
		Param3 string `params:"param3"`
		Param4 string `params:"param4"`
	}
	b.ReportAllocs()
	b.ResetTimer()

	var err error
	for n := 0; n < b.N; n++ {
		err = c.ParamsParser(&res)
	}

	utils.AssertEqual(b, nil, err)
	utils.AssertEqual(b, "john", res.Param1)
	utils.AssertEqual(b, "doe", res.Param2)
	utils.AssertEqual(b, "is", res.Param3)
	utils.AssertEqual(b, "awesome", res.Param4)
}

// go test -run Test_Ctx_Path
func Test_Ctx_Path(t *testing.T) {
	t.Parallel()
	app := New(Config{UnescapePath: true})
	app.Get("/test/:user", func(c *Ctx) error {
		utils.AssertEqual(t, "/Test/John", c.Path())
		// not strict && case insensitive
		utils.AssertEqual(t, "/ABC/", c.Path("/ABC/"))
		utils.AssertEqual(t, "/test/john/", c.Path("/test/john/"))
		return nil
	})

	// test with special chars
	app.Get("/specialChars/:name", func(c *Ctx) error {
		utils.AssertEqual(t, "/specialChars/créer", c.Path())
		// unescape is also working if you set the path afterwards
		utils.AssertEqual(t, "/اختبار/", c.Path("/%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1/"))
		return nil
	})
	resp, err := app.Test(httptest.NewRequest(MethodGet, "/specialChars/cr%C3%A9er", nil))
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
}

// go test -run Test_Ctx_Protocol
func Test_Ctx_Protocol(t *testing.T) {
	t.Parallel()
	app := New()

	freq := &fasthttp.RequestCtx{}
	freq.Request.Header.Set("X-Forwarded", "invalid")

	c := app.AcquireCtx(freq)
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderXForwardedProto, schemeHTTPS)
	utils.AssertEqual(t, schemeHTTPS, c.Protocol())
	c.Request().Header.Reset()

	c.Request().Header.Set(HeaderXForwardedProtocol, schemeHTTPS)
	utils.AssertEqual(t, schemeHTTPS, c.Protocol())
	c.Request().Header.Reset()

	c.Request().Header.Set(HeaderXForwardedProto, "https, http")
	utils.AssertEqual(t, schemeHTTPS, c.Protocol())
	c.Request().Header.Reset()

	c.Request().Header.Set(HeaderXForwardedProtocol, "https, http")
	utils.AssertEqual(t, schemeHTTPS, c.Protocol())
	c.Request().Header.Reset()

	c.Request().Header.Set(HeaderXForwardedSsl, "on")
	utils.AssertEqual(t, schemeHTTPS, c.Protocol())
	c.Request().Header.Reset()

	c.Request().Header.Set(HeaderXUrlScheme, schemeHTTPS)
	utils.AssertEqual(t, schemeHTTPS, c.Protocol())
	c.Request().Header.Reset()

	utils.AssertEqual(t, schemeHTTP, c.Protocol())
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Protocol -benchmem -count=4
func Benchmark_Ctx_Protocol(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	var res string
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		res = c.Protocol()
	}
	utils.AssertEqual(b, schemeHTTP, res)
}

// go test -run Test_Ctx_Protocol_TrustedProxy
func Test_Ctx_Protocol_TrustedProxy(t *testing.T) {
	t.Parallel()
	app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0"}})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	c.Request().Header.Set(HeaderXForwardedProto, schemeHTTPS)
	utils.AssertEqual(t, schemeHTTPS, c.Protocol())
	c.Request().Header.Reset()

	c.Request().Header.Set(HeaderXForwardedProtocol, schemeHTTPS)
	utils.AssertEqual(t, schemeHTTPS, c.Protocol())
	c.Request().Header.Reset()

	c.Request().Header.Set(HeaderXForwardedSsl, "on")
	utils.AssertEqual(t, schemeHTTPS, c.Protocol())
	c.Request().Header.Reset()

	c.Request().Header.Set(HeaderXUrlScheme, schemeHTTPS)
	utils.AssertEqual(t, schemeHTTPS, c.Protocol())
	c.Request().Header.Reset()

	utils.AssertEqual(t, schemeHTTP, c.Protocol())
}

// go test -run Test_Ctx_Protocol_TrustedProxyRange
func Test_Ctx_Protocol_TrustedProxyRange(t *testing.T) {
	t.Parallel()
	app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0/30"}})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	c.Request().Header.Set(HeaderXForwardedProto, schemeHTTPS)
	utils.AssertEqual(t, schemeHTTPS, c.Protocol())
	c.Request().Header.Reset()

	c.Request().Header.Set(HeaderXForwardedProtocol, schemeHTTPS)
	utils.AssertEqual(t, schemeHTTPS, c.Protocol())
	c.Request().Header.Reset()

	c.Request().Header.Set(HeaderXForwardedSsl, "on")
	utils.AssertEqual(t, schemeHTTPS, c.Protocol())
	c.Request().Header.Reset()

	c.Request().Header.Set(HeaderXUrlScheme, schemeHTTPS)
	utils.AssertEqual(t, schemeHTTPS, c.Protocol())
	c.Request().Header.Reset()

	utils.AssertEqual(t, schemeHTTP, c.Protocol())
}

// go test -run Test_Ctx_Protocol_UntrustedProxyRange
func Test_Ctx_Protocol_UntrustedProxyRange(t *testing.T) {
	t.Parallel()
	app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"1.1.1.1/30"}})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	c.Request().Header.Set(HeaderXForwardedProto, schemeHTTPS)
	utils.AssertEqual(t, schemeHTTP, c.Protocol())
	c.Request().Header.Reset()

	c.Request().Header.Set(HeaderXForwardedProtocol, schemeHTTPS)
	utils.AssertEqual(t, schemeHTTP, c.Protocol())
	c.Request().Header.Reset()

	c.Request().Header.Set(HeaderXForwardedSsl, "on")
	utils.AssertEqual(t, schemeHTTP, c.Protocol())
	c.Request().Header.Reset()

	c.Request().Header.Set(HeaderXUrlScheme, schemeHTTPS)
	utils.AssertEqual(t, schemeHTTP, c.Protocol())
	c.Request().Header.Reset()

	utils.AssertEqual(t, schemeHTTP, c.Protocol())
}

// go test -run Test_Ctx_Protocol_UnTrustedProxy
func Test_Ctx_Protocol_UnTrustedProxy(t *testing.T) {
	t.Parallel()
	app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.8.0.1"}})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	c.Request().Header.Set(HeaderXForwardedProto, schemeHTTPS)
	utils.AssertEqual(t, schemeHTTP, c.Protocol())
	c.Request().Header.Reset()

	c.Request().Header.Set(HeaderXForwardedProtocol, schemeHTTPS)
	utils.AssertEqual(t, schemeHTTP, c.Protocol())
	c.Request().Header.Reset()

	c.Request().Header.Set(HeaderXForwardedSsl, "on")
	utils.AssertEqual(t, schemeHTTP, c.Protocol())
	c.Request().Header.Reset()

	c.Request().Header.Set(HeaderXUrlScheme, schemeHTTPS)
	utils.AssertEqual(t, schemeHTTP, c.Protocol())
	c.Request().Header.Reset()

	utils.AssertEqual(t, schemeHTTP, c.Protocol())
}

// go test -run Test_Ctx_Query
func Test_Ctx_Query(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().URI().SetQueryString("search=john&age=20&id=")
	utils.AssertEqual(t, "john", c.Query("search"))
	utils.AssertEqual(t, "20", c.Query("age"))
	utils.AssertEqual(t, "default", c.Query("unknown", "default"))
}

func Test_Ctx_QueryInt(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().URI().SetQueryString("search=john&age=20&id=")

	utils.AssertEqual(t, 0, c.QueryInt("foo"))
	utils.AssertEqual(t, 20, c.QueryInt("age", 12))
	utils.AssertEqual(t, 0, c.QueryInt("search"))
	utils.AssertEqual(t, 1, c.QueryInt("search", 1))
	utils.AssertEqual(t, 0, c.QueryInt("id"))
	utils.AssertEqual(t, 2, c.QueryInt("id", 2))
}

func Test_Ctx_QueryBool(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().URI().SetQueryString("name=alex&want_pizza=false&id=")

	utils.AssertEqual(t, false, c.QueryBool("want_pizza"))
	utils.AssertEqual(t, false, c.QueryBool("want_pizza", true))
	utils.AssertEqual(t, false, c.QueryBool("name"))
	utils.AssertEqual(t, true, c.QueryBool("name", true))
	utils.AssertEqual(t, false, c.QueryBool("id"))
	utils.AssertEqual(t, true, c.QueryBool("id", true))
}

func Test_Ctx_QueryFloat(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().URI().SetQueryString("name=alex&amount=32.23&id=")

	utils.AssertEqual(t, 32.23, c.QueryFloat("amount"))
	utils.AssertEqual(t, 32.23, c.QueryFloat("amount", 3.123))
	utils.AssertEqual(t, 87.123, c.QueryFloat("name", 87.123))
	utils.AssertEqual(t, float64(0), c.QueryFloat("name"))
	utils.AssertEqual(t, 12.87, c.QueryFloat("id", 12.87))
	utils.AssertEqual(t, float64(0), c.QueryFloat("id"))
}

// go test -run Test_Ctx_Range
func Test_Ctx_Range(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	testRange := func(header string, ranges ...RangeSet) {
		c.Request().Header.Set(HeaderRange, header)
		result, err := c.Range(1000)
		if len(ranges) == 0 {
			utils.AssertEqual(t, true, err != nil)
		} else {
			utils.AssertEqual(t, "bytes", result.Type)
			utils.AssertEqual(t, true, err == nil)
		}
		utils.AssertEqual(t, len(ranges), len(result.Ranges))
		for i := range ranges {
			utils.AssertEqual(t, ranges[i], result.Ranges[i])
		}
	}

	testRange("bytes=500")
	testRange("bytes=")
	testRange("bytes=500=")
	testRange("bytes=500-300")
	testRange("bytes=a-700", RangeSet{300, 999})
	testRange("bytes=500-b", RangeSet{500, 999})
	testRange("bytes=500-1000", RangeSet{500, 999})
	testRange("bytes=500-700", RangeSet{500, 700})
	testRange("bytes=0-0,2-1000", RangeSet{0, 0}, RangeSet{2, 999})
	testRange("bytes=0-99,450-549,-100", RangeSet{0, 99}, RangeSet{450, 549}, RangeSet{900, 999})
	testRange("bytes=500-700,601-999", RangeSet{500, 700}, RangeSet{601, 999})
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Range -benchmem -count=4
func Benchmark_Ctx_Range(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	testCases := []struct {
		str   string
		start int
		end   int
	}{
		{"bytes=-700", 300, 999},
		{"bytes=500-", 500, 999},
		{"bytes=500-1000", 500, 999},
		{"bytes=0-700,800-1000", 0, 700},
	}

	for _, tc := range testCases {
		b.Run(tc.str, func(b *testing.B) {
			c.Request().Header.Set(HeaderRange, tc.str)
			var (
				result Range
				err    error
			)
			for n := 0; n < b.N; n++ {
				result, err = c.Range(1000)
			}
			utils.AssertEqual(b, nil, err)
			utils.AssertEqual(b, "bytes", result.Type)
			utils.AssertEqual(b, tc.start, result.Ranges[0].Start)
			utils.AssertEqual(b, tc.end, result.Ranges[0].End)
		})
	}
}

// go test -run Test_Ctx_Route
func Test_Ctx_Route(t *testing.T) {
	t.Parallel()
	app := New()
	app.Get("/test", func(c *Ctx) error {
		utils.AssertEqual(t, "/test", c.Route().Path)
		return nil
	})
	resp, err := app.Test(httptest.NewRequest(MethodGet, "/test", nil))
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")

	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	utils.AssertEqual(t, "/", c.Route().Path)
	utils.AssertEqual(t, MethodGet, c.Route().Method)
	utils.AssertEqual(t, 0, len(c.Route().Handlers))
}

// go test -run Test_Ctx_RouteNormalized
func Test_Ctx_RouteNormalized(t *testing.T) {
	t.Parallel()
	app := New()
	app.Get("/test", func(c *Ctx) error {
		utils.AssertEqual(t, "/test", c.Route().Path)
		return nil
	})
	resp, err := app.Test(httptest.NewRequest(MethodGet, "//test", nil))
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusNotFound, resp.StatusCode, "Status code")
}

// go test -run Test_Ctx_SaveFile
func Test_Ctx_SaveFile(t *testing.T) {
	// TODO We should clean this up
	t.Parallel()
	app := New()

	app.Post("/test", func(c *Ctx) error {
		fh, err := c.FormFile("file")
		utils.AssertEqual(t, nil, err)

		tempFile, err := os.CreateTemp(os.TempDir(), "test-")
		utils.AssertEqual(t, nil, err)

		defer func(file *os.File) {
			err := file.Close()
			utils.AssertEqual(t, nil, err)
			err = os.Remove(file.Name())
			utils.AssertEqual(t, nil, err)
		}(tempFile)
		err = c.SaveFile(fh, tempFile.Name())
		utils.AssertEqual(t, nil, err)

		bs, err := os.ReadFile(tempFile.Name())
		utils.AssertEqual(t, nil, err)
		utils.AssertEqual(t, "hello world", string(bs))
		return nil
	})

	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	ioWriter, err := writer.CreateFormFile("file", "test")
	utils.AssertEqual(t, nil, err)

	_, err = ioWriter.Write([]byte("hello world"))
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, nil, writer.Close())

	req := httptest.NewRequest(MethodPost, "/test", body)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	req.Header.Set("Content-Length", strconv.Itoa(len(body.Bytes())))

	resp, err := app.Test(req)
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
}

// go test -run Test_Ctx_SaveFileToStorage
func Test_Ctx_SaveFileToStorage(t *testing.T) {
	t.Parallel()
	app := New()
	storage := memory.New()

	app.Post("/test", func(c *Ctx) error {
		fh, err := c.FormFile("file")
		utils.AssertEqual(t, nil, err)

		err = c.SaveFileToStorage(fh, "test", storage)
		utils.AssertEqual(t, nil, err)

		file, err := storage.Get("test")
		utils.AssertEqual(t, []byte("hello world"), file)
		utils.AssertEqual(t, nil, err)

		err = storage.Delete("test")
		utils.AssertEqual(t, nil, err)

		return nil
	})

	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	ioWriter, err := writer.CreateFormFile("file", "test")
	utils.AssertEqual(t, nil, err)

	_, err = ioWriter.Write([]byte("hello world"))
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, nil, writer.Close())

	req := httptest.NewRequest(MethodPost, "/test", body)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	req.Header.Set("Content-Length", strconv.Itoa(len(body.Bytes())))

	resp, err := app.Test(req)
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
}

// go test -run Test_Ctx_Secure
func Test_Ctx_Secure(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	// TODO Add TLS conn
	utils.AssertEqual(t, false, c.Secure())
}

// go test -run Test_Ctx_Stale
func Test_Ctx_Stale(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	utils.AssertEqual(t, true, c.Stale())
}

// go test -run Test_Ctx_Subdomains
func Test_Ctx_Subdomains(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().URI().SetHost("john.doe.is.awesome.google.com")
	utils.AssertEqual(t, []string{"john", "doe"}, c.Subdomains(4))

	c.Request().URI().SetHost("localhost:3000")
	utils.AssertEqual(t, []string{"localhost:3000"}, c.Subdomains())
}

// go test -run Test_Ctx_Immutable_AfterHandler
func Test_Ctx_Immutable_AfterHandler(t *testing.T) {
	t.Parallel()

	app := New(Config{
		Immutable:          true,
		ProxyHeader:        HeaderXForwardedFor,
		EnableIPValidation: true,
	})

	type ctxSnapshot struct {
		method          string
		path            string
		originalURL     string
		baseURL         string
		protocol        string
		hostname        string
		paramName       string
		queryFoo        string
		cookieSession   string
		headerUserAgent string
		ip              string
		ips             []string
		subdomains      []string
		body            []byte
		routePath       string
	}

	snapshots := make([]ctxSnapshot, 0, 2)

	app.All("/v1/:name", func(c *Ctx) error {
		snapshots = append(snapshots, ctxSnapshot{
			method:          c.Method(),
			path:            c.Path(),
			originalURL:     c.OriginalURL(),
			baseURL:         c.BaseURL(),
			protocol:        c.Protocol(),
			hostname:        c.Hostname(),
			paramName:       c.Params("name"),
			queryFoo:        c.Query("foo"),
			cookieSession:   c.Cookies("session"),
			headerUserAgent: c.Get(HeaderUserAgent),
			ip:              c.IP(),
			ips:             c.IPs(),
			subdomains:      c.Subdomains(),
			body:            c.Body(),
			routePath:       c.Route().Path,
		})

		return c.SendString("ok")
	})

	req := httptest.NewRequest(MethodPost, "https://initial.invalid/v1/alpha?foo=bar", strings.NewReader("body-one"))
	req.Header.Set(HeaderXForwardedHost, "p1.api.example.com")
	req.Header.Set(HeaderXForwardedProto, "https")
	req.Header.Set(HeaderXForwardedFor, "10.0.0.1, 10.0.0.2")
	req.Header.Set(HeaderUserAgent, "agent-one")
	req.Header.Set(HeaderCookie, "session=alpha")

	originalFirst := req.URL.String()

	resp, err := app.Test(req)
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, StatusOK, resp.StatusCode)
	utils.AssertEqual(t, nil, resp.Body.Close())

	utils.AssertEqual(t, 1, len(snapshots))
	first := snapshots[0]

	follow := httptest.NewRequest(MethodPatch, "http://secondary.invalid/v1/beta?foo=qux", strings.NewReader("body-two"))
	follow.Header.Set(HeaderXForwardedHost, "edge.stage.example.org")
	follow.Header.Set(HeaderXForwardedProto, "http")
	follow.Header.Set(HeaderXForwardedFor, "192.168.1.50")
	follow.Header.Set(HeaderUserAgent, "agent-two")
	follow.Header.Set(HeaderCookie, "session=beta")

	originalSecond := follow.URL.String()

	resp, err = app.Test(follow)
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, StatusOK, resp.StatusCode)
	utils.AssertEqual(t, nil, resp.Body.Close())

	utils.AssertEqual(t, 2, len(snapshots))
	second := snapshots[1]

	// Ensure the first request's state stays valid after the context is released.
	utils.AssertEqual(t, MethodPost, first.method)
	utils.AssertEqual(t, "/v1/alpha", first.path)
	utils.AssertEqual(t, originalFirst, first.originalURL)
	utils.AssertEqual(t, "https://p1.api.example.com", first.baseURL)
	utils.AssertEqual(t, "https", first.protocol)
	utils.AssertEqual(t, "p1.api.example.com", first.hostname)
	utils.AssertEqual(t, "alpha", first.paramName)
	utils.AssertEqual(t, "bar", first.queryFoo)
	utils.AssertEqual(t, "alpha", first.cookieSession)
	utils.AssertEqual(t, "agent-one", first.headerUserAgent)
	utils.AssertEqual(t, "10.0.0.1", first.ip)
	utils.AssertEqual(t, []string{"10.0.0.1", "10.0.0.2"}, first.ips)
	utils.AssertEqual(t, []string{"p1", "api"}, first.subdomains)
	utils.AssertEqual(t, "body-one", string(first.body))
	utils.AssertEqual(t, "/v1/:name", first.routePath)

	// Verify the second request collected distinct immutable copies.
	utils.AssertEqual(t, MethodPatch, second.method)
	utils.AssertEqual(t, "/v1/beta", second.path)
	utils.AssertEqual(t, originalSecond, second.originalURL)
	utils.AssertEqual(t, "http://edge.stage.example.org", second.baseURL)
	utils.AssertEqual(t, "http", second.protocol)
	utils.AssertEqual(t, "edge.stage.example.org", second.hostname)
	utils.AssertEqual(t, "beta", second.paramName)
	utils.AssertEqual(t, "qux", second.queryFoo)
	utils.AssertEqual(t, "beta", second.cookieSession)
	utils.AssertEqual(t, "agent-two", second.headerUserAgent)
	utils.AssertEqual(t, "192.168.1.50", second.ip)
	utils.AssertEqual(t, []string{"192.168.1.50"}, second.ips)
	utils.AssertEqual(t, []string{"edge", "stage"}, second.subdomains)
	utils.AssertEqual(t, "body-two", string(second.body))
	utils.AssertEqual(t, "/v1/:name", second.routePath)
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Subdomains -benchmem -count=4
func Benchmark_Ctx_Subdomains(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().SetRequestURI("http://john.doe.google.com")
	var res []string
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		res = c.Subdomains()
	}
	utils.AssertEqual(b, []string{"john", "doe"}, res)
}

// go test -run Test_Ctx_ClearCookie
func Test_Ctx_ClearCookie(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderCookie, "john=doe")
	c.ClearCookie("john")
	utils.AssertEqual(t, true, strings.HasPrefix(string(c.Response().Header.Peek(HeaderSetCookie)), "john=; expires="))

	c.Request().Header.Set(HeaderCookie, "test1=dummy")
	c.Request().Header.Set(HeaderCookie, "test2=dummy")
	c.ClearCookie()
	utils.AssertEqual(t, true, strings.Contains(string(c.Response().Header.Peek(HeaderSetCookie)), "test1=; expires="))
	utils.AssertEqual(t, true, strings.Contains(string(c.Response().Header.Peek(HeaderSetCookie)), "test2=; expires="))
}

// go test -race -run Test_Ctx_Download
func Test_Ctx_Download(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	utils.AssertEqual(t, nil, c.Download("ctx.go", "Awesome File!"))

	f, err := os.Open("./ctx.go")
	utils.AssertEqual(t, nil, err)
	defer func() {
		utils.AssertEqual(t, nil, f.Close())
	}()

	expect, err := io.ReadAll(f)
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, expect, c.Response().Body())
	utils.AssertEqual(t, `attachment; filename="Awesome+File%21"`, string(c.Response().Header.Peek(HeaderContentDisposition)))

	utils.AssertEqual(t, nil, c.Download("ctx.go"))
	utils.AssertEqual(t, `attachment; filename="ctx.go"`, string(c.Response().Header.Peek(HeaderContentDisposition)))
}

// go test -race -run Test_Ctx_SendFile
func Test_Ctx_SendFile(t *testing.T) {
	t.Parallel()
	app := New()

	// fetch file content
	f, err := os.Open("./ctx.go")
	utils.AssertEqual(t, nil, err)
	defer func() {
		utils.AssertEqual(t, nil, f.Close())
	}()
	expectFileContent, err := io.ReadAll(f)
	utils.AssertEqual(t, nil, err)
	// fetch file info for the not modified test case
	fI, err := os.Stat("./ctx.go")
	utils.AssertEqual(t, nil, err)

	// simple test case
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	err = c.SendFile("ctx.go")
	// check expectation
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, expectFileContent, c.Response().Body())
	utils.AssertEqual(t, StatusOK, c.Response().StatusCode())
	app.ReleaseCtx(c)

	// test with custom error code
	c = app.AcquireCtx(&fasthttp.RequestCtx{})
	err = c.Status(StatusInternalServerError).SendFile("ctx.go")
	// check expectation
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, expectFileContent, c.Response().Body())
	utils.AssertEqual(t, StatusInternalServerError, c.Response().StatusCode())
	app.ReleaseCtx(c)

	// test not modified
	c = app.AcquireCtx(&fasthttp.RequestCtx{})
	c.Request().Header.Set(HeaderIfModifiedSince, fI.ModTime().Format(time.RFC1123))
	err = c.SendFile("ctx.go")
	// check expectation
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, StatusNotModified, c.Response().StatusCode())
	utils.AssertEqual(t, []byte(nil), c.Response().Body())
	app.ReleaseCtx(c)
}

// go test -race -run Test_Ctx_SendFile_404
func Test_Ctx_SendFile_404(t *testing.T) {
	t.Parallel()
	app := New()
	app.Get("/", func(c *Ctx) error {
		err := c.SendFile(filepath.FromSlash("john_dow.go/"))
		utils.AssertEqual(t, false, err == nil)
		return err
	})

	resp, err := app.Test(httptest.NewRequest(MethodGet, "/", nil))
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, StatusNotFound, resp.StatusCode)
}

// go test -race -run Test_Ctx_SendFile_Immutable
func Test_Ctx_SendFile_Immutable(t *testing.T) {
	t.Parallel()
	app := New()
	var endpointsForTest []string
	addEndpoint := func(file, endpoint string) {
		endpointsForTest = append(endpointsForTest, endpoint)
		app.Get(endpoint, func(c *Ctx) error {
			if err := c.SendFile(file); err != nil {
				utils.AssertEqual(t, nil, err)
				return err
			}
			return c.SendStatus(200)
		})
	}

	// relative paths
	addEndpoint("./.github/index.html", "/relativeWithDot")
	addEndpoint(filepath.FromSlash("./.github/index.html"), "/relativeOSWithDot")
	addEndpoint(".github/index.html", "/relative")
	addEndpoint(filepath.FromSlash(".github/index.html"), "/relativeOS")

	// absolute paths
	if path, err := filepath.Abs(".github/index.html"); err != nil {
		utils.AssertEqual(t, nil, err)
	} else {
		addEndpoint(path, "/absolute")
		addEndpoint(filepath.FromSlash(path), "/absoluteOS") // os related
	}

	for _, endpoint := range endpointsForTest {
		t.Run(endpoint, func(t *testing.T) {
			// 1st try
			resp, err := app.Test(httptest.NewRequest(MethodGet, endpoint, nil))
			utils.AssertEqual(t, nil, err)
			utils.AssertEqual(t, StatusOK, resp.StatusCode)
			// 2nd try
			resp, err = app.Test(httptest.NewRequest(MethodGet, endpoint, nil))
			utils.AssertEqual(t, nil, err)
			utils.AssertEqual(t, StatusOK, resp.StatusCode)
		})
	}
}

// go test -race -run Test_Ctx_SendFile_RestoreOriginalURL
func Test_Ctx_SendFile_RestoreOriginalURL(t *testing.T) {
	t.Parallel()
	app := New()
	app.Get("/", func(c *Ctx) error {
		originalURL := utils.CopyString(c.OriginalURL())
		err := c.SendFile("ctx.go")
		utils.AssertEqual(t, originalURL, c.OriginalURL())
		return err
	})

	_, err1 := app.Test(httptest.NewRequest(MethodGet, "/?test=true", nil))
	// second request required to confirm with zero allocation
	_, err2 := app.Test(httptest.NewRequest(MethodGet, "/?test=true", nil))

	utils.AssertEqual(t, nil, err1)
	utils.AssertEqual(t, nil, err2)
}

// go test -run Test_Ctx_JSON
func Test_Ctx_JSON(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	utils.AssertEqual(t, true, c.JSON(complex(1, 1)) != nil)

	// Test without ctype
	err := c.JSON(Map{ // map has no order
		"Name": "Grame",
		"Age":  20,
	})
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, `{"Age":20,"Name":"Grame"}`, string(c.Response().Body()))
	utils.AssertEqual(t, "application/json", string(c.Response().Header.Peek("content-type")))

	// Test with ctype
	err = c.JSON(Map{ // map has no order
		"Name": "Grame",
		"Age":  20,
	}, "application/problem+json")
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, `{"Age":20,"Name":"Grame"}`, string(c.Response().Body()))
	utils.AssertEqual(t, "application/problem+json", string(c.Response().Header.Peek("content-type")))

	testEmpty := func(v interface{}, r string) {
		err := c.JSON(v)
		utils.AssertEqual(t, nil, err)
		utils.AssertEqual(t, r, string(c.Response().Body()))
	}

	testEmpty(nil, "null")
	testEmpty("", `""`)
	testEmpty(0, "0")
	testEmpty([]int{}, "[]")

	t.Run("custom json encoder", func(t *testing.T) {
		t.Parallel()

		app := New(Config{
			JSONEncoder: func(v interface{}) ([]byte, error) {
				return []byte(`["custom","json"]`), nil
			},
		})
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		defer app.ReleaseCtx(c)

		err := c.JSON(Map{ // map has no order
			"Name": "Grame",
			"Age":  20,
		})
		utils.AssertEqual(t, nil, err)
		utils.AssertEqual(t, `["custom","json"]`, string(c.Response().Body()))
		utils.AssertEqual(t, "application/json", string(c.Response().Header.Peek("content-type")))
	})
}

// go test -run=^$ -bench=Benchmark_Ctx_JSON -benchmem -count=4
func Benchmark_Ctx_JSON(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type SomeStruct struct {
		Name string
		Age  uint8
	}
	data := SomeStruct{
		Name: "Grame",
		Age:  20,
	}
	var err error
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		err = c.JSON(data)
	}
	utils.AssertEqual(b, nil, err)
	utils.AssertEqual(b, `{"Name":"Grame","Age":20}`, string(c.Response().Body()))
}

// go test -run=^$ -bench=Benchmark_Ctx_JSON_Ctype -benchmem -count=4
func Benchmark_Ctx_JSON_Ctype(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type SomeStruct struct {
		Name string
		Age  uint8
	}
	data := SomeStruct{
		Name: "Grame",
		Age:  20,
	}
	var err error
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		err = c.JSON(data, "application/problem+json")
	}
	utils.AssertEqual(b, nil, err)
	utils.AssertEqual(b, `{"Name":"Grame","Age":20}`, string(c.Response().Body()))
	utils.AssertEqual(b, "application/problem+json", string(c.Response().Header.Peek("content-type")))
}

// go test -run Test_Ctx_JSONP
func Test_Ctx_JSONP(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	utils.AssertEqual(t, true, c.JSONP(complex(1, 1)) != nil)

	err := c.JSONP(Map{
		"Name": "Grame",
		"Age":  20,
	})
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, `callback({"Age":20,"Name":"Grame"});`, string(c.Response().Body()))
	utils.AssertEqual(t, "text/javascript; charset=utf-8", string(c.Response().Header.Peek("content-type")))

	err = c.JSONP(Map{
		"Name": "Grame",
		"Age":  20,
	}, "john")
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, `john({"Age":20,"Name":"Grame"});`, string(c.Response().Body()))
	utils.AssertEqual(t, "text/javascript; charset=utf-8", string(c.Response().Header.Peek("content-type")))

	t.Run("custom json encoder", func(t *testing.T) {
		t.Parallel()

		app := New(Config{
			JSONEncoder: func(v interface{}) ([]byte, error) {
				return []byte(`["custom","json"]`), nil
			},
		})
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		defer app.ReleaseCtx(c)

		err := c.JSONP(Map{ // map has no order
			"Name": "Grame",
			"Age":  20,
		})
		utils.AssertEqual(t, nil, err)
		utils.AssertEqual(t, `callback(["custom","json"]);`, string(c.Response().Body()))
		utils.AssertEqual(t, "text/javascript; charset=utf-8", string(c.Response().Header.Peek("content-type")))
	})
}

// go test -v  -run=^$ -bench=Benchmark_Ctx_JSONP -benchmem -count=4
func Benchmark_Ctx_JSONP(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type SomeStruct struct {
		Name string
		Age  uint8
	}
	data := SomeStruct{
		Name: "Grame",
		Age:  20,
	}
	callback := "emit"
	var err error
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		err = c.JSONP(data, callback)
	}
	utils.AssertEqual(b, nil, err)
	utils.AssertEqual(b, `emit({"Name":"Grame","Age":20});`, string(c.Response().Body()))
}

// go test -run Test_Ctx_XML
func Test_Ctx_XML(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	utils.AssertEqual(t, true, c.JSON(complex(1, 1)) != nil)

	type xmlResult struct {
		XMLName xml.Name `xml:"Users"`
		Names   []string `xml:"Names"`
		Ages    []int    `xml:"Ages"`
	}

	err := c.XML(xmlResult{
		Names: []string{"Grame", "John"},
		Ages:  []int{1, 12, 20},
	})
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, `<Users><Names>Grame</Names><Names>John</Names><Ages>1</Ages><Ages>12</Ages><Ages>20</Ages></Users>`, string(c.Response().Body()))
	utils.AssertEqual(t, "application/xml", string(c.Response().Header.Peek("content-type")))

	testEmpty := func(v interface{}, r string) {
		err := c.XML(v)
		utils.AssertEqual(t, nil, err)
		utils.AssertEqual(t, r, string(c.Response().Body()))
	}

	testEmpty(nil, "")
	testEmpty("", `<string></string>`)
	testEmpty(0, "<int>0</int>")
	testEmpty([]int{}, "")

	t.Run("custom xml encoder", func(t *testing.T) {
		t.Parallel()

		app := New(Config{
			XMLEncoder: func(v interface{}) ([]byte, error) {
				return []byte(`<custom>xml</custom>`), nil
			},
		})
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		defer app.ReleaseCtx(c)

		type xmlResult struct {
			XMLName xml.Name `xml:"Users"`
			Names   []string `xml:"Names"`
			Ages    []int    `xml:"Ages"`
		}

		err := c.XML(xmlResult{
			Names: []string{"Grame", "John"},
			Ages:  []int{1, 12, 20},
		})

		utils.AssertEqual(t, nil, err)
		utils.AssertEqual(t, `<custom>xml</custom>`, string(c.Response().Body()))
		utils.AssertEqual(t, "application/xml", string(c.Response().Header.Peek("content-type")))
	})
}

// go test -run=^$ -bench=Benchmark_Ctx_XML -benchmem -count=4
func Benchmark_Ctx_XML(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type SomeStruct struct {
		Name string `xml:"Name"`
		Age  uint8  `xml:"Age"`
	}
	data := SomeStruct{
		Name: "Grame",
		Age:  20,
	}
	var err error
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		err = c.XML(data)
	}

	utils.AssertEqual(b, nil, err)
	utils.AssertEqual(b, `<SomeStruct><Name>Grame</Name><Age>20</Age></SomeStruct>`, string(c.Response().Body()))
}

// go test -run Test_Ctx_Links
func Test_Ctx_Links(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	c.Links()
	utils.AssertEqual(t, "", string(c.Response().Header.Peek(HeaderLink)))

	c.Links(
		"http://api.example.com/users?page=2", "next",
		"http://api.example.com/users?page=5", "last",
	)
	utils.AssertEqual(t, `<http://api.example.com/users?page=2>; rel="next",<http://api.example.com/users?page=5>; rel="last"`, string(c.Response().Header.Peek(HeaderLink)))
}

// go test -v  -run=^$ -bench=Benchmark_Ctx_Links -benchmem -count=4
func Benchmark_Ctx_Links(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		c.Links(
			"http://api.example.com/users?page=2", "next",
			"http://api.example.com/users?page=5", "last",
		)
	}
}

// go test -run Test_Ctx_Location
func Test_Ctx_Location(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Location("http://example.com")
	utils.AssertEqual(t, "http://example.com", string(c.Response().Header.Peek(HeaderLocation)))
}

// go test -run Test_Ctx_Next
func Test_Ctx_Next(t *testing.T) {
	t.Parallel()
	app := New()
	app.Use("/", func(c *Ctx) error {
		return c.Next()
	})
	app.Get("/test", func(c *Ctx) error {
		c.Set("X-Next-Result", "Works")
		return nil
	})
	resp, err := app.Test(httptest.NewRequest(MethodGet, "http://example.com/test", nil))
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
	utils.AssertEqual(t, "Works", resp.Header.Get("X-Next-Result"))
}

// go test -run Test_Ctx_Next_Error
func Test_Ctx_Next_Error(t *testing.T) {
	t.Parallel()
	app := New()
	app.Use("/", func(c *Ctx) error {
		c.Set("X-Next-Result", "Works")
		return ErrNotFound
	})

	resp, err := app.Test(httptest.NewRequest(MethodGet, "http://example.com/test", nil))
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusNotFound, resp.StatusCode, "Status code")
	utils.AssertEqual(t, "Works", resp.Header.Get("X-Next-Result"))
}

// go test -run Test_Ctx_Redirect
func Test_Ctx_Redirect(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	err := c.Redirect("http://default.com")
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, 302, c.Response().StatusCode())
	utils.AssertEqual(t, "http://default.com", string(c.Response().Header.Peek(HeaderLocation)))

	err = c.Redirect("http://example.com", 301)
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, 301, c.Response().StatusCode())
	utils.AssertEqual(t, "http://example.com", string(c.Response().Header.Peek(HeaderLocation)))
}

// go test -run Test_Ctx_RedirectToRouteWithParams
func Test_Ctx_RedirectToRouteWithParams(t *testing.T) {
	t.Parallel()
	app := New()
	app.Get("/user/:name", func(c *Ctx) error {
		return c.JSON(c.Params("name"))
	}).Name("user")
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	err := c.RedirectToRoute("user", Map{
		"name": "fiber",
	})
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, 302, c.Response().StatusCode())
	utils.AssertEqual(t, "/user/fiber", string(c.Response().Header.Peek(HeaderLocation)))
}

// go test -run Test_Ctx_RedirectToRouteWithParams
func Test_Ctx_RedirectToRouteWithQueries(t *testing.T) {
	t.Parallel()
	app := New()
	app.Get("/user/:name", func(c *Ctx) error {
		return c.JSON(c.Params("name"))
	}).Name("user")
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	err := c.RedirectToRoute("user", Map{
		"name":    "fiber",
		"queries": map[string]string{"data[0][name]": "john", "data[0][age]": "10", "test": "doe"},
	})
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, 302, c.Response().StatusCode())
	// analysis of query parameters with url parsing, since a map pass is always randomly ordered
	location, err := url.Parse(string(c.Response().Header.Peek(HeaderLocation)))
	utils.AssertEqual(t, nil, err, "url.Parse(location)")
	utils.AssertEqual(t, "/user/fiber", location.Path)
	utils.AssertEqual(t, url.Values{"data[0][name]": []string{"john"}, "data[0][age]": []string{"10"}, "test": []string{"doe"}}, location.Query())
}

// go test -run Test_Ctx_RedirectToRouteWithOptionalParams
func Test_Ctx_RedirectToRouteWithOptionalParams(t *testing.T) {
	t.Parallel()
	app := New()
	app.Get("/user/:name?", func(c *Ctx) error {
		return c.JSON(c.Params("name"))
	}).Name("user")
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	err := c.RedirectToRoute("user", Map{
		"name": "fiber",
	})
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, 302, c.Response().StatusCode())
	utils.AssertEqual(t, "/user/fiber", string(c.Response().Header.Peek(HeaderLocation)))
}

// go test -run Test_Ctx_RedirectToRouteWithOptionalParamsWithoutValue
func Test_Ctx_RedirectToRouteWithOptionalParamsWithoutValue(t *testing.T) {
	t.Parallel()
	app := New()
	app.Get("/user/:name?", func(c *Ctx) error {
		return c.JSON(c.Params("name"))
	}).Name("user")
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	err := c.RedirectToRoute("user", Map{})
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, 302, c.Response().StatusCode())
	utils.AssertEqual(t, "/user/", string(c.Response().Header.Peek(HeaderLocation)))
}

// go test -run Test_Ctx_RedirectToRouteWithGreedyParameters
func Test_Ctx_RedirectToRouteWithGreedyParameters(t *testing.T) {
	t.Parallel()
	app := New()
	app.Get("/user/+", func(c *Ctx) error {
		return c.JSON(c.Params("+"))
	}).Name("user")
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	err := c.RedirectToRoute("user", Map{
		"+": "test/routes",
	})
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, 302, c.Response().StatusCode())
	utils.AssertEqual(t, "/user/test/routes", string(c.Response().Header.Peek(HeaderLocation)))
}

// go test -run Test_Ctx_RedirectBack
func Test_Ctx_RedirectBack(t *testing.T) {
	t.Parallel()
	app := New()
	app.Get("/", func(c *Ctx) error {
		return c.JSON("Home")
	}).Name("home")
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	err := c.RedirectBack("/")
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, 302, c.Response().StatusCode())
	utils.AssertEqual(t, "/", string(c.Response().Header.Peek(HeaderLocation)))
}

// go test -run Test_Ctx_RedirectBackWithReferer
func Test_Ctx_RedirectBackWithReferer(t *testing.T) {
	t.Parallel()
	app := New()
	app.Get("/", func(c *Ctx) error {
		return c.JSON("Home")
	}).Name("home")
	app.Get("/back", func(c *Ctx) error {
		return c.JSON("Back")
	}).Name("back")
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderReferer, "/back")
	err := c.RedirectBack("/")
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, 302, c.Response().StatusCode())
	utils.AssertEqual(t, "/back", c.Get(HeaderReferer))
	utils.AssertEqual(t, "/back", string(c.Response().Header.Peek(HeaderLocation)))
}

// go test -run Test_Ctx_Render
func Test_Ctx_Render(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	err := c.Render("./.github/testdata/index.tmpl", Map{
		"Title": "Hello, World!",
	})
	utils.AssertEqual(t, nil, err)

	utils.AssertEqual(t, "<h1>Hello, World!</h1>", string(c.Response().Body()))

	err = c.Render("./.github/testdata/template-non-exists.html", nil)
	utils.AssertEqual(t, false, err == nil)

	err = c.Render("./.github/testdata/template-invalid.html", nil)
	utils.AssertEqual(t, false, err == nil)
}

func Test_Ctx_RenderWithoutLocals(t *testing.T) {
	t.Parallel()
	app := New(Config{
		PassLocalsToViews: false,
	})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	c.Locals("Title", "Hello, World!")

	err := c.Render("./.github/testdata/index.tmpl", Map{})
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "<h1><no value></h1>", string(c.Response().Body()))
}

func Test_Ctx_RenderWithLocals(t *testing.T) {
	t.Parallel()
	app := New(Config{
		PassLocalsToViews: true,
	})

	t.Run("EmptyBind", func(t *testing.T) {
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		defer app.ReleaseCtx(c)

		c.Locals("Title", "Hello, World!")
		err := c.Render("./.github/testdata/index.tmpl", Map{})

		utils.AssertEqual(t, nil, err)
		utils.AssertEqual(t, "<h1>Hello, World!</h1>", string(c.Response().Body()))
	})

	t.Run("NilBind", func(t *testing.T) {
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		defer app.ReleaseCtx(c)

		c.Locals("Title", "Hello, World!")
		err := c.Render("./.github/testdata/index.tmpl", nil)

		utils.AssertEqual(t, nil, err)
		utils.AssertEqual(t, "<h1>Hello, World!</h1>", string(c.Response().Body()))
	})
}

func Test_Ctx_RenderWithBind(t *testing.T) {
	t.Parallel()

	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})

	err := c.Bind(Map{
		"Title": "Hello, World!",
	})
	utils.AssertEqual(t, nil, err)
	defer app.ReleaseCtx(c)
	err = c.Render("./.github/testdata/index.tmpl", Map{})
	utils.AssertEqual(t, nil, err)

	buf := bytebufferpool.Get()
	_, _ = buf.WriteString("overwrite")
	defer bytebufferpool.Put(buf)

	utils.AssertEqual(t, "<h1>Hello, World!</h1>", string(c.Response().Body()))
}

func Test_Ctx_RenderWithOverwrittenBind(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})

	err := c.Bind(Map{
		"Title": "Hello, World!",
	})
	utils.AssertEqual(t, nil, err)
	defer app.ReleaseCtx(c)
	err = c.Render("./.github/testdata/index.tmpl", Map{
		"Title": "Hello from Fiber!",
	})
	utils.AssertEqual(t, nil, err)

	buf := bytebufferpool.Get()
	_, _ = buf.WriteString("overwrite")
	defer bytebufferpool.Put(buf)

	utils.AssertEqual(t, "<h1>Hello from Fiber!</h1>", string(c.Response().Body()))
}

func Test_Ctx_RenderWithBindLocals(t *testing.T) {
	t.Parallel()
	app := New(Config{
		PassLocalsToViews: true,
	})

	c := app.AcquireCtx(&fasthttp.RequestCtx{})

	err := c.Bind(Map{
		"Title": "Hello, World!",
	})
	utils.AssertEqual(t, nil, err)

	c.Locals("Summary", "Test")
	defer app.ReleaseCtx(c)

	err = c.Render("./.github/testdata/template.tmpl", Map{})
	utils.AssertEqual(t, nil, err)

	utils.AssertEqual(t, "<h1>Hello, World! Test</h1>", string(c.Response().Body()))
}

func Test_Ctx_RenderWithLocalsAndBinding(t *testing.T) {
	t.Parallel()
	engine := &testTemplateEngine{}
	err := engine.Load()
	utils.AssertEqual(t, nil, err)
	app := New(Config{
		PassLocalsToViews: true,
		Views:             engine,
	})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})

	c.Locals("Title", "This is a test.")
	defer app.ReleaseCtx(c)

	err = c.Render("index.tmpl", Map{
		"Title": "Hello, World!",
	})
	utils.AssertEqual(t, nil, err)

	utils.AssertEqual(t, "<h1>Hello, World!</h1>", string(c.Response().Body()))
}

func Benchmark_Ctx_RenderWithLocalsAndBinding(b *testing.B) {
	engine := &testTemplateEngine{}
	err := engine.Load()
	utils.AssertEqual(b, nil, err)
	utils.AssertEqual(b, nil, err)
	app := New(Config{
		PassLocalsToViews: true,
		Views:             engine,
	})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})

	err = c.Bind(Map{
		"Title": "Hello, World!",
	})
	utils.AssertEqual(b, nil, err)
	c.Locals("Summary", "Test")

	defer app.ReleaseCtx(c)

	b.ReportAllocs()
	b.ResetTimer()

	for n := 0; n < b.N; n++ {
		err = c.Render("template.tmpl", Map{})
	}
	utils.AssertEqual(b, nil, err)

	utils.AssertEqual(b, "<h1>Hello, World! Test</h1>", string(c.Response().Body()))
}

func Benchmark_Ctx_RedirectToRoute(b *testing.B) {
	app := New()
	app.Get("/user/:name", func(c *Ctx) error {
		return c.JSON(c.Params("name"))
	}).Name("user")

	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	b.ReportAllocs()
	b.ResetTimer()

	var err error
	for n := 0; n < b.N; n++ {
		err = c.RedirectToRoute("user", Map{
			"name": "fiber",
		})
	}
	utils.AssertEqual(b, nil, err)

	utils.AssertEqual(b, 302, c.Response().StatusCode())
	utils.AssertEqual(b, "/user/fiber", string(c.Response().Header.Peek(HeaderLocation)))
}

func Benchmark_Ctx_RedirectToRouteWithQueries(b *testing.B) {
	app := New()
	app.Get("/user/:name", func(c *Ctx) error {
		return c.JSON(c.Params("name"))
	}).Name("user")

	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	b.ReportAllocs()
	b.ResetTimer()

	var err error
	for n := 0; n < b.N; n++ {
		err = c.RedirectToRoute("user", Map{
			"name":    "fiber",
			"queries": map[string]string{"a": "a", "b": "b"},
		})
	}
	utils.AssertEqual(b, nil, err)

	utils.AssertEqual(b, 302, c.Response().StatusCode())
	// analysis of query parameters with url parsing, since a map pass is always randomly ordered
	location, err := url.Parse(string(c.Response().Header.Peek(HeaderLocation)))
	utils.AssertEqual(b, nil, err, "url.Parse(location)")
	utils.AssertEqual(b, "/user/fiber", location.Path)
	utils.AssertEqual(b, url.Values{"a": []string{"a"}, "b": []string{"b"}}, location.Query())
}

func Benchmark_Ctx_RenderLocals(b *testing.B) {
	engine := &testTemplateEngine{}
	err := engine.Load()
	utils.AssertEqual(b, nil, err)
	app := New(Config{
		PassLocalsToViews: true,
	})
	app.config.Views = engine
	c := app.AcquireCtx(&fasthttp.RequestCtx{})

	c.Locals("Title", "Hello, World!")

	defer app.ReleaseCtx(c)

	b.ReportAllocs()
	b.ResetTimer()

	for n := 0; n < b.N; n++ {
		err = c.Render("index.tmpl", Map{})
	}
	utils.AssertEqual(b, nil, err)

	utils.AssertEqual(b, "<h1>Hello, World!</h1>", string(c.Response().Body()))
}

func Benchmark_Ctx_RenderBind(b *testing.B) {
	engine := &testTemplateEngine{}
	err := engine.Load()
	utils.AssertEqual(b, nil, err)
	app := New()
	app.config.Views = engine
	c := app.AcquireCtx(&fasthttp.RequestCtx{})

	err = c.Bind(Map{
		"Title": "Hello, World!",
	})
	utils.AssertEqual(b, nil, err)

	defer app.ReleaseCtx(c)

	b.ReportAllocs()
	b.ResetTimer()

	for n := 0; n < b.N; n++ {
		err = c.Render("index.tmpl", Map{})
	}
	utils.AssertEqual(b, nil, err)

	utils.AssertEqual(b, "<h1>Hello, World!</h1>", string(c.Response().Body()))
}

// go test -run Test_Ctx_RestartRouting
func Test_Ctx_RestartRouting(t *testing.T) {
	t.Parallel()
	app := New()
	calls := 0
	app.Get("/", func(c *Ctx) error {
		calls++
		if calls < 3 {
			return c.RestartRouting()
		}
		return nil
	})
	resp, err := app.Test(httptest.NewRequest(MethodGet, "http://example.com/", nil))
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
	utils.AssertEqual(t, 3, calls, "Number of calls")
}

// go test -run Test_Ctx_RestartRoutingWithChangedPath
func Test_Ctx_RestartRoutingWithChangedPath(t *testing.T) {
	t.Parallel()
	app := New()
	var executedOldHandler, executedNewHandler bool

	app.Get("/old", func(c *Ctx) error {
		c.Path("/new")
		return c.RestartRouting()
	})
	app.Get("/old", func(c *Ctx) error {
		executedOldHandler = true
		return nil
	})
	app.Get("/new", func(c *Ctx) error {
		executedNewHandler = true
		return nil
	})

	resp, err := app.Test(httptest.NewRequest(MethodGet, "http://example.com/old", nil))
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
	utils.AssertEqual(t, false, executedOldHandler, "Executed old handler")
	utils.AssertEqual(t, true, executedNewHandler, "Executed new handler")
}

// go test -run Test_Ctx_RestartRoutingWithChangedPathAnd404
func Test_Ctx_RestartRoutingWithChangedPathAndCatchAll(t *testing.T) {
	t.Parallel()
	app := New()
	app.Get("/new", func(c *Ctx) error {
		return nil
	})
	app.Use(func(c *Ctx) error {
		c.Path("/new")
		// c.Next() would fail this test as a 404 is returned from the next handler
		return c.RestartRouting()
	})
	app.Use(func(c *Ctx) error {
		return ErrNotFound
	})

	resp, err := app.Test(httptest.NewRequest(MethodGet, "http://example.com/old", nil))
	utils.AssertEqual(t, nil, err, "app.Test(req)")
	utils.AssertEqual(t, StatusOK, resp.StatusCode, "Status code")
}

type testTemplateEngine struct {
	templates *template.Template
	path      string
}

func (t *testTemplateEngine) Render(w io.Writer, name string, bind interface{}, layout ...string) error {
	if len(layout) == 0 {
		if err := t.templates.ExecuteTemplate(w, name, bind); err != nil {
			return fmt.Errorf("failed to execute template without layout: %w", err)
		}
		return nil
	}
	if err := t.templates.ExecuteTemplate(w, name, bind); err != nil {
		return fmt.Errorf("failed to execute template: %w", err)
	}
	if err := t.templates.ExecuteTemplate(w, layout[0], bind); err != nil {
		return fmt.Errorf("failed to execute template with layout: %w", err)
	}
	return nil
}

func (t *testTemplateEngine) Load() error {
	if t.path == "" {
		t.path = "testdata"
	}
	t.templates = template.Must(template.ParseGlob("./.github/" + t.path + "/*.tmpl"))
	return nil
}

// go test -run Test_Ctx_Render_Engine
func Test_Ctx_Render_Engine(t *testing.T) {
	t.Parallel()
	engine := &testTemplateEngine{}
	utils.AssertEqual(t, nil, engine.Load())
	app := New()
	app.config.Views = engine
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	err := c.Render("index.tmpl", Map{
		"Title": "Hello, World!",
	})
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "<h1>Hello, World!</h1>", string(c.Response().Body()))
}

// go test -run Test_Ctx_Render_Engine_With_View_Layout
func Test_Ctx_Render_Engine_With_View_Layout(t *testing.T) {
	t.Parallel()
	engine := &testTemplateEngine{}
	utils.AssertEqual(t, nil, engine.Load())
	app := New(Config{ViewsLayout: "main.tmpl"})
	app.config.Views = engine
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	err := c.Render("index.tmpl", Map{
		"Title": "Hello, World!",
	})
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "<h1>Hello, World!</h1><h1>I'm main</h1>", string(c.Response().Body()))
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Render_Engine -benchmem -count=4
func Benchmark_Ctx_Render_Engine(b *testing.B) {
	engine := &testTemplateEngine{}
	err := engine.Load()
	utils.AssertEqual(b, nil, err)
	app := New()
	app.config.Views = engine
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		err = c.Render("index.tmpl", Map{
			"Title": "Hello, World!",
		})
	}
	utils.AssertEqual(b, nil, err)
	utils.AssertEqual(b, "<h1>Hello, World!</h1>", string(c.Response().Body()))
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Get_Location_From_Route -benchmem -count=4
func Benchmark_Ctx_Get_Location_From_Route(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	app.Get("/user/:name", func(c *Ctx) error {
		return c.SendString(c.Params("name"))
	}).Name("User")

	var err error
	var location string
	for n := 0; n < b.N; n++ {
		location, err = c.getLocationFromRoute(app.GetRoute("User"), Map{"name": "fiber"})
	}
	utils.AssertEqual(b, "/user/fiber", location)
	utils.AssertEqual(b, nil, err)
}

// go test -run Test_Ctx_Get_Location_From_Route_name
func Test_Ctx_Get_Location_From_Route_name(t *testing.T) {
	t.Parallel()

	t.Run("case insensitive", func(t *testing.T) {
		t.Parallel()
		app := New()
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		defer app.ReleaseCtx(c)
		app.Get("/user/:name", func(c *Ctx) error {
			return c.SendString(c.Params("name"))
		}).Name("User")

		location, err := c.GetRouteURL("User", Map{"name": "fiber"})
		utils.AssertEqual(t, nil, err)
		utils.AssertEqual(t, "/user/fiber", location)

		location, err = c.GetRouteURL("User", Map{"Name": "fiber"})
		utils.AssertEqual(t, nil, err)
		utils.AssertEqual(t, "/user/fiber", location)
	})

	t.Run("case sensitive", func(t *testing.T) {
		t.Parallel()
		app := New(Config{CaseSensitive: true})
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		defer app.ReleaseCtx(c)
		app.Get("/user/:name", func(c *Ctx) error {
			return c.SendString(c.Params("name"))
		}).Name("User")

		location, err := c.GetRouteURL("User", Map{"name": "fiber"})
		utils.AssertEqual(t, nil, err)
		utils.AssertEqual(t, "/user/fiber", location)

		location, err = c.GetRouteURL("User", Map{"Name": "fiber"})
		utils.AssertEqual(t, nil, err)
		utils.AssertEqual(t, "/user/", location)
	})
}

// go test -run Test_Ctx_Get_Location_From_Route_name_Optional_greedy
func Test_Ctx_Get_Location_From_Route_name_Optional_greedy(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	app.Get("/:phone/*/send/*", func(c *Ctx) error {
		return c.SendString("Phone: " + c.Params("phone") + "\nFirst Param: " + c.Params("*1") + "\nSecond Param: " + c.Params("*2"))
	}).Name("SendSms")

	location, err := c.GetRouteURL("SendSms", Map{
		"phone": "23456789",
		"*1":    "sms",
		"*2":    "test-msg",
	})
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "/23456789/sms/send/test-msg", location)
}

// go test -run Test_Ctx_Get_Location_From_Route_name_Optional_greedy_one_param
func Test_Ctx_Get_Location_From_Route_name_Optional_greedy_one_param(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	app.Get("/:phone/*/send", func(c *Ctx) error {
		return c.SendString("Phone: " + c.Params("phone") + "\nFirst Param: " + c.Params("*1"))
	}).Name("SendSms")

	location, err := c.GetRouteURL("SendSms", Map{
		"phone": "23456789",
		"*":     "sms",
	})
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "/23456789/sms/send", location)
}

type errorTemplateEngine struct{}

func (errorTemplateEngine) Render(_ io.Writer, _ string, _ interface{}, _ ...string) error {
	return errors.New("errorTemplateEngine")
}

func (errorTemplateEngine) Load() error { return nil }

// go test -run Test_Ctx_Render_Engine_Error
func Test_Ctx_Render_Engine_Error(t *testing.T) {
	t.Parallel()
	app := New()
	app.config.Views = errorTemplateEngine{}
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	err := c.Render("index.tmpl", nil)
	utils.AssertEqual(t, false, err == nil)
}

// go test -run Test_Ctx_Render_Go_Template
func Test_Ctx_Render_Go_Template(t *testing.T) {
	t.Parallel()
	file, err := os.CreateTemp(os.TempDir(), "fiber")
	utils.AssertEqual(t, nil, err)
	defer func() {
		err := os.Remove(file.Name())
		utils.AssertEqual(t, nil, err)
	}()

	_, err = file.Write([]byte("template"))
	utils.AssertEqual(t, nil, err)

	err = file.Close()
	utils.AssertEqual(t, nil, err)

	app := New()

	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	err = c.Render(file.Name(), nil)
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "template", string(c.Response().Body()))
}

// go test -run Test_Ctx_Send
func Test_Ctx_Send(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	utils.AssertEqual(t, nil, c.Send([]byte("Hello, World")))
	utils.AssertEqual(t, nil, c.Send([]byte("Don't crash please")))
	utils.AssertEqual(t, nil, c.Send([]byte("1337")))
	utils.AssertEqual(t, "1337", string(c.Response().Body()))
}

// go test -v  -run=^$ -bench=Benchmark_Ctx_Send -benchmem -count=4
func Benchmark_Ctx_Send(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	byt := []byte("Hello, World!")
	b.ReportAllocs()
	b.ResetTimer()

	var err error
	for n := 0; n < b.N; n++ {
		err = c.Send(byt)
	}
	utils.AssertEqual(b, nil, err)
	utils.AssertEqual(b, "Hello, World!", string(c.Response().Body()))
}

// go test -run Test_Ctx_SendStatus
func Test_Ctx_SendStatus(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	err := c.SendStatus(415)
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, 415, c.Response().StatusCode())
	utils.AssertEqual(t, "Unsupported Media Type", string(c.Response().Body()))
}

// go test -run Test_Ctx_SendString
func Test_Ctx_SendString(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	err := c.SendString("Don't crash please")
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "Don't crash please", string(c.Response().Body()))
}

// go test -run Test_Ctx_SendStream
func Test_Ctx_SendStream(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	err := c.SendStream(bytes.NewReader([]byte("Don't crash please")))
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "Don't crash please", string(c.Response().Body()))

	err = c.SendStream(bytes.NewReader([]byte("Don't crash please")), len([]byte("Don't crash please")))
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "Don't crash please", string(c.Response().Body()))

	err = c.SendStream(bufio.NewReader(bytes.NewReader([]byte("Hello bufio"))))
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "Hello bufio", string(c.Response().Body()))
}

// go test -run Test_Ctx_Set
func Test_Ctx_Set(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Set("X-1", "1")
	c.Set("X-2", "2")
	c.Set("X-3", "3")
	c.Set("X-3", "1337")
	utils.AssertEqual(t, "1", string(c.Response().Header.Peek("x-1")))
	utils.AssertEqual(t, "2", string(c.Response().Header.Peek("x-2")))
	utils.AssertEqual(t, "1337", string(c.Response().Header.Peek("x-3")))
}

// go test -run Test_Ctx_Set_Splitter
func Test_Ctx_Set_Splitter(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	c.Set("Location", "foo\r\nSet-Cookie:%20SESSIONID=MaliciousValue\r\n")
	h := string(c.Response().Header.Peek("Location"))
	utils.AssertEqual(t, false, strings.Contains(h, "\r\n"), h)

	c.Set("Location", "foo\nSet-Cookie:%20SESSIONID=MaliciousValue\n")
	h = string(c.Response().Header.Peek("Location"))
	utils.AssertEqual(t, false, strings.Contains(h, "\n"), h)
}

// go test -v  -run=^$ -bench=Benchmark_Ctx_Set -benchmem -count=4
func Benchmark_Ctx_Set(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	val := "1431-15132-3423"
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		c.Set(HeaderXRequestID, val)
	}
}

// go test -run Test_Ctx_Status
func Test_Ctx_Status(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Status(400)
	utils.AssertEqual(t, 400, c.Response().StatusCode())
	err := c.Status(415).Send([]byte("Hello, World"))
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, 415, c.Response().StatusCode())
	utils.AssertEqual(t, "Hello, World", string(c.Response().Body()))
}

// go test -run Test_Ctx_Type
func Test_Ctx_Type(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Type(".json")
	utils.AssertEqual(t, "application/json", string(c.Response().Header.Peek("Content-Type")))

	c.Type("json", "utf-8")
	utils.AssertEqual(t, "application/json; charset=utf-8", string(c.Response().Header.Peek("Content-Type")))

	c.Type(".html")
	utils.AssertEqual(t, "text/html", string(c.Response().Header.Peek("Content-Type")))

	c.Type("html", "utf-8")
	utils.AssertEqual(t, "text/html; charset=utf-8", string(c.Response().Header.Peek("Content-Type")))
}

// go test -v  -run=^$ -bench=Benchmark_Ctx_Type -benchmem -count=4
func Benchmark_Ctx_Type(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		c.Type(".json")
		c.Type("json")
	}
}

// go test -v  -run=^$ -bench=Benchmark_Ctx_Type_Charset -benchmem -count=4
func Benchmark_Ctx_Type_Charset(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		c.Type(".json", "utf-8")
		c.Type("json", "utf-8")
	}
}

// go test -run Test_Ctx_Vary
func Test_Ctx_Vary(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Vary("Origin")
	c.Vary("User-Agent")
	c.Vary("Accept-Encoding", "Accept")
	utils.AssertEqual(t, "Origin, User-Agent, Accept-Encoding, Accept", string(c.Response().Header.Peek("Vary")))
}

// go test -v  -run=^$ -bench=Benchmark_Ctx_Vary -benchmem -count=4
func Benchmark_Ctx_Vary(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		c.Vary("Origin", "User-Agent")
	}
}

// go test -run Test_Ctx_Write
func Test_Ctx_Write(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	_, err := c.Write([]byte("Hello, "))
	utils.AssertEqual(t, nil, err)
	_, err = c.Write([]byte("World!"))
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "Hello, World!", string(c.Response().Body()))
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Write -benchmem -count=4
func Benchmark_Ctx_Write(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	byt := []byte("Hello, World!")
	b.ReportAllocs()
	b.ResetTimer()

	var err error
	for n := 0; n < b.N; n++ {
		_, err = c.Write(byt)
	}
	utils.AssertEqual(b, nil, err)
}

// go test -run Test_Ctx_Writef
func Test_Ctx_Writef(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	world := "World!"
	_, err := c.Writef("Hello, %s", world)
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "Hello, World!", string(c.Response().Body()))
}

// go test -v -run=^$ -bench=Benchmark_Ctx_Writef -benchmem -count=4
func Benchmark_Ctx_Writef(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	world := "World!"
	b.ReportAllocs()
	b.ResetTimer()

	var err error
	for n := 0; n < b.N; n++ {
		_, err = c.Writef("Hello, %s", world)
	}
	utils.AssertEqual(b, nil, err)
}

// go test -run Test_Ctx_WriteString
func Test_Ctx_WriteString(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	_, err := c.WriteString("Hello, ")
	utils.AssertEqual(t, nil, err)
	_, err = c.WriteString("World!")
	utils.AssertEqual(t, nil, err)
	utils.AssertEqual(t, "Hello, World!", string(c.Response().Body()))
}

// go test -run Test_Ctx_XHR
func Test_Ctx_XHR(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderXRequestedWith, "XMLHttpRequest")
	utils.AssertEqual(t, true, c.XHR())
}

// go test -run=^$ -bench=Benchmark_Ctx_XHR -benchmem -count=4
func Benchmark_Ctx_XHR(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set(HeaderXRequestedWith, "XMLHttpRequest")
	var equal bool
	b.ReportAllocs()
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		equal = c.XHR()
	}
	utils.AssertEqual(b, true, equal)
}

// go test -v  -run=^$ -bench=Benchmark_Ctx_SendString_B -benchmem -count=4
func Benchmark_Ctx_SendString_B(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	body := "Hello, world!"
	b.ReportAllocs()
	b.ResetTimer()

	var err error
	for n := 0; n < b.N; n++ {
		err = c.SendString(body)
	}
	utils.AssertEqual(b, nil, err)
	utils.AssertEqual(b, []byte("Hello, world!"), c.Response().Body())
}

// go test -run Test_Ctx_Queries -v
func Test_Ctx_Queries(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	c.Request().SetBody([]byte(``))
	c.Request().Header.SetContentType("")
	c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football&favouriteDrinks=milo,coke,pepsi&alloc=&no=1&field1=value1&field1=value2&field2=value3&list_a=1&list_a=2&list_a=3&list_b[]=1&list_b[]=2&list_b[]=3&list_c=1,2,3")

	queries := c.Queries()
	utils.AssertEqual(t, "1", queries["id"])
	utils.AssertEqual(t, "tom", queries["name"])
	utils.AssertEqual(t, "basketball,football", queries["hobby"])
	utils.AssertEqual(t, "milo,coke,pepsi", queries["favouriteDrinks"])
	utils.AssertEqual(t, "", queries["alloc"])
	utils.AssertEqual(t, "1", queries["no"])
	utils.AssertEqual(t, "value2", queries["field1"])
	utils.AssertEqual(t, "value3", queries["field2"])
	utils.AssertEqual(t, "3", queries["list_a"])
	utils.AssertEqual(t, "3", queries["list_b[]"])
	utils.AssertEqual(t, "1,2,3", queries["list_c"])

	c.Request().URI().SetQueryString("filters.author.name=John&filters.category.name=Technology&filters[customer][name]=Alice&filters[status]=pending")

	queries = c.Queries()
	utils.AssertEqual(t, "John", queries["filters.author.name"])
	utils.AssertEqual(t, "Technology", queries["filters.category.name"])
	utils.AssertEqual(t, "Alice", queries["filters[customer][name]"])
	utils.AssertEqual(t, "pending", queries["filters[status]"])

	c.Request().URI().SetQueryString("tags=apple,orange,banana&filters[tags]=apple,orange,banana&filters[category][name]=fruits&filters.tags=apple,orange,banana&filters.category.name=fruits")

	queries = c.Queries()
	utils.AssertEqual(t, "apple,orange,banana", queries["tags"])
	utils.AssertEqual(t, "apple,orange,banana", queries["filters[tags]"])
	utils.AssertEqual(t, "fruits", queries["filters[category][name]"])
	utils.AssertEqual(t, "apple,orange,banana", queries["filters.tags"])
	utils.AssertEqual(t, "fruits", queries["filters.category.name"])

	c.Request().URI().SetQueryString("filters[tags][0]=apple&filters[tags][1]=orange&filters[tags][2]=banana&filters[category][name]=fruits")

	queries = c.Queries()
	utils.AssertEqual(t, "apple", queries["filters[tags][0]"])
	utils.AssertEqual(t, "orange", queries["filters[tags][1]"])
	utils.AssertEqual(t, "banana", queries["filters[tags][2]"])
	utils.AssertEqual(t, "fruits", queries["filters[category][name]"])
}

// go test -v  -run=^$ -bench=Benchmark_Ctx_Queries -benchmem -count=4
func Benchmark_Ctx_Queries(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	b.ReportAllocs()
	b.ResetTimer()
	c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football&favouriteDrinks=milo,coke,pepsi&alloc=&no=1")

	var queries map[string]string
	for n := 0; n < b.N; n++ {
		queries = c.Queries()
	}

	utils.AssertEqual(b, "1", queries["id"])
	utils.AssertEqual(b, "tom", queries["name"])
	utils.AssertEqual(b, "basketball,football", queries["hobby"])
	utils.AssertEqual(b, "milo,coke,pepsi", queries["favouriteDrinks"])
	utils.AssertEqual(b, "", queries["alloc"])
	utils.AssertEqual(b, "1", queries["no"])
}

// go test -run Test_Ctx_QueryParser -v
func Test_Ctx_QueryParser(t *testing.T) {
	t.Parallel()
	app := New(Config{EnableSplittingOnParsers: true})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type Query struct {
		ID    int
		Name  string
		Hobby []string
	}
	c.Request().SetBody([]byte(``))
	c.Request().Header.SetContentType("")
	c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football")
	q := new(Query)
	utils.AssertEqual(t, nil, c.QueryParser(q))
	utils.AssertEqual(t, 2, len(q.Hobby))

	c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football")
	q = new(Query)
	utils.AssertEqual(t, nil, c.QueryParser(q))
	utils.AssertEqual(t, 2, len(q.Hobby))

	c.Request().URI().SetQueryString("id=1&name=tom&hobby=scoccer&hobby=basketball,football")
	q = new(Query)
	utils.AssertEqual(t, nil, c.QueryParser(q))
	utils.AssertEqual(t, 3, len(q.Hobby))

	empty := new(Query)
	c.Request().URI().SetQueryString("")
	utils.AssertEqual(t, nil, c.QueryParser(empty))
	utils.AssertEqual(t, 0, len(empty.Hobby))

	c.Request().URI().SetQueryString("id=1&name[=tom")
	q = new(Query)
	utils.AssertEqual(t, "unmatched brackets", c.QueryParser(q).Error())

	type Query2 struct {
		Bool            bool
		ID              int
		Name            string
		Hobby           string
		FavouriteDrinks []string
		Empty           []string
		Alloc           []string
		No              []int64
	}

	c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football&favouriteDrinks=milo,coke,pepsi&alloc=&no=1")
	q2 := new(Query2)
	q2.Bool = true
	q2.Name = "hello world 1"
	utils.AssertEqual(t, nil, c.QueryParser(q2))
	utils.AssertEqual(t, "basketball,football", q2.Hobby)
	utils.AssertEqual(t, true, q2.Bool)
	utils.AssertEqual(t, "tom", q2.Name) // check value get overwritten
	utils.AssertEqual(t, []string{"milo", "coke", "pepsi"}, q2.FavouriteDrinks)
	var nilSlice []string
	utils.AssertEqual(t, nilSlice, q2.Empty)
	utils.AssertEqual(t, []string{""}, q2.Alloc)
	utils.AssertEqual(t, []int64{1}, q2.No)

	type RequiredQuery struct {
		Name string `query:"name,required"`
	}
	rq := new(RequiredQuery)
	c.Request().URI().SetQueryString("")
	utils.AssertEqual(t, "failed to decode: name is empty", c.QueryParser(rq).Error())

	type ArrayQuery struct {
		Data []string
	}
	aq := new(ArrayQuery)
	c.Request().URI().SetQueryString("data[]=john&data[]=doe")
	utils.AssertEqual(t, nil, c.QueryParser(aq))
	utils.AssertEqual(t, 2, len(aq.Data))
}

// go test -run Test_Ctx_QueryParserUsingTag -v
func Test_Ctx_QueryParserUsingTag(t *testing.T) {
	t.Parallel()
	app := New(Config{EnableSplittingOnParsers: true})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type Query struct {
		ID    int      `query:"id"`
		Name  string   `query:"name"`
		Hobby []string `query:"hobby"`
	}
	c.Request().SetBody([]byte(``))
	c.Request().Header.SetContentType("")
	c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football")
	q := new(Query)
	utils.AssertEqual(t, nil, c.QueryParser(q))
	utils.AssertEqual(t, 2, len(q.Hobby))

	c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football")
	q = new(Query)
	utils.AssertEqual(t, nil, c.QueryParser(q))
	utils.AssertEqual(t, 2, len(q.Hobby))

	c.Request().URI().SetQueryString("id=1&name=tom&hobby=scoccer&hobby=basketball,football")
	q = new(Query)
	utils.AssertEqual(t, nil, c.QueryParser(q))
	utils.AssertEqual(t, 3, len(q.Hobby))

	empty := new(Query)
	c.Request().URI().SetQueryString("")
	utils.AssertEqual(t, nil, c.QueryParser(empty))
	utils.AssertEqual(t, 0, len(empty.Hobby))

	type Query2 struct {
		Bool            bool     `query:"bool"`
		ID              int      `query:"id"`
		Name            string   `query:"name"`
		Hobby           string   `query:"hobby"`
		FavouriteDrinks []string `query:"favouriteDrinks"`
		Empty           []string `query:"empty"`
		Alloc           []string `query:"alloc"`
		No              []int64  `query:"no"`
	}

	c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football&favouriteDrinks=milo,coke,pepsi&alloc=&no=1")
	q2 := new(Query2)
	q2.Bool = true
	q2.Name = "hello world 2"
	utils.AssertEqual(t, nil, c.QueryParser(q2))
	utils.AssertEqual(t, "basketball,football", q2.Hobby)
	utils.AssertEqual(t, true, q2.Bool)
	utils.AssertEqual(t, "tom", q2.Name) // check value get overwritten
	utils.AssertEqual(t, []string{"milo", "coke", "pepsi"}, q2.FavouriteDrinks)
	var nilSlice []string
	utils.AssertEqual(t, nilSlice, q2.Empty)
	utils.AssertEqual(t, []string{""}, q2.Alloc)
	utils.AssertEqual(t, []int64{1}, q2.No)

	type RequiredQuery struct {
		Name string `query:"name,required"`
	}
	rq := new(RequiredQuery)
	c.Request().URI().SetQueryString("")
	utils.AssertEqual(t, "failed to decode: name is empty", c.QueryParser(rq).Error())

	type ArrayQuery struct {
		Data []string
	}
	aq := new(ArrayQuery)
	c.Request().URI().SetQueryString("data[]=john&data[]=doe")
	utils.AssertEqual(t, nil, c.QueryParser(aq))
	utils.AssertEqual(t, 2, len(aq.Data))
}

// go test -run Test_Ctx_QueryParser -v
func Test_Ctx_QueryParser_WithoutSplitting(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type Query struct {
		ID    int
		Name  string
		Hobby []string
	}

	c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football")
	q := new(Query)
	utils.AssertEqual(t, nil, c.QueryParser(q))
	utils.AssertEqual(t, 1, len(q.Hobby))

	c.Request().URI().SetQueryString("id=1&name=tom&hobby=scoccer&hobby=basketball,football")
	q = new(Query)
	utils.AssertEqual(t, nil, c.QueryParser(q))
	utils.AssertEqual(t, 2, len(q.Hobby))
}

// go test -run Test_Ctx_QueryParser_WithSetParserDecoder -v
func Test_Ctx_QueryParser_WithSetParserDecoder(t *testing.T) {
	type NonRFCTime time.Time

	nonRFCConverter := func(value string) reflect.Value {
		if v, err := time.Parse("2006-01-02", value); err == nil {
			return reflect.ValueOf(v)
		}
		return reflect.Value{}
	}

	nonRFCTime := ParserType{
		Customtype: NonRFCTime{},
		Converter:  nonRFCConverter,
	}

	SetParserDecoder(ParserConfig{
		IgnoreUnknownKeys: true,
		ParserType:        []ParserType{nonRFCTime},
		ZeroEmpty:         true,
		SetAliasTag:       "query",
	})

	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	type NonRFCTimeInput struct {
		Date  NonRFCTime `query:"date"`
		Title string     `query:"title"`
		Body  string     `query:"body"`
	}

	c.Request().SetBody([]byte(``))
	c.Request().Header.SetContentType("")
	q := new(NonRFCTimeInput)

	c.Request().URI().SetQueryString("date=2021-04-10&title=CustomDateTest&Body=October")
	utils.AssertEqual(t, nil, c.QueryParser(q))
	utils.AssertEqual(t, "CustomDateTest", q.Title)
	date := fmt.Sprintf("%v", q.Date)
	utils.AssertEqual(t, "{0 63753609600 <nil>}", date)
	utils.AssertEqual(t, "October", q.Body)

	c.Request().URI().SetQueryString("date=2021-04-10&title&Body=October")
	q = &NonRFCTimeInput{
		Title: "Existing title",
		Body:  "Existing Body",
	}
	utils.AssertEqual(t, nil, c.QueryParser(q))
	utils.AssertEqual(t, "", q.Title)
}

// go test -run Test_Ctx_QueryParser_Schema -v
func Test_Ctx_QueryParser_Schema(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type Query1 struct {
		Name   string `query:"name,required"`
		Nested struct {
			Age int `query:"age"`
		} `query:"nested,required"`
	}
	c.Request().SetBody([]byte(``))
	c.Request().Header.SetContentType("")
	c.Request().URI().SetQueryString("name=tom&nested.age=10")
	q := new(Query1)
	utils.AssertEqual(t, nil, c.QueryParser(q))

	c.Request().URI().SetQueryString("namex=tom&nested.age=10")
	q = new(Query1)
	utils.AssertEqual(t, "failed to decode: name is empty", c.QueryParser(q).Error())

	c.Request().URI().SetQueryString("name=tom&nested.agex=10")
	q = new(Query1)
	utils.AssertEqual(t, nil, c.QueryParser(q))

	c.Request().URI().SetQueryString("name=tom&test.age=10")
	q = new(Query1)
	utils.AssertEqual(t, "failed to decode: nested is empty", c.QueryParser(q).Error())

	type Query2 struct {
		Name   string `query:"name"`
		Nested struct {
			Age int `query:"age,required"`
		} `query:"nested"`
	}
	c.Request().URI().SetQueryString("name=tom&nested.age=10")
	q2 := new(Query2)
	utils.AssertEqual(t, nil, c.QueryParser(q2))

	c.Request().URI().SetQueryString("nested.age=10")
	q2 = new(Query2)
	utils.AssertEqual(t, nil, c.QueryParser(q2))

	c.Request().URI().SetQueryString("nested.agex=10")
	q2 = new(Query2)
	utils.AssertEqual(t, "failed to decode: nested.age is empty", c.QueryParser(q2).Error())

	c.Request().URI().SetQueryString("nested.agex=10")
	q2 = new(Query2)
	utils.AssertEqual(t, "failed to decode: nested.age is empty", c.QueryParser(q2).Error())

	type Node struct {
		Value int   `query:"val,required"`
		Next  *Node `query:"next,required"`
	}
	c.Request().URI().SetQueryString("val=1&next.val=3")
	n := new(Node)
	utils.AssertEqual(t, nil, c.QueryParser(n))
	utils.AssertEqual(t, 1, n.Value)
	utils.AssertEqual(t, 3, n.Next.Value)

	c.Request().URI().SetQueryString("next.val=2")
	n = new(Node)
	utils.AssertEqual(t, "failed to decode: val is empty", c.QueryParser(n).Error())

	c.Request().URI().SetQueryString("val=3&next.value=2")
	n = new(Node)
	n.Next = new(Node)
	utils.AssertEqual(t, nil, c.QueryParser(n))
	utils.AssertEqual(t, 3, n.Value)
	utils.AssertEqual(t, 0, n.Next.Value)

	type Person struct {
		Name string `query:"name"`
		Age  int    `query:"age"`
	}

	type CollectionQuery struct {
		Data []Person `query:"data"`
	}

	c.Request().URI().SetQueryString("data[0][name]=john&data[0][age]=10&data[1][name]=doe&data[1][age]=12")
	cq := new(CollectionQuery)
	utils.AssertEqual(t, nil, c.QueryParser(cq))
	utils.AssertEqual(t, 2, len(cq.Data))
	utils.AssertEqual(t, "john", cq.Data[0].Name)
	utils.AssertEqual(t, 10, cq.Data[0].Age)
	utils.AssertEqual(t, "doe", cq.Data[1].Name)
	utils.AssertEqual(t, 12, cq.Data[1].Age)

	c.Request().URI().SetQueryString("data[0][name]=john&data[0][age]=10&data[1]name]=doe&data[1][age]=12")
	cq = new(CollectionQuery)
	utils.AssertEqual(t, "unmatched brackets", c.QueryParser(cq).Error())

	c.Request().URI().SetQueryString("data.0.name=john&data.0.age=10&data.1.name=doe&data.1.age=12")
	cq = new(CollectionQuery)
	utils.AssertEqual(t, nil, c.QueryParser(cq))
	utils.AssertEqual(t, 2, len(cq.Data))
	utils.AssertEqual(t, "john", cq.Data[0].Name)
	utils.AssertEqual(t, 10, cq.Data[0].Age)
	utils.AssertEqual(t, "doe", cq.Data[1].Name)
	utils.AssertEqual(t, 12, cq.Data[1].Age)
}

func Test_Ctx_QueryParser_EmbeddedStruct(t *testing.T) {
	t.Parallel()
	app := New(Config{EnableSplittingOnParsers: true})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	type A struct {
		Products []string `query:"products"`
	}

	type Person struct {
		Name string `query:"name"`
		Pass string `query:"pass"`
		A
	}

	c.Request().URI().SetQueryString("name=john&pass=doe&products=shoe,hat")
	p := new(Person)
	utils.AssertEqual(t, nil, c.QueryParser(p))
	utils.AssertEqual(t, []string{"shoe", "hat"}, p.Products)
}

// go test -run Test_Ctx_ReqHeaderParser -v
func Test_Ctx_ReqHeaderParser(t *testing.T) {
	t.Parallel()
	app := New(Config{EnableSplittingOnParsers: true})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type Header struct {
		ID    int
		Name  string
		Hobby []string
	}
	c.Request().SetBody([]byte(``))
	c.Request().Header.SetContentType("")

	c.Request().Header.Add("id", "1")
	c.Request().Header.Add("Name", "John Doe")
	c.Request().Header.Add("Hobby", "golang,fiber")
	q := new(Header)
	utils.AssertEqual(t, nil, c.ReqHeaderParser(q))
	utils.AssertEqual(t, 2, len(q.Hobby))

	c.Request().Header.Del("hobby")
	c.Request().Header.Add("Hobby", "golang,fiber,go")
	q = new(Header)
	utils.AssertEqual(t, nil, c.ReqHeaderParser(q))
	utils.AssertEqual(t, 3, len(q.Hobby))

	empty := new(Header)
	c.Request().Header.Del("hobby")
	utils.AssertEqual(t, nil, c.QueryParser(empty))
	utils.AssertEqual(t, 0, len(empty.Hobby))

	type Header2 struct {
		Bool            bool
		ID              int
		Name            string
		Hobby           string
		FavouriteDrinks []string
		Empty           []string
		Alloc           []string
		No              []int64
	}

	c.Request().Header.Add("id", "2")
	c.Request().Header.Add("Name", "Jane Doe")
	c.Request().Header.Del("hobby")
	c.Request().Header.Add("Hobby", "go,fiber")
	c.Request().Header.Add("favouriteDrinks", "milo,coke,pepsi")
	c.Request().Header.Add("alloc", "")
	c.Request().Header.Add("no", "1")

	h2 := new(Header2)
	h2.Bool = true
	h2.Name = "hello world 3"
	utils.AssertEqual(t, nil, c.ReqHeaderParser(h2))
	utils.AssertEqual(t, "go,fiber", h2.Hobby)
	utils.AssertEqual(t, true, h2.Bool)
	utils.AssertEqual(t, "Jane Doe", h2.Name) // check value get overwritten
	utils.AssertEqual(t, []string{"milo", "coke", "pepsi"}, h2.FavouriteDrinks)
	var nilSlice []string
	utils.AssertEqual(t, nilSlice, h2.Empty)
	utils.AssertEqual(t, []string{""}, h2.Alloc)
	utils.AssertEqual(t, []int64{1}, h2.No)

	type RequiredHeader struct {
		Name string `reqHeader:"name,required"`
	}
	rh := new(RequiredHeader)
	c.Request().Header.Del("name")
	utils.AssertEqual(t, "failed to decode: name is empty", c.ReqHeaderParser(rh).Error())
}

// go test -run Test_Ctx_ReqHeaderParserUsingTag -v
func Test_Ctx_ReqHeaderParserUsingTag(t *testing.T) {
	t.Parallel()
	app := New(Config{EnableSplittingOnParsers: true})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type Header struct {
		ID      int      `reqHeader:"id"`
		Name    string   `reqHeader:"name"`
		Hobby   []string `reqHeader:"hobby"`
		Address []string `reqHeader:"x-secure-address"`
	}
	c.Request().SetBody([]byte(``))
	c.Request().Header.SetContentType("")

	c.Request().Header.Add("id", "1")
	c.Request().Header.Add("Name", "John Doe")
	c.Request().Header.Add("Hobby", "golang,fiber")
	c.Request().Header.Add("x-secure-address", "1st,2st")
	q := new(Header)
	utils.AssertEqual(t, nil, c.ReqHeaderParser(q))
	utils.AssertEqual(t, 2, len(q.Hobby))
	utils.AssertEqual(t, 2, len(q.Address))

	c.Request().Header.Del("hobby")
	c.Request().Header.Add("Hobby", "golang,fiber,go")
	q = new(Header)
	utils.AssertEqual(t, nil, c.ReqHeaderParser(q))
	utils.AssertEqual(t, 3, len(q.Hobby))

	empty := new(Header)
	c.Request().Header.Del("hobby")
	utils.AssertEqual(t, nil, c.QueryParser(empty))
	utils.AssertEqual(t, 0, len(empty.Hobby))

	type Header2 struct {
		Bool            bool     `reqHeader:"bool"`
		ID              int      `reqHeader:"id"`
		Name            string   `reqHeader:"name"`
		Hobby           string   `reqHeader:"hobby"`
		FavouriteDrinks []string `reqHeader:"favouriteDrinks"`
		Empty           []string `reqHeader:"empty"`
		Alloc           []string `reqHeader:"alloc"`
		No              []int64  `reqHeader:"no"`
	}

	c.Request().Header.Add("id", "2")
	c.Request().Header.Add("Name", "Jane Doe")
	c.Request().Header.Del("hobby")
	c.Request().Header.Add("Hobby", "go,fiber")
	c.Request().Header.Add("favouriteDrinks", "milo,coke,pepsi")
	c.Request().Header.Add("alloc", "")
	c.Request().Header.Add("no", "1")

	h2 := new(Header2)
	h2.Bool = true
	h2.Name = "hello world 4"
	utils.AssertEqual(t, nil, c.ReqHeaderParser(h2))
	utils.AssertEqual(t, "go,fiber", h2.Hobby)
	utils.AssertEqual(t, true, h2.Bool)
	utils.AssertEqual(t, "Jane Doe", h2.Name) // check value get overwritten
	utils.AssertEqual(t, []string{"milo", "coke", "pepsi"}, h2.FavouriteDrinks)
	var nilSlice []string
	utils.AssertEqual(t, nilSlice, h2.Empty)
	utils.AssertEqual(t, []string{""}, h2.Alloc)
	utils.AssertEqual(t, []int64{1}, h2.No)

	type RequiredHeader struct {
		Name string `reqHeader:"name,required"`
	}
	rh := new(RequiredHeader)
	c.Request().Header.Del("name")
	utils.AssertEqual(t, "failed to decode: name is empty", c.ReqHeaderParser(rh).Error())
}

// go test -run Test_Ctx_ReqHeaderParser -v
func Test_Ctx_ReqHeaderParser_WithoutSplitting(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type Header struct {
		ID    int
		Name  string
		Hobby []string
	}
	c.Request().SetBody([]byte(``))
	c.Request().Header.SetContentType("")

	c.Request().Header.Add("id", "1")
	c.Request().Header.Add("Name", "John Doe")
	c.Request().Header.Add("Hobby", "golang,fiber")
	q := new(Header)
	utils.AssertEqual(t, nil, c.ReqHeaderParser(q))
	utils.AssertEqual(t, 1, len(q.Hobby))

	c.Request().Header.Del("hobby")
	c.Request().Header.Add("Hobby", "golang,fiber,go")
	q = new(Header)
	utils.AssertEqual(t, nil, c.ReqHeaderParser(q))
	utils.AssertEqual(t, 1, len(q.Hobby))
}

// go test -run Test_Ctx_ReqHeaderParser_WithSetParserDecoder -v
func Test_Ctx_ReqHeaderParser_WithSetParserDecoder(t *testing.T) {
	type NonRFCTime time.Time

	nonRFCConverter := func(value string) reflect.Value {
		if v, err := time.Parse("2006-01-02", value); err == nil {
			return reflect.ValueOf(v)
		}
		return reflect.Value{}
	}

	nonRFCTime := ParserType{
		Customtype: NonRFCTime{},
		Converter:  nonRFCConverter,
	}

	SetParserDecoder(ParserConfig{
		IgnoreUnknownKeys: true,
		ParserType:        []ParserType{nonRFCTime},
		ZeroEmpty:         true,
		SetAliasTag:       "req",
	})

	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	type NonRFCTimeInput struct {
		Date  NonRFCTime `req:"date"`
		Title string     `req:"title"`
		Body  string     `req:"body"`
	}

	c.Request().SetBody([]byte(``))
	c.Request().Header.SetContentType("")
	r := new(NonRFCTimeInput)

	c.Request().Header.Add("Date", "2021-04-10")
	c.Request().Header.Add("Title", "CustomDateTest")
	c.Request().Header.Add("Body", "October")

	utils.AssertEqual(t, nil, c.ReqHeaderParser(r))
	utils.AssertEqual(t, "CustomDateTest", r.Title)
	date := fmt.Sprintf("%v", r.Date)
	utils.AssertEqual(t, "{0 63753609600 <nil>}", date)
	utils.AssertEqual(t, "October", r.Body)

	c.Request().Header.Add("Title", "")
	r = &NonRFCTimeInput{
		Title: "Existing title",
		Body:  "Existing Body",
	}
	utils.AssertEqual(t, nil, c.ReqHeaderParser(r))
	utils.AssertEqual(t, "", r.Title)
}

// go test -run Test_Ctx_ReqHeaderParser_Schema -v
func Test_Ctx_ReqHeaderParser_Schema(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type Header1 struct {
		Name   string `reqHeader:"Name,required"`
		Nested struct {
			Age int `reqHeader:"Age"`
		} `reqHeader:"Nested,required"`
	}
	c.Request().SetBody([]byte(``))
	c.Request().Header.SetContentType("")

	c.Request().Header.Add("Name", "tom")
	c.Request().Header.Add("Nested.Age", "10")
	q := new(Header1)
	utils.AssertEqual(t, nil, c.ReqHeaderParser(q))

	c.Request().Header.Del("Name")
	q = new(Header1)
	utils.AssertEqual(t, "failed to decode: Name is empty", c.ReqHeaderParser(q).Error())

	c.Request().Header.Add("Name", "tom")
	c.Request().Header.Del("Nested.Age")
	c.Request().Header.Add("Nested.Agex", "10")
	q = new(Header1)
	utils.AssertEqual(t, nil, c.ReqHeaderParser(q))

	c.Request().Header.Del("Nested.Agex")
	q = new(Header1)
	utils.AssertEqual(t, "failed to decode: Nested is empty", c.ReqHeaderParser(q).Error())

	c.Request().Header.Del("Nested.Agex")
	c.Request().Header.Del("Name")

	type Header2 struct {
		Name   string `reqHeader:"Name"`
		Nested struct {
			Age int `reqHeader:"age,required"`
		} `reqHeader:"Nested"`
	}

	c.Request().Header.Add("Name", "tom")
	c.Request().Header.Add("Nested.Age", "10")

	h2 := new(Header2)
	utils.AssertEqual(t, nil, c.ReqHeaderParser(h2))

	c.Request().Header.Del("Name")
	h2 = new(Header2)
	utils.AssertEqual(t, nil, c.ReqHeaderParser(h2))

	c.Request().Header.Del("Name")
	c.Request().Header.Del("Nested.Age")
	c.Request().Header.Add("Nested.Agex", "10")
	h2 = new(Header2)
	utils.AssertEqual(t, "failed to decode: Nested.age is empty", c.ReqHeaderParser(h2).Error())

	type Node struct {
		Value int   `reqHeader:"Val,required"`
		Next  *Node `reqHeader:"Next,required"`
	}
	c.Request().Header.Add("Val", "1")
	c.Request().Header.Add("Next.Val", "3")
	n := new(Node)
	utils.AssertEqual(t, nil, c.ReqHeaderParser(n))
	utils.AssertEqual(t, 1, n.Value)
	utils.AssertEqual(t, 3, n.Next.Value)

	c.Request().Header.Del("Val")
	n = new(Node)
	utils.AssertEqual(t, "failed to decode: Val is empty", c.ReqHeaderParser(n).Error())

	c.Request().Header.Add("Val", "3")
	c.Request().Header.Del("Next.Val")
	c.Request().Header.Add("Next.Value", "2")
	n = new(Node)
	n.Next = new(Node)
	utils.AssertEqual(t, nil, c.ReqHeaderParser(n))
	utils.AssertEqual(t, 3, n.Value)
	utils.AssertEqual(t, 0, n.Next.Value)
}

// go test -run Test_Ctx_EqualFieldTypeOfRequestQuery
func Test_Ctx_EqualFieldTypeOfRequestQuery(t *testing.T) {
	t.Parallel()
	var out int
	utils.AssertEqual(t, false, equalFieldType(&out, reflect.Int, "key", queryTag))

	var dummy struct{ f string }
	utils.AssertEqual(t, false, equalFieldType(&dummy, reflect.String, "key", queryTag))

	var dummy2 struct{ f string }
	utils.AssertEqual(t, false, equalFieldType(&dummy2, reflect.String, "f", queryTag))

	var user struct {
		Name    string
		Address string `query:"address"`
		Age     int    `query:"AGE"`
	}
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "name", queryTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "Name", queryTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "address", queryTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "Address", queryTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.Int, "AGE", queryTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.Int, "age", queryTag))
}

// go test -run Test_Ctx_EqualFieldTypeOfRequestHeader
func Test_Ctx_EqualFieldTypeOfRequestHeader(t *testing.T) {
	t.Parallel()
	var out int
	utils.AssertEqual(t, false, equalFieldType(&out, reflect.Int, "key", reqHeaderTag))

	var dummy struct{ f string }
	utils.AssertEqual(t, false, equalFieldType(&dummy, reflect.String, "key", reqHeaderTag))

	var dummy2 struct{ f string }
	utils.AssertEqual(t, false, equalFieldType(&dummy2, reflect.String, "f", reqHeaderTag))

	var user struct {
		Name    string
		Address string `reqHeader:"address"`
		Age     int    `reqHeader:"AGE"`
	}
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "name", reqHeaderTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "Name", reqHeaderTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "address", reqHeaderTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "Address", reqHeaderTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.Int, "AGE", reqHeaderTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.Int, "age", reqHeaderTag))
}

// go test -run Test_Ctx_EqualFieldTypeOfRequestBody
func Test_Ctx_EqualFieldTypeOfRequestBody(t *testing.T) {
	t.Parallel()
	var out int
	utils.AssertEqual(t, false, equalFieldType(&out, reflect.Int, "key", bodyTag))

	var dummy struct{ f string }
	utils.AssertEqual(t, false, equalFieldType(&dummy, reflect.String, "key", bodyTag))

	var dummy2 struct{ f string }
	utils.AssertEqual(t, false, equalFieldType(&dummy2, reflect.String, "f", bodyTag))

	var user struct {
		Name    string
		Address string `form:"address"`
		Age     int    `form:"AGE"`
	}
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "name", bodyTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "Name", bodyTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "address", bodyTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "Address", bodyTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.Int, "AGE", bodyTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.Int, "age", bodyTag))
}

// go test -run Test_Ctx_EqualFieldTypeOfRequestParams
func Test_Ctx_EqualFieldTypeOfRequestParams(t *testing.T) {
	t.Parallel()
	var out int
	utils.AssertEqual(t, false, equalFieldType(&out, reflect.Int, "key", paramsTag))

	var dummy struct{ f string }
	utils.AssertEqual(t, false, equalFieldType(&dummy, reflect.String, "key", paramsTag))

	var dummy2 struct{ f string }
	utils.AssertEqual(t, false, equalFieldType(&dummy2, reflect.String, "f", paramsTag))

	var user struct {
		Name    string
		Address string `params:"address"`
		Age     int    `params:"AGE"`
	}
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "name", paramsTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "Name", paramsTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "address", paramsTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.String, "Address", paramsTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.Int, "AGE", paramsTag))
	utils.AssertEqual(t, true, equalFieldType(&user, reflect.Int, "age", paramsTag))
}

// go test -v  -run=^$ -bench=Benchmark_Ctx_QueryParser -benchmem -count=4
func Benchmark_Ctx_QueryParser(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type Query struct {
		ID    int
		Name  string
		Hobby []string
	}
	c.Request().SetBody([]byte(``))
	c.Request().Header.SetContentType("")
	c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football")
	q := new(Query)
	b.ReportAllocs()
	b.ResetTimer()

	var err error
	for n := 0; n < b.N; n++ {
		err = c.QueryParser(q)
	}
	utils.AssertEqual(b, nil, err)
	utils.AssertEqual(b, nil, c.QueryParser(q))
}

// go test -v  -run=^$ -bench=Benchmark_Ctx_parseQuery -benchmem -count=4
func Benchmark_Ctx_parseQuery(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type Person struct {
		Name string `query:"name"`
		Age  int    `query:"age"`
	}

	type CollectionQuery struct {
		Data []Person `query:"data"`
	}

	c.Request().SetBody([]byte(``))
	c.Request().Header.SetContentType("")
	c.Request().URI().SetQueryString("data[0][name]=john&data[0][age]=10")
	cq := new(CollectionQuery)

	b.ReportAllocs()
	b.ResetTimer()

	var err error
	for n := 0; n < b.N; n++ {
		err = c.QueryParser(cq)
	}

	utils.AssertEqual(b, nil, err)
	utils.AssertEqual(b, nil, c.QueryParser(cq))
}

// go test -v  -run=^$ -bench=Benchmark_Ctx_QueryParser_Comma -benchmem -count=4
func Benchmark_Ctx_QueryParser_Comma(b *testing.B) {
	app := New(Config{EnableSplittingOnParsers: true})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type Query struct {
		ID    int
		Name  string
		Hobby []string
	}
	c.Request().SetBody([]byte(``))
	c.Request().Header.SetContentType("")
	// c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball&hobby=football")
	c.Request().URI().SetQueryString("id=1&name=tom&hobby=basketball,football")
	q := new(Query)
	b.ReportAllocs()
	b.ResetTimer()

	var err error
	for n := 0; n < b.N; n++ {
		err = c.QueryParser(q)
	}
	utils.AssertEqual(b, nil, err)
	utils.AssertEqual(b, nil, c.QueryParser(q))
}

// go test -v  -run=^$ -bench=Benchmark_Ctx_ReqHeaderParser -benchmem -count=4
func Benchmark_Ctx_ReqHeaderParser(b *testing.B) {
	app := New(Config{EnableSplittingOnParsers: true})
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	type ReqHeader struct {
		ID    int
		Name  string
		Hobby []string
	}
	c.Request().SetBody([]byte(``))
	c.Request().Header.SetContentType("")

	c.Request().Header.Add("id", "1")
	c.Request().Header.Add("Name", "John Doe")
	c.Request().Header.Add("Hobby", "golang,fiber")

	q := new(ReqHeader)
	b.ReportAllocs()
	b.ResetTimer()

	var err error
	for n := 0; n < b.N; n++ {
		err = c.ReqHeaderParser(q)
	}
	utils.AssertEqual(b, nil, err)
	utils.AssertEqual(b, nil, c.ReqHeaderParser(q))
}

// go test -v  -run=^$ -bench=Benchmark_equalFieldType -benchmem -count=4
func Benchmark_equalFieldType(b *testing.B) {
	type user struct {
		Name string `query:"name"`
		Age  int    `query:"age"`
	}

	u := new(user)
	b.ReportAllocs()
	b.ResetTimer()

	var res bool
	for n := 0; n < b.N; n++ {
		res = equalFieldType(u, reflect.String, "name", queryTag)
	}

	utils.AssertEqual(b, true, res)
}

// go test -run Test_Ctx_BodyStreamWriter
func Test_Ctx_BodyStreamWriter(t *testing.T) {
	t.Parallel()
	ctx := &fasthttp.RequestCtx{}

	ctx.SetBodyStreamWriter(func(w *bufio.Writer) {
		fmt.Fprintf(w, "body writer line 1\n")
		if err := w.Flush(); err != nil {
			t.Errorf("unexpected error: %s", err)
		}
		fmt.Fprintf(w, "body writer line 2\n")
	})

	utils.AssertEqual(t, true, ctx.IsBodyStream())

	s := ctx.Response.String()
	br := bufio.NewReader(bytes.NewBufferString(s))
	var resp fasthttp.Response
	utils.AssertEqual(t, nil, resp.Read(br))

	body := string(resp.Body())
	expectedBody := "body writer line 1\nbody writer line 2\n"
	utils.AssertEqual(t, expectedBody, body)
}

// go test -v  -run=^$ -bench=Benchmark_Ctx_BodyStreamWriter -benchmem -count=4
func Benchmark_Ctx_BodyStreamWriter(b *testing.B) {
	ctx := &fasthttp.RequestCtx{}
	user := []byte(`{"name":"john"}`)
	b.ReportAllocs()
	b.ResetTimer()

	var err error
	for n := 0; n < b.N; n++ {
		ctx.ResetBody()
		ctx.SetBodyStreamWriter(func(w *bufio.Writer) {
			for i := 0; i < 10; i++ {
				_, err = w.Write(user)
				if err := w.Flush(); err != nil {
					return
				}
			}
		})
	}
	utils.AssertEqual(b, nil, err)
}

func Test_Ctx_String(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	utils.AssertEqual(t, "#0000000000000000 - 0.0.0.0:0 <-> 0.0.0.0:0 - GET http:///", c.String())
}

func TestCtx_ParamsInt(t *testing.T) {
	// Create a test context and set some strings (or params)
	// create a fake app to be used within this test
	t.Parallel()
	app := New()

	// Create some test endpoints

	// For the user id I will use the number 1111, so I should be able to get the number
	// 1111 from the Ctx
	app.Get("/test/:user", func(c *Ctx) error {
		// utils.AssertEqual(t, "john", c.Params("user"))

		num, err := c.ParamsInt("user")

		// Check the number matches
		utils.AssertEqual(t, 1111, num)

		// Check no errors are returned, because we want NO errors in this one
		utils.AssertEqual(t, nil, err)

		return nil
	})

	// In this test case, there will be a bad request where the expected number is NOT
	// a number in the path
	app.Get("/testnoint/:user", func(c *Ctx) error {
		// utils.AssertEqual(t, "john", c.Params("user"))

		num, err := c.ParamsInt("user")

		// Check the number matches
		utils.AssertEqual(t, 0, num)

		// Check an error is returned, because we want NO errors in this one
		utils.AssertEqual(t, true, err != nil)

		return nil
	})

	// For the user id I will use the number 2222, so I should be able to get the number
	// 2222 from the Ctx even when the default value is specified
	app.Get("/testignoredefault/:user", func(c *Ctx) error {
		// utils.AssertEqual(t, "john", c.Params("user"))

		num, err := c.ParamsInt("user", 1111)

		// Check the number matches
		utils.AssertEqual(t, 2222, num)

		// Check no errors are returned, because we want NO errors in this one
		utils.AssertEqual(t, nil, err)

		return nil
	})

	// In this test case, there will be a bad request where the expected number is NOT
	// a number in the path, default value of 1111 should be used instead
	app.Get("/testdefault/:user", func(c *Ctx) error {
		// utils.AssertEqual(t, "john", c.Params("user"))

		num, err := c.ParamsInt("user", 1111)

		// Check the number matches
		utils.AssertEqual(t, 1111, num)

		// Check an error is returned, because we want NO errors in this one
		utils.AssertEqual(t, nil, err)

		return nil
	})

	_, err := app.Test(httptest.NewRequest(MethodGet, "/test/1111", nil))
	utils.AssertEqual(t, nil, err)

	_, err = app.Test(httptest.NewRequest(MethodGet, "/testnoint/xd", nil))
	utils.AssertEqual(t, nil, err)

	_, err = app.Test(httptest.NewRequest(MethodGet, "/testignoredefault/2222", nil))
	utils.AssertEqual(t, nil, err)

	_, err = app.Test(httptest.NewRequest(MethodGet, "/testdefault/xd", nil))
	utils.AssertEqual(t, nil, err)
}

// go test -run Test_Ctx_GetRespHeader
func Test_Ctx_GetRespHeader(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	c.Set("test", "Hello, World 👋!")
	c.Response().Header.Set(HeaderContentType, "application/json")
	utils.AssertEqual(t, c.GetRespHeader("test"), "Hello, World 👋!")
	utils.AssertEqual(t, c.GetRespHeader(HeaderContentType), "application/json")
}

// go test -run Test_Ctx_GetRespHeaders
func Test_Ctx_GetRespHeaders(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	c.Set("test", "Hello, World 👋!")
	c.Set("foo", "bar")
	c.Response().Header.Set("multi", "one")
	c.Response().Header.Add("multi", "two")
	c.Response().Header.Set(HeaderContentType, "application/json")

	utils.AssertEqual(t, c.GetRespHeaders(), map[string][]string{
		"Content-Type": {"application/json"},
		"Foo":          {"bar"},
		"Multi":        {"one", "two"},
		"Test":         {"Hello, World 👋!"},
	})
}

func Benchmark_Ctx_GetRespHeaders(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	c.Response().Header.Set("test", "Hello, World 👋!")
	c.Response().Header.Set("foo", "bar")
	c.Response().Header.Set(HeaderContentType, "application/json")

	b.ReportAllocs()
	b.ResetTimer()

	var headers map[string][]string
	for n := 0; n < b.N; n++ {
		headers = c.GetRespHeaders()
	}

	utils.AssertEqual(b, headers, map[string][]string{
		"Content-Type": {"application/json"},
		"Foo":          {"bar"},
		"Test":         {"Hello, World 👋!"},
	})
}

// go test -run Test_Ctx_GetReqHeaders
func Test_Ctx_GetReqHeaders(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	c.Request().Header.Set("test", "Hello, World 👋!")
	c.Request().Header.Set("foo", "bar")
	c.Request().Header.Set("multi", "one")
	c.Request().Header.Add("multi", "two")
	c.Request().Header.Set(HeaderContentType, "application/json")

	utils.AssertEqual(t, c.GetReqHeaders(), map[string][]string{
		"Content-Type": {"application/json"},
		"Foo":          {"bar"},
		"Test":         {"Hello, World 👋!"},
		"Multi":        {"one", "two"},
	})
}

func Benchmark_Ctx_GetReqHeaders(b *testing.B) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	c.Request().Header.Set("test", "Hello, World 👋!")
	c.Request().Header.Set("foo", "bar")
	c.Request().Header.Set(HeaderContentType, "application/json")

	b.ReportAllocs()
	b.ResetTimer()

	var headers map[string][]string
	for n := 0; n < b.N; n++ {
		headers = c.GetReqHeaders()
	}

	utils.AssertEqual(b, headers, map[string][]string{
		"Content-Type": {"application/json"},
		"Foo":          {"bar"},
		"Test":         {"Hello, World 👋!"},
	})
}

// go test -run Test_Ctx_IsFromLocal_X_Forwarded
func Test_Ctx_IsFromLocal_X_Forwarded(t *testing.T) {
	t.Parallel()
	// Test unset X-Forwarded-For header.
	{
		app := New()
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		defer app.ReleaseCtx(c)
		// fasthttp returns "0.0.0.0" as IP as there is no remote address.
		utils.AssertEqual(t, "0.0.0.0", c.IP())
		utils.AssertEqual(t, false, c.IsFromLocal())
	}
	// Test when setting X-Forwarded-For header to localhost "127.0.0.1"
	{
		app := New()
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		c.Request().Header.Set(HeaderXForwardedFor, "127.0.0.1")
		defer app.ReleaseCtx(c)
		utils.AssertEqual(t, false, c.IsFromLocal())
	}
	// Test when setting X-Forwarded-For header to localhost "::1"
	{
		app := New()
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		c.Request().Header.Set(HeaderXForwardedFor, "::1")
		defer app.ReleaseCtx(c)
		utils.AssertEqual(t, false, c.IsFromLocal())
	}
	// Test when setting X-Forwarded-For to full localhost IPv6 address "0:0:0:0:0:0:0:1"
	{
		app := New()
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		c.Request().Header.Set(HeaderXForwardedFor, "0:0:0:0:0:0:0:1")
		defer app.ReleaseCtx(c)
		utils.AssertEqual(t, false, c.IsFromLocal())
	}
	// Test for a random IP address.
	{
		app := New()
		c := app.AcquireCtx(&fasthttp.RequestCtx{})
		c.Request().Header.Set(HeaderXForwardedFor, "93.46.8.90")
		defer app.ReleaseCtx(c)
		utils.AssertEqual(t, false, c.IsFromLocal())
	}
}

// go test -run Test_Ctx_IsFromLocal_RemoteAddr
func Test_Ctx_IsFromLocal_RemoteAddr(t *testing.T) {
	t.Parallel()

	localIPv4 := net.Addr(&net.TCPAddr{IP: net.ParseIP("127.0.0.1")})
	localIPv6 := net.Addr(&net.TCPAddr{IP: net.ParseIP("::1")})
	localIPv6long := net.Addr(&net.TCPAddr{IP: net.ParseIP("0:0:0:0:0:0:0:1")})

	zeroIPv4 := net.Addr(&net.TCPAddr{IP: net.IPv4zero})

	someIPv4 := net.Addr(&net.TCPAddr{IP: net.ParseIP("93.46.8.90")})
	someIPv6 := net.Addr(&net.TCPAddr{IP: net.ParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334")})

	// Test for the case fasthttp remoteAddr is set to "127.0.0.1".
	{
		app := New()
		fastCtx := &fasthttp.RequestCtx{}
		fastCtx.SetRemoteAddr(localIPv4)
		c := app.AcquireCtx(fastCtx)
		defer app.ReleaseCtx(c)

		utils.AssertEqual(t, "127.0.0.1", c.IP())
		utils.AssertEqual(t, true, c.IsFromLocal())
	}
	// Test for the case fasthttp remoteAddr is set to "::1".
	{
		app := New()
		fastCtx := &fasthttp.RequestCtx{}
		fastCtx.SetRemoteAddr(localIPv6)
		c := app.AcquireCtx(fastCtx)
		defer app.ReleaseCtx(c)
		utils.AssertEqual(t, "::1", c.IP())
		utils.AssertEqual(t, true, c.IsFromLocal())
	}
	// Test for the case fasthttp remoteAddr is set to "0:0:0:0:0:0:0:1".
	{
		app := New()
		fastCtx := &fasthttp.RequestCtx{}
		fastCtx.SetRemoteAddr(localIPv6long)
		c := app.AcquireCtx(fastCtx)
		defer app.ReleaseCtx(c)
		// fasthttp should return "::1" for "0:0:0:0:0:0:0:1".
		// otherwise IsFromLocal() will break.
		utils.AssertEqual(t, "::1", c.IP())
		utils.AssertEqual(t, true, c.IsFromLocal())
	}
	// Test for the case fasthttp remoteAddr is set to "0.0.0.0".
	{
		app := New()
		fastCtx := &fasthttp.RequestCtx{}
		fastCtx.SetRemoteAddr(zeroIPv4)
		c := app.AcquireCtx(fastCtx)
		defer app.ReleaseCtx(c)
		utils.AssertEqual(t, "0.0.0.0", c.IP())
		utils.AssertEqual(t, false, c.IsFromLocal())
	}
	// Test for the case fasthttp remoteAddr is set to "93.46.8.90".
	{
		app := New()
		fastCtx := &fasthttp.RequestCtx{}
		fastCtx.SetRemoteAddr(someIPv4)
		c := app.AcquireCtx(fastCtx)
		defer app.ReleaseCtx(c)
		utils.AssertEqual(t, "93.46.8.90", c.IP())
		utils.AssertEqual(t, false, c.IsFromLocal())
	}
	// Test for the case fasthttp remoteAddr is set to "2001:0db8:85a3:0000:0000:8a2e:0370:7334".
	{
		app := New()
		fastCtx := &fasthttp.RequestCtx{}
		fastCtx.SetRemoteAddr(someIPv6)
		c := app.AcquireCtx(fastCtx)
		defer app.ReleaseCtx(c)
		utils.AssertEqual(t, "2001:db8:85a3::8a2e:370:7334", c.IP())
		utils.AssertEqual(t, false, c.IsFromLocal())
	}
}

// go test -run Test_Ctx_RepeatParserWithSameStruct -v
func Test_Ctx_RepeatParserWithSameStruct(t *testing.T) {
	t.Parallel()
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)

	type Request struct {
		QueryParam  string `query:"query_param"`
		HeaderParam string `reqHeader:"header_param"`
		BodyParam   string `json:"body_param" xml:"body_param" form:"body_param"`
	}

	r := new(Request)

	c.Request().URI().SetQueryString("query_param=query_param")
	utils.AssertEqual(t, nil, c.QueryParser(r))
	utils.AssertEqual(t, "query_param", r.QueryParam)

	c.Request().Header.Add("header_param", "header_param")
	utils.AssertEqual(t, nil, c.ReqHeaderParser(r))
	utils.AssertEqual(t, "header_param", r.HeaderParam)

	var gzipJSON bytes.Buffer
	w := gzip.NewWriter(&gzipJSON)
	_, _ = w.Write([]byte(`{"body_param":"body_param"}`)) //nolint:errcheck // This will never fail
	err := w.Close()
	utils.AssertEqual(t, nil, err)
	c.Request().Header.SetContentType(MIMEApplicationJSON)
	c.Request().Header.Set(HeaderContentEncoding, "gzip")
	c.Request().SetBody(gzipJSON.Bytes())
	c.Request().Header.SetContentLength(len(gzipJSON.Bytes()))
	utils.AssertEqual(t, nil, c.BodyParser(r))
	utils.AssertEqual(t, "body_param", r.BodyParam)
	c.Request().Header.Del(HeaderContentEncoding)

	testDecodeParser := func(contentType, body string) {
		c.Request().Header.SetContentType(contentType)
		c.Request().SetBody([]byte(body))
		c.Request().Header.SetContentLength(len(body))
		utils.AssertEqual(t, nil, c.BodyParser(r))
		utils.AssertEqual(t, "body_param", r.BodyParam)
	}

	testDecodeParser(MIMEApplicationJSON, `{"body_param":"body_param"}`)
	testDecodeParser(MIMEApplicationXML, `<Demo><body_param>body_param</body_param></Demo>`)
	testDecodeParser(MIMEApplicationForm, "body_param=body_param")
	testDecodeParser(MIMEMultipartForm+`;boundary="b"`, "--b\r\nContent-Disposition: form-data; name=\"body_param\"\r\n\r\nbody_param\r\n--b--")
}

// go test -run Test_Ctx_extractIPsFromHeader -v
func Test_Ctx_extractIPsFromHeader(t *testing.T) {
	app := New()
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set("x-forwarded-for", "1.1.1.1,8.8.8.8 , /n, \n,1.1, a.c, 6.,6., , a,,42.118.81.169,10.0.137.108")
	ips := c.IPs()
	res := ips[len(ips)-2]
	utils.AssertEqual(t, "42.118.81.169", res)
}

// go test -run Test_Ctx_extractIPsFromHeader -v
func Test_Ctx_extractIPsFromHeader_EnableValidateIp(t *testing.T) {
	app := New()
	app.config.EnableIPValidation = true
	c := app.AcquireCtx(&fasthttp.RequestCtx{})
	defer app.ReleaseCtx(c)
	c.Request().Header.Set("x-forwarded-for", "1.1.1.1,8.8.8.8 , /n, \n,1.1, a.c, 6.,6., , a,,42.118.81.169,10.0.137.108")
	ips := c.IPs()
	res := ips[len(ips)-2]
	utils.AssertEqual(t, "42.118.81.169", res)
}
