diff --git a/engine/access/rest/websockets/data_providers/account_statuses_provider.go b/engine/access/rest/websockets/data_providers/account_statuses_provider.go index 9acf16f2f81..698973b1108 100644 --- a/engine/access/rest/websockets/data_providers/account_statuses_provider.go +++ b/engine/access/rest/websockets/data_providers/account_statuses_provider.go @@ -117,8 +117,11 @@ func (p *AccountStatusesDataProvider) handleResponse() func(accountStatusesRespo return status.Errorf(codes.Internal, "message index already incremented to %d", messageIndex.Value()) } - var response models.AccountStatusesResponse - response.Build(accountStatusesResponse, index) + var accountStatusesPayload models.AccountStatusesResponse + accountStatusesPayload.Build(accountStatusesResponse, index) + + var response models.BaseDataProvidersResponse + response.Build(p.ID(), p.Topic(), &accountStatusesPayload) p.send <- &response diff --git a/engine/access/rest/websockets/data_providers/account_statuses_provider_test.go b/engine/access/rest/websockets/data_providers/account_statuses_provider_test.go index 7ff14ea8597..641fba5116c 100644 --- a/engine/access/rest/websockets/data_providers/account_statuses_provider_test.go +++ b/engine/access/rest/websockets/data_providers/account_statuses_provider_test.go @@ -139,25 +139,40 @@ func (s *AccountStatusesProviderSuite) subscribeAccountStatusesDataProviderTestC // requireAccountStatuses ensures that the received account statuses information matches the expected data. func (s *AccountStatusesProviderSuite) requireAccountStatuses(actual interface{}, expected interface{}) { - expectedResponse, ok := expected.(*models.AccountStatusesResponse) - require.True(s.T(), ok, "Expected *models.AccountStatusesResponse, got %T", expected) + expectedResponse, expectedResponsePayload := extractPayload[*models.AccountStatusesResponse](s.T(), expected) + actualResponse, actualResponsePayload := extractPayload[*models.AccountStatusesResponse](s.T(), actual) - actualResponse, ok := actual.(*models.AccountStatusesResponse) - require.True(s.T(), ok, "Expected *models.AccountStatusesResponse, got %T", actual) + require.Equal(s.T(), expectedResponsePayload.BlockID, actualResponsePayload.BlockID) + require.Equal(s.T(), len(expectedResponsePayload.AccountEvents), len(actualResponsePayload.AccountEvents)) + require.Equal(s.T(), expectedResponsePayload.MessageIndex, actualResponsePayload.MessageIndex) + require.Equal(s.T(), expectedResponsePayload.Height, actualResponsePayload.Height) + require.Equal(s.T(), expectedResponse.Topic, actualResponse.Topic) - require.Equal(s.T(), expectedResponse.BlockID, actualResponse.BlockID) - require.Equal(s.T(), len(expectedResponse.AccountEvents), len(actualResponse.AccountEvents)) - require.Equal(s.T(), expectedResponse.MessageIndex, actualResponse.MessageIndex) - require.Equal(s.T(), expectedResponse.Height, actualResponse.Height) - - for key, expectedEvents := range expectedResponse.AccountEvents { - actualEvents, ok := actualResponse.AccountEvents[key] + for key, expectedEvents := range expectedResponsePayload.AccountEvents { + actualEvents, ok := actualResponsePayload.AccountEvents[key] require.True(s.T(), ok, "Missing key in actual AccountEvents: %s", key) s.Require().Equal(expectedEvents, actualEvents, "Mismatch for key: %s", key) } } +// expectedAccountStatusesResponses creates the expected responses for the provided events and backend responses. +func (s *AccountStatusesProviderSuite) expectedAccountStatusesResponses(backendResponses []*backend.AccountStatusesResponse) []interface{} { + expectedResponses := make([]interface{}, len(backendResponses)) + + for i, resp := range backendResponses { + var expectedResponsePayload models.AccountStatusesResponse + expectedResponsePayload.Build(resp, uint64(i)) + + expectedResponses[i] = &models.BaseDataProvidersResponse{ + Topic: AccountStatusesTopic, + Payload: &expectedResponsePayload, + } + } + + return expectedResponses +} + // TestAccountStatusesDataProvider_InvalidArguments tests the behavior of the account statuses data provider // when invalid arguments are provided. It verifies that appropriate errors are returned // for missing or conflicting arguments. @@ -254,9 +269,10 @@ func (s *AccountStatusesProviderSuite) TestMessageIndexAccountStatusesProviderRe var responses []*models.AccountStatusesResponse for i := 0; i < accountStatusesCount; i++ { res := <-send - accountStatusesRes, ok := res.(*models.AccountStatusesResponse) - s.Require().True(ok, "Expected *models.AccountStatusesResponse, got %T", res) - responses = append(responses, accountStatusesRes) + + _, accStatusesResponsePayload := extractPayload[*models.AccountStatusesResponse](s.T(), res) + + responses = append(responses, accStatusesResponsePayload) } // Wait for the provider goroutine to finish @@ -289,17 +305,3 @@ func (s *AccountStatusesProviderSuite) backendAccountStatusesResponses(events [] return responses } - -// expectedAccountStatusesResponses creates the expected responses for the provided events and backend responses. -func (s *AccountStatusesProviderSuite) expectedAccountStatusesResponses(backendResponses []*backend.AccountStatusesResponse) []interface{} { - expectedResponses := make([]interface{}, len(backendResponses)) - - for i, resp := range backendResponses { - var expectedResponse models.AccountStatusesResponse - expectedResponse.Build(resp, uint64(i)) - - expectedResponses[i] = &expectedResponse - } - - return expectedResponses -} diff --git a/engine/access/rest/websockets/data_providers/block_digests_provider.go b/engine/access/rest/websockets/data_providers/block_digests_provider.go index 12d46daf03f..21ee91282a9 100644 --- a/engine/access/rest/websockets/data_providers/block_digests_provider.go +++ b/engine/access/rest/websockets/data_providers/block_digests_provider.go @@ -66,9 +66,14 @@ func (p *BlockDigestsDataProvider) Run() error { var block models.BlockDigest block.Build(b) - return &models.BlockDigestMessageResponse{ - Block: &block, - }, nil + var response models.BaseDataProvidersResponse + response.Build( + p.ID(), + p.Topic(), + &block, + ) + + return &response, nil }), ) } diff --git a/engine/access/rest/websockets/data_providers/block_digests_provider_test.go b/engine/access/rest/websockets/data_providers/block_digests_provider_test.go index 395e57dfa04..c220217e724 100644 --- a/engine/access/rest/websockets/data_providers/block_digests_provider_test.go +++ b/engine/access/rest/websockets/data_providers/block_digests_provider_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/onflow/flow-go/engine/access/rest/common/parser" @@ -28,27 +27,23 @@ func (s *BlockDigestsProviderSuite) SetupTest() { s.BlocksProviderSuite.SetupTest() } -// TestBlockDigestsDataProvider_InvalidArguments tests the behavior of the block digests data provider -// when invalid arguments are provided. It verifies that appropriate errors are returned -// for missing or conflicting arguments. -// This test covers the test cases: -// 1. Missing 'block_status' argument. -// 2. Invalid 'block_status' argument. -// 3. Providing both 'start_block_id' and 'start_block_height' simultaneously. -func (s *BlockDigestsProviderSuite) TestBlockDigestsDataProvider_InvalidArguments() { - ctx := context.Background() - send := make(chan interface{}) - - topic := BlockDigestsTopic - - for _, test := range s.invalidArgumentsTestCases() { - s.Run(test.name, func() { - provider, err := NewBlockDigestsDataProvider(ctx, s.log, s.api, "dummy-id", topic, test.arguments, send) - s.Require().Nil(provider) - s.Require().Error(err) - s.Require().Contains(err.Error(), test.expectedErrorMsg) - }) - } +// TestBlockDigestsDataProvider_HappyPath tests the behavior of the block digests data provider +// when it is configured correctly and operating under normal conditions. It +// validates that block digests are correctly streamed to the channel and ensures +// no unexpected errors occur. +func (s *BlockDigestsProviderSuite) TestBlockDigestsDataProvider_HappyPath() { + testHappyPath( + s.T(), + BlockDigestsTopic, + s.factory, + s.validBlockDigestsArgumentsTestCases(), + func(dataChan chan interface{}) { + for _, block := range s.blocks { + dataChan <- flow.NewBlockDigest(block.Header.ID(), block.Header.Height, block.Header.Timestamp) + } + }, + s.requireBlockDigest, + ) } // validBlockDigestsArgumentsTestCases defines test happy cases for block digests data providers. @@ -61,7 +56,10 @@ func (s *BlockDigestsProviderSuite) validBlockDigestsArgumentsTestCases() []test var block models.BlockDigest block.Build(blockDigest) - expectedResponses[i] = &models.BlockDigestMessageResponse{Block: &block} + expectedResponses[i] = &models.BaseDataProvidersResponse{ + Topic: BlockDigestsTopic, + Payload: &block, + } } return []testType{ @@ -114,32 +112,34 @@ func (s *BlockDigestsProviderSuite) validBlockDigestsArgumentsTestCases() []test } } -// TestBlockDigestsDataProvider_HappyPath tests the behavior of the block digests data provider -// when it is configured correctly and operating under normal conditions. It -// validates that block digests are correctly streamed to the channel and ensures -// no unexpected errors occur. -func (s *BlockDigestsProviderSuite) TestBlockDigestsDataProvider_HappyPath() { - testHappyPath( - s.T(), - BlockDigestsTopic, - s.factory, - s.validBlockDigestsArgumentsTestCases(), - func(dataChan chan interface{}) { - for _, block := range s.blocks { - dataChan <- flow.NewBlockDigest(block.Header.ID(), block.Header.Height, block.Header.Timestamp) - } - }, - s.requireBlockDigest, - ) +// requireBlockDigest ensures that the received block header information matches the expected data. +func (s *BlocksProviderSuite) requireBlockDigest(actual interface{}, expected interface{}) { + expectedResponse, expectedResponsePayload := extractPayload[*models.BlockDigest](s.T(), expected) + actualResponse, actualResponsePayload := extractPayload[*models.BlockDigest](s.T(), actual) + + s.Require().Equal(expectedResponse.Topic, actualResponse.Topic) + s.Require().Equal(expectedResponsePayload, actualResponsePayload) } -// requireBlockHeaders ensures that the received block header information matches the expected data. -func (s *BlocksProviderSuite) requireBlockDigest(actual interface{}, expected interface{}) { - actualResponse, ok := actual.(*models.BlockDigestMessageResponse) - require.True(s.T(), ok, "unexpected response type: %T", actual) +// TestBlockDigestsDataProvider_InvalidArguments tests the behavior of the block digests data provider +// when invalid arguments are provided. It verifies that appropriate errors are returned +// for missing or conflicting arguments. +// This test covers the test cases: +// 1. Missing 'block_status' argument. +// 2. Invalid 'block_status' argument. +// 3. Providing both 'start_block_id' and 'start_block_height' simultaneously. +func (s *BlockDigestsProviderSuite) TestBlockDigestsDataProvider_InvalidArguments() { + ctx := context.Background() + send := make(chan interface{}) - expectedResponse, ok := expected.(*models.BlockDigestMessageResponse) - require.True(s.T(), ok, "unexpected response type: %T", expected) + topic := BlockDigestsTopic - s.Require().Equal(expectedResponse.Block, actualResponse.Block) + for _, test := range s.invalidArgumentsTestCases() { + s.Run(test.name, func() { + provider, err := NewBlockDigestsDataProvider(ctx, s.log, s.api, "dummy-id", topic, test.arguments, send) + s.Require().Nil(provider) + s.Require().Error(err) + s.Require().Contains(err.Error(), test.expectedErrorMsg) + }) + } } diff --git a/engine/access/rest/websockets/data_providers/block_headers_provider.go b/engine/access/rest/websockets/data_providers/block_headers_provider.go index d6b39d17082..6f4efc20a90 100644 --- a/engine/access/rest/websockets/data_providers/block_headers_provider.go +++ b/engine/access/rest/websockets/data_providers/block_headers_provider.go @@ -67,9 +67,14 @@ func (p *BlockHeadersDataProvider) Run() error { var header commonmodels.BlockHeader header.Build(h) - return &models.BlockHeaderMessageResponse{ - Header: &header, - }, nil + var response models.BaseDataProvidersResponse + response.Build( + p.ID(), + p.Topic(), + &header, + ) + + return &response, nil }), ) } diff --git a/engine/access/rest/websockets/data_providers/block_headers_provider_test.go b/engine/access/rest/websockets/data_providers/block_headers_provider_test.go index 8834d21d498..ec0a222c93d 100644 --- a/engine/access/rest/websockets/data_providers/block_headers_provider_test.go +++ b/engine/access/rest/websockets/data_providers/block_headers_provider_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" commonmodels "github.com/onflow/flow-go/engine/access/rest/common/models" @@ -29,27 +28,23 @@ func (s *BlockHeadersProviderSuite) SetupTest() { s.BlocksProviderSuite.SetupTest() } -// TestBlockHeadersDataProvider_InvalidArguments tests the behavior of the block headers data provider -// when invalid arguments are provided. It verifies that appropriate errors are returned -// for missing or conflicting arguments. -// This test covers the test cases: -// 1. Missing 'block_status' argument. -// 2. Invalid 'block_status' argument. -// 3. Providing both 'start_block_id' and 'start_block_height' simultaneously. -func (s *BlockHeadersProviderSuite) TestBlockHeadersDataProvider_InvalidArguments() { - ctx := context.Background() - send := make(chan interface{}) - - topic := BlockHeadersTopic - - for _, test := range s.invalidArgumentsTestCases() { - s.Run(test.name, func() { - provider, err := NewBlockHeadersDataProvider(ctx, s.log, s.api, "dummy-id", topic, test.arguments, send) - s.Require().Nil(provider) - s.Require().Error(err) - s.Require().Contains(err.Error(), test.expectedErrorMsg) - }) - } +// TestBlockHeadersDataProvider_HappyPath tests the behavior of the block headers data provider +// when it is configured correctly and operating under normal conditions. It +// validates that block headers are correctly streamed to the channel and ensures +// no unexpected errors occur. +func (s *BlockHeadersProviderSuite) TestBlockHeadersDataProvider_HappyPath() { + testHappyPath( + s.T(), + BlockHeadersTopic, + s.factory, + s.validBlockHeadersArgumentsTestCases(), + func(dataChan chan interface{}) { + for _, block := range s.blocks { + dataChan <- block.Header + } + }, + s.requireBlockHeader, + ) } // validBlockHeadersArgumentsTestCases defines test happy cases for block headers data providers. @@ -60,7 +55,10 @@ func (s *BlockHeadersProviderSuite) validBlockHeadersArgumentsTestCases() []test var header commonmodels.BlockHeader header.Build(b.Header) - expectedResponses[i] = &models.BlockHeaderMessageResponse{Header: &header} + expectedResponses[i] = &models.BaseDataProvidersResponse{ + Topic: BlockHeadersTopic, + Payload: &header, + } } return []testType{ @@ -113,32 +111,34 @@ func (s *BlockHeadersProviderSuite) validBlockHeadersArgumentsTestCases() []test } } -// TestBlockHeadersDataProvider_HappyPath tests the behavior of the block headers data provider -// when it is configured correctly and operating under normal conditions. It -// validates that block headers are correctly streamed to the channel and ensures -// no unexpected errors occur. -func (s *BlockHeadersProviderSuite) TestBlockHeadersDataProvider_HappyPath() { - testHappyPath( - s.T(), - BlockHeadersTopic, - s.factory, - s.validBlockHeadersArgumentsTestCases(), - func(dataChan chan interface{}) { - for _, block := range s.blocks { - dataChan <- block.Header - } - }, - s.requireBlockHeader, - ) -} - // requireBlockHeaders ensures that the received block header information matches the expected data. func (s *BlockHeadersProviderSuite) requireBlockHeader(actual interface{}, expected interface{}) { - actualResponse, ok := actual.(*models.BlockHeaderMessageResponse) - require.True(s.T(), ok, "unexpected response type: %T", actual) + expectedResponse, expectedResponsePayload := extractPayload[*commonmodels.BlockHeader](s.T(), expected) + actualResponse, actualResponsePayload := extractPayload[*commonmodels.BlockHeader](s.T(), actual) - expectedResponse, ok := expected.(*models.BlockHeaderMessageResponse) - require.True(s.T(), ok, "unexpected response type: %T", expected) + s.Require().Equal(expectedResponse.Topic, actualResponse.Topic) + s.Require().Equal(expectedResponsePayload, actualResponsePayload) +} - s.Require().Equal(expectedResponse.Header, actualResponse.Header) +// TestBlockHeadersDataProvider_InvalidArguments tests the behavior of the block headers data provider +// when invalid arguments are provided. It verifies that appropriate errors are returned +// for missing or conflicting arguments. +// This test covers the test cases: +// 1. Missing 'block_status' argument. +// 2. Invalid 'block_status' argument. +// 3. Providing both 'start_block_id' and 'start_block_height' simultaneously. +func (s *BlockHeadersProviderSuite) TestBlockHeadersDataProvider_InvalidArguments() { + ctx := context.Background() + send := make(chan interface{}) + + topic := BlockHeadersTopic + + for _, test := range s.invalidArgumentsTestCases() { + s.Run(test.name, func() { + provider, err := NewBlockHeadersDataProvider(ctx, s.log, s.api, "dummy-id", topic, test.arguments, send) + s.Require().Nil(provider) + s.Require().Error(err) + s.Require().Contains(err.Error(), test.expectedErrorMsg) + }) + } } diff --git a/engine/access/rest/websockets/data_providers/blocks_provider.go b/engine/access/rest/websockets/data_providers/blocks_provider.go index 00f9e6120dc..46bbdd89fd8 100644 --- a/engine/access/rest/websockets/data_providers/blocks_provider.go +++ b/engine/access/rest/websockets/data_providers/blocks_provider.go @@ -86,9 +86,10 @@ func (p *BlocksDataProvider) Run() error { return nil, fmt.Errorf("failed to build block response :%w", err) } - return &models.BlockMessageResponse{ - Block: &block, - }, nil + var response models.BaseDataProvidersResponse + response.Build(p.ID(), p.Topic(), &block) + + return &response, nil }), ) } diff --git a/engine/access/rest/websockets/data_providers/blocks_provider_test.go b/engine/access/rest/websockets/data_providers/blocks_provider_test.go index 2754572dff7..41ba3bf7a0a 100644 --- a/engine/access/rest/websockets/data_providers/blocks_provider_test.go +++ b/engine/access/rest/websockets/data_providers/blocks_provider_test.go @@ -9,7 +9,6 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" accessmock "github.com/onflow/flow-go/access/mock" @@ -82,61 +81,34 @@ func (s *BlocksProviderSuite) SetupTest() { s.Require().NotNil(s.factory) } -// invalidArgumentsTestCases returns a list of test cases with invalid argument combinations -// for testing the behavior of block, block headers, block digests data providers. Each test case includes a name, -// a set of input arguments, and the expected error message that should be returned. -// -// The test cases cover scenarios such as: -// 1. Missing the required 'block_status' argument. -// 2. Providing an unknown or invalid 'block_status' value. -// 3. Supplying both 'start_block_id' and 'start_block_height' simultaneously, which is not allowed. -func (s *BlocksProviderSuite) invalidArgumentsTestCases() []testErrType { - return []testErrType{ - { - name: "missing 'block_status' argument", - arguments: models.Arguments{ - "start_block_id": s.rootBlock.ID().String(), - }, - expectedErrorMsg: "'block_status' must be provided", - }, - { - name: "unknown 'block_status' argument", - arguments: models.Arguments{ - "block_status": unknownBlockStatus, - }, - expectedErrorMsg: fmt.Sprintf("invalid 'block_status', must be '%s' or '%s'", parser.Finalized, parser.Sealed), - }, - { - name: "provide both 'start_block_id' and 'start_block_height' arguments", - arguments: models.Arguments{ - "block_status": parser.Finalized, - "start_block_id": s.rootBlock.ID().String(), - "start_block_height": fmt.Sprintf("%d", s.rootBlock.Header.Height), - }, - expectedErrorMsg: "can only provide either 'start_block_id' or 'start_block_height'", +// TestBlocksDataProvider_HappyPath tests the behavior of the block data provider +// when it is configured correctly and operating under normal conditions. It +// validates that blocks are correctly streamed to the channel and ensures +// no unexpected errors occur. +func (s *BlocksProviderSuite) TestBlocksDataProvider_HappyPath() { + s.linkGenerator.On("BlockLink", mock.AnythingOfType("flow.Identifier")).Return( + func(id flow.Identifier) (string, error) { + for _, block := range s.blocks { + if block.ID() == id { + return fmt.Sprintf("/v1/blocks/%s", id), nil + } + } + return "", assert.AnError }, - } -} - -// TestBlocksDataProvider_InvalidArguments tests the behavior of the block data provider -// when invalid arguments are provided. It verifies that appropriate errors are returned -// for missing or conflicting arguments. -// This test covers the test cases: -// 1. Missing 'block_status' argument. -// 2. Invalid 'block_status' argument. -// 3. Providing both 'start_block_id' and 'start_block_height' simultaneously. -func (s *BlocksProviderSuite) TestBlocksDataProvider_InvalidArguments() { - ctx := context.Background() - send := make(chan interface{}) + ) - for _, test := range s.invalidArgumentsTestCases() { - s.Run(test.name, func() { - provider, err := NewBlocksDataProvider(ctx, s.log, s.api, "dummy-id", nil, BlocksTopic, test.arguments, send) - s.Require().Nil(provider) - s.Require().Error(err) - s.Require().Contains(err.Error(), test.expectedErrorMsg) - }) - } + testHappyPath( + s.T(), + BlocksTopic, + s.factory, + s.validBlockArgumentsTestCases(), + func(dataChan chan interface{}) { + for _, block := range s.blocks { + dataChan <- block + } + }, + s.requireBlock, + ) } // validBlockArgumentsTestCases defines test happy cases for block data providers. @@ -208,45 +180,13 @@ func (s *BlocksProviderSuite) validBlockArgumentsTestCases() []testType { } } -// TestBlocksDataProvider_HappyPath tests the behavior of the block data provider -// when it is configured correctly and operating under normal conditions. It -// validates that blocks are correctly streamed to the channel and ensures -// no unexpected errors occur. -func (s *BlocksProviderSuite) TestBlocksDataProvider_HappyPath() { - s.linkGenerator.On("BlockLink", mock.AnythingOfType("flow.Identifier")).Return( - func(id flow.Identifier) (string, error) { - for _, block := range s.blocks { - if block.ID() == id { - return fmt.Sprintf("/v1/blocks/%s", id), nil - } - } - return "", assert.AnError - }, - ) - - testHappyPath( - s.T(), - BlocksTopic, - s.factory, - s.validBlockArgumentsTestCases(), - func(dataChan chan interface{}) { - for _, block := range s.blocks { - dataChan <- block - } - }, - s.requireBlock, - ) -} - -// requireBlocks ensures that the received block information matches the expected data. +// requireBlock ensures that the received block information matches the expected data. func (s *BlocksProviderSuite) requireBlock(actual interface{}, expected interface{}) { - actualResponse, ok := actual.(*models.BlockMessageResponse) - require.True(s.T(), ok, "unexpected response type: %T", actual) - - expectedResponse, ok := expected.(*models.BlockMessageResponse) - require.True(s.T(), ok, "unexpected response type: %T", expected) + expectedResponse, expectedResponsePayload := extractPayload[*commonmodels.Block](s.T(), expected) + actualResponse, actualResponsePayload := extractPayload[*commonmodels.Block](s.T(), actual) - s.Require().Equal(expectedResponse.Block, actualResponse.Block) + s.Require().Equal(expectedResponse.Topic, actualResponse.Topic) + s.Require().Equal(expectedResponsePayload, actualResponsePayload) } // expectedBlockResponses generates a list of expected block responses for the given blocks. @@ -261,10 +201,68 @@ func (s *BlocksProviderSuite) expectedBlockResponses( err := block.Build(b, nil, s.linkGenerator, status, expand) s.Require().NoError(err) - responses[i] = &models.BlockMessageResponse{ - Block: &block, + responses[i] = &models.BaseDataProvidersResponse{ + Topic: BlocksTopic, + Payload: &block, } } return responses } + +// TestBlocksDataProvider_InvalidArguments tests the behavior of the block data provider +// when invalid arguments are provided. It verifies that appropriate errors are returned +// for missing or conflicting arguments. +// This test covers the test cases: +// 1. Missing 'block_status' argument. +// 2. Invalid 'block_status' argument. +// 3. Providing both 'start_block_id' and 'start_block_height' simultaneously. +func (s *BlocksProviderSuite) TestBlocksDataProvider_InvalidArguments() { + ctx := context.Background() + send := make(chan interface{}) + + for _, test := range s.invalidArgumentsTestCases() { + s.Run(test.name, func() { + provider, err := NewBlocksDataProvider(ctx, s.log, s.api, "dummy-id", nil, BlocksTopic, test.arguments, send) + s.Require().Nil(provider) + s.Require().Error(err) + s.Require().Contains(err.Error(), test.expectedErrorMsg) + }) + } +} + +// invalidArgumentsTestCases returns a list of test cases with invalid argument combinations +// for testing the behavior of block, block headers, block digests data providers. Each test case includes a name, +// a set of input arguments, and the expected error message that should be returned. +// +// The test cases cover scenarios such as: +// 1. Missing the required 'block_status' argument. +// 2. Providing an unknown or invalid 'block_status' value. +// 3. Supplying both 'start_block_id' and 'start_block_height' simultaneously, which is not allowed. +func (s *BlocksProviderSuite) invalidArgumentsTestCases() []testErrType { + return []testErrType{ + { + name: "missing 'block_status' argument", + arguments: models.Arguments{ + "start_block_id": s.rootBlock.ID().String(), + }, + expectedErrorMsg: "'block_status' must be provided", + }, + { + name: "unknown 'block_status' argument", + arguments: models.Arguments{ + "block_status": unknownBlockStatus, + }, + expectedErrorMsg: fmt.Sprintf("invalid 'block_status', must be '%s' or '%s'", parser.Finalized, parser.Sealed), + }, + { + name: "provide both 'start_block_id' and 'start_block_height' arguments", + arguments: models.Arguments{ + "block_status": parser.Finalized, + "start_block_id": s.rootBlock.ID().String(), + "start_block_height": fmt.Sprintf("%d", s.rootBlock.Header.Height), + }, + expectedErrorMsg: "can only provide either 'start_block_id' or 'start_block_height'", + }, + } +} diff --git a/engine/access/rest/websockets/data_providers/events_provider.go b/engine/access/rest/websockets/data_providers/events_provider.go index 1f808dd3e75..d5ff53a844d 100644 --- a/engine/access/rest/websockets/data_providers/events_provider.go +++ b/engine/access/rest/websockets/data_providers/events_provider.go @@ -103,8 +103,12 @@ func (p *EventsDataProvider) handleResponse() func(eventsResponse *backend.Event return fmt.Errorf("message index already incremented to: %d", messageIndex.Value()) } - var response models.EventResponse - response.Build(eventsResponse, index) + var eventsPayload models.EventResponse + eventsPayload.Build(eventsResponse, index) + + var response models.BaseDataProvidersResponse + response.Build(p.ID(), p.Topic(), &eventsPayload) + p.send <- &response return nil diff --git a/engine/access/rest/websockets/data_providers/events_provider_test.go b/engine/access/rest/websockets/data_providers/events_provider_test.go index 4fbe4908ca8..f58a976c3e5 100644 --- a/engine/access/rest/websockets/data_providers/events_provider_test.go +++ b/engine/access/rest/websockets/data_providers/events_provider_test.go @@ -9,7 +9,6 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/onflow/flow-go/engine/access/rest/websockets/models" @@ -139,83 +138,46 @@ func (s *EventsProviderSuite) subscribeEventsDataProviderTestCases(backendRespon // requireEvents ensures that the received event information matches the expected data. func (s *EventsProviderSuite) requireEvents(actual interface{}, expected interface{}) { - expectedResponse, ok := expected.(*models.EventResponse) - require.True(s.T(), ok, "Expected *models.EventResponse, got %T", expected) + expectedResponse, expectedResponsePayload := extractPayload[*models.EventResponse](s.T(), expected) + actualResponse, actualResponsePayload := extractPayload[*models.EventResponse](s.T(), actual) - actualResponse, ok := actual.(*models.EventResponse) - require.True(s.T(), ok, "Expected *models.EventResponse, got %T", actual) - - s.Require().ElementsMatch(expectedResponse.Events, actualResponse.Events) - s.Require().Equal(expectedResponse.MessageIndex, actualResponse.MessageIndex) + s.Require().Equal(expectedResponse.Topic, actualResponse.Topic) + s.Require().Equal(expectedResponsePayload.MessageIndex, actualResponsePayload.MessageIndex) + s.Require().ElementsMatch(expectedResponsePayload.Events, actualResponsePayload.Events) } -// invalidArgumentsTestCases returns a list of test cases with invalid argument combinations -// for testing the behavior of events data providers. Each test case includes a name, -// a set of input arguments, and the expected error message that should be returned. -// -// The test cases cover scenarios such as: -// 1. Supplying both 'start_block_id' and 'start_block_height' simultaneously, which is not allowed. -// 2. Providing invalid 'start_block_id' value. -// 3. Providing invalid 'start_block_height' value. -func invalidArgumentsTestCases() []testErrType { - return []testErrType{ - { - name: "provide both 'start_block_id' and 'start_block_height' arguments", - arguments: models.Arguments{ - "start_block_id": unittest.BlockFixture().ID().String(), - "start_block_height": fmt.Sprintf("%d", unittest.BlockFixture().Header.Height), - }, - expectedErrorMsg: "can only provide either 'start_block_id' or 'start_block_height'", - }, - { - name: "invalid 'start_block_id' argument", - arguments: map[string]interface{}{ - "start_block_id": "invalid_block_id", - }, - expectedErrorMsg: "invalid ID format", - }, - { - name: "invalid 'start_block_height' argument", - arguments: map[string]interface{}{ - "start_block_height": "-1", - }, - expectedErrorMsg: "value must be an unsigned 64 bit integer", - }, +// backendEventsResponses creates backend events responses based on the provided events. +func (s *EventsProviderSuite) backendEventsResponses(events []flow.Event) []*backend.EventsResponse { + responses := make([]*backend.EventsResponse, len(events)) + + for i := range events { + responses[i] = &backend.EventsResponse{ + Height: s.rootBlock.Header.Height, + BlockID: s.rootBlock.ID(), + Events: events, + BlockTimestamp: s.rootBlock.Header.Timestamp, + } } + + return responses } -// TestEventsDataProvider_InvalidArguments tests the behavior of the event data provider -// when invalid arguments are provided. It verifies that appropriate errors are returned -// for missing or conflicting arguments. -// This test covers the test cases: -// 1. Providing both 'start_block_id' and 'start_block_height' simultaneously. -// 2. Invalid 'start_block_id' argument. -// 3. Invalid 'start_block_height' argument. -func (s *EventsProviderSuite) TestEventsDataProvider_InvalidArguments() { - ctx := context.Background() - send := make(chan interface{}) +// expectedEventsResponses creates the expected responses for the provided backend responses. +func (s *EventsProviderSuite) expectedEventsResponses( + backendResponses []*backend.EventsResponse, +) []interface{} { + expectedResponses := make([]interface{}, len(backendResponses)) - topic := EventsTopic + for i, resp := range backendResponses { + var expectedResponsePayload models.EventResponse + expectedResponsePayload.Build(resp, uint64(i)) - for _, test := range invalidArgumentsTestCases() { - s.Run(test.name, func() { - provider, err := NewEventsDataProvider( - ctx, - s.log, - s.api, - "dummy-id", - topic, - test.arguments, - send, - s.chain, - state_stream.DefaultEventFilterConfig, - subscription.DefaultHeartbeatInterval, - ) - s.Require().Nil(provider) - s.Require().Error(err) - s.Require().Contains(err.Error(), test.expectedErrorMsg) - }) + expectedResponses[i] = &models.BaseDataProvidersResponse{ + Topic: EventsTopic, + Payload: &expectedResponsePayload, + } } + return expectedResponses } // TestMessageIndexEventProviderResponse_HappyPath tests that MessageIndex values in response are strictly increasing. @@ -283,15 +245,16 @@ func (s *EventsProviderSuite) TestMessageIndexEventProviderResponse_HappyPath() var responses []*models.EventResponse for i := 0; i < eventsCount; i++ { res := <-send - eventRes, ok := res.(*models.EventResponse) - s.Require().True(ok, "Expected *models.EventResponse, got %T", res) - responses = append(responses, eventRes) + + _, eventResData := extractPayload[*models.EventResponse](s.T(), res) + + responses = append(responses, eventResData) } // Wait for the provider goroutine to finish unittest.RequireCloseBefore(s.T(), done, time.Second, "provider failed to stop") - // Verifying that indices are starting from 1 + // Verifying that indices are starting from 0 s.Require().Equal(uint64(0), responses[0].MessageIndex, "Expected MessageIndex to start with 0") // Verifying that indices are strictly increasing @@ -302,33 +265,71 @@ func (s *EventsProviderSuite) TestMessageIndexEventProviderResponse_HappyPath() } } -// backendEventsResponses creates backend events responses based on the provided events. -func (s *EventsProviderSuite) backendEventsResponses(events []flow.Event) []*backend.EventsResponse { - responses := make([]*backend.EventsResponse, len(events)) +// TestEventsDataProvider_InvalidArguments tests the behavior of the event data provider +// when invalid arguments are provided. It verifies that appropriate errors are returned +// for missing or conflicting arguments. +// This test covers the test cases: +// 1. Providing both 'start_block_id' and 'start_block_height' simultaneously. +// 2. Invalid 'start_block_id' argument. +// 3. Invalid 'start_block_height' argument. +func (s *EventsProviderSuite) TestEventsDataProvider_InvalidArguments() { + ctx := context.Background() + send := make(chan interface{}) - for i := range events { - responses[i] = &backend.EventsResponse{ - Height: s.rootBlock.Header.Height, - BlockID: s.rootBlock.ID(), - Events: events, - BlockTimestamp: s.rootBlock.Header.Timestamp, - } - } + topic := EventsTopic - return responses + for _, test := range invalidArgumentsTestCases() { + s.Run(test.name, func() { + provider, err := NewEventsDataProvider( + ctx, + s.log, + s.api, + "dummy-id", + topic, + test.arguments, + send, + s.chain, + state_stream.DefaultEventFilterConfig, + subscription.DefaultHeartbeatInterval, + ) + s.Require().Nil(provider) + s.Require().Error(err) + s.Require().Contains(err.Error(), test.expectedErrorMsg) + }) + } } -// expectedEventsResponses creates the expected responses for the provided backend responses. -func (s *EventsProviderSuite) expectedEventsResponses( - backendResponses []*backend.EventsResponse, -) []interface{} { - expectedResponses := make([]interface{}, len(backendResponses)) - - for i, resp := range backendResponses { - var expectedResponse models.EventResponse - expectedResponse.Build(resp, uint64(i)) - - expectedResponses[i] = &expectedResponse +// invalidArgumentsTestCases returns a list of test cases with invalid argument combinations +// for testing the behavior of events data providers. Each test case includes a name, +// a set of input arguments, and the expected error message that should be returned. +// +// The test cases cover scenarios such as: +// 1. Supplying both 'start_block_id' and 'start_block_height' simultaneously, which is not allowed. +// 2. Providing invalid 'start_block_id' value. +// 3. Providing invalid 'start_block_height' value. +func invalidArgumentsTestCases() []testErrType { + return []testErrType{ + { + name: "provide both 'start_block_id' and 'start_block_height' arguments", + arguments: models.Arguments{ + "start_block_id": unittest.BlockFixture().ID().String(), + "start_block_height": fmt.Sprintf("%d", unittest.BlockFixture().Header.Height), + }, + expectedErrorMsg: "can only provide either 'start_block_id' or 'start_block_height'", + }, + { + name: "invalid 'start_block_id' argument", + arguments: map[string]interface{}{ + "start_block_id": "invalid_block_id", + }, + expectedErrorMsg: "invalid ID format", + }, + { + name: "invalid 'start_block_height' argument", + arguments: map[string]interface{}{ + "start_block_height": "-1", + }, + expectedErrorMsg: "value must be an unsigned 64 bit integer", + }, } - return expectedResponses } diff --git a/engine/access/rest/websockets/data_providers/send_and_get_transaction_statuses_provider.go b/engine/access/rest/websockets/data_providers/send_and_get_transaction_statuses_provider.go index cf1e54919ec..8185c40953d 100644 --- a/engine/access/rest/websockets/data_providers/send_and_get_transaction_statuses_provider.go +++ b/engine/access/rest/websockets/data_providers/send_and_get_transaction_statuses_provider.go @@ -99,8 +99,11 @@ func (p *SendAndGetTransactionStatusesDataProvider) handleResponse() func(txResu return status.Errorf(codes.Internal, "message index already incremented to %d", messageIndex.Value()) } - var response models.TransactionStatusesResponse - response.Build(p.linkGenerator, txResults[i], index) + var txStatusesPayload models.TransactionStatusesResponse + txStatusesPayload.Build(p.linkGenerator, txResults[i], index) + + var response models.BaseDataProvidersResponse + response.Build(p.ID(), p.Topic(), &txStatusesPayload) p.send <- &response } diff --git a/engine/access/rest/websockets/data_providers/send_and_get_transaction_statuses_provider_test.go b/engine/access/rest/websockets/data_providers/send_and_get_transaction_statuses_provider_test.go index 0907f70c674..353164eb723 100644 --- a/engine/access/rest/websockets/data_providers/send_and_get_transaction_statuses_provider_test.go +++ b/engine/access/rest/websockets/data_providers/send_and_get_transaction_statuses_provider_test.go @@ -73,7 +73,7 @@ func (s *TransactionStatusesProviderSuite) TestSendTransactionStatusesDataProvid ) backendResponse := backendTransactionStatusesResponse(s.rootBlock) - expectedResponse := s.expectedTransactionStatusesResponses(backendResponse) + expectedResponse := s.expectedTransactionStatusesResponses(backendResponse, SendAndGetTransactionStatusesTopic) sendTxStatutesTestCases := []testType{ { @@ -108,16 +108,14 @@ func (s *TransactionStatusesProviderSuite) TestSendTransactionStatusesDataProvid // requireTransactionStatuses ensures that the received transaction statuses information matches the expected data. func (s *SendTransactionStatusesProviderSuite) requireTransactionStatuses( - v interface{}, - expectedResponse interface{}, + actual interface{}, + expected interface{}, ) { - expectedTxStatusesResponse, ok := expectedResponse.(*models.TransactionStatusesResponse) - require.True(s.T(), ok, "expected *models.TransactionStatusesResponse, got %T", expectedResponse) + expectedResponse, expectedResponsePayload := extractPayload[*models.TransactionStatusesResponse](s.T(), expected) + actualResponse, actualResponsePayload := extractPayload[*models.TransactionStatusesResponse](s.T(), actual) - actualResponse, ok := v.(*models.TransactionStatusesResponse) - require.True(s.T(), ok, "expected *models.TransactionStatusesResponse, got %T", v) - - require.Equal(s.T(), expectedTxStatusesResponse.TransactionResult.BlockId, actualResponse.TransactionResult.BlockId) + require.Equal(s.T(), expectedResponse.Topic, actualResponse.Topic) + require.Equal(s.T(), expectedResponsePayload.TransactionResult.BlockId, actualResponsePayload.TransactionResult.BlockId) } // TestSendTransactionStatusesDataProvider_InvalidArguments tests the behavior of the send transaction statuses data provider diff --git a/engine/access/rest/websockets/data_providers/transaction_statuses_provider.go b/engine/access/rest/websockets/data_providers/transaction_statuses_provider.go index 4bdf6a53359..48e9f0ac530 100644 --- a/engine/access/rest/websockets/data_providers/transaction_statuses_provider.go +++ b/engine/access/rest/websockets/data_providers/transaction_statuses_provider.go @@ -110,8 +110,11 @@ func (p *TransactionStatusesDataProvider) handleResponse() func(txResults []*acc return status.Errorf(codes.Internal, "message index already incremented to %d", messageIndex.Value()) } - var response models.TransactionStatusesResponse - response.Build(p.linkGenerator, txResults[i], index) + var txStatusesPayload models.TransactionStatusesResponse + txStatusesPayload.Build(p.linkGenerator, txResults[i], index) + + var response models.BaseDataProvidersResponse + response.Build(p.ID(), p.Topic(), &txStatusesPayload) p.send <- &response } diff --git a/engine/access/rest/websockets/data_providers/transaction_statuses_provider_test.go b/engine/access/rest/websockets/data_providers/transaction_statuses_provider_test.go index d28f6ae671c..82667d288b9 100644 --- a/engine/access/rest/websockets/data_providers/transaction_statuses_provider_test.go +++ b/engine/access/rest/websockets/data_providers/transaction_statuses_provider_test.go @@ -91,7 +91,7 @@ func (s *TransactionStatusesProviderSuite) TestTransactionStatusesDataProvider_H } func (s *TransactionStatusesProviderSuite) subscribeTransactionStatusesDataProviderTestCases(backendResponses []*access.TransactionResult) []testType { - expectedResponses := s.expectedTransactionStatusesResponses(backendResponses) + expectedResponses := s.expectedTransactionStatusesResponses(backendResponses, TransactionStatusesTopic) return []testType{ { @@ -147,102 +147,57 @@ func (s *TransactionStatusesProviderSuite) requireTransactionStatuses( actual interface{}, expected interface{}, ) { - expectedTxStatusesResponse, ok := expected.(*models.TransactionStatusesResponse) - require.True(s.T(), ok, "expected *models.TransactionStatusesResponse, got %T", expected) + expectedResponse, expectedResponsePayload := extractPayload[*models.TransactionStatusesResponse](s.T(), expected) + actualResponse, actualResponsePayload := extractPayload[*models.TransactionStatusesResponse](s.T(), actual) - actualResponse, ok := actual.(*models.TransactionStatusesResponse) - require.True(s.T(), ok, "expected *models.TransactionStatusesResponse, got %T", actual) + require.Equal(s.T(), expectedResponse.Topic, actualResponse.Topic) + require.Equal(s.T(), expectedResponsePayload.TransactionResult.BlockId, actualResponsePayload.TransactionResult.BlockId) +} - require.Equal(s.T(), expectedTxStatusesResponse.TransactionResult.BlockId, actualResponse.TransactionResult.BlockId) +func backendTransactionStatusesResponse(block flow.Block) []*access.TransactionResult { + id := unittest.IdentifierFixture() + cid := unittest.IdentifierFixture() + txr := access.TransactionResult{ + Status: flow.TransactionStatusSealed, + StatusCode: 10, + Events: []flow.Event{ + unittest.EventFixture(flow.EventAccountCreated, 1, 0, id, 200), + }, + ErrorMessage: "", + BlockID: block.ID(), + CollectionID: cid, + BlockHeight: block.Header.Height, + } + + var expectedTxResultsResponses []*access.TransactionResult + + for i := 0; i < 2; i++ { + expectedTxResultsResponses = append(expectedTxResultsResponses, &txr) + } + + return expectedTxResultsResponses } // expectedTransactionStatusesResponses creates the expected responses for the provided backend responses. func (s *TransactionStatusesProviderSuite) expectedTransactionStatusesResponses( backendResponses []*access.TransactionResult, + topic string, ) []interface{} { expectedResponses := make([]interface{}, len(backendResponses)) for i, resp := range backendResponses { - var expectedResponse models.TransactionStatusesResponse - expectedResponse.Build(s.linkGenerator, resp, uint64(i)) + var expectedResponsePayload models.TransactionStatusesResponse + expectedResponsePayload.Build(s.linkGenerator, resp, uint64(i)) - expectedResponses[i] = &expectedResponse + expectedResponses[i] = &models.BaseDataProvidersResponse{ + Topic: topic, + Payload: &expectedResponsePayload, + } } return expectedResponses } -// TestTransactionStatusesDataProvider_InvalidArguments tests the behavior of the transaction statuses data provider -// when invalid arguments are provided. It verifies that appropriate errors are returned -// for missing or conflicting arguments. -func (s *TransactionStatusesProviderSuite) TestTransactionStatusesDataProvider_InvalidArguments() { - ctx := context.Background() - send := make(chan interface{}) - - topic := TransactionStatusesTopic - - for _, test := range invalidTransactionStatusesArgumentsTestCases() { - s.Run(test.name, func() { - provider, err := NewTransactionStatusesDataProvider( - ctx, - s.log, - s.api, - "dummy-id", - s.linkGenerator, - topic, - test.arguments, - send, - ) - s.Require().Nil(provider) - s.Require().Error(err) - s.Require().Contains(err.Error(), test.expectedErrorMsg) - }) - } -} - -// invalidTransactionStatusesArgumentsTestCases returns a list of test cases with invalid argument combinations -// for testing the behavior of transaction statuses data providers. Each test case includes a name, -// a set of input arguments, and the expected error message that should be returned. -// -// The test cases cover scenarios such as: -// 1. Providing both 'start_block_id' and 'start_block_height' simultaneously. -// 2. Providing invalid 'tx_id' value. -// 3. Providing invalid 'start_block_id' value. -// 4. Invalid 'start_block_id' argument. -func invalidTransactionStatusesArgumentsTestCases() []testErrType { - return []testErrType{ - { - name: "provide both 'start_block_id' and 'start_block_height' arguments", - arguments: models.Arguments{ - "start_block_id": unittest.BlockFixture().ID().String(), - "start_block_height": fmt.Sprintf("%d", unittest.BlockFixture().Header.Height), - }, - expectedErrorMsg: "can only provide either 'start_block_id' or 'start_block_height'", - }, - { - name: "invalid 'tx_id' argument", - arguments: map[string]interface{}{ - "tx_id": "invalid_tx_id", - }, - expectedErrorMsg: "invalid ID format", - }, - { - name: "invalid 'start_block_id' argument", - arguments: map[string]interface{}{ - "start_block_id": "invalid_block_id", - }, - expectedErrorMsg: "invalid ID format", - }, - { - name: "invalid 'start_block_height' argument", - arguments: map[string]interface{}{ - "start_block_height": "-1", - }, - expectedErrorMsg: "value must be an unsigned 64 bit integer", - }, - } -} - // TestMessageIndexTransactionStatusesProviderResponse_HappyPath tests that MessageIndex values in response are strictly increasing. func (s *TransactionStatusesProviderSuite) TestMessageIndexTransactionStatusesProviderResponse_HappyPath() { ctx := context.Background() @@ -321,9 +276,10 @@ func (s *TransactionStatusesProviderSuite) TestMessageIndexTransactionStatusesPr var responses []*models.TransactionStatusesResponse for i := 0; i < txStatusesCount; i++ { res := <-send - txStatusesRes, ok := res.(*models.TransactionStatusesResponse) - s.Require().True(ok, "Expected *models.TransactionStatusesResponse, got %T", res) - responses = append(responses, txStatusesRes) + + _, txStatusesResData := extractPayload[*models.TransactionStatusesResponse](s.T(), res) + + responses = append(responses, txStatusesResData) } // Wait for the provider goroutine to finish @@ -340,26 +296,73 @@ func (s *TransactionStatusesProviderSuite) TestMessageIndexTransactionStatusesPr } } -func backendTransactionStatusesResponse(block flow.Block) []*access.TransactionResult { - id := unittest.IdentifierFixture() - cid := unittest.IdentifierFixture() - txr := access.TransactionResult{ - Status: flow.TransactionStatusSealed, - StatusCode: 10, - Events: []flow.Event{ - unittest.EventFixture(flow.EventAccountCreated, 1, 0, id, 200), - }, - ErrorMessage: "", - BlockID: block.ID(), - CollectionID: cid, - BlockHeight: block.Header.Height, - } +// TestTransactionStatusesDataProvider_InvalidArguments tests the behavior of the transaction statuses data provider +// when invalid arguments are provided. It verifies that appropriate errors are returned +// for missing or conflicting arguments. +func (s *TransactionStatusesProviderSuite) TestTransactionStatusesDataProvider_InvalidArguments() { + ctx := context.Background() + send := make(chan interface{}) - var expectedTxResultsResponses []*access.TransactionResult + topic := TransactionStatusesTopic - for i := 0; i < 2; i++ { - expectedTxResultsResponses = append(expectedTxResultsResponses, &txr) + for _, test := range invalidTransactionStatusesArgumentsTestCases() { + s.Run(test.name, func() { + provider, err := NewTransactionStatusesDataProvider( + ctx, + s.log, + s.api, + "dummy-id", + s.linkGenerator, + topic, + test.arguments, + send, + ) + s.Require().Nil(provider) + s.Require().Error(err) + s.Require().Contains(err.Error(), test.expectedErrorMsg) + }) } +} - return expectedTxResultsResponses +// invalidTransactionStatusesArgumentsTestCases returns a list of test cases with invalid argument combinations +// for testing the behavior of transaction statuses data providers. Each test case includes a name, +// a set of input arguments, and the expected error message that should be returned. +// +// The test cases cover scenarios such as: +// 1. Providing both 'start_block_id' and 'start_block_height' simultaneously. +// 2. Providing invalid 'tx_id' value. +// 3. Providing invalid 'start_block_id' value. +// 4. Invalid 'start_block_id' argument. +func invalidTransactionStatusesArgumentsTestCases() []testErrType { + return []testErrType{ + { + name: "provide both 'start_block_id' and 'start_block_height' arguments", + arguments: models.Arguments{ + "start_block_id": unittest.BlockFixture().ID().String(), + "start_block_height": fmt.Sprintf("%d", unittest.BlockFixture().Header.Height), + }, + expectedErrorMsg: "can only provide either 'start_block_id' or 'start_block_height'", + }, + { + name: "invalid 'tx_id' argument", + arguments: map[string]interface{}{ + "tx_id": "invalid_tx_id", + }, + expectedErrorMsg: "invalid ID format", + }, + { + name: "invalid 'start_block_id' argument", + arguments: map[string]interface{}{ + "start_block_id": "invalid_block_id", + }, + expectedErrorMsg: "invalid ID format", + }, + { + name: "invalid 'start_block_height' argument", + arguments: map[string]interface{}{ + "start_block_height": "-1", + }, + expectedErrorMsg: "value must be an unsigned 64 bit integer", + }, + } } diff --git a/engine/access/rest/websockets/data_providers/unit_test.go b/engine/access/rest/websockets/data_providers/unit_test.go index cbc75393db9..334155e45af 100644 --- a/engine/access/rest/websockets/data_providers/unit_test.go +++ b/engine/access/rest/websockets/data_providers/unit_test.go @@ -100,3 +100,14 @@ func testHappyPath( }) } } + +// extractPayload extracts the BaseDataProvidersResponse and its typed Payload. +func extractPayload[T any](t *testing.T, v interface{}) (*models.BaseDataProvidersResponse, T) { + response, ok := v.(*models.BaseDataProvidersResponse) + require.True(t, ok, "Expected *models.BaseDataProvidersResponse, got %T", v) + + payload, ok := response.Payload.(T) + require.True(t, ok, "Unexpected response payload type: %T", response.Payload) + + return response, payload +} diff --git a/engine/access/rest/websockets/models/base_message.go b/engine/access/rest/websockets/models/base_message.go index 09c10d3ef8c..a800a7e94a9 100644 --- a/engine/access/rest/websockets/models/base_message.go +++ b/engine/access/rest/websockets/models/base_message.go @@ -25,3 +25,19 @@ type ErrorMessage struct { Code int `json:"code"` // Code is an error code that categorizes an error Message string `json:"message"` } + +// BaseDataProvidersResponse represents a base structure for responses from subscriptions. +type BaseDataProvidersResponse struct { + SubscriptionID string `json:"subscription_id"` // Unique subscriptionID + Topic string `json:"topic"` // Topic of the subscription + Payload interface{} `json:"payload"` // Payload that's being returned within a subscription. +} + +// Build creates BaseDataProvidersResponse instance for consistent responses of the data providers. +func (b *BaseDataProvidersResponse) Build(subscriptionID string, topic string, payload interface{}) { + *b = BaseDataProvidersResponse{ + SubscriptionID: subscriptionID, + Topic: topic, + Payload: payload, + } +}