github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/internal/pubsub/query/query_test.go (about) 1 package query_test 2 3 import ( 4 "fmt" 5 "strings" 6 "testing" 7 "time" 8 9 "github.com/ari-anchor/sei-tendermint/abci/types" 10 "github.com/ari-anchor/sei-tendermint/internal/pubsub/query" 11 "github.com/ari-anchor/sei-tendermint/internal/pubsub/query/syntax" 12 ) 13 14 // Example events from the OpenAPI documentation: 15 // 16 // https://github.com/ari-anchor/sei-tendermint/blob/master/rpc/openapi/openapi.yaml 17 // 18 // Redactions: 19 // 20 // - Add an explicit "tm" event for the built-in attributes. 21 // - Remove Index fields (not relevant to tests). 22 // - Add explicit balance values (to use in tests). 23 var apiEvents = []types.Event{ 24 { 25 Type: "tm", 26 Attributes: []types.EventAttribute{ 27 {Key: []byte("event"), Value: []byte("Tx")}, 28 {Key: []byte("hash"), Value: []byte("XYZ")}, 29 {Key: []byte("height"), Value: []byte("5")}, 30 }, 31 }, 32 { 33 Type: "rewards.withdraw", 34 Attributes: []types.EventAttribute{ 35 {Key: []byte("address"), Value: []byte("AddrA")}, 36 {Key: []byte("source"), Value: []byte("SrcX")}, 37 {Key: []byte("amount"), Value: []byte("100")}, 38 {Key: []byte("balance"), Value: []byte("1500")}, 39 }, 40 }, 41 { 42 Type: "rewards.withdraw", 43 Attributes: []types.EventAttribute{ 44 {Key: []byte("address"), Value: []byte("AddrB")}, 45 {Key: []byte("source"), Value: []byte("SrcY")}, 46 {Key: []byte("amount"), Value: []byte("45")}, 47 {Key: []byte("balance"), Value: []byte("999")}, 48 }, 49 }, 50 { 51 Type: "transfer", 52 Attributes: []types.EventAttribute{ 53 {Key: []byte("sender"), Value: []byte("AddrC")}, 54 {Key: []byte("recipient"), Value: []byte("AddrD")}, 55 {Key: []byte("amount"), Value: []byte("160")}, 56 }, 57 }, 58 } 59 60 func TestCompiledMatches(t *testing.T) { 61 var ( 62 txDate = "2017-01-01" 63 txTime = "2018-05-03T14:45:00Z" 64 ) 65 66 testCases := []struct { 67 s string 68 events []types.Event 69 matches bool 70 }{ 71 {`tm.events.type='NewBlock'`, 72 newTestEvents(`tm|events.type=NewBlock`), 73 true}, 74 {`tx.gas > 7`, 75 newTestEvents(`tx|gas=8`), 76 true}, 77 {`transfer.amount > 7`, 78 newTestEvents(`transfer|amount=8stake`), 79 true}, 80 {`transfer.amount > 7`, 81 newTestEvents(`transfer|amount=8.045`), 82 true}, 83 {`transfer.amount > 7.043`, 84 newTestEvents(`transfer|amount=8.045stake`), 85 true}, 86 {`transfer.amount > 8.045`, 87 newTestEvents(`transfer|amount=8.045stake`), 88 false}, 89 {`tx.gas > 7 AND tx.gas < 9`, 90 newTestEvents(`tx|gas=8`), 91 true}, 92 {`body.weight >= 3.5`, 93 newTestEvents(`body|weight=3.5`), 94 true}, 95 {`account.balance < 1000.0`, 96 newTestEvents(`account|balance=900`), 97 true}, 98 {`apples.kg <= 4`, 99 newTestEvents(`apples|kg=4.0`), 100 true}, 101 {`body.weight >= 4.5`, 102 newTestEvents(`body|weight=4.5`), 103 true}, 104 {`oranges.kg < 4 AND watermellons.kg > 10`, 105 newTestEvents(`oranges|kg=3`, `watermellons|kg=12`), 106 true}, 107 {`peaches.kg < 4`, 108 newTestEvents(`peaches|kg=5`), 109 false}, 110 {`tx.date > DATE 2017-01-01`, 111 newTestEvents(`tx|date=` + time.Now().Format(syntax.DateFormat)), 112 true}, 113 {`tx.date = DATE 2017-01-01`, 114 newTestEvents(`tx|date=` + txDate), 115 true}, 116 {`tx.date = DATE 2018-01-01`, 117 newTestEvents(`tx|date=` + txDate), 118 false}, 119 {`tx.time >= TIME 2013-05-03T14:45:00Z`, 120 newTestEvents(`tx|time=` + time.Now().Format(syntax.TimeFormat)), 121 true}, 122 {`tx.time = TIME 2013-05-03T14:45:00Z`, 123 newTestEvents(`tx|time=` + txTime), 124 false}, 125 {`abci.owner.name CONTAINS 'Igor'`, 126 newTestEvents(`abci|owner.name=Igor|owner.name=Ivan`), 127 true}, 128 {`abci.owner.name CONTAINS 'Igor'`, 129 newTestEvents(`abci|owner.name=Pavel|owner.name=Ivan`), 130 false}, 131 {`abci.owner.name = 'Igor'`, 132 newTestEvents(`abci|owner.name=Igor|owner.name=Ivan`), 133 true}, 134 {`abci.owner.name = 'Ivan'`, 135 newTestEvents(`abci|owner.name=Igor|owner.name=Ivan`), 136 true}, 137 {`abci.owner.name = 'Ivan' AND abci.owner.name = 'Igor'`, 138 newTestEvents(`abci|owner.name=Igor|owner.name=Ivan`), 139 true}, 140 {`abci.owner.name = 'Ivan' AND abci.owner.name = 'John'`, 141 newTestEvents(`abci|owner.name=Igor|owner.name=Ivan`), 142 false}, 143 {`tm.events.type='NewBlock'`, 144 newTestEvents(`tm|events.type=NewBlock`, `app|name=fuzzed`), 145 true}, 146 {`app.name = 'fuzzed'`, 147 newTestEvents(`tm|events.type=NewBlock`, `app|name=fuzzed`), 148 true}, 149 {`tm.events.type='NewBlock' AND app.name = 'fuzzed'`, 150 newTestEvents(`tm|events.type=NewBlock`, `app|name=fuzzed`), 151 true}, 152 {`tm.events.type='NewHeader' AND app.name = 'fuzzed'`, 153 newTestEvents(`tm|events.type=NewBlock`, `app|name=fuzzed`), 154 false}, 155 {`slash EXISTS`, 156 newTestEvents(`slash|reason=missing_signature|power=6000`), 157 true}, 158 {`slash EXISTS`, 159 newTestEvents(`transfer|recipient=cosmos1gu6y2a0ffteesyeyeesk23082c6998xyzmt9mz|sender=cosmos1crje20aj4gxdtyct7z3knxqry2jqt2fuaey6u5`), 160 false}, 161 {`slash.reason EXISTS AND slash.power > 1000`, 162 newTestEvents(`slash|reason=missing_signature|power=6000`), 163 true}, 164 {`slash.reason EXISTS AND slash.power > 1000`, 165 newTestEvents(`slash|reason=missing_signature|power=500`), 166 false}, 167 {`slash.reason EXISTS`, 168 newTestEvents(`transfer|recipient=cosmos1gu6y2a0ffteesyeyeesk23082c6998xyzmt9mz|sender=cosmos1crje20aj4gxdtyct7z3knxqry2jqt2fuaey6u5`), 169 false}, 170 171 // Test cases based on the OpenAPI examples. 172 {`tm.event = 'Tx' AND rewards.withdraw.address = 'AddrA'`, 173 apiEvents, true}, 174 {`tm.event = 'Tx' AND rewards.withdraw.address = 'AddrA' AND rewards.withdraw.source = 'SrcY'`, 175 apiEvents, true}, 176 {`tm.event = 'Tx' AND transfer.sender = 'AddrA'`, 177 apiEvents, false}, 178 {`tm.event = 'Tx' AND transfer.sender = 'AddrC'`, 179 apiEvents, true}, 180 {`tm.event = 'Tx' AND transfer.sender = 'AddrZ'`, 181 apiEvents, false}, 182 {`tm.event = 'Tx' AND rewards.withdraw.address = 'AddrZ'`, 183 apiEvents, false}, 184 {`tm.event = 'Tx' AND rewards.withdraw.source = 'W'`, 185 apiEvents, false}, 186 } 187 188 // NOTE: The original implementation allowed arbitrary prefix matches on 189 // attribute tags, e.g., "sl" would match "slash". 190 // 191 // That is weird and probably wrong: "foo.ba" should not match "foo.bar", 192 // or there is no way to distinguish the case where there were two values 193 // for "foo.bar" or one value each for "foo.ba" and "foo.bar". 194 // 195 // Apart from a single test case, I could not find any attested usage of 196 // this implementation detail. It isn't documented in the OpenAPI docs and 197 // is not shown in any of the example inputs. 198 // 199 // On that basis, I removed that test case. This implementation still does 200 // correctly handle variable type/attribute splits ("x", "y.z" / "x.y", "z") 201 // since that was required by the original "flattened" event representation. 202 203 for i, tc := range testCases { 204 t.Run(fmt.Sprintf("%02d", i+1), func(t *testing.T) { 205 c, err := query.New(tc.s) 206 if err != nil { 207 t.Fatalf("NewCompiled %#q: unexpected error: %v", tc.s, err) 208 } 209 210 got := c.Matches(tc.events) 211 if got != tc.matches { 212 t.Errorf("Query: %#q\nInput: %+v\nMatches: got %v, want %v", 213 tc.s, tc.events, got, tc.matches) 214 } 215 }) 216 } 217 } 218 219 func TestAllMatchesAll(t *testing.T) { 220 events := newTestEvents( 221 ``, 222 `Asher|Roth=`, 223 `Route|66=`, 224 `Rilly|Blue=`, 225 ) 226 for i := 0; i < len(events); i++ { 227 if !query.All.Matches(events[:i]) { 228 t.Errorf("Did not match on %+v ", events[:i]) 229 } 230 } 231 } 232 233 // newTestEvent constructs an Event message from a template string. 234 // The format is "type|attr1=val1|attr2=val2|...". 235 func newTestEvent(s string) types.Event { 236 var event types.Event 237 parts := strings.Split(s, "|") 238 event.Type = parts[0] 239 if len(parts) == 1 { 240 return event // type only, no attributes 241 } 242 for _, kv := range parts[1:] { 243 key, val := splitKV(kv) 244 event.Attributes = append(event.Attributes, types.EventAttribute{ 245 Key: []byte(key), 246 Value: []byte(val), 247 }) 248 } 249 return event 250 } 251 252 // newTestEvents constructs a slice of Event messages by applying newTestEvent 253 // to each element of ss. 254 func newTestEvents(ss ...string) []types.Event { 255 events := make([]types.Event, len(ss)) 256 for i, s := range ss { 257 events[i] = newTestEvent(s) 258 } 259 return events 260 } 261 262 func splitKV(s string) (key, value string) { 263 kv := strings.SplitN(s, "=", 2) 264 return kv[0], kv[1] 265 }