497 lines
15 KiB
Go
497 lines
15 KiB
Go
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"io"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestIsSupportedEncoding(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("gzip is supported", func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.True(t, isSupportedEncoding("gzip"))
|
|
})
|
|
|
|
t.Run("GZIP is supported (case-insensitive)", func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.True(t, isSupportedEncoding("GZIP"))
|
|
})
|
|
|
|
t.Run("Gzip is supported (case-insensitive)", func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.True(t, isSupportedEncoding("Gzip"))
|
|
})
|
|
|
|
t.Run("br is not supported", func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.False(t, isSupportedEncoding("br"))
|
|
})
|
|
|
|
t.Run("deflate is not supported", func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.False(t, isSupportedEncoding("deflate"))
|
|
})
|
|
}
|
|
|
|
func TestParseEncodingWithQuality(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("returns encoding with default quality 1.0", func(t *testing.T) {
|
|
t.Parallel()
|
|
eq := parseEncodingWithQuality("gzip")
|
|
assert.Equal(t, "gzip", eq.encoding)
|
|
assert.InDelta(t, 1.0, eq.quality, 0.001)
|
|
})
|
|
|
|
t.Run("parses quality value", func(t *testing.T) {
|
|
t.Parallel()
|
|
eq := parseEncodingWithQuality("gzip;q=0.5")
|
|
assert.Equal(t, "gzip", eq.encoding)
|
|
assert.InDelta(t, 0.5, eq.quality, 0.001)
|
|
})
|
|
|
|
t.Run("parses quality value with whitespace", func(t *testing.T) {
|
|
t.Parallel()
|
|
eq := parseEncodingWithQuality("gzip ; q=0.8")
|
|
assert.Equal(t, "gzip", eq.encoding)
|
|
assert.InDelta(t, 0.8, eq.quality, 0.001)
|
|
})
|
|
|
|
t.Run("handles q=0", func(t *testing.T) {
|
|
t.Parallel()
|
|
eq := parseEncodingWithQuality("gzip;q=0")
|
|
assert.Equal(t, "gzip", eq.encoding)
|
|
assert.InDelta(t, 0.0, eq.quality, 0.001)
|
|
})
|
|
|
|
t.Run("handles invalid quality value", func(t *testing.T) {
|
|
t.Parallel()
|
|
eq := parseEncodingWithQuality("gzip;q=invalid")
|
|
assert.Equal(t, "gzip", eq.encoding)
|
|
assert.InDelta(t, 1.0, eq.quality, 0.001) // defaults to 1.0 on parse error
|
|
})
|
|
|
|
t.Run("trims whitespace from encoding", func(t *testing.T) {
|
|
t.Parallel()
|
|
eq := parseEncodingWithQuality(" gzip ")
|
|
assert.Equal(t, "gzip", eq.encoding)
|
|
assert.InDelta(t, 1.0, eq.quality, 0.001)
|
|
})
|
|
|
|
t.Run("normalizes encoding to lowercase", func(t *testing.T) {
|
|
t.Parallel()
|
|
eq := parseEncodingWithQuality("GZIP")
|
|
assert.Equal(t, "gzip", eq.encoding)
|
|
})
|
|
|
|
t.Run("normalizes mixed case encoding", func(t *testing.T) {
|
|
t.Parallel()
|
|
eq := parseEncodingWithQuality("Gzip;q=0.5")
|
|
assert.Equal(t, "gzip", eq.encoding)
|
|
assert.InDelta(t, 0.5, eq.quality, 0.001)
|
|
})
|
|
}
|
|
|
|
func TestParseEncoding(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("returns encoding as-is", func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.Equal(t, "gzip", parseEncoding("gzip"))
|
|
})
|
|
|
|
t.Run("trims whitespace", func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.Equal(t, "gzip", parseEncoding(" gzip "))
|
|
})
|
|
|
|
t.Run("strips quality value", func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.Equal(t, "gzip", parseEncoding("gzip;q=1.0"))
|
|
})
|
|
|
|
t.Run("strips quality value with whitespace", func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.Equal(t, "gzip", parseEncoding("gzip ; q=0.5"))
|
|
})
|
|
}
|
|
|
|
func TestParseContentEncoding(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("returns identity when no header", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodPost, "/test", nil)
|
|
|
|
encoding, err := parseContentEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, EncodingIdentity, encoding)
|
|
})
|
|
|
|
t.Run("returns gzip when Content-Encoding is gzip", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodPost, "/test", nil)
|
|
req.Header.Set("Content-Encoding", "gzip")
|
|
|
|
encoding, err := parseContentEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "gzip", encoding)
|
|
})
|
|
|
|
t.Run("returns gzip when Content-Encoding is GZIP (case-insensitive)", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodPost, "/test", nil)
|
|
req.Header.Set("Content-Encoding", "GZIP")
|
|
|
|
encoding, err := parseContentEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "gzip", encoding)
|
|
})
|
|
|
|
t.Run("returns gzip when Content-Encoding is Gzip (case-insensitive)", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodPost, "/test", nil)
|
|
req.Header.Set("Content-Encoding", "Gzip")
|
|
|
|
encoding, err := parseContentEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "gzip", encoding)
|
|
})
|
|
|
|
t.Run("returns identity for identity encoding", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodPost, "/test", nil)
|
|
req.Header.Set("Content-Encoding", "identity")
|
|
|
|
encoding, err := parseContentEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, EncodingIdentity, encoding)
|
|
})
|
|
|
|
t.Run("returns error for unsupported encoding", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodPost, "/test", nil)
|
|
req.Header.Set("Content-Encoding", "br")
|
|
|
|
_, err := parseContentEncoding(req)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "unsupported Content-Encoding")
|
|
assert.Contains(t, err.Error(), "supported: [gzip]")
|
|
})
|
|
|
|
t.Run("handles gzip with quality value", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodPost, "/test", nil)
|
|
req.Header.Set("Content-Encoding", "gzip;q=1.0")
|
|
|
|
encoding, err := parseContentEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "gzip", encoding)
|
|
})
|
|
}
|
|
|
|
func TestParseAcceptEncoding(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("returns identity when no header", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/test", nil)
|
|
|
|
encoding, err := parseAcceptEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, EncodingIdentity, encoding)
|
|
})
|
|
|
|
t.Run("returns gzip when Accept-Encoding is gzip", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/test", nil)
|
|
req.Header.Set("Accept-Encoding", "gzip")
|
|
|
|
encoding, err := parseAcceptEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "gzip", encoding)
|
|
})
|
|
|
|
t.Run("returns gzip when Accept-Encoding is GZIP (case-insensitive)", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/test", nil)
|
|
req.Header.Set("Accept-Encoding", "GZIP")
|
|
|
|
encoding, err := parseAcceptEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "gzip", encoding)
|
|
})
|
|
|
|
t.Run("returns gzip when gzip is among multiple encodings", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/test", nil)
|
|
req.Header.Set("Accept-Encoding", "deflate, gzip, br")
|
|
|
|
encoding, err := parseAcceptEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "gzip", encoding)
|
|
})
|
|
|
|
t.Run("returns gzip with quality value", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/test", nil)
|
|
req.Header.Set("Accept-Encoding", "gzip;q=1.0")
|
|
|
|
encoding, err := parseAcceptEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "gzip", encoding)
|
|
})
|
|
|
|
t.Run("returns identity for identity encoding", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/test", nil)
|
|
req.Header.Set("Accept-Encoding", "identity")
|
|
|
|
encoding, err := parseAcceptEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, EncodingIdentity, encoding)
|
|
})
|
|
|
|
t.Run("returns identity for wildcard encoding", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/test", nil)
|
|
req.Header.Set("Accept-Encoding", "*")
|
|
|
|
encoding, err := parseAcceptEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, EncodingIdentity, encoding)
|
|
})
|
|
|
|
t.Run("falls back to identity for unsupported encoding only", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/test", nil)
|
|
req.Header.Set("Accept-Encoding", "br")
|
|
|
|
encoding, err := parseAcceptEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, EncodingIdentity, encoding)
|
|
})
|
|
|
|
t.Run("falls back to identity when only unsupported encodings", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/test", nil)
|
|
req.Header.Set("Accept-Encoding", "deflate, br")
|
|
|
|
encoding, err := parseAcceptEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, EncodingIdentity, encoding)
|
|
})
|
|
|
|
t.Run("selects gzip when it has highest quality", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/test", nil)
|
|
req.Header.Set("Accept-Encoding", "br;q=0.5, gzip;q=1.0, deflate;q=0.8")
|
|
|
|
encoding, err := parseAcceptEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "gzip", encoding)
|
|
})
|
|
|
|
t.Run("selects gzip even with lower quality when others unsupported", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/test", nil)
|
|
req.Header.Set("Accept-Encoding", "br;q=1.0, gzip;q=0.5")
|
|
|
|
encoding, err := parseAcceptEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "gzip", encoding)
|
|
})
|
|
|
|
t.Run("returns identity when it has higher quality than gzip", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/test", nil)
|
|
req.Header.Set("Accept-Encoding", "gzip;q=0.5, identity;q=1.0")
|
|
|
|
encoding, err := parseAcceptEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, EncodingIdentity, encoding)
|
|
})
|
|
|
|
t.Run("skips encoding with q=0", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/test", nil)
|
|
req.Header.Set("Accept-Encoding", "gzip;q=0, identity")
|
|
|
|
encoding, err := parseAcceptEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, EncodingIdentity, encoding)
|
|
})
|
|
|
|
t.Run("falls back to identity when gzip rejected and no other supported", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/test", nil)
|
|
req.Header.Set("Accept-Encoding", "gzip;q=0, br")
|
|
|
|
encoding, err := parseAcceptEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, EncodingIdentity, encoding)
|
|
})
|
|
|
|
t.Run("returns error when identity explicitly rejected and no supported encoding", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/test", nil)
|
|
req.Header.Set("Accept-Encoding", "br, identity;q=0")
|
|
|
|
_, err := parseAcceptEncoding(req)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "no acceptable encoding found")
|
|
})
|
|
|
|
t.Run("returns gzip for wildcard when identity rejected", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/test", nil)
|
|
req.Header.Set("Accept-Encoding", "*, identity;q=0")
|
|
|
|
encoding, err := parseAcceptEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "gzip", encoding) // wildcard with identity rejected returns supported encoding
|
|
})
|
|
|
|
t.Run("returns error when wildcard rejected and no explicit identity", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/test", nil)
|
|
req.Header.Set("Accept-Encoding", "*;q=0")
|
|
|
|
_, err := parseAcceptEncoding(req)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "no acceptable encoding found")
|
|
})
|
|
|
|
t.Run("returns identity when wildcard rejected but identity explicitly accepted", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/test", nil)
|
|
req.Header.Set("Accept-Encoding", "*;q=0, identity")
|
|
|
|
encoding, err := parseAcceptEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, EncodingIdentity, encoding)
|
|
})
|
|
|
|
t.Run("returns gzip when wildcard rejected but gzip explicitly accepted", func(t *testing.T) {
|
|
t.Parallel()
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodGet, "/test", nil)
|
|
req.Header.Set("Accept-Encoding", "*;q=0, gzip")
|
|
|
|
encoding, err := parseAcceptEncoding(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, EncodingGzip, encoding)
|
|
})
|
|
}
|
|
|
|
func TestGetDecompressedBody(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
t.Run("returns original body when no Content-Encoding header", func(t *testing.T) {
|
|
t.Parallel()
|
|
content := []byte("test content")
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodPost, "/test", bytes.NewReader(content))
|
|
|
|
body, err := getDecompressedBody(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, req.Body, body, "should return original body")
|
|
|
|
data, err := io.ReadAll(body)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, content, data)
|
|
})
|
|
|
|
t.Run("decompresses gzip body when Content-Encoding is gzip", func(t *testing.T) {
|
|
t.Parallel()
|
|
originalContent := []byte("test content to compress")
|
|
|
|
var compressed bytes.Buffer
|
|
gw := gzip.NewWriter(&compressed)
|
|
_, err := gw.Write(originalContent)
|
|
require.NoError(t, err)
|
|
err = gw.Close()
|
|
require.NoError(t, err)
|
|
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodPost, "/test", bytes.NewReader(compressed.Bytes()))
|
|
req.Header.Set("Content-Encoding", "gzip")
|
|
|
|
body, err := getDecompressedBody(req)
|
|
require.NoError(t, err)
|
|
defer body.Close()
|
|
|
|
assert.NotEqual(t, req.Body, body, "should return a new gzip reader")
|
|
|
|
data, err := io.ReadAll(body)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, originalContent, data)
|
|
})
|
|
|
|
t.Run("returns error for invalid gzip data", func(t *testing.T) {
|
|
t.Parallel()
|
|
invalidGzip := []byte("this is not gzip data")
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodPost, "/test", bytes.NewReader(invalidGzip))
|
|
req.Header.Set("Content-Encoding", "gzip")
|
|
|
|
_, err := getDecompressedBody(req)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "failed to create gzip reader")
|
|
})
|
|
|
|
t.Run("returns original body for identity encoding", func(t *testing.T) {
|
|
t.Parallel()
|
|
content := []byte("test content")
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodPost, "/test", bytes.NewReader(content))
|
|
req.Header.Set("Content-Encoding", "identity")
|
|
|
|
body, err := getDecompressedBody(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, req.Body, body, "should return original body")
|
|
|
|
data, err := io.ReadAll(body)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, content, data)
|
|
})
|
|
|
|
t.Run("returns error for unsupported encoding", func(t *testing.T) {
|
|
t.Parallel()
|
|
content := []byte("test content")
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodPost, "/test", bytes.NewReader(content))
|
|
req.Header.Set("Content-Encoding", "br")
|
|
|
|
_, err := getDecompressedBody(req)
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "unsupported Content-Encoding")
|
|
})
|
|
|
|
t.Run("handles gzip with quality value", func(t *testing.T) {
|
|
t.Parallel()
|
|
originalContent := []byte("test content to compress")
|
|
|
|
var compressed bytes.Buffer
|
|
gw := gzip.NewWriter(&compressed)
|
|
_, err := gw.Write(originalContent)
|
|
require.NoError(t, err)
|
|
err = gw.Close()
|
|
require.NoError(t, err)
|
|
|
|
req, _ := http.NewRequestWithContext(t.Context(), http.MethodPost, "/test", bytes.NewReader(compressed.Bytes()))
|
|
req.Header.Set("Content-Encoding", "gzip;q=1.0")
|
|
|
|
body, err := getDecompressedBody(req)
|
|
require.NoError(t, err)
|
|
defer body.Close()
|
|
|
|
data, err := io.ReadAll(body)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, originalContent, data)
|
|
})
|
|
}
|