code.vegaprotocol.io/vega@v0.79.0/core/datasource/spec/engine_test.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package spec_test
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"strconv"
    22  	"testing"
    23  	"time"
    24  
    25  	bmok "code.vegaprotocol.io/vega/core/broker/mocks"
    26  	"code.vegaprotocol.io/vega/core/datasource"
    27  	"code.vegaprotocol.io/vega/core/datasource/common"
    28  	"code.vegaprotocol.io/vega/core/datasource/definition"
    29  	"code.vegaprotocol.io/vega/core/datasource/internal/timetrigger"
    30  	dsspec "code.vegaprotocol.io/vega/core/datasource/spec"
    31  	"code.vegaprotocol.io/vega/core/datasource/spec/mocks"
    32  	"code.vegaprotocol.io/vega/core/events"
    33  	"code.vegaprotocol.io/vega/logging"
    34  	vegapb "code.vegaprotocol.io/vega/protos/vega"
    35  	datapb "code.vegaprotocol.io/vega/protos/vega/data/v1"
    36  
    37  	"github.com/golang/mock/gomock"
    38  	"github.com/stretchr/testify/assert"
    39  	"github.com/stretchr/testify/require"
    40  )
    41  
    42  func TestOracleEngine(t *testing.T) {
    43  	t.Run("Oracle listens to given public keys succeeds", testOracleEngineListensToSignersSucceeds)
    44  	t.Run("Oracle listens to given public keys fails", testOracleEngineListensToSignersFails)
    45  	t.Run("Subscribing to oracle engine succeeds", testOracleEngineSubscribingSucceeds)
    46  	t.Run("Subscribing to oracle engine with without callback fails", testOracleEngineSubscribingWithoutCallbackFails)
    47  	t.Run("Broadcasting to matching data succeeds", testOracleEngineBroadcastingMatchingDataSucceeds)
    48  	t.Run("Unsubscribing known ID from oracle engine succeeds", testOracleEngineUnsubscribingKnownIDSucceeds)
    49  	t.Run("Unsubscribing unknown ID from oracle engine panics", testOracleEngineUnsubscribingUnknownIDPanics)
    50  	t.Run("Updating current time succeeds", testOracleEngineUpdatingCurrentTimeSucceeds)
    51  	t.Run("Subscribing to oracle spec activation succeeds", testOracleEngineSubscribingToSpecActivationSucceeds)
    52  	t.Run("Builtin time trigger succeeds", testBuiltinTimeTriggerSucceeds)
    53  }
    54  
    55  func testOracleEngineListensToSignersSucceeds(t *testing.T) {
    56  	// test conditions
    57  	ctx := context.Background()
    58  	currentTime := time.Now()
    59  	engine := newEngine(ctx, t, currentTime)
    60  
    61  	// test oracle engine with 1 subscriber and 1 key provided
    62  	btcEquals42 := spec(t, "BTC", datapb.Condition_OPERATOR_EQUALS, "42")
    63  	engine.broker.expectNewSpecSubscription(currentTime, btcEquals42.spec.OriginalSpec)
    64  	_, _, _ = engine.Subscribe(ctx, btcEquals42.spec, btcEquals42.subscriber.Cb)
    65  
    66  	// test oracle data with single PubKey
    67  	data := common.Data{
    68  		Signers: []*common.Signer{
    69  			common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
    70  		},
    71  		Data: map[string]string{
    72  			"my_key": "not an integer",
    73  		},
    74  	}
    75  
    76  	result := engine.ListensToSigners(data)
    77  	assert.True(t, result)
    78  
    79  	// test oracle engine with 2 subscribers and multiple keys provided for one of them
    80  	ethEquals42 := spec(t, "ETH", datapb.Condition_OPERATOR_LESS_THAN, "84", "0xCAFED00X", "0xCAFED00D", "0xBEARISH7", "0xBULLISH5")
    81  	engine.broker.expectNewSpecSubscription(currentTime, ethEquals42.spec.OriginalSpec)
    82  	_, _, _ = engine.Subscribe(ctx, ethEquals42.spec, ethEquals42.subscriber.Cb)
    83  
    84  	signersAppend := []*common.Signer{
    85  		common.CreateSignerFromString("0xBEARISH7", common.SignerTypePubKey),
    86  		common.CreateSignerFromString("0xBULLISH5", common.SignerTypePubKey),
    87  	}
    88  
    89  	data.Signers = append(data.Signers, signersAppend...)
    90  	result = engine.ListensToSigners(data)
    91  	assert.True(t, result)
    92  
    93  	// test oracle data with 3 subscribers and multiple keys for some of them
    94  	btcGreater21 := spec(t, "BTC", datapb.Condition_OPERATOR_GREATER_THAN, "21", "0xCAFED00D", "0xBEARISH7", "0xBULLISH5", "0xMILK123", "OxMILK456")
    95  	engine.broker.expectNewSpecSubscription(currentTime, btcGreater21.spec.OriginalSpec)
    96  	_, _, _ = engine.Subscribe(ctx, btcGreater21.spec, btcGreater21.subscriber.Cb)
    97  
    98  	data.Signers = append(data.Signers, common.CreateSignerFromString("0xMILK123", common.SignerTypePubKey))
    99  	result = engine.ListensToSigners(data)
   100  	assert.True(t, result)
   101  }
   102  
   103  func testOracleEngineListensToSignersFails(t *testing.T) {
   104  	// test conditions
   105  	ctx := context.Background()
   106  	currentTime := time.Now()
   107  	engine := newEngine(ctx, t, currentTime)
   108  
   109  	// test oracle engine with single subscriber and wrong key
   110  	btcEquals42 := spec(t, "BTC", datapb.Condition_OPERATOR_EQUALS, "42", "0xWRONGKEY")
   111  	engine.broker.expectNewSpecSubscription(currentTime, btcEquals42.spec.OriginalSpec)
   112  	_, _, _ = engine.Subscribe(ctx, btcEquals42.spec, btcEquals42.subscriber.Cb)
   113  
   114  	data := common.Data{
   115  		Signers: []*common.Signer{
   116  			common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
   117  			common.CreateSignerFromString("0xBEARISH17", common.SignerTypePubKey),
   118  		},
   119  		Data: map[string]string{
   120  			"my_key": "not an integer",
   121  		},
   122  		MetaData: map[string]string{},
   123  	}
   124  
   125  	result := engine.ListensToSigners(data)
   126  	assert.False(t, result)
   127  
   128  	// test oracle engine with 2 subscribers and multiple missing keys
   129  	ethEquals42 := spec(t, "ETH", datapb.Condition_OPERATOR_LESS_THAN, "84", "0xBEARISH7", "0xBULLISH5")
   130  	engine.broker.expectNewSpecSubscription(currentTime, ethEquals42.spec.OriginalSpec)
   131  	_, _, _ = engine.Subscribe(ctx, ethEquals42.spec, ethEquals42.subscriber.Cb)
   132  
   133  	signersAppend := []*common.Signer{
   134  		common.CreateSignerFromString("0xMILK123", common.SignerTypePubKey),
   135  		common.CreateSignerFromString("OxMILK456", common.SignerTypePubKey),
   136  	}
   137  	data.Signers = append(data.Signers, signersAppend...)
   138  	result = engine.ListensToSigners(data)
   139  	assert.False(t, result)
   140  }
   141  
   142  func testOracleEngineSubscribingSucceeds(t *testing.T) {
   143  	// given
   144  	btcEquals42 := spec(t, "BTC", datapb.Condition_OPERATOR_EQUALS, "42")
   145  	ethLess84 := spec(t, "ETH", datapb.Condition_OPERATOR_LESS_THAN, "84")
   146  
   147  	// setup
   148  	ctx := context.Background()
   149  	currentTime := time.Now()
   150  
   151  	engine := newEngine(ctx, t, currentTime)
   152  	engine.broker.expectNewSpecSubscription(currentTime, btcEquals42.spec.OriginalSpec)
   153  	engine.broker.expectNewSpecSubscription(currentTime, ethLess84.spec.OriginalSpec)
   154  
   155  	// when
   156  	id1, _, _ := engine.Subscribe(ctx, btcEquals42.spec, btcEquals42.subscriber.Cb)
   157  	id2, _, _ := engine.Subscribe(ctx, ethLess84.spec, ethLess84.subscriber.Cb)
   158  
   159  	// then
   160  	assert.Equal(t, dsspec.SubscriptionID(1), id1)
   161  	assert.Equal(t, dsspec.SubscriptionID(2), id2)
   162  }
   163  
   164  func testOracleEngineSubscribingToSpecActivationSucceeds(t *testing.T) {
   165  	// given
   166  	btcEquals42 := spec(t, "BTC", datapb.Condition_OPERATOR_EQUALS, "42")
   167  	ethLess84 := spec(t, "ETH", datapb.Condition_OPERATOR_LESS_THAN, "84")
   168  
   169  	subscriber1 := dummySubscriber{}
   170  	subscriber2 := dummySubscriber{}
   171  
   172  	// setup
   173  	ctx := context.Background()
   174  	currentTime := time.Now()
   175  
   176  	subscriber := newTestActivationSubscriber()
   177  
   178  	engine := newEngine(ctx, t, currentTime)
   179  	engine.AddSpecActivationListener(subscriber)
   180  
   181  	engine.broker.expectNewSpecSubscription(currentTime, btcEquals42.spec.OriginalSpec)
   182  
   183  	id1, _, _ := engine.Subscribe(ctx, btcEquals42.spec, subscriber1.Cb)
   184  
   185  	engine.broker.expectNewSpecSubscription(currentTime, ethLess84.spec.OriginalSpec)
   186  	id2, _, _ := engine.Subscribe(ctx, ethLess84.spec, subscriber1.Cb)
   187  
   188  	engine.broker.expectNewSpecSubscription(currentTime, ethLess84.spec.OriginalSpec)
   189  	id3, _, _ := engine.Subscribe(ctx, ethLess84.spec, subscriber2.Cb)
   190  
   191  	assert.Equal(t, 2, len(subscriber.activeSpecs))
   192  
   193  	engine.Unsubscribe(ctx, id3)
   194  
   195  	assert.Equal(t, 2, len(subscriber.activeSpecs))
   196  
   197  	engine.broker.expectSpecSubscriptionDeactivation(currentTime, ethLess84.spec.OriginalSpec)
   198  	engine.Unsubscribe(ctx, id2)
   199  	assert.Equal(t, 1, len(subscriber.activeSpecs))
   200  
   201  	engine.broker.expectSpecSubscriptionDeactivation(currentTime, btcEquals42.spec.OriginalSpec)
   202  	engine.Unsubscribe(ctx, id1)
   203  	assert.Equal(t, 0, len(subscriber.activeSpecs))
   204  }
   205  
   206  type testActivationSubscriber struct {
   207  	activeSpecs map[string]datasource.Spec
   208  }
   209  
   210  func newTestActivationSubscriber() testActivationSubscriber {
   211  	return testActivationSubscriber{activeSpecs: make(map[string]datasource.Spec)}
   212  }
   213  
   214  func (t testActivationSubscriber) OnSpecActivated(ctx context.Context, oracleSpec datasource.Spec) error {
   215  	t.activeSpecs[oracleSpec.ID] = oracleSpec
   216  	return nil
   217  }
   218  
   219  func (t testActivationSubscriber) OnSpecDeactivated(ctx context.Context, oracleSpec datasource.Spec) {
   220  	delete(t.activeSpecs, oracleSpec.ID)
   221  }
   222  
   223  func testOracleEngineSubscribingWithoutCallbackFails(t *testing.T) {
   224  	// given
   225  	s := spec(t, "BTC", datapb.Condition_OPERATOR_EQUALS, "42")
   226  
   227  	// setup
   228  	ctx := context.Background()
   229  	currentTime := time.Now()
   230  	engine := newEngine(ctx, t, currentTime)
   231  
   232  	// when
   233  	subscribe := func() {
   234  		engine.Subscribe(ctx, s.spec, nil)
   235  	}
   236  
   237  	// then
   238  	assert.Panics(t, subscribe)
   239  }
   240  
   241  func testOracleEngineBroadcastingMatchingDataSucceeds(t *testing.T) {
   242  	// given
   243  	btcEquals42 := spec(t, "BTC", datapb.Condition_OPERATOR_EQUALS, "42")
   244  	btcGreater21 := spec(t, "BTC", datapb.Condition_OPERATOR_GREATER_THAN, "21")
   245  	ethEquals42 := spec(t, "ETH", datapb.Condition_OPERATOR_EQUALS, "42")
   246  	ethLess84 := spec(t, "ETH", datapb.Condition_OPERATOR_LESS_THAN, "84")
   247  	btcGreater100 := spec(t, "BTC", datapb.Condition_OPERATOR_GREATER_THAN, "100")
   248  	dataBTC42 := dataWithPrice("BTC", "42")
   249  
   250  	// setup
   251  	ctx := context.Background()
   252  	currentTime := time.Now()
   253  	engine := newEngine(ctx, t, currentTime)
   254  	engine.broker.expectNewSpecSubscription(currentTime, btcEquals42.spec.OriginalSpec)
   255  	engine.broker.expectNewSpecSubscription(currentTime, btcGreater21.spec.OriginalSpec)
   256  	engine.broker.expectNewSpecSubscription(currentTime, ethEquals42.spec.OriginalSpec)
   257  	engine.broker.expectNewSpecSubscription(currentTime, ethLess84.spec.OriginalSpec)
   258  	engine.broker.expectNewSpecSubscription(currentTime, btcGreater100.spec.OriginalSpec)
   259  	engine.broker.expectMatchedDataEvent(currentTime, &dataBTC42.proto, []string{
   260  		btcEquals42.spec.OriginalSpec.ID,
   261  		btcGreater21.spec.OriginalSpec.ID,
   262  	})
   263  
   264  	// when
   265  	engine.Subscribe(ctx, btcEquals42.spec, btcEquals42.subscriber.Cb)
   266  	engine.Subscribe(ctx, ethEquals42.spec, ethEquals42.subscriber.Cb)
   267  	engine.Subscribe(ctx, btcGreater21.spec, btcGreater21.subscriber.Cb)
   268  	engine.Subscribe(ctx, ethLess84.spec, ethLess84.subscriber.Cb)
   269  	engine.Subscribe(ctx, btcGreater100.spec, btcGreater100.subscriber.Cb)
   270  	errB := engine.BroadcastData(context.Background(), dataBTC42.data)
   271  
   272  	// ensure vega-time is set
   273  	dataBTC42.data.MetaData = map[string]string{
   274  		"vega-time": strconv.FormatInt(currentTime.Unix(), 10),
   275  	}
   276  	// then
   277  	require.NoError(t, errB)
   278  	assert.Equal(t, &dataBTC42.data, btcEquals42.subscriber.ReceivedData)
   279  	assert.Equal(t, &dataBTC42.data, btcGreater21.subscriber.ReceivedData)
   280  	assert.Nil(t, ethEquals42.subscriber.ReceivedData)
   281  	assert.Nil(t, ethLess84.subscriber.ReceivedData)
   282  	assert.Nil(t, btcGreater100.subscriber.ReceivedData)
   283  }
   284  
   285  func testOracleEngineUnsubscribingUnknownIDPanics(t *testing.T) {
   286  	// setup
   287  	ctx := context.Background()
   288  	currentTime := time.Now()
   289  	engine := newEngine(ctx, t, currentTime)
   290  
   291  	// when
   292  	unsubscribe := func() {
   293  		engine.Unsubscribe(ctx, dsspec.SubscriptionID(1))
   294  	}
   295  
   296  	// then
   297  	assert.Panics(t, unsubscribe)
   298  }
   299  
   300  func testOracleEngineUnsubscribingKnownIDSucceeds(t *testing.T) {
   301  	// given
   302  	btcEquals42 := spec(t, "BTC", datapb.Condition_OPERATOR_EQUALS, "42")
   303  	ethEquals42 := spec(t, "ETH", datapb.Condition_OPERATOR_EQUALS, "42")
   304  	ctx := context.Background()
   305  	currentTime := time.Now()
   306  	engine := newEngine(ctx, t, currentTime)
   307  
   308  	// expect
   309  	engine.broker.expectNewSpecSubscription(currentTime, btcEquals42.spec.OriginalSpec)
   310  
   311  	// when
   312  	idS1, _, _ := engine.Subscribe(ctx, btcEquals42.spec, btcEquals42.subscriber.Cb)
   313  
   314  	// expect
   315  	engine.broker.expectNewSpecSubscription(currentTime, ethEquals42.spec.OriginalSpec)
   316  
   317  	// when
   318  	_, _, _ = engine.Subscribe(ctx, ethEquals42.spec, ethEquals42.subscriber.Cb)
   319  
   320  	// expect
   321  	engine.broker.expectSpecSubscriptionDeactivation(currentTime, btcEquals42.spec.OriginalSpec)
   322  
   323  	// when
   324  	engine.Unsubscribe(ctx, idS1)
   325  
   326  	// given
   327  	dataETH42 := dataWithPrice("ETH", "42")
   328  
   329  	// expect
   330  	engine.broker.expectMatchedDataEvent(currentTime, &dataETH42.proto, []string{
   331  		ethEquals42.spec.OriginalSpec.ID,
   332  	})
   333  	// vega-time will be set
   334  	dataETH42.data.MetaData = map[string]string{
   335  		"vega-time": strconv.FormatInt(currentTime.Unix(), 10),
   336  	}
   337  
   338  	// when
   339  	err := engine.BroadcastData(context.Background(), dataETH42.data)
   340  
   341  	// then
   342  	require.NoError(t, err)
   343  	assert.Equal(t, &dataETH42.data, ethEquals42.subscriber.ReceivedData)
   344  }
   345  
   346  func testOracleEngineUpdatingCurrentTimeSucceeds(t *testing.T) {
   347  	// setup
   348  	ctx := context.Background()
   349  	time30 := time.Unix(30, 0)
   350  	time60 := time.Unix(60, 0)
   351  	engine := newEngine(ctx, t, time30)
   352  	assert.Equal(t, time30, engine.ts.GetTimeNow())
   353  
   354  	engine2 := newEngine(ctx, t, time60)
   355  	assert.Equal(t, time60, engine2.ts.GetTimeNow())
   356  }
   357  
   358  func testBuiltinTimeTriggerSucceeds(t *testing.T) {
   359  	// given
   360  	trigger := triggerSpec(t, time.Now(), 5)
   361  
   362  	// setup
   363  	ctx := context.Background()
   364  	currentTime := time.Now()
   365  	engine := newEngine(ctx, t, currentTime)
   366  
   367  	engine.broker.EXPECT().Send(gomock.Any()).Times(1)
   368  	engine.Subscribe(ctx, trigger.spec, trigger.subscriber.Cb)
   369  
   370  	// broadcast a time that will not trigger
   371  	data := common.Data{
   372  		Data: map[string]string{
   373  			dsspec.BuiltinTimeTrigger: strconv.FormatInt(currentTime.Add(-time.Minute).Unix(), 10),
   374  		},
   375  	}
   376  	engine.BroadcastData(ctx, data)
   377  
   378  	// now broadcast one that will
   379  	engine.broker.EXPECT().Send(gomock.Any()).Times(1)
   380  	data = common.Data{
   381  		Data: map[string]string{
   382  			dsspec.BuiltinTimeTrigger: strconv.FormatInt(currentTime.Add(time.Minute).Unix(), 10),
   383  		},
   384  	}
   385  	engine.BroadcastData(ctx, data)
   386  }
   387  
   388  type testEngine struct {
   389  	*dsspec.Engine
   390  	ts     *testTimeService
   391  	broker *testBroker
   392  }
   393  
   394  // newEngine returns new Oracle test engine, but with preset time, so we can test against its value.
   395  func newEngine(ctx context.Context, t *testing.T, tm time.Time) *testEngine {
   396  	t.Helper()
   397  	broker := newBroker(ctx, t)
   398  
   399  	ts := newTimeService(ctx, t)
   400  	ts.EXPECT().GetTimeNow().DoAndReturn(
   401  		func() time.Time {
   402  			return tm
   403  		}).AnyTimes()
   404  
   405  	te := &testEngine{
   406  		Engine: dsspec.NewEngine(
   407  			logging.NewTestLogger(),
   408  			dsspec.NewDefaultConfig(),
   409  			ts,
   410  			broker,
   411  		),
   412  		ts:     ts,
   413  		broker: broker,
   414  	}
   415  
   416  	return te
   417  }
   418  
   419  type dataBundle struct {
   420  	data  common.Data
   421  	proto vegapb.OracleData
   422  }
   423  
   424  func dataWithPrice(currency, price string) dataBundle {
   425  	priceName := fmt.Sprintf("prices.%s.value", currency)
   426  	signers := []*common.Signer{
   427  		common.CreateSignerFromString("0xCAFED00D", common.SignerTypePubKey),
   428  	}
   429  
   430  	return dataBundle{
   431  		data: common.Data{
   432  			Data: map[string]string{
   433  				priceName: price,
   434  			},
   435  			Signers: signers,
   436  		},
   437  		proto: vegapb.OracleData{
   438  			ExternalData: &datapb.ExternalData{
   439  				Data: &datapb.Data{
   440  					Data: []*datapb.Property{
   441  						{
   442  							Name:  priceName,
   443  							Value: price,
   444  						},
   445  					},
   446  					MetaData: []*datapb.Property{},
   447  					Signers:  common.SignersIntoProto(signers),
   448  				},
   449  			},
   450  		},
   451  	}
   452  }
   453  
   454  type specBundle struct {
   455  	spec       dsspec.Spec
   456  	subscriber dummySubscriber
   457  }
   458  
   459  func triggerSpec(t *testing.T, initial time.Time, every int64) specBundle {
   460  	t.Helper()
   461  
   462  	cfg := &timetrigger.SpecConfiguration{
   463  		Conditions: []*common.SpecCondition{
   464  			{
   465  				Operator: common.SpecConditionOperator(2),
   466  				Value:    "12",
   467  			},
   468  			{
   469  				Operator: common.SpecConditionOperator(2),
   470  				Value:    "17",
   471  			},
   472  		},
   473  		Triggers: common.InternalTimeTriggers{
   474  			{
   475  				Initial: &initial,
   476  				Every:   every,
   477  			},
   478  		},
   479  	}
   480  
   481  	testSpec := vegapb.NewDataSourceSpec(definition.NewWith(cfg).IntoProto())
   482  	typedOracleSpec := datasource.SpecFromProto(testSpec)
   483  
   484  	// Initialise trigger
   485  	balh := typedOracleSpec.Data.Content().(timetrigger.SpecConfiguration)
   486  	balh.SetNextTrigger(initial)
   487  
   488  	spec, err := dsspec.New(*typedOracleSpec)
   489  	if err != nil {
   490  		t.Fatalf("Couldn't create oracle spec: %v", err)
   491  	}
   492  	return specBundle{
   493  		spec:       *spec,
   494  		subscriber: dummySubscriber{},
   495  	}
   496  }
   497  
   498  func spec(t *testing.T, currency string, op datapb.Condition_Operator, price string, keys ...string) specBundle {
   499  	t.Helper()
   500  	var signers []*datapb.Signer
   501  	if len(keys) > 0 {
   502  		signers = make([]*datapb.Signer, len(keys))
   503  		for i, k := range keys {
   504  			signers[i] = &datapb.Signer{
   505  				Signer: &datapb.Signer_PubKey{
   506  					PubKey: &datapb.PubKey{
   507  						Key: k,
   508  					},
   509  				},
   510  			}
   511  		}
   512  	}
   513  	if len(keys) == 0 {
   514  		signers = []*datapb.Signer{
   515  			{
   516  				Signer: &datapb.Signer_PubKey{
   517  					PubKey: &datapb.PubKey{
   518  						Key: "0xCAFED00D",
   519  					},
   520  				},
   521  			},
   522  		}
   523  	}
   524  
   525  	testSpec := vegapb.NewDataSourceSpec(
   526  		vegapb.NewDataSourceDefinition(
   527  			vegapb.DataSourceContentTypeOracle,
   528  		).SetOracleConfig(
   529  			&vegapb.DataSourceDefinitionExternal_Oracle{
   530  				Oracle: &vegapb.DataSourceSpecConfiguration{
   531  					Signers: signers,
   532  					Filters: []*datapb.Filter{
   533  						{
   534  							Key: &datapb.PropertyKey{
   535  								Name: fmt.Sprintf("prices.%s.value", currency),
   536  								Type: datapb.PropertyKey_TYPE_INTEGER,
   537  							},
   538  							Conditions: []*datapb.Condition{
   539  								{
   540  									Value:    price,
   541  									Operator: op,
   542  								},
   543  							},
   544  						},
   545  					},
   546  				},
   547  			},
   548  		),
   549  	)
   550  
   551  	typedOracleSpec := datasource.SpecFromProto(testSpec)
   552  
   553  	spec, err := dsspec.New(*typedOracleSpec)
   554  	if err != nil {
   555  		t.Fatalf("Couldn't create oracle spec: %v", err)
   556  	}
   557  	return specBundle{
   558  		spec:       *spec,
   559  		subscriber: dummySubscriber{},
   560  	}
   561  }
   562  
   563  type dummySubscriber struct {
   564  	ReceivedData *common.Data
   565  }
   566  
   567  func (d *dummySubscriber) Cb(_ context.Context, data common.Data) error {
   568  	d.ReceivedData = &data
   569  	return nil
   570  }
   571  
   572  type testBroker struct {
   573  	*bmok.MockBroker
   574  	ctx context.Context
   575  }
   576  
   577  type testTimeService struct {
   578  	*mocks.MockTimeService
   579  	ctx context.Context
   580  }
   581  
   582  func newBroker(ctx context.Context, t *testing.T) *testBroker {
   583  	t.Helper()
   584  	ctrl := gomock.NewController(t)
   585  	return &testBroker{
   586  		MockBroker: bmok.NewMockBroker(ctrl),
   587  		ctx:        ctx,
   588  	}
   589  }
   590  
   591  func newTimeService(ctx context.Context, t *testing.T) *testTimeService {
   592  	t.Helper()
   593  	ctrl := gomock.NewController(t)
   594  	return &testTimeService{
   595  		MockTimeService: mocks.NewMockTimeService(ctrl),
   596  		ctx:             ctx,
   597  	}
   598  }
   599  
   600  func (b *testBroker) expectNewSpecSubscription(currentTime time.Time, spec *datasource.Spec) {
   601  	proto := spec.IntoProto()
   602  	proto.CreatedAt = currentTime.UnixNano()
   603  	proto.Status = vegapb.DataSourceSpec_STATUS_ACTIVE
   604  	b.EXPECT().Send(events.NewOracleSpecEvent(b.ctx, &vegapb.OracleSpec{ExternalDataSourceSpec: &vegapb.ExternalDataSourceSpec{Spec: proto}})).Times(1)
   605  }
   606  
   607  func (b *testBroker) expectSpecSubscriptionDeactivation(currentTime time.Time, spec *datasource.Spec) {
   608  	proto := spec.IntoProto()
   609  	proto.CreatedAt = currentTime.UnixNano()
   610  	proto.Status = vegapb.DataSourceSpec_STATUS_DEACTIVATED
   611  	b.EXPECT().Send(events.NewOracleSpecEvent(b.ctx, &vegapb.OracleSpec{ExternalDataSourceSpec: &vegapb.ExternalDataSourceSpec{Spec: proto}})).Times(1)
   612  }
   613  
   614  func (b *testBroker) expectMatchedDataEvent(currentTime time.Time, data *vegapb.OracleData, specIDs []string) {
   615  	data.ExternalData.Data.MatchedSpecIds = specIDs
   616  	data.ExternalData.Data.BroadcastAt = currentTime.UnixNano()
   617  	data.ExternalData.Data.MetaData = []*datapb.Property{
   618  		{
   619  			Name:  "vega-time",
   620  			Value: strconv.FormatInt(currentTime.Unix(), 10),
   621  		},
   622  	}
   623  	b.EXPECT().Send(events.NewOracleDataEvent(b.ctx, *data)).Times(1)
   624  }