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  }