repo_description: node of the decentralized oracle network, bridging on and off-chain computation
repo_topics: blockchain chainlink ethereum golang oracle solidity
code: package bridges_test
  
  import (
  	"errors"
  	"testing"
  	"time"
  
  	"github.com/stretchr/testify/assert"
  	"github.com/stretchr/testify/mock"
  	"github.com/stretchr/testify/require"
  
  	"github.com/smartcontractkit/chainlink/v2/core/bridges"
  	"github.com/smartcontractkit/chainlink/v2/core/bridges/mocks"
  	"github.com/smartcontractkit/chainlink/v2/core/logger"
  )
  
  func TestBridgeCache_Type(t *testing.T) {
  	t.Parallel()
  
  	t.Run("loads bridge from data source - single", func(t *testing.T) {
  		t.Parallel()
  
  		mORM := new(mocks.ORM)
  		lggr, _ := logger.NewLogger()
  		cache := bridges.NewCache(mORM, lggr, bridges.DefaultUpsertInterval)
  
  		bridge := bridges.BridgeName("test")
  		expected := bridges.BridgeType{
  			Name: bridge,
  		}
  
  		// first call to find should fallthrough to data source
  		mORM.On("FindBridge", mock.Anything, bridge).Return(expected, nil)
  		result, err := cache.FindBridge(t.Context(), bridge)
  
  		require.NoError(t, err)
  		assert.Equal(t, expected, result)
  
  		// calling find again should return from cache
  		result, err = cache.FindBridge(t.Context(), bridge)
  
  		require.NoError(t, err)
  		assert.Equal(t, expected, result)
  	})
  
  	t.Run("loads bridge from data source - multiple", func(t *testing.T) {
  		t.Parallel()
  
  		mORM := new(mocks.ORM)
  		lggr, _ := logger.NewLogger()
  		cache := bridges.NewCache(mORM, lggr, bridges.DefaultUpsertInterval)
  
  		ctx := t.Context()
  		nameA := bridges.BridgeName("A")
  		nameB := bridges.BridgeName("B")
  		nameC := bridges.BridgeName("C")
  
  		initialExpected := []bridges.BridgeType{
  			{Name: nameA},
  			{Name: nameB},
  		}
  		finalExpected := append(initialExpected, bridges.BridgeType{Name: nameC})
  
  		mORM.On("FindBridges", mock.Anything, []bridges.BridgeName{nameA, nameB}).Return(initialExpected, nil)
  		types, err := cache.FindBridges(ctx, []bridges.BridgeName{nameA, nameB})
  
  		require.NoError(t, err)
  		assert.Equal(t, initialExpected, types)
  
  		// second call to FindBridges only includes the value not in the cache
  		mORM.On("FindBridges", mock.Anything, []bridges.BridgeName{nameC}).
  			Return([]bridges.BridgeType{{Name: nameC}}, nil)
  
  		types, err = cache.FindBridges(ctx, []bridges.BridgeName{nameA, nameB, nameC})
  
  		require.NoError(t, err)
  		assert.Equal(t, finalExpected, types)
  	})
  
  	t.Run("creates, updates, and deletes bridge", func(t *testing.T) {
  		t.Parallel()
  
  		mORM := new(mocks.ORM)
  		lggr, _ := logger.NewLogger()
  		cache := bridges.NewCache(mORM, lggr, bridges.DefaultUpsertInterval)
  
  		ctx := t.Context()
  		bridge := bridges.BridgeName("test")
  		expected := &bridges.BridgeType{
  			Name:          bridge,
  			Confirmations: 42,
  		}
  
  		mORM.On("CreateBridgeType", mock.Anything, expected).Return(nil)
  		assert.NoError(t, cache.CreateBridgeType(ctx, expected))
  
  		result, err := cache.FindBridge(ctx, bridge)
  
  		require.NoError(t, err)
  		assert.Equal(t, *expected, result)
  
  		btr := &bridges.BridgeTypeRequest{
  			Confirmations: 21,
  		}
  
  		mORM.On("UpdateBridgeType", mock.Anything, expected, btr).Return(nil).Run(func(args mock.Arguments) {
  			btp := args.Get(1).(*bridges.BridgeType)
  			req := args.Get(2).(*bridges.BridgeTypeRequest)
  
  			btp.Confirmations = req.Confirmations
  		})
  		require.NoError(t, cache.UpdateBridgeType(ctx, expected, btr))
  		assert.Equal(t, expected.Confirmations, btr.Confirmations)
  
  		result, err = cache.FindBridge(ctx, bridge)
  
  		require.NoError(t, err)
  		assert.Equal(t, *expected, result)
  
  		mORM.On("DeleteBridgeType", mock.Anything, expected).Return(nil)
  		require.NoError(t, cache.DeleteBridgeType(ctx, expected))
  
  		// bridge type is removed from cache so call to find fallsback to the data store
  		mORM.On("FindBridge", mock.Anything, bridge).Return(bridges.BridgeType{}, errors.New("not found"))
  		_, err = cache.FindBridge(ctx, bridge)
  		require.Error(t, err)
  	})
  }
  
  func TestBridgeCache_Response(t *testing.T) {
  	t.Parallel()
  
  	t.Run("loads response from data source", func(t *testing.T) {
  		t.Parallel()
  
  		mORM := new(mocks.ORM)
  		lggr, _ := logger.NewLogger()
  		cache := bridges.NewCache(mORM, lggr, bridges.DefaultUpsertInterval)
  
  		ctx := t.Context()
  		dotId := "test"
  		specId := int32(42)
  		responseData := []byte("test")
  
  		mORM.On("GetCachedResponseWithFinished", mock.Anything, dotId, specId, time.Second).
  			Return(responseData, time.Now(), nil)
  
  		response, err := cache.GetCachedResponse(ctx, dotId, specId, time.Second)
  
  		require.NoError(t, err)
  		assert.Equal(t, responseData, response)
  
  		response, err = cache.GetCachedResponse(ctx, dotId, specId, time.Second)
  
  		require.NoError(t, err)
  		assert.Equal(t, responseData, response)
  	})
  
  	t.Run("async upserts bridge response", func(t *testing.T) {
  		t.Parallel()
  
  		mORM := new(mocks.ORM)
  		lggr, _ := logger.NewLogger()
  		cache := bridges.NewCache(mORM, lggr, bridges.DefaultUpsertInterval)
  
  		t.Cleanup(func() {
  			require.NoError(t, cache.Close())
  		})
  
  		require.NoError(t, cache.Start(t.Context()))
  
  		ctx := t.Context()
  		dotId := "test"
  		specId := int32(42)
  		expected := []byte("test")
  		chBulkUpsertCalled := make(chan struct{}, 1)
  
  		mORM.On("BulkUpsertBridgeResponse", mock.Anything, mock.Anything).Return(nil).Run(func(_ mock.Arguments) {
  			chBulkUpsertCalled <- struct{}{}
  		})
  
  		require.NoError(t, cache.UpsertBridgeResponse(ctx, dotId, specId, expected))
  
  		response, err := cache.GetCachedResponse(ctx, dotId, specId, time.Second)
  
  		require.NoError(t, err)
  		assert.Equal(t, expected, response)
  
  		timer := time.NewTimer(2 * bridges.DefaultUpsertInterval)
  
  		select {
  		case <-chBulkUpsertCalled:
  			timer.Stop()
  		case <-timer.C:
  			timer.Stop()
  
  			t.Log("upsert interval exceeded without expected upsert")
  			t.FailNow()
  		}
  	})
  }
  