frostfs-sdk-go/client/object_patch_test.go
Airat Arifullin d4c96c3fd8 [#249] client: Introduce ObjectPatch
Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
2024-08-08 11:24:00 +03:00

196 lines
5 KiB
Go

package client
import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"testing"
v2object "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
"github.com/stretchr/testify/require"
)
type mockPatchStream struct {
streamedPayloadPatches []*object.PayloadPatch
}
func (m *mockPatchStream) Write(r *v2object.PatchRequest) error {
pp := new(object.PayloadPatch)
pp.FromV2(r.GetBody().GetPatch())
if r.GetBody().GetPatch() != nil {
bodyChunk := r.GetBody().GetPatch().Chunk
pp.Chunk = make([]byte, len(bodyChunk))
copy(pp.Chunk, bodyChunk)
}
m.streamedPayloadPatches = append(m.streamedPayloadPatches, pp)
return nil
}
func (m *mockPatchStream) Close() error {
return nil
}
func TestObjectPatcher(t *testing.T) {
t.Run("no split payload patch", func(t *testing.T) {
m := &mockPatchStream{}
pk, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
patcher := objectPatcher{
client: &Client{},
stream: m,
addr: oidtest.Address(),
key: pk,
maxChunkLen: defaultGRPCPayloadChunkLen,
}
patchPayload := []byte("011111")
success := patcher.PatchAttributes(context.Background(), nil, false)
require.True(t, success)
success = patcher.PatchPayload(context.Background(), newRange(0, 6), bytes.NewReader(patchPayload))
require.True(t, success)
require.Len(t, m.streamedPayloadPatches, 2)
// m.streamedPayloadPatches[0] is attribute patch, so skip it
requireRangeChunk(t, m.streamedPayloadPatches[1], 0, 6, "011111")
})
t.Run("splitted payload patch", func(t *testing.T) {
m := &mockPatchStream{}
pk, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
const maxChunkLen = 2
patcher := objectPatcher{
client: &Client{},
stream: m,
addr: oidtest.Address(),
key: pk,
maxChunkLen: maxChunkLen,
}
patchPayload := []byte("012345")
success := patcher.PatchAttributes(context.Background(), nil, false)
require.True(t, success)
success = patcher.PatchPayload(context.Background(), newRange(0, 6), bytes.NewReader(patchPayload))
require.True(t, success)
require.Len(t, m.streamedPayloadPatches, 4)
// m.streamedPayloadPatches[0] is attribute patch, so skip it
requireRangeChunk(t, m.streamedPayloadPatches[1], 0, 2, "01")
requireRangeChunk(t, m.streamedPayloadPatches[2], 2, 2, "23")
requireRangeChunk(t, m.streamedPayloadPatches[3], 4, 2, "45")
})
t.Run("splitted payload patch with zero-length subpatches", func(t *testing.T) {
m := &mockPatchStream{}
pk, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
const maxChunkLen = 2
patcher := objectPatcher{
client: &Client{},
stream: m,
addr: oidtest.Address(),
key: pk,
maxChunkLen: maxChunkLen,
}
patchPayload := []byte("0123456789!@")
success := patcher.PatchAttributes(context.Background(), nil, false)
require.True(t, success)
success = patcher.PatchPayload(context.Background(), newRange(0, 4), bytes.NewReader(patchPayload))
require.True(t, success)
require.Len(t, m.streamedPayloadPatches, 7)
// m.streamedPayloadPatches[0] is attribute patch, so skip it
requireRangeChunk(t, m.streamedPayloadPatches[1], 0, 2, "01")
requireRangeChunk(t, m.streamedPayloadPatches[2], 2, 2, "23")
requireRangeChunk(t, m.streamedPayloadPatches[3], 4, 0, "45")
requireRangeChunk(t, m.streamedPayloadPatches[4], 6, 0, "67")
requireRangeChunk(t, m.streamedPayloadPatches[5], 8, 0, "89")
requireRangeChunk(t, m.streamedPayloadPatches[6], 10, 0, "!@")
})
t.Run("splitted payload patch with zero-length subpatches only", func(t *testing.T) {
m := &mockPatchStream{}
pk, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
const maxChunkLen = 2
patcher := objectPatcher{
client: &Client{},
stream: m,
addr: oidtest.Address(),
key: pk,
maxChunkLen: maxChunkLen,
}
patchPayload := []byte("0123456789!@")
success := patcher.PatchAttributes(context.Background(), nil, false)
require.True(t, success)
success = patcher.PatchPayload(context.Background(), newRange(0, 0), bytes.NewReader(patchPayload))
require.True(t, success)
require.Len(t, m.streamedPayloadPatches, 7)
// m.streamedPayloadPatches[0] is attribute patch, so skip it
requireRangeChunk(t, m.streamedPayloadPatches[1], 0, 0, "01")
requireRangeChunk(t, m.streamedPayloadPatches[2], 2, 0, "23")
requireRangeChunk(t, m.streamedPayloadPatches[3], 4, 0, "45")
requireRangeChunk(t, m.streamedPayloadPatches[4], 6, 0, "67")
requireRangeChunk(t, m.streamedPayloadPatches[5], 8, 0, "89")
requireRangeChunk(t, m.streamedPayloadPatches[6], 10, 0, "!@")
})
}
func requireRangeChunk(t *testing.T, pp *object.PayloadPatch, offset, length int, chunk string) {
require.NotNil(t, pp)
require.Equal(t, uint64(offset), pp.Range.GetOffset())
require.Equal(t, uint64(length), pp.Range.GetLength())
require.Equal(t, []byte(chunk), pp.Chunk)
}
func newRange(offest, length uint64) *object.Range {
rng := &object.Range{}
rng.SetOffset(offest)
rng.SetLength(length)
return rng
}