github.com/koko1123/flow-go-1@v0.29.6/engine/access/rest/blocks_test.go (about)

     1  package rest
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"net/url"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  
    13  	"github.com/koko1123/flow-go-1/engine/access/rest/request"
    14  	"github.com/koko1123/flow-go-1/engine/access/rest/util"
    15  
    16  	mocks "github.com/stretchr/testify/mock"
    17  	"github.com/stretchr/testify/require"
    18  	"google.golang.org/grpc/codes"
    19  	"google.golang.org/grpc/status"
    20  
    21  	"github.com/koko1123/flow-go-1/access/mock"
    22  	"github.com/koko1123/flow-go-1/engine/access/rest/middleware"
    23  	"github.com/koko1123/flow-go-1/model/flow"
    24  	"github.com/koko1123/flow-go-1/utils/unittest"
    25  )
    26  
    27  type testVector struct {
    28  	description      string
    29  	request          *http.Request
    30  	expectedStatus   int
    31  	expectedResponse string
    32  }
    33  
    34  // TestGetBlocks tests the get blocks by ID and get blocks by heights API
    35  func TestGetBlocks(t *testing.T) {
    36  	backend := &mock.API{}
    37  
    38  	blkCnt := 10
    39  	blockIDs, heights, blocks, executionResults := generateMocks(backend, blkCnt)
    40  
    41  	singleBlockExpandedResponse := expectedBlockResponsesExpanded(blocks[:1], executionResults[:1], true)
    42  	multipleBlockExpandedResponse := expectedBlockResponsesExpanded(blocks, executionResults, true)
    43  
    44  	singleBlockCondensedResponse := expectedBlockResponsesExpanded(blocks[:1], executionResults[:1], false)
    45  	multipleBlockCondensedResponse := expectedBlockResponsesExpanded(blocks, executionResults, false)
    46  
    47  	invalidID := unittest.IdentifierFixture().String()
    48  	invalidHeight := fmt.Sprintf("%d", blkCnt+1)
    49  
    50  	maxIDs := flow.IdentifierList(unittest.IdentifierListFixture(request.MaxBlockRequestHeightRange + 1))
    51  
    52  	testVectors := []testVector{
    53  		{
    54  			description:      "Get single expanded block by ID",
    55  			request:          getByIDsExpandedURL(t, blockIDs[:1]),
    56  			expectedStatus:   http.StatusOK,
    57  			expectedResponse: singleBlockExpandedResponse,
    58  		},
    59  		{
    60  			description:      "Get multiple expanded blocks by IDs",
    61  			request:          getByIDsExpandedURL(t, blockIDs),
    62  			expectedStatus:   http.StatusOK,
    63  			expectedResponse: multipleBlockExpandedResponse,
    64  		},
    65  		{
    66  			description:      "Get single condensed block by ID",
    67  			request:          getByIDsCondensedURL(t, blockIDs[:1]),
    68  			expectedStatus:   http.StatusOK,
    69  			expectedResponse: singleBlockCondensedResponse,
    70  		},
    71  		{
    72  			description:      "Get multiple condensed blocks by IDs",
    73  			request:          getByIDsCondensedURL(t, blockIDs),
    74  			expectedStatus:   http.StatusOK,
    75  			expectedResponse: multipleBlockCondensedResponse,
    76  		},
    77  		{
    78  			description:      "Get single expanded block by height",
    79  			request:          getByHeightsExpandedURL(t, heights[:1]...),
    80  			expectedStatus:   http.StatusOK,
    81  			expectedResponse: singleBlockExpandedResponse,
    82  		},
    83  		{
    84  			description:      "Get multiple expanded blocks by heights",
    85  			request:          getByHeightsExpandedURL(t, heights...),
    86  			expectedStatus:   http.StatusOK,
    87  			expectedResponse: multipleBlockExpandedResponse,
    88  		},
    89  		{
    90  			description:      "Get multiple expanded blocks by start and end height",
    91  			request:          getByStartEndHeightExpandedURL(t, heights[0], heights[len(heights)-1]),
    92  			expectedStatus:   http.StatusOK,
    93  			expectedResponse: multipleBlockExpandedResponse,
    94  		},
    95  		{
    96  			description:      "Get block by ID not found",
    97  			request:          getByIDsExpandedURL(t, []string{invalidID}),
    98  			expectedStatus:   http.StatusNotFound,
    99  			expectedResponse: fmt.Sprintf(`{"code":404, "message":"error looking up block with ID %s"}`, invalidID),
   100  		},
   101  		{
   102  			description:      "Get block by height not found",
   103  			request:          getByHeightsExpandedURL(t, invalidHeight),
   104  			expectedStatus:   http.StatusNotFound,
   105  			expectedResponse: fmt.Sprintf(`{"code":404, "message":"error looking up block at height %s"}`, invalidHeight),
   106  		},
   107  		{
   108  			description:      "Get block by end height less than start height",
   109  			request:          getByStartEndHeightExpandedURL(t, heights[len(heights)-1], heights[0]),
   110  			expectedStatus:   http.StatusBadRequest,
   111  			expectedResponse: `{"code":400, "message": "start height must be less than or equal to end height"}`,
   112  		},
   113  		{
   114  			description:      "Get block by both heights and start and end height",
   115  			request:          requestURL(t, nil, heights[len(heights)-1], heights[0], true, heights...),
   116  			expectedStatus:   http.StatusBadRequest,
   117  			expectedResponse: `{"code":400, "message": "can only provide either heights or start and end height range"}`,
   118  		},
   119  		{
   120  			description:      "Get block with missing height param",
   121  			request:          getByHeightsExpandedURL(t), // no height query param specified
   122  			expectedStatus:   http.StatusBadRequest,
   123  			expectedResponse: `{"code":400, "message": "must provide either heights or start and end height range"}`,
   124  		},
   125  		{
   126  			description:      "Get block with missing height values",
   127  			request:          getByHeightsExpandedURL(t, ""), // height query param specified with no value
   128  			expectedStatus:   http.StatusBadRequest,
   129  			expectedResponse: `{"code":400, "message": "must provide either heights or start and end height range"}`,
   130  		},
   131  		{
   132  			description:      "Get block by more than maximum permissible number of IDs",
   133  			request:          getByIDsCondensedURL(t, maxIDs.Strings()), // height query param specified with no value
   134  			expectedStatus:   http.StatusBadRequest,
   135  			expectedResponse: fmt.Sprintf(`{"code":400, "message": "at most %d IDs can be requested at a time"}`, request.MaxBlockRequestHeightRange),
   136  		},
   137  	}
   138  
   139  	for _, tv := range testVectors {
   140  		responseRec, err := executeRequest(tv.request, backend)
   141  		assert.NoError(t, err)
   142  		require.Equal(t, tv.expectedStatus, responseRec.Code, "failed test %s: incorrect response code", tv.description)
   143  		actualResp := responseRec.Body.String()
   144  		require.JSONEq(t, tv.expectedResponse, actualResp, "Failed: %s: incorrect response body", tv.description)
   145  	}
   146  }
   147  
   148  func requestURL(t *testing.T, ids []string, start string, end string, expandResponse bool, heights ...string) *http.Request {
   149  	u, _ := url.Parse("/v1/blocks")
   150  	q := u.Query()
   151  
   152  	if len(ids) > 0 {
   153  		u, _ = url.Parse(u.String() + "/" + strings.Join(ids, ","))
   154  	}
   155  
   156  	if start != "" {
   157  		q.Add(startHeightQueryParam, start)
   158  		q.Add(endHeightQueryParam, end)
   159  	}
   160  
   161  	if len(heights) > 0 {
   162  		heightsStr := strings.Join(heights, ",")
   163  		q.Add(heightQueryParam, heightsStr)
   164  	}
   165  
   166  	if expandResponse {
   167  		var expands []string
   168  		expands = append(expands, ExpandableFieldPayload)
   169  		expands = append(expands, ExpandableExecutionResult)
   170  		expandsStr := strings.Join(expands, ",")
   171  		q.Add(middleware.ExpandQueryParam, expandsStr)
   172  	}
   173  
   174  	u.RawQuery = q.Encode()
   175  
   176  	req, err := http.NewRequest("GET", u.String(), nil)
   177  	require.NoError(t, err)
   178  	return req
   179  }
   180  
   181  func getByIDsExpandedURL(t *testing.T, ids []string) *http.Request {
   182  	return requestURL(t, ids, "", "", true)
   183  }
   184  
   185  func getByHeightsExpandedURL(t *testing.T, heights ...string) *http.Request {
   186  	return requestURL(t, nil, "", "", true, heights...)
   187  }
   188  
   189  func getByStartEndHeightExpandedURL(t *testing.T, start, end string) *http.Request {
   190  	return requestURL(t, nil, start, end, true)
   191  }
   192  
   193  func getByIDsCondensedURL(t *testing.T, ids []string) *http.Request {
   194  	return requestURL(t, ids, "", "", false)
   195  }
   196  
   197  func generateMocks(backend *mock.API, count int) ([]string, []string, []*flow.Block, []*flow.ExecutionResult) {
   198  	blockIDs := make([]string, count)
   199  	heights := make([]string, count)
   200  	blocks := make([]*flow.Block, count)
   201  	executionResults := make([]*flow.ExecutionResult, count)
   202  
   203  	for i := 0; i < count; i++ {
   204  		block := unittest.BlockFixture()
   205  		block.Header.Height = uint64(i)
   206  		blocks[i] = &block
   207  		blockIDs[i] = block.Header.ID().String()
   208  		heights[i] = fmt.Sprintf("%d", block.Header.Height)
   209  
   210  		executionResult := unittest.ExecutionResultFixture()
   211  		executionResult.BlockID = block.ID()
   212  		executionResults[i] = executionResult
   213  
   214  		backend.Mock.On("GetBlockByID", mocks.Anything, block.ID()).Return(&block, flow.BlockStatusSealed, nil)
   215  		backend.Mock.On("GetBlockByHeight", mocks.Anything, block.Header.Height).Return(&block, flow.BlockStatusSealed, nil)
   216  		backend.Mock.On("GetExecutionResultForBlockID", mocks.Anything, block.ID()).Return(executionResults[i], nil)
   217  	}
   218  
   219  	// any other call to the backend should return a not found error
   220  	backend.Mock.On("GetBlockByID", mocks.Anything, mocks.Anything).Return(nil, flow.BlockStatusUnknown, status.Error(codes.NotFound, "not found"))
   221  	backend.Mock.On("GetBlockByHeight", mocks.Anything, mocks.Anything).Return(nil, flow.BlockStatusUnknown, status.Error(codes.NotFound, "not found"))
   222  
   223  	return blockIDs, heights, blocks, executionResults
   224  }
   225  
   226  func expectedBlockResponsesExpanded(blocks []*flow.Block, execResult []*flow.ExecutionResult, expanded bool) string {
   227  	blockResponses := make([]string, len(blocks))
   228  	for i, b := range blocks {
   229  		blockResponses[i] = expectedBlockResponse(b, execResult[i], expanded)
   230  	}
   231  	return fmt.Sprintf("[%s]", strings.Join(blockResponses, ","))
   232  }
   233  
   234  func expectedBlockResponse(block *flow.Block, execResult *flow.ExecutionResult, expanded bool) string {
   235  	id := block.ID().String()
   236  	execResultID := execResult.ID().String()
   237  	execLink := fmt.Sprintf("/v1/execution_results/%s", execResultID)
   238  	blockLink := fmt.Sprintf("/v1/blocks/%s", id)
   239  	payloadLink := fmt.Sprintf("/v1/blocks/%s/payload", id)
   240  
   241  	timestamp := block.Header.Timestamp.Format(time.RFC3339Nano)
   242  
   243  	if expanded {
   244  		return fmt.Sprintf(`
   245  	{
   246  		"header": {
   247  			"id": "%s",
   248  			"parent_id": "%s",
   249  			"height": "%d",
   250  			"timestamp": "%s",
   251  			"parent_voter_signature": "%s"
   252  		},
   253  		"payload": {
   254  			"collection_guarantees": [],
   255  			"block_seals": []
   256  		},
   257  		"execution_result": %s,
   258  		"_expandable": {},
   259  		"_links": {
   260  			"_self": "%s"
   261  		}
   262  	}`, id, block.Header.ParentID.String(), block.Header.Height, timestamp,
   263  			util.ToBase64(block.Header.ParentVoterSigData), executionResultExpectedStr(execResult), blockLink)
   264  	}
   265  
   266  	return fmt.Sprintf(`
   267  	{
   268  		"header": {
   269  			"id": "%s",
   270  			"parent_id": "%s",
   271  			"height": "%d",
   272  			"timestamp": "%s",
   273  			"parent_voter_signature": "%s"
   274  		},
   275  		"_expandable": {
   276              "payload": "%s",
   277              "execution_result": "%s"
   278          },
   279  		"_links": {
   280  			"_self": "%s"
   281  		}
   282  	}`, id, block.Header.ParentID.String(), block.Header.Height, timestamp,
   283  		util.ToBase64(block.Header.ParentVoterSigData), payloadLink, execLink, blockLink)
   284  }