Files
zeekayandHanzo Dev f60e254acb module: rename to github.com/zap-proto/fiber/v3
Fork of gofiber/fiber v3 based on the v3.2.0 tag. Rewrites all self-imports
github.com/gofiber/fiber/v3 -> github.com/zap-proto/fiber/v3. Upstream
gofiber/schema and gofiber/utils/v2 dependencies are left untouched.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-10 12:49:35 -07:00

825 lines
20 KiB
Go

package client
import (
"bytes"
"encoding/xml"
"fmt"
"io"
"net"
"net/url"
"path/filepath"
"strings"
"testing"
"github.com/fxamacker/cbor/v2"
"github.com/zap-proto/fiber/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/valyala/fasthttp"
)
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, err := unsafeRandString(tt.args)
require.NoError(t, err)
require.Len(t, got, tt.args)
})
}
t.Run("valid characters", func(t *testing.T) {
t.Parallel()
got, err := unsafeRandString(32)
require.NoError(t, err)
for i := 0; i < len(got); i++ {
require.Contains(t, letterBytes, string(got[i]))
}
})
}
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":
flag3 = true
default:
t.Fatalf("unexpected query param value: %s", v)
}
}
require.True(t, flag1)
require.True(t, flag2)
require.True(t, flag3)
})
t.Run("request disable path normalizing should be respected", func(t *testing.T) {
t.Parallel()
client := New()
req := AcquireRequest().
SetURL("https://example.my.host/other.host%2Fpath%2Fto%2Fdata%23123").
SetDisablePathNormalizing(true)
t.Cleanup(func() {
ReleaseRequest(req)
})
err := parserRequestURL(client, req)
require.NoError(t, err)
require.Equal(t, "https://example.my.host/other.host%2Fpath%2Fto%2Fdata%23123", req.RawRequest.URI().String())
})
t.Run("client disable path normalizing should be respected", func(t *testing.T) {
t.Parallel()
client := New().SetDisablePathNormalizing(true)
req := AcquireRequest().
SetURL("https://example.my.host/other.host%2Fpath%2Fto%2Fdata%23123")
t.Cleanup(func() {
ReleaseRequest(req)
})
err := parserRequestURL(client, req)
require.NoError(t, err)
require.Equal(t, "https://example.my.host/other.host%2Fpath%2Fto%2Fdata%23123", req.RawRequest.URI().String())
})
}
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": "circle 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.Empty(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.Empty(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("<body><name>foo</name></body>"), 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": "circle and square",
})
err := parserRequestBody(client, req)
require.NoError(t, err)
require.Equal(t, "ball=circle+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 body open error", func(t *testing.T) {
t.Parallel()
client := New()
missingPath := filepath.Join(t.TempDir(), "missing.txt")
req := AcquireRequest().AddFile(missingPath)
err := parserRequestBody(client, req)
require.ErrorContains(t, err, "open file error")
})
t.Run("file body missing path and name", func(t *testing.T) {
t.Parallel()
client := New()
file := AcquireFile(SetFileReader(io.NopCloser(strings.NewReader("world"))))
req := AcquireRequest().AddFiles(file)
err := parserRequestBody(client, req)
require.ErrorIs(t, err, ErrFileNoName)
})
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)
})
t.Run("unsupported body type", func(t *testing.T) {
t.Parallel()
client := New()
req := AcquireRequest()
req.bodyType = 999 // some invalid type
err := parserRequestBody(client, req)
require.ErrorIs(t, err, ErrBodyTypeNotSupported)
})
}
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) {
fmt.Fprintf(l.buf, format, v...)
}
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())
}
func Benchmark_Parser_Request_Body_File(b *testing.B) {
b.Helper()
const (
fileCount = 3
fileSize = 32 << 10 // 32KB payload per file
)
formValues := map[string]string{
"username": "fiber",
"api_key": "d5942ef5",
}
fileContents := make([][]byte, fileCount)
for i := range fileContents {
fileContents[i] = bytes.Repeat([]byte{byte('a' + i)}, fileSize)
}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
var totalBytes int64
for _, c := range fileContents {
totalBytes += int64(len(c))
}
b.SetBytes(totalBytes)
req := newBenchmarkRequest(formValues, fileContents)
if err := parserRequestBodyFile(req); err != nil {
b.Fatalf("parserRequestBodyFile: %v", err)
}
releaseBenchmarkRequest(req)
}
}
func newBenchmarkRequest(formValues map[string]string, fileContents [][]byte) *Request {
req := &Request{
boundary: "FiberBenchmarkBoundary",
formData: FormData{Args: fasthttp.AcquireArgs()},
RawRequest: fasthttp.AcquireRequest(),
files: make([]*File, len(fileContents)),
}
req.RawRequest.Header.SetContentType("multipart/form-data; boundary=" + req.boundary)
for key, value := range formValues {
req.formData.Set(key, value)
}
for i, content := range fileContents {
req.files[i] = AcquireFile(
SetFileName(fmt.Sprintf("file-%d.bin", i)),
SetFileFieldName(fmt.Sprintf("file%d", i)),
SetFileReader(io.NopCloser(bytes.NewReader(content))),
)
}
return req
}
func releaseBenchmarkRequest(req *Request) {
fasthttp.ReleaseRequest(req.RawRequest)
fasthttp.ReleaseArgs(req.formData.Args)
for _, f := range req.files {
ReleaseFile(f)
}
}