| package httpclient
|
|
|
| import (
|
| "net/http"
|
| "net/url"
|
| "testing"
|
|
|
| "github.com/stretchr/testify/require"
|
| )
|
|
|
| func TestIsHTTPStatusCodeRetryable(t *testing.T) {
|
| t.Run("429 is retryable", func(t *testing.T) {
|
| require.True(t, IsHTTPStatusCodeRetryable(429))
|
| })
|
|
|
| t.Run("4xx errors (except 429) are not retryable", func(t *testing.T) {
|
| require.False(t, IsHTTPStatusCodeRetryable(400))
|
| require.False(t, IsHTTPStatusCodeRetryable(401))
|
| require.False(t, IsHTTPStatusCodeRetryable(403))
|
| require.False(t, IsHTTPStatusCodeRetryable(404))
|
| require.False(t, IsHTTPStatusCodeRetryable(422))
|
| })
|
|
|
| t.Run("5xx errors are retryable", func(t *testing.T) {
|
| require.True(t, IsHTTPStatusCodeRetryable(500))
|
| require.True(t, IsHTTPStatusCodeRetryable(502))
|
| require.True(t, IsHTTPStatusCodeRetryable(503))
|
| require.True(t, IsHTTPStatusCodeRetryable(504))
|
| })
|
|
|
| t.Run("non-error status codes are not retryable", func(t *testing.T) {
|
| require.False(t, IsHTTPStatusCodeRetryable(200))
|
| require.False(t, IsHTTPStatusCodeRetryable(201))
|
| require.False(t, IsHTTPStatusCodeRetryable(301))
|
| require.False(t, IsHTTPStatusCodeRetryable(302))
|
| })
|
| }
|
|
|
| func TestMergeHTTPHeaders(t *testing.T) {
|
|
|
| RegisterMergeWithAppendHeaders("User-Agent", "Accept")
|
|
|
| tests := []struct {
|
| name string
|
|
|
| dest http.Header
|
| src http.Header
|
| want http.Header
|
| }{
|
| {
|
| name: "given src Authorization header, should skip sensitive header",
|
| dest: http.Header{
|
| "Content-Type": []string{"application/json"},
|
| },
|
| src: http.Header{
|
| "Content-Type": []string{"application/json"},
|
| "Authorization": []string{"Bearer 123456"},
|
| },
|
| want: http.Header{
|
| "Content-Type": []string{"application/json"},
|
| },
|
| },
|
| {
|
| name: "given src User-Agent header, should merge them",
|
| dest: http.Header{
|
| "Content-Type": []string{"application/json"},
|
| },
|
| src: http.Header{
|
| "Content-Type": []string{"application/json"},
|
| "User-Agent": []string{"Mozilla/5.0"},
|
| },
|
| want: http.Header{
|
| "Content-Type": []string{"application/json"},
|
| "User-Agent": []string{"Mozilla/5.0"},
|
| },
|
| },
|
| {
|
| name: "should add non-duplicate values to existing headers",
|
| dest: http.Header{
|
| "Content-Type": []string{"application/json"},
|
| "Accept": []string{"application/json"},
|
| },
|
| src: http.Header{
|
| "Accept": []string{"text/plain", "application/json"},
|
| "User-Agent": []string{"Mozilla/5.0"},
|
| },
|
| want: http.Header{
|
| "Content-Type": []string{"application/json"},
|
| "Accept": []string{"application/json", "text/plain"},
|
| "User-Agent": []string{"Mozilla/5.0"},
|
| },
|
| },
|
| {
|
| name: "should add all non-duplicate values from multiple values",
|
| dest: http.Header{
|
| "Accept": []string{"application/json"},
|
| },
|
| src: http.Header{
|
| "Accept": []string{"text/plain", "application/xml", "text/html"},
|
| },
|
| want: http.Header{
|
| "Accept": []string{"application/json", "text/plain", "application/xml", "text/html"},
|
| },
|
| },
|
| {
|
| name: "should skip all duplicate values",
|
| dest: http.Header{
|
| "Accept": []string{"application/json", "text/plain"},
|
| },
|
| src: http.Header{
|
| "Accept": []string{"application/json", "text/plain"},
|
| },
|
| want: http.Header{
|
| "Accept": []string{"application/json", "text/plain"},
|
| },
|
| },
|
| {
|
| name: "should skip only duplicate values and add new ones",
|
| dest: http.Header{
|
| "Accept": []string{"application/json", "text/plain"},
|
| },
|
| src: http.Header{
|
| "Accept": []string{"text/plain", "application/xml", "application/json"},
|
| },
|
| want: http.Header{
|
| "Accept": []string{"application/json", "text/plain", "application/xml"},
|
| },
|
| },
|
| {
|
| name: "should block transport-managed headers and skip sensitive ones",
|
| dest: http.Header{
|
| "Content-Type": []string{"application/json"},
|
| },
|
| src: http.Header{
|
| "Authorization": []string{"Bearer token"},
|
| "Api-Key": []string{"key123"},
|
| "X-Api-Key": []string{"xkey456"},
|
| "X-Api-Secret": []string{"secret789"},
|
| "X-Api-Token": []string{"token000"},
|
| "Content-Type": []string{"text/plain"},
|
| "Content-Length": []string{"100"},
|
| "Transfer-Encoding": []string{"chunked"},
|
| "User-Agent": []string{"Test/1.0"},
|
| },
|
| want: http.Header{
|
| "Content-Type": []string{"application/json"},
|
| "User-Agent": []string{"Test/1.0"},
|
| },
|
| },
|
| {
|
| name: "empty src headers should not change dest",
|
| dest: http.Header{
|
| "Content-Type": []string{"application/json"},
|
| },
|
| src: http.Header{},
|
| want: http.Header{
|
| "Content-Type": []string{"application/json"},
|
| },
|
| },
|
| {
|
| name: "empty dest headers should merge non-blocked src headers",
|
| dest: http.Header{},
|
| src: http.Header{
|
| "User-Agent": []string{"Test/1.0"},
|
| "Accept": []string{"*/*"},
|
| "Authorization": []string{"Bearer token"},
|
| },
|
| want: http.Header{
|
| "User-Agent": []string{"Test/1.0"},
|
| "Accept": []string{"*/*"},
|
| },
|
| },
|
| {
|
| name: "should merge multiple custom headers",
|
| dest: http.Header{
|
| "Content-Type": []string{"application/json"},
|
| },
|
| src: http.Header{
|
| "X-Request-ID": []string{"req-123"},
|
| "X-Trace-ID": []string{"trace-456"},
|
| "User-Agent": []string{"Custom/1.0"},
|
| "Accept-Encoding": []string{"gzip, deflate"},
|
| },
|
| want: http.Header{
|
| "Content-Type": []string{"application/json"},
|
| "X-Request-ID": []string{"req-123"},
|
| "X-Trace-ID": []string{"trace-456"},
|
| "User-Agent": []string{"Custom/1.0"},
|
| },
|
| },
|
| {
|
| name: "should handle headers with multiple values",
|
| dest: http.Header{
|
| "Content-Type": []string{"application/json"},
|
| },
|
| src: http.Header{
|
| "Accept": []string{"application/json", "text/plain"},
|
| },
|
| want: http.Header{
|
| "Content-Type": []string{"application/json"},
|
| "Accept": []string{"application/json", "text/plain"},
|
| },
|
| },
|
| {
|
| name: "should add non-duplicate values when header exists",
|
| dest: http.Header{
|
| "User-Agent": []string{"AxonHub/1.0"},
|
| },
|
| src: http.Header{
|
| "Content-Type": []string{"application/json"},
|
| "User-Agent": []string{"Mozilla/5.0"},
|
| "Accept": []string{"*/*"},
|
| },
|
| want: http.Header{
|
| "User-Agent": []string{"AxonHub/1.0", "Mozilla/5.0"},
|
| "Accept": []string{"*/*"},
|
| },
|
| },
|
| {
|
| name: "should overwrite non-appendable headers",
|
| dest: http.Header{
|
| "X-Custom-Header": []string{"old-value"},
|
| },
|
| src: http.Header{
|
| "X-Custom-Header": []string{"new-value"},
|
| },
|
| want: http.Header{
|
| "X-Custom-Header": []string{"new-value"},
|
| },
|
| },
|
| }
|
| for _, tt := range tests {
|
| t.Run(tt.name, func(t *testing.T) {
|
| got := MergeHTTPHeaders(tt.dest, tt.src)
|
| require.Equal(t, tt.want, got)
|
| })
|
| }
|
| }
|
|
|
| func TestRegisterAppendHeaders(t *testing.T) {
|
| RegisterMergeWithAppendHeaders("X-New-Append")
|
|
|
| dest := http.Header{"X-New-Append": []string{"old"}}
|
| src := http.Header{"X-New-Append": []string{"new"}}
|
|
|
| got := MergeHTTPHeaders(dest, src)
|
| require.Equal(t, []string{"old", "new"}, got["X-New-Append"])
|
| }
|
|
|
| func TestMergeHTTPQuery(t *testing.T) {
|
| tests := []struct {
|
| name string
|
| dest url.Values
|
| src url.Values
|
| want url.Values
|
| }{
|
| {
|
| name: "should merge new query parameters",
|
| dest: url.Values{"q": []string{"golang"}},
|
| src: url.Values{"page": []string{"1"}},
|
| want: url.Values{"q": []string{"golang"}, "page": []string{"1"}},
|
| },
|
| {
|
| name: "should not overwrite existing query parameters",
|
| dest: url.Values{"q": []string{"golang"}},
|
| src: url.Values{"q": []string{"java"}},
|
| want: url.Values{"q": []string{"golang"}},
|
| },
|
| {
|
| name: "should handle empty src",
|
| dest: url.Values{"q": []string{"golang"}},
|
| src: nil,
|
| want: url.Values{"q": []string{"golang"}},
|
| },
|
| {
|
| name: "should handle empty dest",
|
| dest: nil,
|
| src: url.Values{"page": []string{"1"}},
|
| want: url.Values{"page": []string{"1"}},
|
| },
|
| }
|
|
|
| for _, tt := range tests {
|
| t.Run(tt.name, func(t *testing.T) {
|
| got := MergeHTTPQuery(tt.dest, tt.src)
|
| require.Equal(t, tt.want, got)
|
| })
|
| }
|
| }
|
|
|
| func TestMergeInboundRequest(t *testing.T) {
|
| t.Run("should merge both headers and query", func(t *testing.T) {
|
| dest := &Request{
|
| Headers: http.Header{"Content-Type": []string{"application/json"}},
|
| Query: url.Values{"q": []string{"old"}},
|
| }
|
| src := &Request{
|
| Headers: http.Header{"User-Agent": []string{"Test"}},
|
| Query: url.Values{"page": []string{"1"}},
|
| }
|
|
|
| got := MergeInboundRequest(dest, src)
|
| require.Equal(t, "application/json", got.Headers.Get("Content-Type"))
|
| require.Equal(t, "Test", got.Headers.Get("User-Agent"))
|
| require.Equal(t, "old", got.Query.Get("q"))
|
| require.Equal(t, "1", got.Query.Get("page"))
|
| })
|
|
|
| t.Run("should return dest if src is nil", func(t *testing.T) {
|
| dest := &Request{Headers: http.Header{"X-Test": []string{"val"}}}
|
| got := MergeInboundRequest(dest, nil)
|
| require.Equal(t, dest, got)
|
| })
|
| }
|
|
|