github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/libs/pubsub/query/query_test.go (about)

     1  package query_test
     2  
     3  import (
     4  	"fmt"
     5  	"math/big"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/badrootd/nibiru-cometbft/libs/pubsub/query"
    13  )
    14  
    15  func TestBigNumbers(t *testing.T) {
    16  	bigInt := "10000000000000000000"
    17  	bigIntAsFloat := "10000000000000000000.0"
    18  	bigFloat := "10000000000000000000.6"
    19  	bigFloatLowerRounding := "10000000000000000000.1"
    20  	doubleBigInt := "20000000000000000000"
    21  
    22  	testCases := []struct {
    23  		s        string
    24  		events   map[string][]string
    25  		err      bool
    26  		matches  bool
    27  		matchErr bool
    28  	}{
    29  
    30  		{"account.balance <= " + bigInt, map[string][]string{"account.balance": {bigInt}}, false, true, false},
    31  		{"account.balance <= " + bigInt, map[string][]string{"account.balance": {bigIntAsFloat}}, false, true, false},
    32  		{"account.balance <= " + doubleBigInt, map[string][]string{"account.balance": {bigInt}}, false, true, false},
    33  		{"account.balance <= " + bigInt, map[string][]string{"account.balance": {"10000000000000000001"}}, false, false, false},
    34  		{"account.balance <= " + doubleBigInt, map[string][]string{"account.balance": {bigFloat}}, false, true, false},
    35  		// To maintain compatibility with the old implementation which did a simple cast of float to int64, we do not round the float
    36  		// Thus both 10000000000000000000.6 and "10000000000000000000.1 are equal to 10000000000000000000
    37  		// and the test does not find a match
    38  		{"account.balance > " + bigInt, map[string][]string{"account.balance": {bigFloat}}, false, false, false},
    39  		{"account.balance > " + bigInt, map[string][]string{"account.balance": {bigFloatLowerRounding}}, true, false, false},
    40  		// This test should also find a match, but floats that are too big cannot be properly converted, thus
    41  		// 10000000000000000000.6 gets rounded to 10000000000000000000
    42  		{"account.balance > " + bigIntAsFloat, map[string][]string{"account.balance": {bigFloat}}, false, false, false},
    43  		{"account.balance > 11234.0", map[string][]string{"account.balance": {"11234.6"}}, false, true, false},
    44  		{"account.balance <= " + bigInt, map[string][]string{"account.balance": {"1000.45"}}, false, true, false},
    45  	}
    46  
    47  	for _, tc := range testCases {
    48  		q, err := query.New(tc.s)
    49  		if !tc.err {
    50  			require.Nil(t, err)
    51  		}
    52  		require.NotNil(t, q, "Query '%s' should not be nil", tc.s)
    53  
    54  		if tc.matches {
    55  			match, err := q.Matches(tc.events)
    56  			assert.Nil(t, err, "Query '%s' should not error on match %v", tc.s, tc.events)
    57  			assert.True(t, match, "Query '%s' should match %v", tc.s, tc.events)
    58  		} else {
    59  			match, err := q.Matches(tc.events)
    60  			assert.Equal(t, tc.matchErr, err != nil, "Unexpected error for query '%s' match %v", tc.s, tc.events)
    61  			assert.False(t, match, "Query '%s' should not match %v", tc.s, tc.events)
    62  		}
    63  	}
    64  }
    65  
    66  func TestMatches(t *testing.T) {
    67  	var (
    68  		txDate = "2017-01-01"
    69  		txTime = "2018-05-03T14:45:00Z"
    70  	)
    71  
    72  	testCases := []struct {
    73  		s        string
    74  		events   map[string][]string
    75  		err      bool
    76  		matches  bool
    77  		matchErr bool
    78  	}{
    79  		{"tm.events.type='NewBlock'", map[string][]string{"tm.events.type": {"NewBlock"}}, false, true, false},
    80  		{"tx.gas > 7", map[string][]string{"tx.gas": {"8"}}, false, true, false},
    81  		{"transfer.amount > 7", map[string][]string{"transfer.amount": {"8stake"}}, false, true, false},
    82  		{"transfer.amount > 7", map[string][]string{"transfer.amount": {"8.045stake"}}, false, true, false},
    83  		{"transfer.amount > 7.043", map[string][]string{"transfer.amount": {"8.045stake"}}, false, true, false},
    84  		{"transfer.amount > 8.045", map[string][]string{"transfer.amount": {"8.045stake"}}, false, false, false},
    85  		{"tx.gas > 7 AND tx.gas < 9", map[string][]string{"tx.gas": {"8"}}, false, true, false},
    86  		{"body.weight >= 3.5", map[string][]string{"body.weight": {"3.5"}}, false, true, false},
    87  		{"account.balance < 1000.0", map[string][]string{"account.balance": {"900"}}, false, true, false},
    88  		{"apples.kg <= 4", map[string][]string{"apples.kg": {"4.0"}}, false, true, false},
    89  		{"body.weight >= 4.5", map[string][]string{"body.weight": {fmt.Sprintf("%v", float32(4.5))}}, false, true, false},
    90  		{
    91  			"oranges.kg < 4 AND watermellons.kg > 10",
    92  			map[string][]string{"oranges.kg": {"3"}, "watermellons.kg": {"12"}},
    93  			false,
    94  			true,
    95  			false,
    96  		},
    97  		{"peaches.kg < 4", map[string][]string{"peaches.kg": {"5"}}, false, false, false},
    98  		{
    99  			"tx.date > DATE 2017-01-01",
   100  			map[string][]string{"tx.date": {time.Now().Format(query.DateLayout)}},
   101  			false,
   102  			true,
   103  			false,
   104  		},
   105  		{"tx.date = DATE 2017-01-01", map[string][]string{"tx.date": {txDate}}, false, true, false},
   106  		{"tx.date = DATE 2018-01-01", map[string][]string{"tx.date": {txDate}}, false, false, false},
   107  		{
   108  			"tx.time >= TIME 2013-05-03T14:45:00Z",
   109  			map[string][]string{"tx.time": {time.Now().Format(query.TimeLayout)}},
   110  			false,
   111  			true,
   112  			false,
   113  		},
   114  		{"tx.time = TIME 2013-05-03T14:45:00Z", map[string][]string{"tx.time": {txTime}}, false, false, false},
   115  		{"abci.owner.name CONTAINS 'Igor'", map[string][]string{"abci.owner.name": {"Igor,Ivan"}}, false, true, false},
   116  		{"abci.owner.name CONTAINS 'Igor'", map[string][]string{"abci.owner.name": {"Pavel,Ivan"}}, false, false, false},
   117  		{"abci.owner.name = 'Igor'", map[string][]string{"abci.owner.name": {"Igor", "Ivan"}}, false, true, false},
   118  		{
   119  			"abci.owner.name = 'Ivan'",
   120  			map[string][]string{"abci.owner.name": {"Igor", "Ivan"}},
   121  			false,
   122  			true,
   123  			false,
   124  		},
   125  		{
   126  			"abci.owner.name = 'Ivan' AND abci.owner.name = 'Igor'",
   127  			map[string][]string{"abci.owner.name": {"Igor", "Ivan"}},
   128  			false,
   129  			true,
   130  			false,
   131  		},
   132  		{
   133  			"abci.owner.name = 'Ivan' AND abci.owner.name = 'John'",
   134  			map[string][]string{"abci.owner.name": {"Igor", "Ivan"}},
   135  			false,
   136  			false,
   137  			false,
   138  		},
   139  		{
   140  			"tm.events.type='NewBlock'",
   141  			map[string][]string{"tm.events.type": {"NewBlock"}, "app.name": {"fuzzed"}},
   142  			false,
   143  			true,
   144  			false,
   145  		},
   146  		{
   147  			"app.name = 'fuzzed'",
   148  			map[string][]string{"tm.events.type": {"NewBlock"}, "app.name": {"fuzzed"}},
   149  			false,
   150  			true,
   151  			false,
   152  		},
   153  		{
   154  			"tm.events.type='NewBlock' AND app.name = 'fuzzed'",
   155  			map[string][]string{"tm.events.type": {"NewBlock"}, "app.name": {"fuzzed"}},
   156  			false,
   157  			true,
   158  			false,
   159  		},
   160  		{
   161  			"tm.events.type='NewHeader' AND app.name = 'fuzzed'",
   162  			map[string][]string{"tm.events.type": {"NewBlock"}, "app.name": {"fuzzed"}},
   163  			false,
   164  			false,
   165  			false,
   166  		},
   167  		{"slash EXISTS",
   168  			map[string][]string{"slash.reason": {"missing_signature"}, "slash.power": {"6000"}},
   169  			false,
   170  			true,
   171  			false,
   172  		},
   173  		{"sl EXISTS",
   174  			map[string][]string{"slash.reason": {"missing_signature"}, "slash.power": {"6000"}},
   175  			false,
   176  			true,
   177  			false,
   178  		},
   179  		{"slash EXISTS",
   180  			map[string][]string{"transfer.recipient": {"cosmos1gu6y2a0ffteesyeyeesk23082c6998xyzmt9mz"},
   181  				"transfer.sender": {"cosmos1crje20aj4gxdtyct7z3knxqry2jqt2fuaey6u5"}},
   182  			false,
   183  			false,
   184  			false,
   185  		},
   186  		{"slash.reason EXISTS AND slash.power > 1000",
   187  			map[string][]string{"slash.reason": {"missing_signature"}, "slash.power": {"6000"}},
   188  			false,
   189  			true,
   190  			false,
   191  		},
   192  		{"slash.reason EXISTS AND slash.power > 1000",
   193  			map[string][]string{"slash.reason": {"missing_signature"}, "slash.power": {"500"}},
   194  			false,
   195  			false,
   196  			false,
   197  		},
   198  		{"slash.reason EXISTS",
   199  			map[string][]string{"transfer.recipient": {"cosmos1gu6y2a0ffteesyeyeesk23082c6998xyzmt9mz"},
   200  				"transfer.sender": {"cosmos1crje20aj4gxdtyct7z3knxqry2jqt2fuaey6u5"}},
   201  			false,
   202  			false,
   203  			false,
   204  		},
   205  	}
   206  
   207  	for _, tc := range testCases {
   208  		q, err := query.New(tc.s)
   209  		if !tc.err {
   210  			require.Nil(t, err)
   211  		}
   212  		require.NotNil(t, q, "Query '%s' should not be nil", tc.s)
   213  
   214  		if tc.matches {
   215  			match, err := q.Matches(tc.events)
   216  			assert.Nil(t, err, "Query '%s' should not error on match %v", tc.s, tc.events)
   217  			assert.True(t, match, "Query '%s' should match %v", tc.s, tc.events)
   218  		} else {
   219  			match, err := q.Matches(tc.events)
   220  			assert.Equal(t, tc.matchErr, err != nil, "Unexpected error for query '%s' match %v", tc.s, tc.events)
   221  			assert.False(t, match, "Query '%s' should not match %v", tc.s, tc.events)
   222  		}
   223  	}
   224  }
   225  
   226  func TestMustParse(t *testing.T) {
   227  	assert.Panics(t, func() { query.MustParse("=") })
   228  	assert.NotPanics(t, func() { query.MustParse("tm.events.type='NewBlock'") })
   229  }
   230  
   231  func TestConditions(t *testing.T) {
   232  	txTime, err := time.Parse(time.RFC3339, "2013-05-03T14:45:00Z")
   233  	require.NoError(t, err)
   234  
   235  	bigInt := new(big.Int)
   236  	bigInt, ok := bigInt.SetString("10000000000000000000", 10)
   237  	require.True(t, ok)
   238  
   239  	testCases := []struct {
   240  		s          string
   241  		conditions []query.Condition
   242  	}{
   243  		{
   244  			s: "tm.events.type='NewBlock'",
   245  			conditions: []query.Condition{
   246  				{CompositeKey: "tm.events.type", Op: query.OpEqual, Operand: "NewBlock"},
   247  			},
   248  		},
   249  		{
   250  			s: "tx.gas > 7 AND tx.gas < 9",
   251  			conditions: []query.Condition{
   252  				{CompositeKey: "tx.gas", Op: query.OpGreater, Operand: big.NewInt(7)},
   253  				{CompositeKey: "tx.gas", Op: query.OpLess, Operand: big.NewInt(9)},
   254  			},
   255  		},
   256  		{
   257  
   258  			s: "tx.gas > 7.5 AND tx.gas < 9",
   259  			conditions: []query.Condition{
   260  				{CompositeKey: "tx.gas", Op: query.OpGreater, Operand: 7.5},
   261  				{CompositeKey: "tx.gas", Op: query.OpLess, Operand: big.NewInt(9)},
   262  			},
   263  		},
   264  		{
   265  
   266  			s: "tx.gas > " + bigInt.String() + " AND tx.gas < 9",
   267  			conditions: []query.Condition{
   268  				{CompositeKey: "tx.gas", Op: query.OpGreater, Operand: bigInt},
   269  				{CompositeKey: "tx.gas", Op: query.OpLess, Operand: big.NewInt(9)},
   270  			},
   271  		},
   272  		{
   273  			s: "tx.time >= TIME 2013-05-03T14:45:00Z",
   274  			conditions: []query.Condition{
   275  				{CompositeKey: "tx.time", Op: query.OpGreaterEqual, Operand: txTime},
   276  			},
   277  		},
   278  		{
   279  			s: "slashing EXISTS",
   280  			conditions: []query.Condition{
   281  				{CompositeKey: "slashing", Op: query.OpExists},
   282  			},
   283  		},
   284  	}
   285  
   286  	for _, tc := range testCases {
   287  		q, err := query.New(tc.s)
   288  		require.Nil(t, err)
   289  
   290  		c, err := q.Conditions()
   291  		require.NoError(t, err)
   292  		assert.Equal(t, tc.conditions, c)
   293  	}
   294  }