diff options
Diffstat (limited to 'filter_test.go')
| -rw-r--r-- | filter_test.go | 415 |
1 files changed, 415 insertions, 0 deletions
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 @@ | |||
| 1 | package nostr | ||
| 2 | |||
| 3 | import ( | ||
| 4 | "encoding/json" | ||
| 5 | "testing" | ||
| 6 | ) | ||
| 7 | |||
| 8 | func TestFilterMarshalJSON(t *testing.T) { | ||
| 9 | since := int64(1704067200) | ||
| 10 | until := int64(1704153600) | ||
| 11 | |||
| 12 | filter := Filter{ | ||
| 13 | IDs: []string{"abc123"}, | ||
| 14 | Kinds: []int{1, 7}, | ||
| 15 | Authors: []string{"def456"}, | ||
| 16 | Tags: map[string][]string{ | ||
| 17 | "e": {"event1", "event2"}, | ||
| 18 | "p": {"pubkey1"}, | ||
| 19 | }, | ||
| 20 | Since: &since, | ||
| 21 | Until: &until, | ||
| 22 | Limit: 100, | ||
| 23 | } | ||
| 24 | |||
| 25 | data, err := filter.MarshalJSON() | ||
| 26 | if err != nil { | ||
| 27 | t.Fatalf("MarshalJSON() error = %v", err) | ||
| 28 | } | ||
| 29 | |||
| 30 | // Parse and check structure | ||
| 31 | var m map[string]interface{} | ||
| 32 | if err := json.Unmarshal(data, &m); err != nil { | ||
| 33 | t.Fatalf("Failed to unmarshal: %v", err) | ||
| 34 | } | ||
| 35 | |||
| 36 | // Check regular fields | ||
| 37 | if _, ok := m["ids"]; !ok { | ||
| 38 | t.Error("ids field missing") | ||
| 39 | } | ||
| 40 | if _, ok := m["kinds"]; !ok { | ||
| 41 | t.Error("kinds field missing") | ||
| 42 | } | ||
| 43 | if _, ok := m["authors"]; !ok { | ||
| 44 | t.Error("authors field missing") | ||
| 45 | } | ||
| 46 | if _, ok := m["since"]; !ok { | ||
| 47 | t.Error("since field missing") | ||
| 48 | } | ||
| 49 | if _, ok := m["until"]; !ok { | ||
| 50 | t.Error("until field missing") | ||
| 51 | } | ||
| 52 | if _, ok := m["limit"]; !ok { | ||
| 53 | t.Error("limit field missing") | ||
| 54 | } | ||
| 55 | |||
| 56 | // Check tag filters with # prefix | ||
| 57 | if _, ok := m["#e"]; !ok { | ||
| 58 | t.Error("#e field missing") | ||
| 59 | } | ||
| 60 | if _, ok := m["#p"]; !ok { | ||
| 61 | t.Error("#p field missing") | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | func TestFilterMarshalJSONOmitsEmpty(t *testing.T) { | ||
| 66 | filter := Filter{ | ||
| 67 | Kinds: []int{1}, | ||
| 68 | } | ||
| 69 | |||
| 70 | data, err := filter.MarshalJSON() | ||
| 71 | if err != nil { | ||
| 72 | t.Fatalf("MarshalJSON() error = %v", err) | ||
| 73 | } | ||
| 74 | |||
| 75 | var m map[string]interface{} | ||
| 76 | if err := json.Unmarshal(data, &m); err != nil { | ||
| 77 | t.Fatalf("Failed to unmarshal: %v", err) | ||
| 78 | } | ||
| 79 | |||
| 80 | if _, ok := m["ids"]; ok { | ||
| 81 | t.Error("empty ids should be omitted") | ||
| 82 | } | ||
| 83 | if _, ok := m["authors"]; ok { | ||
| 84 | t.Error("empty authors should be omitted") | ||
| 85 | } | ||
| 86 | if _, ok := m["since"]; ok { | ||
| 87 | t.Error("nil since should be omitted") | ||
| 88 | } | ||
| 89 | if _, ok := m["until"]; ok { | ||
| 90 | t.Error("nil until should be omitted") | ||
| 91 | } | ||
| 92 | if _, ok := m["limit"]; ok { | ||
| 93 | t.Error("zero limit should be omitted") | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | func TestFilterUnmarshalJSON(t *testing.T) { | ||
| 98 | jsonData := `{ | ||
| 99 | "ids": ["abc123"], | ||
| 100 | "kinds": [1, 7], | ||
| 101 | "authors": ["def456"], | ||
| 102 | "#e": ["event1", "event2"], | ||
| 103 | "#p": ["pubkey1"], | ||
| 104 | "since": 1704067200, | ||
| 105 | "until": 1704153600, | ||
| 106 | "limit": 100 | ||
| 107 | }` | ||
| 108 | |||
| 109 | var filter Filter | ||
| 110 | if err := json.Unmarshal([]byte(jsonData), &filter); err != nil { | ||
| 111 | t.Fatalf("UnmarshalJSON() error = %v", err) | ||
| 112 | } | ||
| 113 | |||
| 114 | if len(filter.IDs) != 1 || filter.IDs[0] != "abc123" { | ||
| 115 | t.Errorf("IDs = %v, want [abc123]", filter.IDs) | ||
| 116 | } | ||
| 117 | if len(filter.Kinds) != 2 { | ||
| 118 | t.Errorf("Kinds length = %d, want 2", len(filter.Kinds)) | ||
| 119 | } | ||
| 120 | if len(filter.Authors) != 1 || filter.Authors[0] != "def456" { | ||
| 121 | t.Errorf("Authors = %v, want [def456]", filter.Authors) | ||
| 122 | } | ||
| 123 | if filter.Since == nil || *filter.Since != 1704067200 { | ||
| 124 | t.Errorf("Since = %v, want 1704067200", filter.Since) | ||
| 125 | } | ||
| 126 | if filter.Until == nil || *filter.Until != 1704153600 { | ||
| 127 | t.Errorf("Until = %v, want 1704153600", filter.Until) | ||
| 128 | } | ||
| 129 | if filter.Limit != 100 { | ||
| 130 | t.Errorf("Limit = %d, want 100", filter.Limit) | ||
| 131 | } | ||
| 132 | |||
| 133 | // Check tag filters | ||
| 134 | if len(filter.Tags["e"]) != 2 { | ||
| 135 | t.Errorf("Tags[e] length = %d, want 2", len(filter.Tags["e"])) | ||
| 136 | } | ||
| 137 | if len(filter.Tags["p"]) != 1 { | ||
| 138 | t.Errorf("Tags[p] length = %d, want 1", len(filter.Tags["p"])) | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | func TestFilterMatchesIDs(t *testing.T) { | ||
| 143 | filter := Filter{ | ||
| 144 | IDs: []string{"abc", "def456"}, | ||
| 145 | } | ||
| 146 | |||
| 147 | tests := []struct { | ||
| 148 | id string | ||
| 149 | want bool | ||
| 150 | }{ | ||
| 151 | {"abc123", true}, // matches prefix "abc" | ||
| 152 | {"abcdef", true}, // matches prefix "abc" | ||
| 153 | {"def456", true}, // exact match | ||
| 154 | {"def456xyz", true}, // matches prefix "def456" | ||
| 155 | {"xyz789", false}, // no match | ||
| 156 | {"ab", false}, // "ab" doesn't start with "abc" | ||
| 157 | } | ||
| 158 | |||
| 159 | for _, tt := range tests { | ||
| 160 | event := &Event{ID: tt.id} | ||
| 161 | if got := filter.Matches(event); got != tt.want { | ||
| 162 | t.Errorf("Matches() with ID %s = %v, want %v", tt.id, got, tt.want) | ||
| 163 | } | ||
| 164 | } | ||
| 165 | } | ||
| 166 | |||
| 167 | func TestFilterMatchesAuthors(t *testing.T) { | ||
| 168 | filter := Filter{ | ||
| 169 | Authors: []string{"pubkey1", "pubkey2"}, | ||
| 170 | } | ||
| 171 | |||
| 172 | tests := []struct { | ||
| 173 | pubkey string | ||
| 174 | want bool | ||
| 175 | }{ | ||
| 176 | {"pubkey1", true}, | ||
| 177 | {"pubkey1abc", true}, // Prefix match | ||
| 178 | {"pubkey2", true}, | ||
| 179 | {"pubkey3", false}, | ||
| 180 | } | ||
| 181 | |||
| 182 | for _, tt := range tests { | ||
| 183 | event := &Event{PubKey: tt.pubkey} | ||
| 184 | if got := filter.Matches(event); got != tt.want { | ||
| 185 | t.Errorf("Matches() with PubKey %s = %v, want %v", tt.pubkey, got, tt.want) | ||
| 186 | } | ||
| 187 | } | ||
| 188 | } | ||
| 189 | |||
| 190 | func TestFilterMatchesKinds(t *testing.T) { | ||
| 191 | filter := Filter{ | ||
| 192 | Kinds: []int{1, 7}, | ||
| 193 | } | ||
| 194 | |||
| 195 | tests := []struct { | ||
| 196 | kind int | ||
| 197 | want bool | ||
| 198 | }{ | ||
| 199 | {1, true}, | ||
| 200 | {7, true}, | ||
| 201 | {0, false}, | ||
| 202 | {4, false}, | ||
| 203 | } | ||
| 204 | |||
| 205 | for _, tt := range tests { | ||
| 206 | event := &Event{Kind: tt.kind} | ||
| 207 | if got := filter.Matches(event); got != tt.want { | ||
| 208 | t.Errorf("Matches() with Kind %d = %v, want %v", tt.kind, got, tt.want) | ||
| 209 | } | ||
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | func TestFilterMatchesSince(t *testing.T) { | ||
| 214 | since := int64(1704067200) | ||
| 215 | filter := Filter{ | ||
| 216 | Since: &since, | ||
| 217 | } | ||
| 218 | |||
| 219 | tests := []struct { | ||
| 220 | createdAt int64 | ||
| 221 | want bool | ||
| 222 | }{ | ||
| 223 | {1704067200, true}, // Equal | ||
| 224 | {1704067201, true}, // After | ||
| 225 | {1704067199, false}, // Before | ||
| 226 | } | ||
| 227 | |||
| 228 | for _, tt := range tests { | ||
| 229 | event := &Event{CreatedAt: tt.createdAt} | ||
| 230 | if got := filter.Matches(event); got != tt.want { | ||
| 231 | t.Errorf("Matches() with CreatedAt %d = %v, want %v", tt.createdAt, got, tt.want) | ||
| 232 | } | ||
| 233 | } | ||
| 234 | } | ||
| 235 | |||
| 236 | func TestFilterMatchesUntil(t *testing.T) { | ||
| 237 | until := int64(1704067200) | ||
| 238 | filter := Filter{ | ||
| 239 | Until: &until, | ||
| 240 | } | ||
| 241 | |||
| 242 | tests := []struct { | ||
| 243 | createdAt int64 | ||
| 244 | want bool | ||
| 245 | }{ | ||
| 246 | {1704067200, true}, // Equal | ||
| 247 | {1704067199, true}, // Before | ||
| 248 | {1704067201, false}, // After | ||
| 249 | } | ||
| 250 | |||
| 251 | for _, tt := range tests { | ||
| 252 | event := &Event{CreatedAt: tt.createdAt} | ||
| 253 | if got := filter.Matches(event); got != tt.want { | ||
| 254 | t.Errorf("Matches() with CreatedAt %d = %v, want %v", tt.createdAt, got, tt.want) | ||
| 255 | } | ||
| 256 | } | ||
| 257 | } | ||
| 258 | |||
| 259 | func TestFilterMatchesTags(t *testing.T) { | ||
| 260 | filter := Filter{ | ||
| 261 | Tags: map[string][]string{ | ||
| 262 | "e": {"event1"}, | ||
| 263 | "p": {"pubkey1", "pubkey2"}, | ||
| 264 | }, | ||
| 265 | } | ||
| 266 | |||
| 267 | tests := []struct { | ||
| 268 | name string | ||
| 269 | tags Tags | ||
| 270 | want bool | ||
| 271 | }{ | ||
| 272 | { | ||
| 273 | name: "matches all", | ||
| 274 | tags: Tags{{"e", "event1"}, {"p", "pubkey1"}}, | ||
| 275 | want: true, | ||
| 276 | }, | ||
| 277 | { | ||
| 278 | name: "matches with different p", | ||
| 279 | tags: Tags{{"e", "event1"}, {"p", "pubkey2"}}, | ||
| 280 | want: true, | ||
| 281 | }, | ||
| 282 | { | ||
| 283 | name: "missing e tag", | ||
| 284 | tags: Tags{{"p", "pubkey1"}}, | ||
| 285 | want: false, | ||
| 286 | }, | ||
| 287 | { | ||
| 288 | name: "wrong e value", | ||
| 289 | tags: Tags{{"e", "event2"}, {"p", "pubkey1"}}, | ||
| 290 | want: false, | ||
| 291 | }, | ||
| 292 | { | ||
| 293 | name: "extra tags ok", | ||
| 294 | tags: Tags{{"e", "event1"}, {"p", "pubkey1"}, {"t", "test"}}, | ||
| 295 | want: true, | ||
| 296 | }, | ||
| 297 | } | ||
| 298 | |||
| 299 | for _, tt := range tests { | ||
| 300 | t.Run(tt.name, func(t *testing.T) { | ||
| 301 | event := &Event{Tags: tt.tags} | ||
| 302 | if got := filter.Matches(event); got != tt.want { | ||
| 303 | t.Errorf("Matches() = %v, want %v", got, tt.want) | ||
| 304 | } | ||
| 305 | }) | ||
| 306 | } | ||
| 307 | } | ||
| 308 | |||
| 309 | func TestFilterMatchesEmpty(t *testing.T) { | ||
| 310 | // Empty filter matches everything | ||
| 311 | filter := Filter{} | ||
| 312 | event := &Event{ | ||
| 313 | ID: "abc123", | ||
| 314 | PubKey: "pubkey1", | ||
| 315 | CreatedAt: 1704067200, | ||
| 316 | Kind: 1, | ||
| 317 | Tags: Tags{{"e", "event1"}}, | ||
| 318 | Content: "test", | ||
| 319 | } | ||
| 320 | |||
| 321 | if !filter.Matches(event) { | ||
| 322 | t.Error("Empty filter should match all events") | ||
| 323 | } | ||
| 324 | } | ||
| 325 | |||
| 326 | func TestFilterClone(t *testing.T) { | ||
| 327 | since := int64(1704067200) | ||
| 328 | until := int64(1704153600) | ||
| 329 | |||
| 330 | original := &Filter{ | ||
| 331 | IDs: []string{"id1", "id2"}, | ||
| 332 | Kinds: []int{1, 7}, | ||
| 333 | Authors: []string{"author1"}, | ||
| 334 | Tags: map[string][]string{ | ||
| 335 | "e": {"event1"}, | ||
| 336 | }, | ||
| 337 | Since: &since, | ||
| 338 | Until: &until, | ||
| 339 | Limit: 100, | ||
| 340 | } | ||
| 341 | |||
| 342 | clone := original.Clone() | ||
| 343 | |||
| 344 | // Modify original | ||
| 345 | original.IDs[0] = "modified" | ||
| 346 | original.Kinds[0] = 999 | ||
| 347 | original.Authors[0] = "modified" | ||
| 348 | original.Tags["e"][0] = "modified" | ||
| 349 | *original.Since = 0 | ||
| 350 | *original.Until = 0 | ||
| 351 | original.Limit = 0 | ||
| 352 | |||
| 353 | // Clone should be unchanged | ||
| 354 | if clone.IDs[0] != "id1" { | ||
| 355 | t.Error("Clone IDs was modified") | ||
| 356 | } | ||
| 357 | if clone.Kinds[0] != 1 { | ||
| 358 | t.Error("Clone Kinds was modified") | ||
| 359 | } | ||
| 360 | if clone.Authors[0] != "author1" { | ||
| 361 | t.Error("Clone Authors was modified") | ||
| 362 | } | ||
| 363 | if clone.Tags["e"][0] != "event1" { | ||
| 364 | t.Error("Clone Tags was modified") | ||
| 365 | } | ||
| 366 | if *clone.Since != 1704067200 { | ||
| 367 | t.Error("Clone Since was modified") | ||
| 368 | } | ||
| 369 | if *clone.Until != 1704153600 { | ||
| 370 | t.Error("Clone Until was modified") | ||
| 371 | } | ||
| 372 | if clone.Limit != 100 { | ||
| 373 | t.Error("Clone Limit was modified") | ||
| 374 | } | ||
| 375 | } | ||
| 376 | |||
| 377 | func TestFilterJSONRoundTrip(t *testing.T) { | ||
| 378 | since := int64(1704067200) | ||
| 379 | original := Filter{ | ||
| 380 | IDs: []string{"abc123"}, | ||
| 381 | Kinds: []int{1}, | ||
| 382 | Authors: []string{"def456"}, | ||
| 383 | Tags: map[string][]string{ | ||
| 384 | "e": {"event1"}, | ||
| 385 | }, | ||
| 386 | Since: &since, | ||
| 387 | Limit: 50, | ||
| 388 | } | ||
| 389 | |||
| 390 | data, err := json.Marshal(original) | ||
| 391 | if err != nil { | ||
| 392 | t.Fatalf("Marshal error: %v", err) | ||
| 393 | } | ||
| 394 | |||
| 395 | var decoded Filter | ||
| 396 | if err := json.Unmarshal(data, &decoded); err != nil { | ||
| 397 | t.Fatalf("Unmarshal error: %v", err) | ||
| 398 | } | ||
| 399 | |||
| 400 | if len(decoded.IDs) != 1 || decoded.IDs[0] != "abc123" { | ||
| 401 | t.Errorf("IDs mismatch") | ||
| 402 | } | ||
| 403 | if len(decoded.Kinds) != 1 || decoded.Kinds[0] != 1 { | ||
| 404 | t.Errorf("Kinds mismatch") | ||
| 405 | } | ||
| 406 | if len(decoded.Tags["e"]) != 1 || decoded.Tags["e"][0] != "event1" { | ||
| 407 | t.Errorf("Tags mismatch") | ||
| 408 | } | ||
| 409 | if decoded.Since == nil || *decoded.Since != since { | ||
| 410 | t.Errorf("Since mismatch") | ||
| 411 | } | ||
| 412 | if decoded.Limit != 50 { | ||
| 413 | t.Errorf("Limit mismatch") | ||
| 414 | } | ||
| 415 | } | ||
