From d4fd2467d691a69a0ba75348086424b9fb33a297 Mon Sep 17 00:00:00 2001 From: bndw Date: Sat, 7 Feb 2026 15:20:57 -0800 Subject: wip --- filter_test.go | 415 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 415 insertions(+) create mode 100644 filter_test.go (limited to 'filter_test.go') diff --git a/filter_test.go b/filter_test.go new file mode 100644 index 0000000..ebe2b1d --- /dev/null +++ b/filter_test.go @@ -0,0 +1,415 @@ +package nostr + +import ( + "encoding/json" + "testing" +) + +func TestFilterMarshalJSON(t *testing.T) { + since := int64(1704067200) + until := int64(1704153600) + + filter := Filter{ + IDs: []string{"abc123"}, + Kinds: []int{1, 7}, + Authors: []string{"def456"}, + Tags: map[string][]string{ + "e": {"event1", "event2"}, + "p": {"pubkey1"}, + }, + Since: &since, + Until: &until, + Limit: 100, + } + + data, err := filter.MarshalJSON() + if err != nil { + t.Fatalf("MarshalJSON() error = %v", err) + } + + // Parse and check structure + var m map[string]interface{} + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + + // Check regular fields + if _, ok := m["ids"]; !ok { + t.Error("ids field missing") + } + if _, ok := m["kinds"]; !ok { + t.Error("kinds field missing") + } + if _, ok := m["authors"]; !ok { + t.Error("authors field missing") + } + if _, ok := m["since"]; !ok { + t.Error("since field missing") + } + if _, ok := m["until"]; !ok { + t.Error("until field missing") + } + if _, ok := m["limit"]; !ok { + t.Error("limit field missing") + } + + // Check tag filters with # prefix + if _, ok := m["#e"]; !ok { + t.Error("#e field missing") + } + if _, ok := m["#p"]; !ok { + t.Error("#p field missing") + } +} + +func TestFilterMarshalJSONOmitsEmpty(t *testing.T) { + filter := Filter{ + Kinds: []int{1}, + } + + data, err := filter.MarshalJSON() + if err != nil { + t.Fatalf("MarshalJSON() error = %v", err) + } + + var m map[string]interface{} + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + + if _, ok := m["ids"]; ok { + t.Error("empty ids should be omitted") + } + if _, ok := m["authors"]; ok { + t.Error("empty authors should be omitted") + } + if _, ok := m["since"]; ok { + t.Error("nil since should be omitted") + } + if _, ok := m["until"]; ok { + t.Error("nil until should be omitted") + } + if _, ok := m["limit"]; ok { + t.Error("zero limit should be omitted") + } +} + +func TestFilterUnmarshalJSON(t *testing.T) { + jsonData := `{ + "ids": ["abc123"], + "kinds": [1, 7], + "authors": ["def456"], + "#e": ["event1", "event2"], + "#p": ["pubkey1"], + "since": 1704067200, + "until": 1704153600, + "limit": 100 + }` + + var filter Filter + if err := json.Unmarshal([]byte(jsonData), &filter); err != nil { + t.Fatalf("UnmarshalJSON() error = %v", err) + } + + if len(filter.IDs) != 1 || filter.IDs[0] != "abc123" { + t.Errorf("IDs = %v, want [abc123]", filter.IDs) + } + if len(filter.Kinds) != 2 { + t.Errorf("Kinds length = %d, want 2", len(filter.Kinds)) + } + if len(filter.Authors) != 1 || filter.Authors[0] != "def456" { + t.Errorf("Authors = %v, want [def456]", filter.Authors) + } + if filter.Since == nil || *filter.Since != 1704067200 { + t.Errorf("Since = %v, want 1704067200", filter.Since) + } + if filter.Until == nil || *filter.Until != 1704153600 { + t.Errorf("Until = %v, want 1704153600", filter.Until) + } + if filter.Limit != 100 { + t.Errorf("Limit = %d, want 100", filter.Limit) + } + + // Check tag filters + if len(filter.Tags["e"]) != 2 { + t.Errorf("Tags[e] length = %d, want 2", len(filter.Tags["e"])) + } + if len(filter.Tags["p"]) != 1 { + t.Errorf("Tags[p] length = %d, want 1", len(filter.Tags["p"])) + } +} + +func TestFilterMatchesIDs(t *testing.T) { + filter := Filter{ + IDs: []string{"abc", "def456"}, + } + + tests := []struct { + id string + want bool + }{ + {"abc123", true}, // matches prefix "abc" + {"abcdef", true}, // matches prefix "abc" + {"def456", true}, // exact match + {"def456xyz", true}, // matches prefix "def456" + {"xyz789", false}, // no match + {"ab", false}, // "ab" doesn't start with "abc" + } + + for _, tt := range tests { + event := &Event{ID: tt.id} + if got := filter.Matches(event); got != tt.want { + t.Errorf("Matches() with ID %s = %v, want %v", tt.id, got, tt.want) + } + } +} + +func TestFilterMatchesAuthors(t *testing.T) { + filter := Filter{ + Authors: []string{"pubkey1", "pubkey2"}, + } + + tests := []struct { + pubkey string + want bool + }{ + {"pubkey1", true}, + {"pubkey1abc", true}, // Prefix match + {"pubkey2", true}, + {"pubkey3", false}, + } + + for _, tt := range tests { + event := &Event{PubKey: tt.pubkey} + if got := filter.Matches(event); got != tt.want { + t.Errorf("Matches() with PubKey %s = %v, want %v", tt.pubkey, got, tt.want) + } + } +} + +func TestFilterMatchesKinds(t *testing.T) { + filter := Filter{ + Kinds: []int{1, 7}, + } + + tests := []struct { + kind int + want bool + }{ + {1, true}, + {7, true}, + {0, false}, + {4, false}, + } + + for _, tt := range tests { + event := &Event{Kind: tt.kind} + if got := filter.Matches(event); got != tt.want { + t.Errorf("Matches() with Kind %d = %v, want %v", tt.kind, got, tt.want) + } + } +} + +func TestFilterMatchesSince(t *testing.T) { + since := int64(1704067200) + filter := Filter{ + Since: &since, + } + + tests := []struct { + createdAt int64 + want bool + }{ + {1704067200, true}, // Equal + {1704067201, true}, // After + {1704067199, false}, // Before + } + + for _, tt := range tests { + event := &Event{CreatedAt: tt.createdAt} + if got := filter.Matches(event); got != tt.want { + t.Errorf("Matches() with CreatedAt %d = %v, want %v", tt.createdAt, got, tt.want) + } + } +} + +func TestFilterMatchesUntil(t *testing.T) { + until := int64(1704067200) + filter := Filter{ + Until: &until, + } + + tests := []struct { + createdAt int64 + want bool + }{ + {1704067200, true}, // Equal + {1704067199, true}, // Before + {1704067201, false}, // After + } + + for _, tt := range tests { + event := &Event{CreatedAt: tt.createdAt} + if got := filter.Matches(event); got != tt.want { + t.Errorf("Matches() with CreatedAt %d = %v, want %v", tt.createdAt, got, tt.want) + } + } +} + +func TestFilterMatchesTags(t *testing.T) { + filter := Filter{ + Tags: map[string][]string{ + "e": {"event1"}, + "p": {"pubkey1", "pubkey2"}, + }, + } + + tests := []struct { + name string + tags Tags + want bool + }{ + { + name: "matches all", + tags: Tags{{"e", "event1"}, {"p", "pubkey1"}}, + want: true, + }, + { + name: "matches with different p", + tags: Tags{{"e", "event1"}, {"p", "pubkey2"}}, + want: true, + }, + { + name: "missing e tag", + tags: Tags{{"p", "pubkey1"}}, + want: false, + }, + { + name: "wrong e value", + tags: Tags{{"e", "event2"}, {"p", "pubkey1"}}, + want: false, + }, + { + name: "extra tags ok", + tags: Tags{{"e", "event1"}, {"p", "pubkey1"}, {"t", "test"}}, + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + event := &Event{Tags: tt.tags} + if got := filter.Matches(event); got != tt.want { + t.Errorf("Matches() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFilterMatchesEmpty(t *testing.T) { + // Empty filter matches everything + filter := Filter{} + event := &Event{ + ID: "abc123", + PubKey: "pubkey1", + CreatedAt: 1704067200, + Kind: 1, + Tags: Tags{{"e", "event1"}}, + Content: "test", + } + + if !filter.Matches(event) { + t.Error("Empty filter should match all events") + } +} + +func TestFilterClone(t *testing.T) { + since := int64(1704067200) + until := int64(1704153600) + + original := &Filter{ + IDs: []string{"id1", "id2"}, + Kinds: []int{1, 7}, + Authors: []string{"author1"}, + Tags: map[string][]string{ + "e": {"event1"}, + }, + Since: &since, + Until: &until, + Limit: 100, + } + + clone := original.Clone() + + // Modify original + original.IDs[0] = "modified" + original.Kinds[0] = 999 + original.Authors[0] = "modified" + original.Tags["e"][0] = "modified" + *original.Since = 0 + *original.Until = 0 + original.Limit = 0 + + // Clone should be unchanged + if clone.IDs[0] != "id1" { + t.Error("Clone IDs was modified") + } + if clone.Kinds[0] != 1 { + t.Error("Clone Kinds was modified") + } + if clone.Authors[0] != "author1" { + t.Error("Clone Authors was modified") + } + if clone.Tags["e"][0] != "event1" { + t.Error("Clone Tags was modified") + } + if *clone.Since != 1704067200 { + t.Error("Clone Since was modified") + } + if *clone.Until != 1704153600 { + t.Error("Clone Until was modified") + } + if clone.Limit != 100 { + t.Error("Clone Limit was modified") + } +} + +func TestFilterJSONRoundTrip(t *testing.T) { + since := int64(1704067200) + original := Filter{ + IDs: []string{"abc123"}, + Kinds: []int{1}, + Authors: []string{"def456"}, + Tags: map[string][]string{ + "e": {"event1"}, + }, + Since: &since, + Limit: 50, + } + + data, err := json.Marshal(original) + if err != nil { + t.Fatalf("Marshal error: %v", err) + } + + var decoded Filter + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("Unmarshal error: %v", err) + } + + if len(decoded.IDs) != 1 || decoded.IDs[0] != "abc123" { + t.Errorf("IDs mismatch") + } + if len(decoded.Kinds) != 1 || decoded.Kinds[0] != 1 { + t.Errorf("Kinds mismatch") + } + if len(decoded.Tags["e"]) != 1 || decoded.Tags["e"][0] != "event1" { + t.Errorf("Tags mismatch") + } + if decoded.Since == nil || *decoded.Since != since { + t.Errorf("Since mismatch") + } + if decoded.Limit != 50 { + t.Errorf("Limit mismatch") + } +} -- cgit v1.2.3