filter_test.go raw

   1  package nostr
   2  
   3  import (
   4  	"strings"
   5  	"testing"
   6  )
   7  
   8  func TestParseFilter(t *testing.T) {
   9  	raw := []byte(`{"ids":["eaeb41f2e1d44abc3949052406db478aeed872a64e46022daace5955cd65b316"],"authors":["f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"],"kinds":[0,1,30023],"#e":["abc"],"#p":["def","ghi"],"since":100,"until":200,"limit":50,"search":"hello world"}`)
  10  	f, err := ParseFilter(raw)
  11  	if err != nil {
  12  		t.Fatal(err)
  13  	}
  14  	if len(f.IDs) != 1 || len(f.Authors) != 1 || len(f.Kinds) != 3 {
  15  		t.Fatalf("unexpected filter: %+v", f)
  16  	}
  17  	if f.Since != 100 || f.Until != 200 || f.Limit != 50 || f.Search != "hello world" {
  18  		t.Fatalf("unexpected scalars: %+v", f)
  19  	}
  20  	if len(f.Tags["e"]) != 1 || len(f.Tags["p"]) != 2 {
  21  		t.Fatalf("unexpected tags: %+v", f.Tags)
  22  	}
  23  }
  24  
  25  func TestFilterMatches(t *testing.T) {
  26  	e := &Event{Kind: 1, CreatedAt: 150, Content: "hello world"}
  27  	e.PubKey[0] = 0xaa
  28  	e.ID[0] = 0xbb
  29  	e.Tags = Tags{{"p", "deadbeef"}, {"t", "nostr"}}
  30  	id := Hex(e.ID[:])
  31  	pub := Hex(e.PubKey[:])
  32  
  33  	base := func() *Filter { return &Filter{} }
  34  	cases := []struct {
  35  		name string
  36  		f    *Filter
  37  		want bool
  38  	}{
  39  		{"empty", base(), true},
  40  		{"kind match", &Filter{Kinds: []int{1}}, true},
  41  		{"kind miss", &Filter{Kinds: []int{2}}, false},
  42  		{"id match", &Filter{IDs: []string{id}}, true},
  43  		{"id miss", &Filter{IDs: []string{Hex(make([]byte, 32))}}, false},
  44  		{"author match", &Filter{Authors: []string{pub}}, true},
  45  		{"tag match", &Filter{Tags: map[string][]string{"p": {"deadbeef"}}}, true},
  46  		{"tag miss", &Filter{Tags: map[string][]string{"p": {"cafe"}}}, false},
  47  		{"two tags and", &Filter{Tags: map[string][]string{"p": {"deadbeef"}, "t": {"nostr"}}}, true},
  48  		{"since pass", &Filter{Since: 150}, true},
  49  		{"since fail", &Filter{Since: 151}, false},
  50  		{"until pass", &Filter{Until: 150}, true},
  51  		{"until fail", &Filter{Until: 149}, false},
  52  		{"multi-letter tag", &Filter{Tags: map[string][]string{"ee": {"x"}}}, false},
  53  	}
  54  	for _, c := range cases {
  55  		if got := c.f.Matches(e); got != c.want {
  56  			t.Errorf("%s: got %v, want %v", c.name, got, c.want)
  57  		}
  58  	}
  59  }
  60  
  61  func TestFilterRejectsBadHex(t *testing.T) {
  62  	if _, err := ParseFilter([]byte(`{"ids":["nothex"]}`)); err == nil {
  63  		t.Fatal("expected error for non-hex id")
  64  	}
  65  	if _, err := ParseFilter([]byte(`{"authors":["ABCDEF"]}`)); err == nil {
  66  		t.Fatal("expected error for uppercase author")
  67  	}
  68  	if _, err := ParseFilter([]byte(`{"kinds":[70000]}`)); err == nil {
  69  		t.Fatal("expected error for out-of-range kind")
  70  	}
  71  }
  72  
  73  func TestFilterUnknownKeysIgnored(t *testing.T) {
  74  	f, err := ParseFilter([]byte(`{"kinds":[1],"future_extension":{"x":1},"limit":10}`))
  75  	if err != nil {
  76  		t.Fatal(err)
  77  	}
  78  	if len(f.Kinds) != 1 || f.Limit != 10 {
  79  		t.Fatalf("filter: %+v", f)
  80  	}
  81  }
  82  
  83  func TestFilterLimitClamp(t *testing.T) {
  84  	f, err := ParseFilter([]byte(`{"limit":1000000000000}`))
  85  	if err != nil {
  86  		t.Fatal(err)
  87  	}
  88  	if f.Limit != 1<<31-1 {
  89  		t.Fatalf("limit = %d, want MaxInt32", f.Limit)
  90  	}
  91  	if f, err = ParseFilter([]byte(`{"limit":-5}`)); err != nil || f.Limit != 0 {
  92  		t.Fatalf("negative limit: %+v %v", f, err)
  93  	}
  94  }
  95  
  96  func TestFilterTypeErrors(t *testing.T) {
  97  	cases := []string{
  98  		`{"kinds":["one"]}`,
  99  		`{"ids":5}`,
 100  		`{"#e":"not-an-array"}`,
 101  		`{"since":"yesterday"}`,
 102  		`{"#e":["ok",5]}`,
 103  		`[]`,
 104  	}
 105  	for _, raw := range cases {
 106  		if _, err := ParseFilter([]byte(raw)); err == nil {
 107  			t.Errorf("accepted %s", raw)
 108  		}
 109  	}
 110  }
 111  
 112  func TestFilterAppendJSONRoundTrip(t *testing.T) {
 113  	f := &Filter{
 114  		IDs:     []string{"eaeb41f2e1d44abc3949052406db478aeed872a64e46022daace5955cd65b316"},
 115  		Authors: []string{"f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"},
 116  		Kinds:   []int{1, 2},
 117  		Tags:    map[string][]string{"e": {"abc"}},
 118  		Since:   10,
 119  		Until:   20,
 120  		Limit:   5,
 121  		Search:  "hello",
 122  	}
 123  	back, err := ParseFilter(f.AppendJSON(nil))
 124  	if err != nil {
 125  		t.Fatalf("reparse %s: %v", f, err)
 126  	}
 127  	if len(back.IDs) != 1 || len(back.Kinds) != 2 || back.Since != 10 || back.Until != 20 ||
 128  		back.Limit != 5 || back.Search != "hello" || len(back.Tags["e"]) != 1 {
 129  		t.Fatalf("round trip: %+v", back)
 130  	}
 131  	if TagsKey("#e") != "e" || TagsKey("e") != "e" {
 132  		t.Fatal("TagsKey")
 133  	}
 134  }
 135  
 136  func TestParseGraphFilter(t *testing.T) {
 137  	seed := strings.Repeat("ab", 32)
 138  	f, err := ParseFilter([]byte(`{"_graph":["` + seed + `",2,"PP","BOTH"]}`))
 139  	if err != nil {
 140  		t.Fatal(err)
 141  	}
 142  	if f.Graph == nil || f.Graph.Seed != seed || f.Graph.Depth != 2 ||
 143  		f.Graph.Edge != GraphEdgePubkeyPubkey || f.Graph.Direction != GraphDirBoth {
 144  		t.Fatalf("array form: %+v", f.Graph)
 145  	}
 146  	back, err := ParseFilter(f.AppendJSON(nil))
 147  	if err != nil {
 148  		t.Fatal(err)
 149  	}
 150  	if back.Graph == nil || *back.Graph != *f.Graph {
 151  		t.Fatalf("round trip: %+v", back.Graph)
 152  	}
 153  
 154  	object := `{"_graph":{"seed":"` + strings.ToUpper(seed) + `","edge":"pe","direction":"in"}}`
 155  	f, err = ParseFilter([]byte(object))
 156  	if err != nil {
 157  		t.Fatal(err)
 158  	}
 159  	if f.Graph.Seed != seed || f.Graph.Depth != 1 || f.Graph.Edge != GraphEdgePubkeyEvent ||
 160  		f.Graph.Direction != GraphDirIn {
 161  		t.Fatalf("object form: %+v", f.Graph)
 162  	}
 163  }
 164  
 165  func TestParseGraphFilterRejectsBadValues(t *testing.T) {
 166  	seed := strings.Repeat("ab", 32)
 167  	cases := []string{
 168  		`{"_graph":[]}`,
 169  		`{"_graph":["` + seed + `",1,"pp"]}`,
 170  		`{"_graph":["` + seed + `",1,"pp","out",1]}`,
 171  		`{"_graph":["short",1,"pp","out"]}`,
 172  		`{"_graph":["` + seed + `",17,"pp","out"]}`,
 173  		`{"_graph":["` + seed + `",1,"xx","out"]}`,
 174  		`{"_graph":["` + seed + `",1,"pp","sideways"]}`,
 175  		`{"_graph":"pp"}`,
 176  		`{"_graph":{"":"x"}}`,
 177  		`{"_graph":{"pubkey":"` + seed + `"}}`,
 178  	}
 179  	for _, raw := range cases {
 180  		if _, err := ParseFilter([]byte(raw)); err == nil {
 181  			t.Errorf("accepted %s", raw)
 182  		}
 183  	}
 184  }
 185