| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190 |
- package repository
- import (
- "bytes"
- "compress/flate"
- "compress/gzip"
- "io"
- "log/slog"
- "net/http"
- "testing"
- "github.com/andybalholm/brotli"
- "github.com/klauspost/compress/zstd"
- "github.com/stretchr/testify/require"
- "github.com/tidwall/gjson"
- )
- func TestDecompressResponseBodyZstdUsage(t *testing.T) {
- payload := []byte(`{"usage":{"input_tokens":123,"output_tokens":45,"cache_read_input_tokens":67}}`)
- compressed := compressZstd(t, payload)
- resp := newEncodedResponse("zstd", compressed)
- decompressResponseBody(resp)
- body, err := io.ReadAll(resp.Body)
- require.NoError(t, err)
- require.Equal(t, payload, body)
- require.Equal(t, int64(123), gjson.GetBytes(body, "usage.input_tokens").Int())
- require.Equal(t, int64(45), gjson.GetBytes(body, "usage.output_tokens").Int())
- require.Equal(t, int64(67), gjson.GetBytes(body, "usage.cache_read_input_tokens").Int())
- require.Empty(t, resp.Header.Get("Content-Encoding"))
- require.Empty(t, resp.Header.Get("Content-Length"))
- require.Equal(t, int64(-1), resp.ContentLength)
- require.NoError(t, resp.Body.Close())
- }
- func TestDecompressResponseBodyExistingEncodings(t *testing.T) {
- payload := []byte(`{"ok":true}`)
- tests := []struct {
- name string
- encoding string
- compress func(*testing.T, []byte) []byte
- }{
- {name: "gzip", encoding: "gzip", compress: compressGzip},
- {name: "brotli", encoding: "br", compress: compressBrotli},
- {name: "deflate", encoding: "deflate", compress: compressDeflate},
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- resp := newEncodedResponse(tt.encoding, tt.compress(t, payload))
- decompressResponseBody(resp)
- body, err := io.ReadAll(resp.Body)
- require.NoError(t, err)
- require.Equal(t, payload, body)
- require.Empty(t, resp.Header.Get("Content-Encoding"))
- require.Empty(t, resp.Header.Get("Content-Length"))
- require.Equal(t, int64(-1), resp.ContentLength)
- require.NoError(t, resp.Body.Close())
- })
- }
- }
- func TestDecompressResponseBodyWithoutEncodingLeavesBodyUntouched(t *testing.T) {
- originalBody := &responseTestBody{Reader: bytes.NewReader([]byte("plain"))}
- resp := &http.Response{
- Header: make(http.Header),
- Body: originalBody,
- ContentLength: 5,
- }
- decompressResponseBody(resp)
- require.Same(t, originalBody, resp.Body)
- require.Equal(t, int64(5), resp.ContentLength)
- body, err := io.ReadAll(resp.Body)
- require.NoError(t, err)
- require.Equal(t, "plain", string(body))
- require.NoError(t, resp.Body.Close())
- }
- func TestDecompressResponseBodyInvalidZstdWarnsAndPreservesBody(t *testing.T) {
- previousLogger := slog.Default()
- var logOutput bytes.Buffer
- slog.SetDefault(slog.New(slog.NewTextHandler(&logOutput, nil)))
- t.Cleanup(func() {
- slog.SetDefault(previousLogger)
- })
- payload := []byte("not a zstd response")
- resp := newEncodedResponse("zstd", payload)
- require.NotPanics(t, func() {
- decompressResponseBody(resp)
- })
- body, err := io.ReadAll(resp.Body)
- require.NoError(t, err)
- require.Equal(t, payload, body)
- require.Equal(t, "zstd", resp.Header.Get("Content-Encoding"))
- require.Equal(t, int64(len(payload)), resp.ContentLength)
- require.Contains(t, logOutput.String(), "msg=zstd_decompress_failed")
- require.NoError(t, resp.Body.Close())
- }
- func TestDecompressResponseBodyEmptyZstdWarnsAndPreservesBody(t *testing.T) {
- previousLogger := slog.Default()
- var logOutput bytes.Buffer
- slog.SetDefault(slog.New(slog.NewTextHandler(&logOutput, nil)))
- t.Cleanup(func() {
- slog.SetDefault(previousLogger)
- })
- resp := newEncodedResponse("zstd", nil)
- require.NotPanics(t, func() {
- decompressResponseBody(resp)
- })
- body, err := io.ReadAll(resp.Body)
- require.NoError(t, err)
- require.Empty(t, body)
- require.Equal(t, "zstd", resp.Header.Get("Content-Encoding"))
- require.Equal(t, int64(0), resp.ContentLength)
- require.Contains(t, logOutput.String(), "msg=zstd_decompress_failed")
- require.NoError(t, resp.Body.Close())
- }
- type responseTestBody struct {
- io.Reader
- }
- func (b *responseTestBody) Close() error {
- return nil
- }
- func newEncodedResponse(encoding string, body []byte) *http.Response {
- header := make(http.Header)
- header.Set("Content-Encoding", encoding)
- header.Set("Content-Length", "123")
- return &http.Response{
- Header: header,
- Body: io.NopCloser(bytes.NewReader(body)),
- ContentLength: int64(len(body)),
- }
- }
- func compressZstd(t *testing.T, payload []byte) []byte {
- t.Helper()
- var buf bytes.Buffer
- zw, err := zstd.NewWriter(&buf)
- require.NoError(t, err)
- _, err = zw.Write(payload)
- require.NoError(t, err)
- require.NoError(t, zw.Close())
- return buf.Bytes()
- }
- func compressGzip(t *testing.T, payload []byte) []byte {
- t.Helper()
- var buf bytes.Buffer
- zw := gzip.NewWriter(&buf)
- _, err := zw.Write(payload)
- require.NoError(t, err)
- require.NoError(t, zw.Close())
- return buf.Bytes()
- }
- func compressBrotli(t *testing.T, payload []byte) []byte {
- t.Helper()
- var buf bytes.Buffer
- zw := brotli.NewWriter(&buf)
- _, err := zw.Write(payload)
- require.NoError(t, err)
- require.NoError(t, zw.Close())
- return buf.Bytes()
- }
- func compressDeflate(t *testing.T, payload []byte) []byte {
- t.Helper()
- var buf bytes.Buffer
- zw, err := flate.NewWriter(&buf, flate.DefaultCompression)
- require.NoError(t, err)
- _, err = zw.Write(payload)
- require.NoError(t, err)
- require.NoError(t, zw.Close())
- return buf.Bytes()
- }
|