git.frostfs.info/TrueCloudLab/frostfs-sdk-go@v0.0.0-20241022124111-5361f0ecebd3/client/object_search_test.go (about)

     1  package client
     2  
     3  import (
     4  	"crypto/ecdsa"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"testing"
     9  
    10  	v2object "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
    11  	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
    12  	signatureV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
    13  	oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
    14  	oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
    15  	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  func TestObjectSearch(t *testing.T) {
    20  	ids := make([]oid.ID, 20)
    21  	for i := range ids {
    22  		ids[i] = oidtest.ID()
    23  	}
    24  
    25  	p, resp := testListReaderResponse(t)
    26  
    27  	buf := make([]oid.ID, 2)
    28  	checkRead := func(t *testing.T, expected []oid.ID) {
    29  		n, ok := resp.Read(buf)
    30  		require.True(t, ok == (len(expected) == len(buf)), "expected no error")
    31  		require.Equal(t, len(expected), n, "expected %d items to be read", len(expected))
    32  		require.Equal(t, expected, buf[:len(expected)])
    33  	}
    34  
    35  	// nil panic
    36  	require.Panics(t, func() { resp.Read(nil) })
    37  
    38  	// both ID fetched
    39  	resp.stream = newSearchStream(p, nil, ids[:3])
    40  	checkRead(t, ids[:2])
    41  
    42  	// one ID cached, second fetched
    43  	resp.stream = newSearchStream(p, nil, ids[3:6])
    44  	checkRead(t, ids[2:4])
    45  
    46  	// both ID cached
    47  	resp.stream = nil // shouldn't be called, panic if so
    48  	checkRead(t, ids[4:6])
    49  
    50  	// both ID fetched in 2 requests, with empty one in the middle
    51  	resp.stream = newSearchStream(p, nil, ids[6:7], nil, ids[7:8])
    52  	checkRead(t, ids[6:8])
    53  
    54  	// read from tail multiple times
    55  	resp.stream = newSearchStream(p, nil, ids[8:11])
    56  	buf = buf[:1]
    57  	checkRead(t, ids[8:9])
    58  	checkRead(t, ids[9:10])
    59  	checkRead(t, ids[10:11])
    60  
    61  	// handle EOF
    62  	buf = buf[:2]
    63  	resp.stream = newSearchStream(p, io.EOF, ids[11:12])
    64  	checkRead(t, ids[11:12])
    65  }
    66  
    67  func TestObjectIterate(t *testing.T) {
    68  	ids := make([]oid.ID, 3)
    69  	for i := range ids {
    70  		ids[i] = oidtest.ID()
    71  	}
    72  
    73  	t.Run("iterate all sequence", func(t *testing.T) {
    74  		p, resp := testListReaderResponse(t)
    75  
    76  		resp.stream = newSearchStream(p, io.EOF, ids[0:2], nil, ids[2:3])
    77  
    78  		var actual []oid.ID
    79  		require.NoError(t, resp.Iterate(func(id oid.ID) bool {
    80  			actual = append(actual, id)
    81  			return false
    82  		}))
    83  		require.Equal(t, ids[:3], actual)
    84  	})
    85  	t.Run("stop by return value", func(t *testing.T) {
    86  		p, resp := testListReaderResponse(t)
    87  
    88  		var actual []oid.ID
    89  		resp.stream = &singleStreamResponder{key: p, idList: [][]oid.ID{ids}}
    90  		require.NoError(t, resp.Iterate(func(id oid.ID) bool {
    91  			actual = append(actual, id)
    92  			return len(actual) == 2
    93  		}))
    94  		require.Equal(t, ids[:2], actual)
    95  	})
    96  	t.Run("stop after error", func(t *testing.T) {
    97  		p, resp := testListReaderResponse(t)
    98  		expectedErr := errors.New("test error")
    99  
   100  		resp.stream = newSearchStream(p, expectedErr, ids[:2])
   101  
   102  		var actual []oid.ID
   103  		err := resp.Iterate(func(id oid.ID) bool {
   104  			actual = append(actual, id)
   105  			return false
   106  		})
   107  		require.True(t, errors.Is(err, expectedErr), "got: %v", err)
   108  		require.Equal(t, ids[:2], actual)
   109  	})
   110  }
   111  
   112  func testListReaderResponse(t *testing.T) (*ecdsa.PrivateKey, *ObjectListReader) {
   113  	p, err := keys.NewPrivateKey()
   114  	require.NoError(t, err)
   115  
   116  	return &p.PrivateKey, &ObjectListReader{
   117  		cancelCtxStream: func() {},
   118  		client:          &Client{},
   119  		tail:            nil,
   120  	}
   121  }
   122  
   123  func newSearchStream(key *ecdsa.PrivateKey, endError error, idList ...[]oid.ID) *singleStreamResponder {
   124  	return &singleStreamResponder{
   125  		key:      key,
   126  		endError: endError,
   127  		idList:   idList,
   128  	}
   129  }
   130  
   131  type singleStreamResponder struct {
   132  	key      *ecdsa.PrivateKey
   133  	n        int
   134  	endError error
   135  	idList   [][]oid.ID
   136  }
   137  
   138  func (s *singleStreamResponder) Read(resp *v2object.SearchResponse) error {
   139  	if s.n >= len(s.idList) {
   140  		if s.endError != nil {
   141  			return s.endError
   142  		}
   143  		panic("unexpected call to `Read`")
   144  	}
   145  
   146  	var body v2object.SearchResponseBody
   147  
   148  	if s.idList[s.n] != nil {
   149  		ids := make([]refs.ObjectID, len(s.idList[s.n]))
   150  		for i := range s.idList[s.n] {
   151  			s.idList[s.n][i].WriteToV2(&ids[i])
   152  		}
   153  		body.SetIDList(ids)
   154  	}
   155  	resp.SetBody(&body)
   156  
   157  	err := signatureV2.SignServiceMessage(s.key, resp)
   158  	if err != nil {
   159  		panic(fmt.Errorf("error: %w", err))
   160  	}
   161  
   162  	s.n++
   163  	return nil
   164  }