package client import ( "bytes" "encoding/xml" "fmt" "io" "net" "net/url" "strings" "testing" "github.com/fxamacker/cbor/v2" "github.com/gofiber/fiber/v3" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func Test_Rand_String(t *testing.T) { t.Parallel() tests := []struct { name string args int }{ { name: "test generate", args: 16, }, { name: "test generate smaller string", args: 8, }, { name: "test generate larger string", args: 32, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got := unsafeRandString(tt.args) require.Len(t, got, tt.args) }) } } func Test_Parser_Request_URL(t *testing.T) { t.Parallel() t.Run("client baseurl should be set", func(t *testing.T) { t.Parallel() client := New().SetBaseURL("http://example.com/api") req := AcquireRequest().SetURL("") err := parserRequestURL(client, req) require.NoError(t, err) require.Equal(t, "http://example.com/api", req.RawRequest.URI().String()) }) t.Run("request url should be set", func(t *testing.T) { t.Parallel() client := New() req := AcquireRequest().SetURL("http://example.com/api") err := parserRequestURL(client, req) require.NoError(t, err) require.Equal(t, "http://example.com/api", req.RawRequest.URI().String()) }) t.Run("the request url will override baseurl with protocol", func(t *testing.T) { t.Parallel() client := New().SetBaseURL("http://example.com/api") req := AcquireRequest().SetURL("http://example.com/api/v1") err := parserRequestURL(client, req) require.NoError(t, err) require.Equal(t, "http://example.com/api/v1", req.RawRequest.URI().String()) }) t.Run("the request url should be append after baseurl without protocol", func(t *testing.T) { t.Parallel() client := New().SetBaseURL("http://example.com/api") req := AcquireRequest().SetURL("/v1") err := parserRequestURL(client, req) require.NoError(t, err) require.Equal(t, "http://example.com/api/v1", req.RawRequest.URI().String()) }) t.Run("the url is error", func(t *testing.T) { t.Parallel() client := New().SetBaseURL("example.com/api") req := AcquireRequest().SetURL("/v1") err := parserRequestURL(client, req) require.Equal(t, ErrURLFormat, err) }) t.Run("the path param from client", func(t *testing.T) { t.Parallel() client := New(). SetBaseURL("http://example.com/api/:id"). SetPathParam("id", "5") req := AcquireRequest() err := parserRequestURL(client, req) require.NoError(t, err) require.Equal(t, "http://example.com/api/5", req.RawRequest.URI().String()) }) t.Run("the path param from request", func(t *testing.T) { t.Parallel() client := New(). SetBaseURL("http://example.com/api/:id/:name"). SetPathParam("id", "5") req := AcquireRequest(). SetURL("/{key}"). SetPathParams(map[string]string{ "name": "fiber", "key": "val", }). DelPathParams("key") err := parserRequestURL(client, req) require.NoError(t, err) require.Equal(t, "http://example.com/api/5/fiber/%7Bkey%7D", req.RawRequest.URI().String()) }) t.Run("the path param from request and client", func(t *testing.T) { t.Parallel() client := New(). SetBaseURL("http://example.com/api/:id/:name"). SetPathParam("id", "5") req := AcquireRequest(). SetURL("/:key"). SetPathParams(map[string]string{ "name": "fiber", "key": "val", "id": "12", }) err := parserRequestURL(client, req) require.NoError(t, err) require.Equal(t, "http://example.com/api/12/fiber/val", req.RawRequest.URI().String()) }) t.Run("query params from client should be set", func(t *testing.T) { t.Parallel() client := New(). SetParam("foo", "bar") req := AcquireRequest().SetURL("http://example.com/api/v1") err := parserRequestURL(client, req) require.NoError(t, err) require.Equal(t, []byte("foo=bar"), req.RawRequest.URI().QueryString()) }) t.Run("query params from request should be set", func(t *testing.T) { t.Parallel() client := New() req := AcquireRequest(). SetURL("http://example.com/api/v1"). SetParam("bar", "foo") err := parserRequestURL(client, req) require.NoError(t, err) require.Equal(t, []byte("bar=foo"), req.RawRequest.URI().QueryString()) }) t.Run("query params should be merged", func(t *testing.T) { t.Parallel() client := New(). SetParam("bar", "foo1") req := AcquireRequest(). SetURL("http://example.com/api/v1?bar=foo2"). SetParam("bar", "foo") err := parserRequestURL(client, req) require.NoError(t, err) values, err := url.ParseQuery(string(req.RawRequest.URI().QueryString())) require.NoError(t, err) flag1, flag2, flag3 := false, false, false for _, v := range values["bar"] { switch v { case "foo1": flag1 = true case "foo2": flag2 = true case "foo": //nolint:goconst // test flag3 = true } } require.True(t, flag1) require.True(t, flag2) require.True(t, flag3) }) } func Test_Parser_Request_Header(t *testing.T) { t.Parallel() t.Run("client header should be set", func(t *testing.T) { t.Parallel() client := New(). SetHeaders(map[string]string{ fiber.HeaderContentType: "application/json", }) req := AcquireRequest() err := parserRequestHeader(client, req) require.NoError(t, err) require.Equal(t, []byte("application/json"), req.RawRequest.Header.ContentType()) }) t.Run("request header should be set", func(t *testing.T) { t.Parallel() client := New() req := AcquireRequest(). SetHeaders(map[string]string{ fiber.HeaderContentType: "application/json, utf-8", }) err := parserRequestHeader(client, req) require.NoError(t, err) require.Equal(t, []byte("application/json, utf-8"), req.RawRequest.Header.ContentType()) }) t.Run("request header should override client header", func(t *testing.T) { t.Parallel() client := New(). SetHeader(fiber.HeaderContentType, "application/xml") req := AcquireRequest(). SetHeader(fiber.HeaderContentType, "application/json, utf-8") err := parserRequestHeader(client, req) require.NoError(t, err) require.Equal(t, []byte("application/json, utf-8"), req.RawRequest.Header.ContentType()) }) t.Run("auto set json header", func(t *testing.T) { t.Parallel() type jsonData struct { Name string `json:"name"` } client := New() req := AcquireRequest(). SetJSON(jsonData{ Name: "foo", }) err := parserRequestHeader(client, req) require.NoError(t, err) require.Equal(t, []byte(applicationJSON), req.RawRequest.Header.ContentType()) //nolint:testifylint // test }) t.Run("auto set xml header", func(t *testing.T) { t.Parallel() type xmlData struct { XMLName xml.Name `xml:"body"` Name string `xml:"name"` } client := New() req := AcquireRequest(). SetXML(xmlData{ Name: "foo", }) err := parserRequestHeader(client, req) require.NoError(t, err) require.Equal(t, []byte(applicationXML), req.RawRequest.Header.ContentType()) }) t.Run("auto set form data header", func(t *testing.T) { t.Parallel() client := New() req := AcquireRequest(). SetFormDataWithMap(map[string]string{ "foo": "bar", "ball": "cricle and square", }) err := parserRequestHeader(client, req) require.NoError(t, err) require.Equal(t, applicationForm, string(req.RawRequest.Header.ContentType())) }) t.Run("auto set file header", func(t *testing.T) { t.Parallel() client := New() req := AcquireRequest(). AddFileWithReader("hello", io.NopCloser(strings.NewReader("world"))). SetFormData("foo", "bar") err := parserRequestHeader(client, req) require.NoError(t, err) require.Contains(t, string(req.RawRequest.Header.MultipartFormBoundary()), "--FiberFormBoundary") require.Contains(t, string(req.RawRequest.Header.ContentType()), multipartFormData) }) t.Run("ua should have default value", func(t *testing.T) { t.Parallel() client := New() req := AcquireRequest() err := parserRequestHeader(client, req) require.NoError(t, err) require.Equal(t, []byte("fiber"), req.RawRequest.Header.UserAgent()) }) t.Run("ua in client should be set", func(t *testing.T) { t.Parallel() client := New().SetUserAgent("foo") req := AcquireRequest() err := parserRequestHeader(client, req) require.NoError(t, err) require.Equal(t, []byte("foo"), req.RawRequest.Header.UserAgent()) }) t.Run("ua in request should have higher level", func(t *testing.T) { t.Parallel() client := New().SetUserAgent("foo") req := AcquireRequest().SetUserAgent("bar") err := parserRequestHeader(client, req) require.NoError(t, err) require.Equal(t, []byte("bar"), req.RawRequest.Header.UserAgent()) }) t.Run("referer in client should be set", func(t *testing.T) { t.Parallel() client := New().SetReferer("https://example.com") req := AcquireRequest() err := parserRequestHeader(client, req) require.NoError(t, err) require.Equal(t, []byte("https://example.com"), req.RawRequest.Header.Referer()) }) t.Run("referer in request should have higher level", func(t *testing.T) { t.Parallel() client := New().SetReferer("http://example.com") req := AcquireRequest().SetReferer("https://example.com") err := parserRequestHeader(client, req) require.NoError(t, err) require.Equal(t, []byte("https://example.com"), req.RawRequest.Header.Referer()) }) t.Run("client cookie should be set", func(t *testing.T) { t.Parallel() client := New(). SetCookie("foo", "bar"). SetCookies(map[string]string{ "bar": "foo", "bar1": "foo1", }). DelCookies("bar1") req := AcquireRequest() err := parserRequestHeader(client, req) require.NoError(t, err) require.Equal(t, "bar", string(req.RawRequest.Header.Cookie("foo"))) require.Equal(t, "foo", string(req.RawRequest.Header.Cookie("bar"))) require.Equal(t, "", string(req.RawRequest.Header.Cookie("bar1"))) }) t.Run("request cookie should be set", func(t *testing.T) { t.Parallel() type cookies struct { Foo string `cookie:"foo"` Bar int `cookie:"bar"` } client := New() req := AcquireRequest(). SetCookiesWithStruct(&cookies{ Foo: "bar", Bar: 67, }) err := parserRequestHeader(client, req) require.NoError(t, err) require.Equal(t, "bar", string(req.RawRequest.Header.Cookie("foo"))) require.Equal(t, "67", string(req.RawRequest.Header.Cookie("bar"))) require.Equal(t, "", string(req.RawRequest.Header.Cookie("bar1"))) }) t.Run("request cookie will override client cookie", func(t *testing.T) { t.Parallel() type cookies struct { Foo string `cookie:"foo"` Bar int `cookie:"bar"` } client := New(). SetCookie("foo", "bar"). SetCookies(map[string]string{ "bar": "foo", "bar1": "foo1", }) req := AcquireRequest(). SetCookiesWithStruct(&cookies{ Foo: "bar", Bar: 67, }) err := parserRequestHeader(client, req) require.NoError(t, err) require.Equal(t, "bar", string(req.RawRequest.Header.Cookie("foo"))) require.Equal(t, "67", string(req.RawRequest.Header.Cookie("bar"))) require.Equal(t, "foo1", string(req.RawRequest.Header.Cookie("bar1"))) }) } func Test_Parser_Request_Body(t *testing.T) { t.Parallel() t.Run("json body", func(t *testing.T) { t.Parallel() type jsonData struct { Name string `json:"name"` } client := New() req := AcquireRequest(). SetJSON(jsonData{ Name: "foo", }) err := parserRequestBody(client, req) require.NoError(t, err) require.Equal(t, []byte("{\"name\":\"foo\"}"), req.RawRequest.Body()) //nolint:testifylint // test }) t.Run("xml body", func(t *testing.T) { t.Parallel() type xmlData struct { XMLName xml.Name `xml:"body"` Name string `xml:"name"` } client := New() req := AcquireRequest(). SetXML(xmlData{ Name: "foo", }) err := parserRequestBody(client, req) require.NoError(t, err) require.Equal(t, []byte("foo"), req.RawRequest.Body()) }) t.Run("CBOR body", func(t *testing.T) { t.Parallel() type cborData struct { Name string `cbor:"name"` Age int `cbor:"age"` } data := cborData{ Name: "foo", Age: 12, } client := New() req := AcquireRequest(). SetCBOR(data) err := parserRequestBody(client, req) require.NoError(t, err) encoded, err := cbor.Marshal(data) require.NoError(t, err) require.Equal(t, encoded, req.RawRequest.Body()) }) t.Run("form data body", func(t *testing.T) { t.Parallel() client := New() req := AcquireRequest(). SetFormDataWithMap(map[string]string{ "ball": "cricle and square", }) err := parserRequestBody(client, req) require.NoError(t, err) require.Equal(t, "ball=cricle+and+square", string(req.RawRequest.Body())) }) t.Run("form data body error", func(t *testing.T) { t.Parallel() client := New() req := AcquireRequest(). SetFormDataWithMap(map[string]string{ "": "", }) err := parserRequestBody(client, req) require.NoError(t, err) }) t.Run("file body", func(t *testing.T) { t.Parallel() client := New() req := AcquireRequest(). AddFileWithReader("hello", io.NopCloser(strings.NewReader("world"))) err := parserRequestBody(client, req) require.NoError(t, err) require.Contains(t, string(req.RawRequest.Body()), "----FiberFormBoundary") require.Contains(t, string(req.RawRequest.Body()), "world") }) t.Run("file and form data", func(t *testing.T) { t.Parallel() client := New() req := AcquireRequest(). AddFileWithReader("hello", io.NopCloser(strings.NewReader("world"))). SetFormData("foo", "bar") err := parserRequestBody(client, req) require.NoError(t, err) require.Contains(t, string(req.RawRequest.Body()), "----FiberFormBoundary") require.Contains(t, string(req.RawRequest.Body()), "world") require.Contains(t, string(req.RawRequest.Body()), "bar") }) t.Run("raw body", func(t *testing.T) { t.Parallel() client := New() req := AcquireRequest(). SetRawBody([]byte("hello world")) err := parserRequestBody(client, req) require.NoError(t, err) require.Equal(t, []byte("hello world"), req.RawRequest.Body()) }) t.Run("raw body error", func(t *testing.T) { t.Parallel() client := New() req := AcquireRequest(). SetRawBody([]byte("hello world")) req.body = nil err := parserRequestBody(client, req) require.ErrorIs(t, err, ErrBodyType) }) } type dummyLogger struct { buf *bytes.Buffer } func (*dummyLogger) Trace(_ ...any) {} func (*dummyLogger) Debug(_ ...any) {} func (*dummyLogger) Info(_ ...any) {} func (*dummyLogger) Warn(_ ...any) {} func (*dummyLogger) Error(_ ...any) {} func (*dummyLogger) Fatal(_ ...any) {} func (*dummyLogger) Panic(_ ...any) {} func (*dummyLogger) Tracef(_ string, _ ...any) {} func (l *dummyLogger) Debugf(format string, v ...any) { _, _ = l.buf.WriteString(fmt.Sprintf(format, v...)) //nolint:errcheck // not needed } func (*dummyLogger) Infof(_ string, _ ...any) {} func (*dummyLogger) Warnf(_ string, _ ...any) {} func (*dummyLogger) Errorf(_ string, _ ...any) {} func (*dummyLogger) Fatalf(_ string, _ ...any) {} func (*dummyLogger) Panicf(_ string, _ ...any) {} func (*dummyLogger) Tracew(_ string, _ ...any) {} func (*dummyLogger) Debugw(_ string, _ ...any) {} func (*dummyLogger) Infow(_ string, _ ...any) {} func (*dummyLogger) Warnw(_ string, _ ...any) {} func (*dummyLogger) Errorw(_ string, _ ...any) {} func (*dummyLogger) Fatalw(_ string, _ ...any) {} func (*dummyLogger) Panicw(_ string, _ ...any) {} func Test_Client_Logger_Debug(t *testing.T) { t.Parallel() app := fiber.New() app.Get("/", func(c fiber.Ctx) error { return c.SendString("response") }) addrChan := make(chan string) go func() { assert.NoError(t, app.Listen(":0", fiber.ListenConfig{ DisableStartupMessage: true, ListenerAddrFunc: func(addr net.Addr) { addrChan <- addr.String() }, })) }() defer func(app *fiber.App) { require.NoError(t, app.Shutdown()) }(app) var buf bytes.Buffer logger := &dummyLogger{buf: &buf} client := New() client.Debug().SetLogger(logger) addr := <-addrChan resp, err := client.Get("http://" + addr) require.NoError(t, err) defer resp.Close() require.NoError(t, err) require.Contains(t, buf.String(), "Host: "+addr) require.Contains(t, buf.String(), "Content-Length: 8") } func Test_Client_Logger_DisableDebug(t *testing.T) { t.Parallel() app := fiber.New() app.Get("/", func(c fiber.Ctx) error { return c.SendString("response") }) addrChan := make(chan string) go func() { assert.NoError(t, app.Listen(":0", fiber.ListenConfig{ DisableStartupMessage: true, ListenerAddrFunc: func(addr net.Addr) { addrChan <- addr.String() }, })) }() defer func(app *fiber.App) { require.NoError(t, app.Shutdown()) }(app) var buf bytes.Buffer logger := &dummyLogger{buf: &buf} client := New() client.DisableDebug().SetLogger(logger) addr := <-addrChan resp, err := client.Get("http://" + addr) require.NoError(t, err) defer resp.Close() require.NoError(t, err) require.Empty(t, buf.String()) }