package client import ( "context" "crypto/tls" "encoding/hex" "errors" "io" "net" "os" "reflect" "sync" "testing" "time" "github.com/gofiber/fiber/v3" "github.com/gofiber/fiber/v3/addon/retry" "github.com/gofiber/fiber/v3/internal/tlstest" "github.com/gofiber/utils/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/valyala/bytebufferpool" "github.com/valyala/fasthttp" ) func startTestServerWithPort(t *testing.T, beforeStarting func(app *fiber.App)) (*fiber.App, string) { t.Helper() app := fiber.New() if beforeStarting != nil { beforeStarting(app) } addrChan := make(chan string) errChan := make(chan error, 1) go func() { err := app.Listen(":0", fiber.ListenConfig{ DisableStartupMessage: true, ListenerAddrFunc: func(addr net.Addr) { addrChan <- addr.String() }, }) if err != nil { errChan <- err } }() select { case addr := <-addrChan: return app, addr case err := <-errChan: t.Fatalf("Failed to start test server: %v", err) } return nil, "" } func Test_New_With_Client(t *testing.T) { t.Parallel() t.Run("with valid client", func(t *testing.T) { t.Parallel() c := &fasthttp.Client{ MaxConnsPerHost: 5, } client := NewWithClient(c) require.NotNil(t, client) }) t.Run("with nil client", func(t *testing.T) { t.Parallel() require.PanicsWithValue(t, "fasthttp.Client must not be nil", func() { NewWithClient(nil) }) }) } func Test_Client_Add_Hook(t *testing.T) { t.Parallel() t.Run("add request hooks", func(t *testing.T) { t.Parallel() buf := bytebufferpool.Get() defer bytebufferpool.Put(buf) client := New().AddRequestHook(func(_ *Client, _ *Request) error { buf.WriteString("hook1") return nil }) require.Len(t, client.RequestHook(), 1) client.AddRequestHook(func(_ *Client, _ *Request) error { buf.WriteString("hook2") return nil }, func(_ *Client, _ *Request) error { buf.WriteString("hook3") return nil }) require.Len(t, client.RequestHook(), 3) }) t.Run("add response hooks", func(t *testing.T) { t.Parallel() client := New().AddResponseHook(func(_ *Client, _ *Response, _ *Request) error { return nil }) require.Len(t, client.ResponseHook(), 1) client.AddResponseHook(func(_ *Client, _ *Response, _ *Request) error { return nil }, func(_ *Client, _ *Response, _ *Request) error { return nil }) require.Len(t, client.ResponseHook(), 3) }) } func Test_Client_Add_Hook_CheckOrder(t *testing.T) { t.Parallel() buf := bytebufferpool.Get() defer bytebufferpool.Put(buf) client := New(). AddRequestHook(func(_ *Client, _ *Request) error { buf.WriteString("hook1") return nil }). AddRequestHook(func(_ *Client, _ *Request) error { buf.WriteString("hook2") return nil }). AddRequestHook(func(_ *Client, _ *Request) error { buf.WriteString("hook3") return nil }) for _, hook := range client.RequestHook() { require.NoError(t, hook(client, &Request{})) } require.Equal(t, "hook1hook2hook3", buf.String()) } func Test_Client_Marshal(t *testing.T) { t.Parallel() t.Run("set json marshal", func(t *testing.T) { t.Parallel() client := New(). SetJSONMarshal(func(_ any) ([]byte, error) { return []byte("hello"), nil }) val, err := client.JSONMarshal()(nil) require.NoError(t, err) require.Equal(t, []byte("hello"), val) }) t.Run("set json marshal error", func(t *testing.T) { t.Parallel() emptyErr := errors.New("empty json") client := New(). SetJSONMarshal(func(_ any) ([]byte, error) { return nil, emptyErr }) val, err := client.JSONMarshal()(nil) require.Nil(t, val) require.ErrorIs(t, err, emptyErr) }) t.Run("set json unmarshal", func(t *testing.T) { t.Parallel() client := New(). SetJSONUnmarshal(func(_ []byte, _ any) error { return errors.New("empty json") }) err := client.JSONUnmarshal()(nil, nil) require.Equal(t, errors.New("empty json"), err) }) t.Run("set json unmarshal error", func(t *testing.T) { t.Parallel() client := New(). SetJSONUnmarshal(func(_ []byte, _ any) error { return errors.New("empty json") }) err := client.JSONUnmarshal()(nil, nil) require.Equal(t, errors.New("empty json"), err) }) t.Run("set xml marshal", func(t *testing.T) { t.Parallel() client := New(). SetXMLMarshal(func(_ any) ([]byte, error) { return []byte("hello"), nil }) val, err := client.XMLMarshal()(nil) require.NoError(t, err) require.Equal(t, []byte("hello"), val) }) t.Run("set xml marshal error", func(t *testing.T) { t.Parallel() client := New(). SetXMLMarshal(func(_ any) ([]byte, error) { return nil, errors.New("empty xml") }) val, err := client.XMLMarshal()(nil) require.Nil(t, val) require.Equal(t, errors.New("empty xml"), err) }) t.Run("set cbor marshal", func(t *testing.T) { t.Parallel() bs, err := hex.DecodeString("f6") if err != nil { t.Error(err) } client := New(). SetCBORMarshal(func(_ any) ([]byte, error) { return bs, nil }) val, err := client.CBORMarshal()(nil) require.NoError(t, err) require.Equal(t, bs, val) }) t.Run("set cbor marshal error", func(t *testing.T) { t.Parallel() client := New().SetCBORMarshal(func(_ any) ([]byte, error) { return nil, errors.New("invalid struct") }) val, err := client.CBORMarshal()(nil) require.Nil(t, val) require.Equal(t, errors.New("invalid struct"), err) }) t.Run("set xml unmarshal", func(t *testing.T) { t.Parallel() client := New(). SetXMLUnmarshal(func(_ []byte, _ any) error { return errors.New("empty xml") }) err := client.XMLUnmarshal()(nil, nil) require.Equal(t, errors.New("empty xml"), err) }) t.Run("set xml unmarshal error", func(t *testing.T) { t.Parallel() client := New(). SetXMLUnmarshal(func(_ []byte, _ any) error { return errors.New("empty xml") }) err := client.XMLUnmarshal()(nil, nil) require.Equal(t, errors.New("empty xml"), err) }) } func Test_Client_SetBaseURL(t *testing.T) { t.Parallel() client := New().SetBaseURL("http://example.com") require.Equal(t, "http://example.com", client.BaseURL()) } func Test_Client_Invalid_URL(t *testing.T) { t.Parallel() app, dial, start := createHelperServer(t) app.Get("/", func(c fiber.Ctx) error { return c.SendString(c.Hostname()) }) go start() _, err := New().SetDial(dial). R(). Get("http//example") require.ErrorIs(t, err, ErrURLFormat) } func Test_Client_Unsupported_Protocol(t *testing.T) { t.Parallel() _, err := New(). R(). Get("ftp://example.com") require.ErrorIs(t, err, ErrURLFormat) } func Test_Client_ConcurrencyRequests(t *testing.T) { t.Parallel() app, dial, start := createHelperServer(t) app.All("/", func(c fiber.Ctx) error { return c.SendString(c.Hostname() + " " + c.Method()) }) go start() client := New().SetDial(dial) wg := sync.WaitGroup{} for i := 0; i < 5; i++ { for _, method := range []string{"GET", "POST", "PUT", "DELETE", "PATCH"} { wg.Add(1) go func(m string) { defer wg.Done() resp, err := client.Custom("http://example.com", m) assert.NoError(t, err) assert.Equal(t, "example.com "+m, utils.UnsafeString(resp.RawResponse.Body())) }(method) } } wg.Wait() } func Test_Get(t *testing.T) { t.Parallel() setupApp := func() (*fiber.App, string) { app, addr := startTestServerWithPort(t, func(app *fiber.App) { app.Get("/", func(c fiber.Ctx) error { return c.SendString(c.Hostname()) }) }) return app, addr } t.Run("global get function", func(t *testing.T) { t.Parallel() app, addr := setupApp() defer func() { require.NoError(t, app.Shutdown()) }() resp, err := Get("http://" + addr) require.NoError(t, err) require.Equal(t, "0.0.0.0", utils.UnsafeString(resp.RawResponse.Body())) }) t.Run("client get", func(t *testing.T) { t.Parallel() app, addr := setupApp() defer func() { require.NoError(t, app.Shutdown()) }() resp, err := New().Get("http://" + addr) require.NoError(t, err) require.Equal(t, "0.0.0.0", utils.UnsafeString(resp.RawResponse.Body())) }) } func Test_Head(t *testing.T) { t.Parallel() setupApp := func() (*fiber.App, string) { app, addr := startTestServerWithPort(t, func(app *fiber.App) { app.Head("/", func(c fiber.Ctx) error { return c.SendString(c.Hostname()) }) }) return app, addr } t.Run("global head function", func(t *testing.T) { t.Parallel() app, addr := setupApp() defer func() { require.NoError(t, app.Shutdown()) }() resp, err := Head("http://" + addr) require.NoError(t, err) require.Equal(t, "7", resp.Header(fiber.HeaderContentLength)) require.Equal(t, "", utils.UnsafeString(resp.RawResponse.Body())) }) t.Run("client head", func(t *testing.T) { t.Parallel() app, addr := setupApp() defer func() { require.NoError(t, app.Shutdown()) }() resp, err := New().Head("http://" + addr) require.NoError(t, err) require.Equal(t, "7", resp.Header(fiber.HeaderContentLength)) require.Equal(t, "", utils.UnsafeString(resp.RawResponse.Body())) }) } func Test_Post(t *testing.T) { t.Parallel() setupApp := func() (*fiber.App, string) { app, addr := startTestServerWithPort(t, func(app *fiber.App) { app.Post("/", func(c fiber.Ctx) error { return c.Status(fiber.StatusCreated). SendString(c.FormValue("foo")) }) }) return app, addr } t.Run("global post function", func(t *testing.T) { t.Parallel() app, addr := setupApp() defer func() { require.NoError(t, app.Shutdown()) }() for i := 0; i < 5; i++ { resp, err := Post("http://"+addr, Config{ FormData: map[string]string{ "foo": "bar", }, }) require.NoError(t, err) require.Equal(t, fiber.StatusCreated, resp.StatusCode()) require.Equal(t, "bar", resp.String()) } }) t.Run("client post", func(t *testing.T) { t.Parallel() app, addr := setupApp() defer func() { require.NoError(t, app.Shutdown()) }() for i := 0; i < 5; i++ { resp, err := New().Post("http://"+addr, Config{ FormData: map[string]string{ "foo": "bar", }, }) require.NoError(t, err) require.Equal(t, fiber.StatusCreated, resp.StatusCode()) require.Equal(t, "bar", resp.String()) } }) } func Test_Put(t *testing.T) { t.Parallel() setupApp := func() (*fiber.App, string) { app, addr := startTestServerWithPort(t, func(app *fiber.App) { app.Put("/", func(c fiber.Ctx) error { return c.SendString(c.FormValue("foo")) }) }) return app, addr } t.Run("global put function", func(t *testing.T) { t.Parallel() app, addr := setupApp() defer func() { require.NoError(t, app.Shutdown()) }() for i := 0; i < 5; i++ { resp, err := Put("http://"+addr, Config{ FormData: map[string]string{ "foo": "bar", }, }) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode()) require.Equal(t, "bar", resp.String()) } }) t.Run("client put", func(t *testing.T) { t.Parallel() app, addr := setupApp() defer func() { require.NoError(t, app.Shutdown()) }() for i := 0; i < 5; i++ { resp, err := New().Put("http://"+addr, Config{ FormData: map[string]string{ "foo": "bar", }, }) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode()) require.Equal(t, "bar", resp.String()) } }) } func Test_Delete(t *testing.T) { t.Parallel() setupApp := func() (*fiber.App, string) { app, addr := startTestServerWithPort(t, func(app *fiber.App) { app.Delete("/", func(c fiber.Ctx) error { return c.Status(fiber.StatusNoContent). SendString("deleted") }) }) return app, addr } t.Run("global delete function", func(t *testing.T) { t.Parallel() app, addr := setupApp() defer func() { require.NoError(t, app.Shutdown()) }() time.Sleep(1 * time.Second) for i := 0; i < 5; i++ { resp, err := Delete("http://"+addr, Config{ FormData: map[string]string{ "foo": "bar", }, }) require.NoError(t, err) require.Equal(t, fiber.StatusNoContent, resp.StatusCode()) require.Equal(t, "", resp.String()) } }) t.Run("client delete", func(t *testing.T) { t.Parallel() app, addr := setupApp() defer func() { require.NoError(t, app.Shutdown()) }() for i := 0; i < 5; i++ { resp, err := New().Delete("http://"+addr, Config{ FormData: map[string]string{ "foo": "bar", }, }) require.NoError(t, err) require.Equal(t, fiber.StatusNoContent, resp.StatusCode()) require.Equal(t, "", resp.String()) } }) } func Test_Options(t *testing.T) { t.Parallel() setupApp := func() (*fiber.App, string) { app, addr := startTestServerWithPort(t, func(app *fiber.App) { app.Options("/", func(c fiber.Ctx) error { c.Set(fiber.HeaderAllow, "GET, POST, PUT, DELETE, PATCH") return c.Status(fiber.StatusNoContent).SendString("") }) }) return app, addr } t.Run("global options function", func(t *testing.T) { t.Parallel() app, addr := setupApp() defer func() { require.NoError(t, app.Shutdown()) }() for i := 0; i < 5; i++ { resp, err := Options("http://" + addr) require.NoError(t, err) require.Equal(t, "GET, POST, PUT, DELETE, PATCH", resp.Header(fiber.HeaderAllow)) require.Equal(t, fiber.StatusNoContent, resp.StatusCode()) require.Equal(t, "", resp.String()) } }) t.Run("client options", func(t *testing.T) { t.Parallel() app, addr := setupApp() defer func() { require.NoError(t, app.Shutdown()) }() for i := 0; i < 5; i++ { resp, err := New().Options("http://" + addr) require.NoError(t, err) require.Equal(t, "GET, POST, PUT, DELETE, PATCH", resp.Header(fiber.HeaderAllow)) require.Equal(t, fiber.StatusNoContent, resp.StatusCode()) require.Equal(t, "", resp.String()) } }) } func Test_Patch(t *testing.T) { t.Parallel() setupApp := func() (*fiber.App, string) { app, addr := startTestServerWithPort(t, func(app *fiber.App) { app.Patch("/", func(c fiber.Ctx) error { return c.SendString(c.FormValue("foo")) }) }) return app, addr } t.Run("global patch function", func(t *testing.T) { t.Parallel() app, addr := setupApp() defer func() { require.NoError(t, app.Shutdown()) }() time.Sleep(1 * time.Second) for i := 0; i < 5; i++ { resp, err := Patch("http://"+addr, Config{ FormData: map[string]string{ "foo": "bar", }, }) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode()) require.Equal(t, "bar", resp.String()) } }) t.Run("client patch", func(t *testing.T) { t.Parallel() app, addr := setupApp() defer func() { require.NoError(t, app.Shutdown()) }() for i := 0; i < 5; i++ { resp, err := New().Patch("http://"+addr, Config{ FormData: map[string]string{ "foo": "bar", }, }) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode()) require.Equal(t, "bar", resp.String()) } }) } func Test_Client_UserAgent(t *testing.T) { t.Parallel() setupApp := func() (*fiber.App, string) { app, addr := startTestServerWithPort(t, func(app *fiber.App) { app.Get("/", func(c fiber.Ctx) error { return c.Send(c.Request().Header.UserAgent()) }) }) return app, addr } t.Run("default", func(t *testing.T) { t.Parallel() app, addr := setupApp() defer func() { require.NoError(t, app.Shutdown()) }() for i := 0; i < 5; i++ { resp, err := Get("http://" + addr) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode()) require.Equal(t, defaultUserAgent, resp.String()) } }) t.Run("custom", func(t *testing.T) { t.Parallel() app, addr := setupApp() defer func() { require.NoError(t, app.Shutdown()) }() for i := 0; i < 5; i++ { c := New(). SetUserAgent("ua") resp, err := c.Get("http://" + addr) require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode()) require.Equal(t, "ua", resp.String()) } }) } func Test_Client_Header(t *testing.T) { t.Parallel() t.Run("add header", func(t *testing.T) { t.Parallel() req := New() req.AddHeader("foo", "bar").AddHeader("foo", "fiber") res := req.Header("foo") require.Len(t, res, 2) require.Equal(t, "bar", res[0]) require.Equal(t, "fiber", res[1]) }) t.Run("set header", func(t *testing.T) { t.Parallel() req := New() req.AddHeader("foo", "bar").SetHeader("foo", "fiber") res := req.Header("foo") require.Len(t, res, 1) require.Equal(t, "fiber", res[0]) }) t.Run("add headers", func(t *testing.T) { t.Parallel() req := New() req.SetHeader("foo", "bar"). AddHeaders(map[string][]string{ "foo": {"fiber", "buaa"}, "bar": {"foo"}, }) res := req.Header("foo") require.Len(t, res, 3) require.Equal(t, "bar", res[0]) require.Equal(t, "fiber", res[1]) require.Equal(t, "buaa", res[2]) res = req.Header("bar") require.Len(t, res, 1) require.Equal(t, "foo", res[0]) }) t.Run("set headers", func(t *testing.T) { t.Parallel() req := New() req.SetHeader("foo", "bar"). SetHeaders(map[string]string{ "foo": "fiber", "bar": "foo", }) res := req.Header("foo") require.Len(t, res, 1) require.Equal(t, "fiber", res[0]) res = req.Header("bar") require.Len(t, res, 1) require.Equal(t, "foo", res[0]) }) t.Run("set header case insensitive", func(t *testing.T) { t.Parallel() req := New() req.SetHeader("foo", "bar"). AddHeader("FOO", "fiber") res := req.Header("foo") require.Len(t, res, 2) require.Equal(t, "bar", res[0]) require.Equal(t, "fiber", res[1]) }) } func Test_Client_Header_With_Server(t *testing.T) { handler := func(c fiber.Ctx) error { c.Request().Header.VisitAll(func(key, value []byte) { if k := string(key); k == "K1" || k == "K2" { _, _ = c.Write(key) //nolint:errcheck // It is fine to ignore the error here _, _ = c.Write(value) //nolint:errcheck // It is fine to ignore the error here } }) return nil } wrapAgent := func(c *Client) { c.SetHeader("k1", "v1"). AddHeader("k1", "v11"). AddHeaders(map[string][]string{ "k1": {"v22", "v33"}, }). SetHeaders(map[string]string{ "k2": "v2", }). AddHeader("k2", "v22") } testClient(t, handler, wrapAgent, "K1v1K1v11K1v22K1v33K2v2K2v22") } func Test_Client_Cookie(t *testing.T) { t.Parallel() t.Run("set cookie", func(t *testing.T) { t.Parallel() req := New(). SetCookie("foo", "bar") require.Equal(t, "bar", req.Cookie("foo")) req.SetCookie("foo", "bar1") require.Equal(t, "bar1", req.Cookie("foo")) }) t.Run("set cookies", func(t *testing.T) { t.Parallel() req := New(). SetCookies(map[string]string{ "foo": "bar", "bar": "foo", }) require.Equal(t, "bar", req.Cookie("foo")) require.Equal(t, "foo", req.Cookie("bar")) req.SetCookies(map[string]string{ "foo": "bar1", }) require.Equal(t, "bar1", req.Cookie("foo")) require.Equal(t, "foo", req.Cookie("bar")) }) t.Run("set cookies with struct", func(t *testing.T) { t.Parallel() type args struct { CookieString string `cookie:"string"` CookieInt int `cookie:"int"` } req := New().SetCookiesWithStruct(&args{ CookieInt: 5, CookieString: "foo", }) require.Equal(t, "5", req.Cookie("int")) require.Equal(t, "foo", req.Cookie("string")) }) t.Run("del cookies", func(t *testing.T) { t.Parallel() req := New(). SetCookies(map[string]string{ "foo": "bar", "bar": "foo", }) require.Equal(t, "bar", req.Cookie("foo")) require.Equal(t, "foo", req.Cookie("bar")) req.DelCookies("foo") require.Equal(t, "", req.Cookie("foo")) require.Equal(t, "foo", req.Cookie("bar")) }) } func Test_Client_Cookie_With_Server(t *testing.T) { t.Parallel() handler := func(c fiber.Ctx) error { return c.SendString( c.Cookies("k1") + c.Cookies("k2") + c.Cookies("k3") + c.Cookies("k4")) } wrapAgent := func(c *Client) { c.SetCookie("k1", "v1"). SetCookies(map[string]string{ "k2": "v2", "k3": "v3", "k4": "v4", }).DelCookies("k4") } testClient(t, handler, wrapAgent, "v1v2v3") } func Test_Client_CookieJar(t *testing.T) { handler := func(c fiber.Ctx) error { return c.SendString( c.Cookies("k1") + c.Cookies("k2") + c.Cookies("k3")) } jar := AcquireCookieJar() defer ReleaseCookieJar(jar) jar.SetKeyValue("example.com", "k1", "v1") jar.SetKeyValue("example.com", "k2", "v2") jar.SetKeyValue("example", "k3", "v3") wrapAgent := func(c *Client) { c.SetCookieJar(jar) } testClient(t, handler, wrapAgent, "v1v2") } func Test_Client_CookieJar_Response(t *testing.T) { t.Parallel() t.Run("without expiration", func(t *testing.T) { t.Parallel() handler := func(c fiber.Ctx) error { c.Cookie(&fiber.Cookie{ Name: "k4", Value: "v4", }) return c.SendString( c.Cookies("k1") + c.Cookies("k2") + c.Cookies("k3")) } jar := AcquireCookieJar() defer ReleaseCookieJar(jar) jar.SetKeyValue("example.com", "k1", "v1") jar.SetKeyValue("example.com", "k2", "v2") jar.SetKeyValue("example", "k3", "v3") wrapAgent := func(c *Client) { c.SetCookieJar(jar) } testClient(t, handler, wrapAgent, "v1v2") require.Len(t, jar.getCookiesByHost("example.com"), 3) }) t.Run("with expiration", func(t *testing.T) { t.Parallel() handler := func(c fiber.Ctx) error { c.Cookie(&fiber.Cookie{ Name: "k4", Value: "v4", Expires: time.Now().Add(1 * time.Nanosecond), }) return c.SendString( c.Cookies("k1") + c.Cookies("k2") + c.Cookies("k3")) } jar := AcquireCookieJar() defer ReleaseCookieJar(jar) jar.SetKeyValue("example.com", "k1", "v1") jar.SetKeyValue("example.com", "k2", "v2") jar.SetKeyValue("example", "k3", "v3") wrapAgent := func(c *Client) { c.SetCookieJar(jar) } testClient(t, handler, wrapAgent, "v1v2") require.Len(t, jar.getCookiesByHost("example.com"), 2) }) t.Run("override cookie value", func(t *testing.T) { t.Parallel() handler := func(c fiber.Ctx) error { c.Cookie(&fiber.Cookie{ Name: "k1", Value: "v2", }) return c.SendString( c.Cookies("k1") + c.Cookies("k2")) } jar := AcquireCookieJar() defer ReleaseCookieJar(jar) jar.SetKeyValue("example.com", "k1", "v1") jar.SetKeyValue("example.com", "k2", "v2") wrapAgent := func(c *Client) { c.SetCookieJar(jar) } testClient(t, handler, wrapAgent, "v1v2") for _, cookie := range jar.getCookiesByHost("example.com") { if string(cookie.Key()) == "k1" { require.Equal(t, "v2", string(cookie.Value())) } } }) t.Run("different domain", func(t *testing.T) { t.Parallel() handler := func(c fiber.Ctx) error { return c.SendString(c.Cookies("k1")) } jar := AcquireCookieJar() defer ReleaseCookieJar(jar) jar.SetKeyValue("example.com", "k1", "v1") wrapAgent := func(c *Client) { c.SetCookieJar(jar) } testClient(t, handler, wrapAgent, "v1") require.Len(t, jar.getCookiesByHost("example.com"), 1) require.Empty(t, jar.getCookiesByHost("example")) }) } func Test_Client_Referer(t *testing.T) { handler := func(c fiber.Ctx) error { return c.Send(c.Request().Header.Referer()) } wrapAgent := func(c *Client) { c.SetReferer("http://referer.com") } testClient(t, handler, wrapAgent, "http://referer.com") } func Test_Client_QueryParam(t *testing.T) { t.Parallel() t.Run("add param", func(t *testing.T) { t.Parallel() req := New() req.AddParam("foo", "bar").AddParam("foo", "fiber") res := req.Param("foo") require.Len(t, res, 2) require.Equal(t, "bar", res[0]) require.Equal(t, "fiber", res[1]) }) t.Run("set param", func(t *testing.T) { t.Parallel() req := New() req.AddParam("foo", "bar").SetParam("foo", "fiber") res := req.Param("foo") require.Len(t, res, 1) require.Equal(t, "fiber", res[0]) }) t.Run("add params", func(t *testing.T) { t.Parallel() req := New() req.SetParam("foo", "bar"). AddParams(map[string][]string{ "foo": {"fiber", "buaa"}, "bar": {"foo"}, }) res := req.Param("foo") require.Len(t, res, 3) require.Equal(t, "bar", res[0]) require.Equal(t, "fiber", res[1]) require.Equal(t, "buaa", res[2]) res = req.Param("bar") require.Len(t, res, 1) require.Equal(t, "foo", res[0]) }) t.Run("set headers", func(t *testing.T) { t.Parallel() req := New() req.SetParam("foo", "bar"). SetParams(map[string]string{ "foo": "fiber", "bar": "foo", }) res := req.Param("foo") require.Len(t, res, 1) require.Equal(t, "fiber", res[0]) res = req.Param("bar") require.Len(t, res, 1) require.Equal(t, "foo", res[0]) }) t.Run("set params with struct", func(t *testing.T) { t.Parallel() type args struct { TString string TSlice []string TIntSlice []int `param:"int_slice"` TInt int TFloat float64 TBool bool } p := New() p.SetParamsWithStruct(&args{ TInt: 5, TString: "string", TFloat: 3.1, TBool: true, TSlice: []string{"foo", "bar"}, TIntSlice: []int{1, 2}, }) require.Empty(t, p.Param("unexport")) require.Len(t, p.Param("TInt"), 1) require.Equal(t, "5", p.Param("TInt")[0]) require.Len(t, p.Param("TString"), 1) require.Equal(t, "string", p.Param("TString")[0]) require.Len(t, p.Param("TFloat"), 1) require.Equal(t, "3.1", p.Param("TFloat")[0]) require.Len(t, p.Param("TBool"), 1) tslice := p.Param("TSlice") require.Len(t, tslice, 2) require.Equal(t, "foo", tslice[0]) require.Equal(t, "bar", tslice[1]) tint := p.Param("TSlice") require.Len(t, tint, 2) require.Equal(t, "foo", tint[0]) require.Equal(t, "bar", tint[1]) }) t.Run("del params", func(t *testing.T) { t.Parallel() req := New() req.SetParam("foo", "bar"). SetParams(map[string]string{ "foo": "fiber", "bar": "foo", }).DelParams("foo", "bar") res := req.Param("foo") require.Empty(t, res) res = req.Param("bar") require.Empty(t, res) }) } func Test_Client_QueryParam_With_Server(t *testing.T) { handler := func(c fiber.Ctx) error { _, _ = c.WriteString(c.Query("k1")) //nolint:errcheck // It is fine to ignore the error here _, _ = c.WriteString(c.Query("k2")) //nolint:errcheck // It is fine to ignore the error here return nil } wrapAgent := func(c *Client) { c.SetParam("k1", "v1"). AddParam("k2", "v2") } testClient(t, handler, wrapAgent, "v1v2") } func Test_Client_PathParam(t *testing.T) { t.Parallel() t.Run("set path param", func(t *testing.T) { t.Parallel() req := New(). SetPathParam("foo", "bar") require.Equal(t, "bar", req.PathParam("foo")) req.SetPathParam("foo", "bar1") require.Equal(t, "bar1", req.PathParam("foo")) }) t.Run("set path params", func(t *testing.T) { t.Parallel() req := New(). SetPathParams(map[string]string{ "foo": "bar", "bar": "foo", }) require.Equal(t, "bar", req.PathParam("foo")) require.Equal(t, "foo", req.PathParam("bar")) req.SetPathParams(map[string]string{ "foo": "bar1", }) require.Equal(t, "bar1", req.PathParam("foo")) require.Equal(t, "foo", req.PathParam("bar")) }) t.Run("set path params with struct", func(t *testing.T) { t.Parallel() type args struct { CookieString string `path:"string"` CookieInt int `path:"int"` } req := New().SetPathParamsWithStruct(&args{ CookieInt: 5, CookieString: "foo", }) require.Equal(t, "5", req.PathParam("int")) require.Equal(t, "foo", req.PathParam("string")) }) t.Run("del path params", func(t *testing.T) { t.Parallel() req := New(). SetPathParams(map[string]string{ "foo": "bar", "bar": "foo", }) require.Equal(t, "bar", req.PathParam("foo")) require.Equal(t, "foo", req.PathParam("bar")) req.DelPathParams("foo") require.Equal(t, "", req.PathParam("foo")) require.Equal(t, "foo", req.PathParam("bar")) }) } func Test_Client_PathParam_With_Server(t *testing.T) { app, dial, start := createHelperServer(t) app.Get("/:test", func(c fiber.Ctx) error { return c.SendString(c.Params("test")) }) go start() resp, err := New().SetDial(dial). SetPathParam("path", "test"). Get("http://example.com/:path") require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode()) require.Equal(t, "test", resp.String()) } func Test_Client_TLS(t *testing.T) { t.Parallel() serverTLSConf, clientTLSConf, err := tlstest.GetTLSConfigs() require.NoError(t, err) ln, err := net.Listen(fiber.NetworkTCP4, "127.0.0.1:0") require.NoError(t, err) ln = tls.NewListener(ln, serverTLSConf) app := fiber.New() app.Get("/", func(c fiber.Ctx) error { return c.SendString("tls") }) go func() { assert.NoError(t, app.Listener(ln, fiber.ListenConfig{ DisableStartupMessage: true, })) }() time.Sleep(1 * time.Second) client := New() resp, err := client.SetTLSConfig(clientTLSConf).Get("https://" + ln.Addr().String()) require.NoError(t, err) require.Equal(t, clientTLSConf, client.TLSConfig()) require.Equal(t, fiber.StatusOK, resp.StatusCode()) require.Equal(t, "tls", resp.String()) } func Test_Client_TLS_Error(t *testing.T) { t.Parallel() serverTLSConf, clientTLSConf, err := tlstest.GetTLSConfigs() clientTLSConf.MaxVersion = tls.VersionTLS12 serverTLSConf.MinVersion = tls.VersionTLS13 require.NoError(t, err) ln, err := net.Listen(fiber.NetworkTCP4, "127.0.0.1:0") require.NoError(t, err) ln = tls.NewListener(ln, serverTLSConf) app := fiber.New() app.Get("/", func(c fiber.Ctx) error { return c.SendString("tls") }) go func() { assert.NoError(t, app.Listener(ln, fiber.ListenConfig{ DisableStartupMessage: true, })) }() time.Sleep(1 * time.Second) client := New() resp, err := client.SetTLSConfig(clientTLSConf).Get("https://" + ln.Addr().String()) require.Error(t, err) require.Equal(t, clientTLSConf, client.TLSConfig()) require.Nil(t, resp) } func Test_Client_TLS_Empty_TLSConfig(t *testing.T) { t.Parallel() serverTLSConf, clientTLSConf, err := tlstest.GetTLSConfigs() require.NoError(t, err) ln, err := net.Listen(fiber.NetworkTCP4, "127.0.0.1:0") require.NoError(t, err) ln = tls.NewListener(ln, serverTLSConf) app := fiber.New() app.Get("/", func(c fiber.Ctx) error { return c.SendString("tls") }) go func() { assert.NoError(t, app.Listener(ln, fiber.ListenConfig{ DisableStartupMessage: true, })) }() time.Sleep(1 * time.Second) client := New() resp, err := client.Get("https://" + ln.Addr().String()) require.Error(t, err) require.NotEqual(t, clientTLSConf, client.TLSConfig()) require.Nil(t, resp) } func Test_Client_SetCertificates(t *testing.T) { t.Parallel() serverTLSConf, _, err := tlstest.GetTLSConfigs() require.NoError(t, err) client := New().SetCertificates(serverTLSConf.Certificates...) require.Len(t, client.TLSConfig().Certificates, 1) } func Test_Client_SetRootCertificate(t *testing.T) { t.Parallel() client := New().SetRootCertificate("../.github/testdata/ssl.pem") require.NotNil(t, client.TLSConfig().RootCAs) } func Test_Client_SetRootCertificateFromString(t *testing.T) { t.Parallel() file, err := os.Open("../.github/testdata/ssl.pem") defer func() { require.NoError(t, file.Close()) }() require.NoError(t, err) pem, err := io.ReadAll(file) require.NoError(t, err) client := New().SetRootCertificateFromString(string(pem)) require.NotNil(t, client.TLSConfig().RootCAs) } func Test_Client_R(t *testing.T) { t.Parallel() client := New() req := client.R() require.Equal(t, "Request", reflect.TypeOf(req).Elem().Name()) require.Equal(t, client, req.Client()) } func Test_Replace(t *testing.T) { app, dial, start := createHelperServer(t) app.Get("/", func(c fiber.Ctx) error { return c.SendString(string(c.Request().Header.Peek("k1"))) }) go start() C().SetDial(dial) resp, err := Get("http://example.com") require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode()) require.Equal(t, "", resp.String()) r := New().SetDial(dial).SetHeader("k1", "v1") clean := Replace(r) resp, err = Get("http://example.com") require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode()) require.Equal(t, "v1", resp.String()) clean() C().SetDial(dial) resp, err = Get("http://example.com") require.NoError(t, err) require.Equal(t, fiber.StatusOK, resp.StatusCode()) require.Equal(t, "", resp.String()) C().SetDial(nil) } func Test_Set_Config_To_Request(t *testing.T) { t.Parallel() t.Run("set ctx", func(t *testing.T) { t.Parallel() type ctxKey struct{} var key ctxKey = struct{}{} ctx := context.Background() ctx = context.WithValue(ctx, key, "v1") req := AcquireRequest() setConfigToRequest(req, Config{Ctx: ctx}) require.Equal(t, "v1", req.Context().Value(key)) }) t.Run("set useragent", func(t *testing.T) { t.Parallel() req := AcquireRequest() setConfigToRequest(req, Config{UserAgent: "agent"}) require.Equal(t, "agent", req.UserAgent()) }) t.Run("set referer", func(t *testing.T) { t.Parallel() req := AcquireRequest() setConfigToRequest(req, Config{Referer: "referer"}) require.Equal(t, "referer", req.Referer()) }) t.Run("set header", func(t *testing.T) { req := AcquireRequest() setConfigToRequest(req, Config{Header: map[string]string{ "k1": "v1", }}) require.Equal(t, "v1", req.Header("k1")[0]) }) t.Run("set params", func(t *testing.T) { t.Parallel() req := AcquireRequest() setConfigToRequest(req, Config{Param: map[string]string{ "k1": "v1", }}) require.Equal(t, "v1", req.Param("k1")[0]) }) t.Run("set cookies", func(t *testing.T) { t.Parallel() req := AcquireRequest() setConfigToRequest(req, Config{Cookie: map[string]string{ "k1": "v1", }}) require.Equal(t, "v1", req.Cookie("k1")) }) t.Run("set pathparam", func(t *testing.T) { t.Parallel() req := AcquireRequest() setConfigToRequest(req, Config{PathParam: map[string]string{ "k1": "v1", }}) require.Equal(t, "v1", req.PathParam("k1")) }) t.Run("set timeout", func(t *testing.T) { t.Parallel() req := AcquireRequest() setConfigToRequest(req, Config{Timeout: 1 * time.Second}) require.Equal(t, 1*time.Second, req.Timeout()) }) t.Run("set maxredirects", func(t *testing.T) { t.Parallel() req := AcquireRequest() setConfigToRequest(req, Config{MaxRedirects: 1}) require.Equal(t, 1, req.MaxRedirects()) }) t.Run("set body", func(t *testing.T) { t.Parallel() req := AcquireRequest() setConfigToRequest(req, Config{Body: "test"}) require.Equal(t, "test", req.body) }) t.Run("set file", func(t *testing.T) { t.Parallel() req := AcquireRequest() setConfigToRequest(req, Config{File: []*File{ { name: "test", path: "path", }, }}) require.Equal(t, "path", req.File("test").path) }) } func Test_Client_SetProxyURL(t *testing.T) { t.Parallel() app, dial, start := createHelperServer(t) app.Get("/", func(c fiber.Ctx) error { return c.SendString(c.Get("isProxy")) }) go start() fasthttpClient := &fasthttp.Client{ Dial: dial, NoDefaultUserAgentHeader: true, DisablePathNormalizing: true, } // Create a simple proxy sever proxyServer := fiber.New() proxyServer.Use("*", func(c fiber.Ctx) error { req := fasthttp.AcquireRequest() resp := fasthttp.AcquireResponse() req.SetRequestURI(c.BaseURL()) req.Header.SetMethod(fasthttp.MethodGet) c.Request().Header.VisitAll(func(key, value []byte) { req.Header.AddBytesKV(key, value) }) req.Header.Set("isProxy", "true") if err := fasthttpClient.Do(req, resp); err != nil { return err } c.Status(resp.StatusCode()) c.RequestCtx().SetBody(resp.Body()) return nil }) addrChan := make(chan string) go func() { assert.NoError(t, proxyServer.Listen(":0", fiber.ListenConfig{ DisableStartupMessage: true, ListenerAddrFunc: func(addr net.Addr) { addrChan <- addr.String() }, })) }() t.Cleanup(func() { require.NoError(t, app.Shutdown()) }) time.Sleep(1 * time.Second) t.Run("success", func(t *testing.T) { t.Parallel() client := New() err := client.SetProxyURL(<-addrChan) require.NoError(t, err) resp, err := client.Get("http://localhost:3000") require.NoError(t, err) require.Equal(t, 200, resp.StatusCode()) require.Equal(t, "true", string(resp.Body())) }) t.Run("error", func(t *testing.T) { t.Parallel() client := New() err := client.SetProxyURL(":this is not a proxy") require.NoError(t, err) _, err = client.Get("http://localhost:3000") require.Error(t, err) }) } func Test_Client_SetRetryConfig(t *testing.T) { t.Parallel() retryConfig := &retry.Config{ InitialInterval: 1 * time.Second, MaxRetryCount: 3, } core, client, req := newCore(), New(), AcquireRequest() req.SetURL("http://exampleretry.com") client.SetRetryConfig(retryConfig) _, err := core.execute(context.Background(), client, req) require.Error(t, err) require.Equal(t, retryConfig.InitialInterval, client.RetryConfig().InitialInterval) require.Equal(t, retryConfig.MaxRetryCount, client.RetryConfig().MaxRetryCount) } func Benchmark_Client_Request(b *testing.B) { app, dial, start := createHelperServer(b) app.Get("/", func(c fiber.Ctx) error { return c.SendString("hello world") }) go start() client := New().SetDial(dial) b.ResetTimer() b.ReportAllocs() var err error var resp *Response for i := 0; i < b.N; i++ { resp, err = client.Get("http://example.com") resp.Close() } require.NoError(b, err) } func Benchmark_Client_Request_Parallel(b *testing.B) { app, dial, start := createHelperServer(b) app.Get("/", func(c fiber.Ctx) error { return c.SendString("hello world") }) go start() client := New().SetDial(dial) b.ResetTimer() b.ReportAllocs() b.RunParallel(func(pb *testing.PB) { var err error var resp *Response for pb.Next() { resp, err = client.Get("http://example.com") resp.Close() } require.NoError(b, err) }) }