github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/access/rpc/backend/backend_stream_block_headers_test.go (about)

     1  package backend
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/mock"
     9  	"github.com/stretchr/testify/require"
    10  	"github.com/stretchr/testify/suite"
    11  	"google.golang.org/grpc/codes"
    12  	"google.golang.org/grpc/status"
    13  
    14  	"github.com/onflow/flow-go/engine/access/subscription"
    15  	"github.com/onflow/flow-go/model/flow"
    16  	"github.com/onflow/flow-go/utils/unittest"
    17  )
    18  
    19  type BackendBlockHeadersSuite struct {
    20  	BackendBlocksSuite
    21  }
    22  
    23  func TestBackendBlockHeadersSuite(t *testing.T) {
    24  	suite.Run(t, new(BackendBlockHeadersSuite))
    25  }
    26  
    27  // SetupTest initializes the test suite with required dependencies.
    28  func (s *BackendBlockHeadersSuite) SetupTest() {
    29  	s.BackendBlocksSuite.SetupTest()
    30  }
    31  
    32  // TestSubscribeBlockHeadersFromStartBlockID tests the SubscribeBlockHeadersFromStartBlockID method.
    33  func (s *BackendBlockHeadersSuite) TestSubscribeBlockHeadersFromStartBlockID() {
    34  	s.blockTracker.On(
    35  		"GetStartHeightFromBlockID",
    36  		mock.AnythingOfType("flow.Identifier"),
    37  	).Return(func(startBlockID flow.Identifier) (uint64, error) {
    38  		return s.blockTrackerReal.GetStartHeightFromBlockID(startBlockID)
    39  	}, nil)
    40  
    41  	call := func(ctx context.Context, startValue interface{}, blockStatus flow.BlockStatus) subscription.Subscription {
    42  		return s.backend.SubscribeBlockHeadersFromStartBlockID(ctx, startValue.(flow.Identifier), blockStatus)
    43  	}
    44  
    45  	s.subscribe(call, s.requireBlockHeaders, s.subscribeFromStartBlockIdTestCases())
    46  }
    47  
    48  // TestSubscribeBlockHeadersFromStartHeight tests the SubscribeBlockHeadersFromStartHeight method.
    49  func (s *BackendBlockHeadersSuite) TestSubscribeBlockHeadersFromStartHeight() {
    50  	s.blockTracker.On(
    51  		"GetStartHeightFromHeight",
    52  		mock.AnythingOfType("uint64"),
    53  	).Return(func(startHeight uint64) (uint64, error) {
    54  		return s.blockTrackerReal.GetStartHeightFromHeight(startHeight)
    55  	}, nil)
    56  
    57  	call := func(ctx context.Context, startValue interface{}, blockStatus flow.BlockStatus) subscription.Subscription {
    58  		return s.backend.SubscribeBlockHeadersFromStartHeight(ctx, startValue.(uint64), blockStatus)
    59  	}
    60  
    61  	s.subscribe(call, s.requireBlockHeaders, s.subscribeFromStartHeightTestCases())
    62  }
    63  
    64  // TestSubscribeBlockHeadersFromLatest tests the SubscribeBlockHeadersFromLatest method.
    65  func (s *BackendBlockHeadersSuite) TestSubscribeBlockHeadersFromLatest() {
    66  	s.blockTracker.On(
    67  		"GetStartHeightFromLatest",
    68  		mock.Anything,
    69  	).Return(func(ctx context.Context) (uint64, error) {
    70  		return s.blockTrackerReal.GetStartHeightFromLatest(ctx)
    71  	}, nil)
    72  
    73  	call := func(ctx context.Context, startValue interface{}, blockStatus flow.BlockStatus) subscription.Subscription {
    74  		return s.backend.SubscribeBlockHeadersFromLatest(ctx, blockStatus)
    75  	}
    76  
    77  	s.subscribe(call, s.requireBlockHeaders, s.subscribeFromLatestTestCases())
    78  }
    79  
    80  // requireBlockHeaders ensures that the received block header information matches the expected data.
    81  func (s *BackendBlockHeadersSuite) requireBlockHeaders(v interface{}, expectedBlock *flow.Block) {
    82  	actualHeader, ok := v.(*flow.Header)
    83  	require.True(s.T(), ok, "unexpected response type: %T", v)
    84  
    85  	s.Require().Equal(expectedBlock.Header.Height, actualHeader.Height)
    86  	s.Require().Equal(expectedBlock.Header.ID(), actualHeader.ID())
    87  	s.Require().Equal(*expectedBlock.Header, *actualHeader)
    88  }
    89  
    90  // TestSubscribeBlockHeadersHandlesErrors tests error handling scenarios for the SubscribeBlockHeadersFromStartBlockID and SubscribeBlockHeadersFromStartHeight methods in the Backend.
    91  // It ensures that the method correctly returns errors for various invalid input cases.
    92  //
    93  // Test Cases:
    94  //
    95  // 1. Returns error for unindexed start block id:
    96  //   - Tests that subscribing to block headers with an unindexed start block ID results in a NotFound error.
    97  //
    98  // 2. Returns error for start height before root height:
    99  //   - Validates that attempting to subscribe to block headers with a start height before the root height results in an InvalidArgument error.
   100  //
   101  // 3. Returns error for unindexed start height:
   102  //   - Tests that subscribing to block headers with an unindexed start height results in a NotFound error.
   103  //
   104  // Each test case checks for specific error conditions and ensures that the methods responds appropriately.
   105  func (s *BackendBlockHeadersSuite) TestSubscribeBlockHeadersHandlesErrors() {
   106  	ctx, cancel := context.WithCancel(context.Background())
   107  	defer cancel()
   108  
   109  	// mock block tracker for GetStartHeightFromBlockID
   110  	s.blockTracker.On(
   111  		"GetStartHeightFromBlockID",
   112  		mock.AnythingOfType("flow.Identifier"),
   113  	).Return(func(startBlockID flow.Identifier) (uint64, error) {
   114  		return s.blockTrackerReal.GetStartHeightFromBlockID(startBlockID)
   115  	}, nil)
   116  
   117  	s.Run("returns error for unknown start block id is provided", func() {
   118  		subCtx, subCancel := context.WithCancel(ctx)
   119  		defer subCancel()
   120  
   121  		sub := s.backend.SubscribeBlockHeadersFromStartBlockID(subCtx, unittest.IdentifierFixture(), flow.BlockStatusFinalized)
   122  		assert.Equal(s.T(), codes.NotFound, status.Code(sub.Err()), "expected %s, got %v: %v", codes.NotFound, status.Code(sub.Err()).String(), sub.Err())
   123  	})
   124  
   125  	// mock block tracker for GetStartHeightFromHeight
   126  	s.blockTracker.On(
   127  		"GetStartHeightFromHeight",
   128  		mock.AnythingOfType("uint64"),
   129  	).Return(func(startHeight uint64) (uint64, error) {
   130  		return s.blockTrackerReal.GetStartHeightFromHeight(startHeight)
   131  	}, nil)
   132  
   133  	s.Run("returns error if start height before root height", func() {
   134  		subCtx, subCancel := context.WithCancel(ctx)
   135  		defer subCancel()
   136  
   137  		sub := s.backend.SubscribeBlockHeadersFromStartHeight(subCtx, s.rootBlock.Header.Height-1, flow.BlockStatusFinalized)
   138  		assert.Equal(s.T(), codes.InvalidArgument, status.Code(sub.Err()), "expected %s, got %v: %v", codes.InvalidArgument, status.Code(sub.Err()).String(), sub.Err())
   139  	})
   140  
   141  	s.Run("returns error for unknown start height is provided", func() {
   142  		subCtx, subCancel := context.WithCancel(ctx)
   143  		defer subCancel()
   144  
   145  		sub := s.backend.SubscribeBlockHeadersFromStartHeight(subCtx, s.blocksArray[len(s.blocksArray)-1].Header.Height+10, flow.BlockStatusFinalized)
   146  		assert.Equal(s.T(), codes.NotFound, status.Code(sub.Err()), "expected %s, got %v: %v", codes.NotFound, status.Code(sub.Err()).String(), sub.Err())
   147  	})
   148  }