github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/access/rest/routes/events_test.go (about)

     1  package routes
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/http"
     7  	"net/url"
     8  	"strings"
     9  	"testing"
    10  	"time"
    11  
    12  	mocks "github.com/stretchr/testify/mock"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	"google.golang.org/grpc/codes"
    16  	"google.golang.org/grpc/status"
    17  
    18  	"github.com/onflow/flow/protobuf/go/flow/entities"
    19  
    20  	"github.com/onflow/flow-go/access/mock"
    21  	"github.com/onflow/flow-go/engine/access/rest/util"
    22  	"github.com/onflow/flow-go/model/flow"
    23  	"github.com/onflow/flow-go/utils/unittest"
    24  )
    25  
    26  func TestGetEvents(t *testing.T) {
    27  	backend := &mock.API{}
    28  	events := generateEventsMocks(backend, 5)
    29  
    30  	allBlockIDs := make([]string, len(events))
    31  	for i, e := range events {
    32  		allBlockIDs[i] = e.BlockID.String()
    33  	}
    34  	startHeight := fmt.Sprint(events[0].BlockHeight)
    35  	endHeight := fmt.Sprint(events[len(events)-1].BlockHeight)
    36  
    37  	// remove events from the last block to test that an empty BlockEvents is returned when the last
    38  	// block contains no events
    39  	truncatedEvents := append(events[:len(events)-1], flow.BlockEvents{
    40  		BlockHeight:    events[len(events)-1].BlockHeight,
    41  		BlockID:        events[len(events)-1].BlockID,
    42  		BlockTimestamp: events[len(events)-1].BlockTimestamp,
    43  	})
    44  
    45  	testVectors := []testVector{
    46  		// valid
    47  		{
    48  			description:      "Get events for a single block by ID",
    49  			request:          getEventReq(t, "A.179b6b1cb6755e31.Foo.Bar", "", "", []string{events[0].BlockID.String()}),
    50  			expectedStatus:   http.StatusOK,
    51  			expectedResponse: testBlockEventResponse(t, []flow.BlockEvents{events[0]}),
    52  		},
    53  		{
    54  			description:      "Get events by all block IDs",
    55  			request:          getEventReq(t, "A.179b6b1cb6755e31.Foo.Bar", "", "", allBlockIDs),
    56  			expectedStatus:   http.StatusOK,
    57  			expectedResponse: testBlockEventResponse(t, events),
    58  		},
    59  		{
    60  			description:      "Get events for height range",
    61  			request:          getEventReq(t, "A.179b6b1cb6755e31.Foo.Bar", startHeight, endHeight, nil),
    62  			expectedStatus:   http.StatusOK,
    63  			expectedResponse: testBlockEventResponse(t, events),
    64  		},
    65  		{
    66  			description:      "Get events range ending at sealed block",
    67  			request:          getEventReq(t, "A.179b6b1cb6755e31.Foo.Bar", "0", "sealed", nil),
    68  			expectedStatus:   http.StatusOK,
    69  			expectedResponse: testBlockEventResponse(t, events),
    70  		},
    71  		{
    72  			description:      "Get events range ending after last block",
    73  			request:          getEventReq(t, "A.179b6b1cb6755e31.Foo.Bar", "0", fmt.Sprint(events[len(events)-1].BlockHeight+5), nil),
    74  			expectedStatus:   http.StatusOK,
    75  			expectedResponse: testBlockEventResponse(t, truncatedEvents),
    76  		},
    77  		// invalid
    78  		{
    79  			description:      "Get invalid - missing all fields",
    80  			request:          getEventReq(t, "", "", "", nil),
    81  			expectedStatus:   http.StatusBadRequest,
    82  			expectedResponse: `{"code":400,"message":"must provide either block IDs or start and end height range"}`,
    83  		},
    84  		{
    85  			description:      "Get invalid - missing query event type",
    86  			request:          getEventReq(t, "", "", "", []string{events[0].BlockID.String()}),
    87  			expectedStatus:   http.StatusBadRequest,
    88  			expectedResponse: `{"code":400,"message":"event type must be provided"}`,
    89  		},
    90  		{
    91  			description:      "Get invalid - missing end height",
    92  			request:          getEventReq(t, "A.179b6b1cb6755e31.Foo.Bar", "100", "", nil),
    93  			expectedStatus:   http.StatusBadRequest,
    94  			expectedResponse: `{"code":400,"message":"must provide either block IDs or start and end height range"}`,
    95  		},
    96  		{
    97  			description:      "Get invalid - start height bigger than end height",
    98  			request:          getEventReq(t, "A.179b6b1cb6755e31.Foo.Bar", "100", "50", nil),
    99  			expectedStatus:   http.StatusBadRequest,
   100  			expectedResponse: `{"code":400,"message":"start height must be less than or equal to end height"}`,
   101  		},
   102  		{
   103  			description:      "Get invalid - too big interval",
   104  			request:          getEventReq(t, "A.179b6b1cb6755e31.Foo.Bar", "0", "5000", nil),
   105  			expectedStatus:   http.StatusBadRequest,
   106  			expectedResponse: `{"code":400,"message":"height range 5000 exceeds maximum allowed of 250"}`,
   107  		},
   108  		{
   109  			description:      "Get invalid - can not provide all params",
   110  			request:          getEventReq(t, "A.179b6b1cb6755e31.Foo.Bar", "100", "120", []string{"10e782612a014b5c9c7d17994d7e67157064f3dd42fa92cd080bfb0fe22c3f71"}),
   111  			expectedStatus:   http.StatusBadRequest,
   112  			expectedResponse: `{"code":400,"message":"can only provide either block IDs or start and end height range"}`,
   113  		},
   114  		{
   115  			description:      "Get invalid - invalid height format",
   116  			request:          getEventReq(t, "A.179b6b1cb6755e31.Foo.Bar", "foo", "120", nil),
   117  			expectedStatus:   http.StatusBadRequest,
   118  			expectedResponse: `{"code":400,"message":"invalid start height: invalid height format"}`,
   119  		},
   120  		{
   121  			description:      "Get invalid - latest block smaller than start",
   122  			request:          getEventReq(t, "A.179b6b1cb6755e31.Foo.Bar", "100000", "sealed", nil),
   123  			expectedStatus:   http.StatusBadRequest,
   124  			expectedResponse: `{"code":400,"message":"current retrieved end height value is lower than start height"}`,
   125  		},
   126  	}
   127  
   128  	for _, test := range testVectors {
   129  		t.Run(test.description, func(t *testing.T) {
   130  			assertResponse(t, test.request, test.expectedStatus, test.expectedResponse, backend)
   131  		})
   132  	}
   133  
   134  }
   135  
   136  func getEventReq(t *testing.T, eventType string, start string, end string, blockIDs []string) *http.Request {
   137  	u, _ := url.Parse("/v1/events")
   138  	q := u.Query()
   139  
   140  	if len(blockIDs) > 0 {
   141  		q.Add(BlockQueryParam, strings.Join(blockIDs, ","))
   142  	}
   143  
   144  	if start != "" && end != "" {
   145  		q.Add(startHeightQueryParam, start)
   146  		q.Add(endHeightQueryParam, end)
   147  	}
   148  
   149  	q.Add(EventTypeQuery, eventType)
   150  
   151  	u.RawQuery = q.Encode()
   152  
   153  	req, err := http.NewRequest("GET", u.String(), nil)
   154  	require.NoError(t, err)
   155  
   156  	return req
   157  }
   158  
   159  func generateEventsMocks(backend *mock.API, n int) []flow.BlockEvents {
   160  	events := make([]flow.BlockEvents, n)
   161  	ids := make([]flow.Identifier, n)
   162  
   163  	var lastHeader *flow.Header
   164  	for i := 0; i < n; i++ {
   165  		header := unittest.BlockHeaderFixture(unittest.WithHeaderHeight(uint64(i)))
   166  		ids[i] = header.ID()
   167  
   168  		events[i] = unittest.BlockEventsFixture(header, 2)
   169  
   170  		backend.Mock.
   171  			On("GetEventsForBlockIDs", mocks.Anything, mocks.Anything, []flow.Identifier{header.ID()}, entities.EventEncodingVersion_JSON_CDC_V0).
   172  			Return([]flow.BlockEvents{events[i]}, nil)
   173  
   174  		lastHeader = header
   175  	}
   176  
   177  	backend.Mock.
   178  		On("GetEventsForBlockIDs", mocks.Anything, mocks.Anything, ids, entities.EventEncodingVersion_JSON_CDC_V0).
   179  		Return(events, nil)
   180  
   181  	// range from first to last block
   182  	backend.Mock.On(
   183  		"GetEventsForHeightRange",
   184  		mocks.Anything,
   185  		mocks.Anything,
   186  		events[0].BlockHeight,
   187  		events[len(events)-1].BlockHeight,
   188  		entities.EventEncodingVersion_JSON_CDC_V0,
   189  	).Return(events, nil)
   190  
   191  	// range from first to last block + 5
   192  	backend.Mock.On(
   193  		"GetEventsForHeightRange",
   194  		mocks.Anything,
   195  		mocks.Anything,
   196  		events[0].BlockHeight,
   197  		events[len(events)-1].BlockHeight+5,
   198  		entities.EventEncodingVersion_JSON_CDC_V0,
   199  	).Return(append(events[:len(events)-1], unittest.BlockEventsFixture(lastHeader, 0)), nil)
   200  
   201  	latestBlock := unittest.BlockHeaderFixture()
   202  	latestBlock.Height = uint64(n - 1)
   203  
   204  	// default not found
   205  	backend.Mock.
   206  		On("GetEventsForBlockIDs", mocks.Anything, mocks.Anything, mocks.Anything, entities.EventEncodingVersion_JSON_CDC_V0).
   207  		Return(nil, status.Error(codes.NotFound, "not found"))
   208  
   209  	backend.Mock.
   210  		On("GetEventsForHeightRange", mocks.Anything, mocks.Anything).
   211  		Return(nil, status.Error(codes.NotFound, "not found"))
   212  
   213  	backend.Mock.
   214  		On("GetLatestBlockHeader", mocks.Anything, true).
   215  		Return(latestBlock, flow.BlockStatusSealed, nil)
   216  
   217  	return events
   218  }
   219  
   220  func testBlockEventResponse(t *testing.T, events []flow.BlockEvents) string {
   221  
   222  	type eventResponse struct {
   223  		Type             flow.EventType  `json:"type"`
   224  		TransactionID    flow.Identifier `json:"transaction_id"`
   225  		TransactionIndex string          `json:"transaction_index"`
   226  		EventIndex       string          `json:"event_index"`
   227  		Payload          string          `json:"payload"`
   228  	}
   229  
   230  	type blockEventsResponse struct {
   231  		BlockID        flow.Identifier `json:"block_id"`
   232  		BlockHeight    string          `json:"block_height"`
   233  		BlockTimestamp string          `json:"block_timestamp"`
   234  		Events         []eventResponse `json:"events,omitempty"`
   235  	}
   236  
   237  	res := make([]blockEventsResponse, len(events))
   238  
   239  	for i, e := range events {
   240  		events := make([]eventResponse, len(e.Events))
   241  
   242  		for i, ev := range e.Events {
   243  			events[i] = eventResponse{
   244  				Type:             ev.Type,
   245  				TransactionID:    ev.TransactionID,
   246  				TransactionIndex: fmt.Sprint(ev.TransactionIndex),
   247  				EventIndex:       fmt.Sprint(ev.EventIndex),
   248  				Payload:          util.ToBase64(ev.Payload),
   249  			}
   250  		}
   251  
   252  		res[i] = blockEventsResponse{
   253  			BlockID:        e.BlockID,
   254  			BlockHeight:    fmt.Sprint(e.BlockHeight),
   255  			BlockTimestamp: e.BlockTimestamp.Format(time.RFC3339Nano),
   256  			Events:         events,
   257  		}
   258  	}
   259  
   260  	data, err := json.Marshal(res)
   261  	require.NoError(t, err)
   262  
   263  	return string(data)
   264  }