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 }