github.com/hyperledger-labs/bdls@v2.1.1+incompatible/core/chaincode/query_response_generator_test.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package chaincode_test
     8  
     9  import (
    10  	"errors"
    11  	"fmt"
    12  	"math"
    13  	"testing"
    14  
    15  	"github.com/hyperledger/fabric-protos-go/ledger/queryresult"
    16  	"github.com/hyperledger/fabric/core/chaincode"
    17  	"github.com/hyperledger/fabric/core/chaincode/mock"
    18  	"github.com/stretchr/testify/assert"
    19  )
    20  
    21  const totalQueryLimit = 103
    22  
    23  func TestBuildQueryResponse(t *testing.T) {
    24  	queryResult := &queryresult.KV{
    25  		Key:       "key",
    26  		Namespace: "namespace",
    27  		Value:     []byte("value"),
    28  	}
    29  
    30  	// test various boundry cases around maxResultLimit
    31  	const maxResultLimit = 10
    32  	testCases := []struct {
    33  		recordCount          int
    34  		expectedResultCount  int
    35  		expectedHasMoreCount int
    36  		isPaginated          bool
    37  		maxResultLimit       int
    38  		totalQueryLimit      int
    39  	}{
    40  		{0, 0, 0, false, maxResultLimit, totalQueryLimit},
    41  		{1, 1, 0, false, maxResultLimit, totalQueryLimit},
    42  		{10, 10, 0, false, maxResultLimit, totalQueryLimit},
    43  		{maxResultLimit - 2, maxResultLimit - 2, 0, false, maxResultLimit, totalQueryLimit},
    44  		{maxResultLimit - 1, maxResultLimit - 1, 0, false, maxResultLimit, totalQueryLimit},
    45  		{maxResultLimit, maxResultLimit, 0, false, maxResultLimit, totalQueryLimit},
    46  		{maxResultLimit + 1, maxResultLimit + 1, 1, false, maxResultLimit, totalQueryLimit},
    47  		{maxResultLimit + 2, maxResultLimit + 2, 1, false, maxResultLimit, totalQueryLimit},
    48  		{int(math.Floor(maxResultLimit * 1.5)), int(math.Floor(maxResultLimit * 1.5)), 1, false, maxResultLimit, totalQueryLimit},
    49  		{maxResultLimit * 2, maxResultLimit * 2, 1, false, maxResultLimit, totalQueryLimit},
    50  		{10*maxResultLimit - 2, 10*maxResultLimit - 2, 9, false, maxResultLimit, totalQueryLimit},
    51  		{10*maxResultLimit - 1, 10*maxResultLimit - 1, 9, false, maxResultLimit, totalQueryLimit},
    52  		{10 * maxResultLimit, 10 * maxResultLimit, 9, false, maxResultLimit, totalQueryLimit},
    53  		{10*maxResultLimit + 1, 10*maxResultLimit + 1, 10, false, maxResultLimit, totalQueryLimit},
    54  		{10*maxResultLimit + 2, 10*maxResultLimit + 2, 10, false, maxResultLimit, totalQueryLimit},
    55  		{10*maxResultLimit + 3, 10*maxResultLimit + 3, 10, false, maxResultLimit, totalQueryLimit},
    56  		{10*maxResultLimit + 5, 10*maxResultLimit + 3, 10, false, maxResultLimit, totalQueryLimit},
    57  		{10, 5, 1, false, 4, 5},
    58  		{10, 5, 0, false, 5, 5},
    59  		{10, 5, 0, false, 6, 5},
    60  		{0, 0, 0, true, maxResultLimit, totalQueryLimit},
    61  		{1, 1, 0, true, maxResultLimit, totalQueryLimit},
    62  		{10, 10, 0, true, maxResultLimit, totalQueryLimit},
    63  		{maxResultLimit, maxResultLimit, 0, true, maxResultLimit, totalQueryLimit},
    64  		{maxResultLimit + 1, maxResultLimit + 1, 0, true, maxResultLimit, totalQueryLimit},
    65  		{10*maxResultLimit + 2, 10*maxResultLimit + 2, 0, true, maxResultLimit, totalQueryLimit},
    66  		{10*maxResultLimit + 3, totalQueryLimit, 0, true, maxResultLimit, totalQueryLimit},
    67  		{10*maxResultLimit + 4, totalQueryLimit, 0, true, maxResultLimit, totalQueryLimit},
    68  		{10, 5, 0, true, 4, 5},
    69  		{10, 5, 0, false, 5, 5},
    70  		{10, 5, 0, false, 6, 5},
    71  	}
    72  
    73  	for _, tc := range testCases {
    74  		t.Run(fmt.Sprintf("%d", tc.expectedResultCount), func(t *testing.T) {
    75  			txSimulator := &mock.TxSimulator{}
    76  			transactionContext := &chaincode.TransactionContext{
    77  				TXSimulator: txSimulator,
    78  			}
    79  
    80  			resultsIterator := &mock.QueryResultsIterator{}
    81  			transactionContext.InitializeQueryContext("query-id", resultsIterator)
    82  			for i := 0; i < tc.recordCount; i++ {
    83  				resultsIterator.NextReturnsOnCall(i, queryResult, nil)
    84  			}
    85  			resultsIterator.NextReturnsOnCall(tc.recordCount, nil, nil)
    86  			responseGenerator := &chaincode.QueryResponseGenerator{
    87  				MaxResultLimit: tc.maxResultLimit,
    88  			}
    89  			totalResultCount := 0
    90  			for hasMoreCount := 0; hasMoreCount <= tc.expectedHasMoreCount; hasMoreCount++ {
    91  				queryResponse, err := responseGenerator.BuildQueryResponse(transactionContext, resultsIterator, "query-id", tc.isPaginated, int32(tc.totalQueryLimit))
    92  				assert.NoError(t, err)
    93  
    94  				switch {
    95  				case hasMoreCount < tc.expectedHasMoreCount:
    96  					// max limit sized batch retrieved, more expected
    97  					assert.True(t, queryResponse.GetHasMore())
    98  					assert.Len(t, queryResponse.GetResults(), tc.maxResultLimit)
    99  				default:
   100  					// remainder retrieved, no more expected
   101  					assert.Len(t, queryResponse.GetResults(), tc.expectedResultCount-totalResultCount)
   102  					assert.False(t, queryResponse.GetHasMore())
   103  
   104  				}
   105  				totalResultCount += len(queryResponse.GetResults())
   106  			}
   107  
   108  			// assert the total number of records is correct
   109  			assert.Equal(t, tc.expectedResultCount, totalResultCount)
   110  
   111  			if tc.isPaginated {
   112  				// this case checks if the expected method was called to close the recordset
   113  				assert.Equal(t, 1, resultsIterator.GetBookmarkAndCloseCallCount())
   114  			} else {
   115  				assert.Equal(t, 1, resultsIterator.CloseCallCount())
   116  			}
   117  		})
   118  	}
   119  }
   120  
   121  func TestBuildQueryResponseErrors(t *testing.T) {
   122  	validResult := &queryresult.KV{Key: "key-name"}
   123  	invalidResult := brokenProto{}
   124  
   125  	tests := []struct {
   126  		errorOnNextCall        int
   127  		brokenResultOnNextCall int
   128  		expectedErrValue       string
   129  	}{
   130  		{-1, 2, "marshal-failed"},
   131  		{-1, 3, "marshal-failed"},
   132  		{2, -1, "next-failed"},
   133  	}
   134  
   135  	for i, tc := range tests {
   136  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   137  			txSimulator := &mock.TxSimulator{}
   138  			transactionContext := &chaincode.TransactionContext{TXSimulator: txSimulator}
   139  			resultsIterator := &mock.QueryResultsIterator{}
   140  			resultsIterator.NextReturns(validResult, nil)
   141  			if tc.errorOnNextCall >= 0 {
   142  				resultsIterator.NextReturnsOnCall(tc.errorOnNextCall, nil, errors.New("next-failed"))
   143  			}
   144  			if tc.brokenResultOnNextCall >= 0 {
   145  				resultsIterator.NextReturnsOnCall(tc.brokenResultOnNextCall, invalidResult, nil)
   146  			}
   147  
   148  			transactionContext.InitializeQueryContext("query-id", resultsIterator)
   149  			responseGenerator := &chaincode.QueryResponseGenerator{
   150  				MaxResultLimit: 3,
   151  			}
   152  
   153  			resp, err := responseGenerator.BuildQueryResponse(transactionContext, resultsIterator, "query-id", false, totalQueryLimit)
   154  			if tc.expectedErrValue == "" {
   155  				assert.NoError(t, err)
   156  			} else {
   157  				assert.EqualError(t, err, tc.expectedErrValue)
   158  			}
   159  			assert.Nil(t, resp)
   160  			assert.Equal(t, 1, resultsIterator.CloseCallCount())
   161  		})
   162  	}
   163  }