Files
search-go/encoding_test.go
zeekayandHanzo Dev f62ee2198f Go 1.27.1, and the code it can say more simply
Modules declare go 1.27.1 and the build images follow. go fix applied the
modernizers, and the test and example pointer helpers went once new(expr)
could say them; omitzero is not applied, since it changes what the JSON says.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-16 19:12:06 -07:00

230 lines
5.6 KiB
Go

package meilisearch
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"io"
"strings"
"sync"
"testing"
)
type mockData struct {
Name string
Age int
}
type errorWriter struct{}
func (e *errorWriter) Write(p []byte) (int, error) {
return 0, errors.New("write error")
}
type errorReader struct{}
func (e *errorReader) Read(p []byte) (int, error) {
return 0, errors.New("read error")
}
type mockEncoder struct{}
func (m *mockEncoder) Encode(r io.Reader) (io.ReadCloser, error) {
return nil, nil
}
func (m *mockEncoder) Decode(data []byte, v any) error {
msg, ok := v.(*meilisearchApiError)
if !ok {
return fmt.Errorf("wrong type")
}
msg.Message = "mocked message"
msg.Code = "mocked code"
msg.Type = "mocked type"
msg.Link = "mocked link"
return nil
}
func (m *mockEncoder) Decoder(r io.Reader) (streamDecoder, error) {
return &jsonStreamDecoder{Decoder: json.NewDecoder(r)}, nil
}
func Test_Encode_ErrorOnNewWriter(t *testing.T) {
g := &gzipEncoder{
gzWriterPool: &sync.Pool{
New: func() any {
return &gzipWriter{
writer: nil,
err: errors.New("new writer error"),
}
},
},
bufferPool: &sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
},
}
d := &flateEncoder{
flWriterPool: &sync.Pool{
New: func() any {
return &flateWriter{
writer: nil,
err: errors.New("new writer error"),
}
},
},
bufferPool: &sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
},
}
_, err := g.Encode(bytes.NewReader([]byte("test")))
require.Error(t, err)
_, err = d.Encode(bytes.NewReader([]byte("test")))
require.Error(t, err)
}
func Test_Encode_ErrorInCopyZeroAlloc(t *testing.T) {
g := newEncoding(GzipEncoding, DefaultCompression)
d := newEncoding(DeflateEncoding, DefaultCompression)
b := newEncoding(BrotliEncoding, DefaultCompression)
_, err := g.Encode(&errorReader{})
require.Error(t, err)
_, err = d.Encode(&errorReader{})
require.Error(t, err)
_, err = b.Encode(&errorReader{})
require.Error(t, err)
}
func Test_InvalidContentType(t *testing.T) {
enc := newEncoding("invalid", DefaultCompression)
require.Nil(t, enc)
}
func testEncoder(t *testing.T, enc encoder, original *mockData) {
originalJSON, err := json.Marshal(original)
assert.NoError(t, err)
readCloser := io.NopCloser(bytes.NewReader(originalJSON))
encodedReader, err := enc.Encode(readCloser)
assert.NoError(t, err)
defer func() {
_ = encodedReader.Close()
}()
encodedData, err := io.ReadAll(encodedReader)
assert.NoError(t, err)
assert.NotEmpty(t, encodedData)
var decoded mockData
err = enc.Decode(encodedData, &decoded)
assert.NoError(t, err)
assert.Equal(t, original, &decoded)
var invalidTarget int
err = enc.Decode(encodedData, &invalidTarget)
assert.Error(t, err)
}
func TestGzipEncoder(t *testing.T) {
testEncoder(t, newEncoding(GzipEncoding, DefaultCompression), &mockData{Name: "John Doe", Age: 30})
}
func TestDeflateEncoder(t *testing.T) {
testEncoder(t, newEncoding(DeflateEncoding, DefaultCompression), &mockData{Name: "Jane Doe", Age: 25})
}
func TestBrotliEncoder(t *testing.T) {
testEncoder(t, newEncoding(BrotliEncoding, DefaultCompression), &mockData{Name: "Jane Doe", Age: 25})
}
func TestEncoder_EmptyData(t *testing.T) {
testEncoder(t, newEncoding(GzipEncoding, DefaultCompression), &mockData{})
testEncoder(t, newEncoding(DeflateEncoding, DefaultCompression), &mockData{})
testEncoder(t, newEncoding(BrotliEncoding, DefaultCompression), &mockData{})
}
func TestEncoder_InvalidDecode(t *testing.T) {
encoders := []encoder{
newEncoding(GzipEncoding, DefaultCompression),
newEncoding(DeflateEncoding, DefaultCompression),
newEncoding(BrotliEncoding, DefaultCompression),
}
for _, enc := range encoders {
var decoded mockData
err := enc.Decode([]byte("invalid data"), &decoded)
assert.Error(t, err)
}
}
func TestCopyZeroAlloc(t *testing.T) {
t.Run("RegularCopy", func(t *testing.T) {
src := strings.NewReader("hello world")
dst := &bytes.Buffer{}
n, err := copyZeroAlloc(dst, src)
assert.NoError(t, err)
assert.Equal(t, int64(11), n)
assert.Equal(t, "hello world", dst.String())
})
t.Run("EmptySource", func(t *testing.T) {
src := strings.NewReader("")
dst := &bytes.Buffer{}
n, err := copyZeroAlloc(dst, src)
assert.NoError(t, err)
assert.Equal(t, int64(0), n)
assert.Equal(t, "", dst.String())
})
t.Run("LargeDataCopy", func(t *testing.T) {
data := strings.Repeat("a", 10000)
src := strings.NewReader(data)
dst := &bytes.Buffer{}
n, err := copyZeroAlloc(dst, src)
assert.NoError(t, err)
assert.Equal(t, int64(len(data)), n)
assert.Equal(t, data, dst.String())
})
t.Run("ErrorOnWrite", func(t *testing.T) {
src := strings.NewReader("hello world")
dst := &errorWriter{}
n, err := copyZeroAlloc(dst, src)
assert.Error(t, err)
assert.Equal(t, int64(0), n)
assert.Equal(t, "write error", err.Error())
})
t.Run("ErrorOnRead", func(t *testing.T) {
src := &errorReader{}
dst := &bytes.Buffer{}
n, err := copyZeroAlloc(dst, src)
assert.Error(t, err)
assert.Equal(t, int64(0), n)
assert.Equal(t, "read error", err.Error())
})
t.Run("ConcurrentAccess", func(t *testing.T) {
var wg sync.WaitGroup
data := "concurrent data"
var mu sync.Mutex
dst := &bytes.Buffer{}
for range 10 {
wg.Go(func() {
src := strings.NewReader(data)
buf := &bytes.Buffer{}
_, _ = copyZeroAlloc(buf, src)
mu.Lock()
defer mu.Unlock()
dst.Write(buf.Bytes())
})
}
wg.Wait()
mu.Lock()
assert.Equal(t, strings.Repeat(data, 10), dst.String())
mu.Unlock()
})
}