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") } }