query-events_test.go raw

   1  package database
   2  
   3  import (
   4  	"context"
   5  	"fmt"
   6  	"os"
   7  	"testing"
   8  
   9  	"next.orly.dev/pkg/nostr/encoders/event"
  10  	"next.orly.dev/pkg/nostr/encoders/filter"
  11  	"next.orly.dev/pkg/nostr/encoders/hex"
  12  	"next.orly.dev/pkg/nostr/encoders/kind"
  13  	"next.orly.dev/pkg/nostr/encoders/tag"
  14  	"next.orly.dev/pkg/nostr/encoders/timestamp"
  15  	"next.orly.dev/pkg/nostr/interfaces/signer/p8k"
  16  	"next.orly.dev/pkg/lol/chk"
  17  	"next.orly.dev/pkg/utils"
  18  )
  19  
  20  // setupFreshTestDB creates a new isolated test database for tests that modify data.
  21  // Use this for tests that need to write/delete events.
  22  func setupFreshTestDB(t *testing.T) (*D, context.Context, func()) {
  23  	if testing.Short() {
  24  		t.Skip("skipping test that requires fresh database in short mode")
  25  	}
  26  
  27  	tempDir, err := os.MkdirTemp("", "test-db-*")
  28  	if err != nil {
  29  		t.Fatalf("Failed to create temporary directory: %v", err)
  30  	}
  31  
  32  	ctx, cancel := context.WithCancel(context.Background())
  33  
  34  	db, err := New(ctx, cancel, tempDir, "info")
  35  	if err != nil {
  36  		os.RemoveAll(tempDir)
  37  		t.Fatalf("Failed to create database: %v", err)
  38  	}
  39  
  40  	cleanup := func() {
  41  		db.Close()
  42  		cancel()
  43  		os.RemoveAll(tempDir)
  44  	}
  45  
  46  	return db, ctx, cleanup
  47  }
  48  
  49  func TestQueryEventsByID(t *testing.T) {
  50  	// Use shared database (read-only test)
  51  	db, ctx := GetSharedDB(t)
  52  	events := GetSharedEvents(t)
  53  
  54  	if len(events) < 4 {
  55  		t.Fatalf("Need at least 4 saved events, got %d", len(events))
  56  	}
  57  	testEvent := events[3]
  58  
  59  	evs, err := db.QueryEvents(
  60  		ctx, &filter.F{
  61  			Ids: tag.NewFromBytesSlice(testEvent.ID),
  62  		},
  63  	)
  64  	if err != nil {
  65  		t.Fatalf("Failed to query events by ID: %v", err)
  66  	}
  67  
  68  	if len(evs) != 1 {
  69  		t.Fatalf("Expected 1 event, got %d", len(evs))
  70  	}
  71  
  72  	if !utils.FastEqual(evs[0].ID, testEvent.ID) {
  73  		t.Fatalf(
  74  			"Event ID doesn't match. Got %x, expected %x", evs[0].ID,
  75  			testEvent.ID,
  76  		)
  77  	}
  78  }
  79  
  80  func TestQueryEventsByKind(t *testing.T) {
  81  	// Use shared database (read-only test)
  82  	db, ctx := GetSharedDB(t)
  83  
  84  	testKind := kind.New(1) // Kind 1 is typically text notes
  85  	kindFilter := kind.NewS(testKind)
  86  
  87  	evs, err := db.QueryEvents(
  88  		ctx, &filter.F{
  89  			Kinds: kindFilter,
  90  			Tags:  tag.NewS(),
  91  		},
  92  	)
  93  	if err != nil {
  94  		t.Fatalf("Failed to query events by kind: %v", err)
  95  	}
  96  
  97  	if len(evs) == 0 {
  98  		t.Fatal("Expected events with kind 1, but got none")
  99  	}
 100  
 101  	for i, ev := range evs {
 102  		if ev.Kind != testKind.K {
 103  			t.Fatalf(
 104  				"Event %d has incorrect kind. Got %d, expected %d", i,
 105  				ev.Kind, testKind.K,
 106  			)
 107  		}
 108  	}
 109  }
 110  
 111  func TestQueryEventsByAuthor(t *testing.T) {
 112  	// Use shared database (read-only test)
 113  	db, ctx := GetSharedDB(t)
 114  	events := GetSharedEvents(t)
 115  
 116  	if len(events) < 2 {
 117  		t.Fatalf("Need at least 2 saved events, got %d", len(events))
 118  	}
 119  
 120  	authorFilter := tag.NewFromBytesSlice(events[1].Pubkey)
 121  
 122  	evs, err := db.QueryEvents(
 123  		ctx, &filter.F{
 124  			Authors: authorFilter,
 125  		},
 126  	)
 127  	if err != nil {
 128  		t.Fatalf("Failed to query events by author: %v", err)
 129  	}
 130  
 131  	if len(evs) == 0 {
 132  		t.Fatal("Expected events from author, but got none")
 133  	}
 134  
 135  	for i, ev := range evs {
 136  		if !utils.FastEqual(ev.Pubkey, events[1].Pubkey) {
 137  			t.Fatalf(
 138  				"Event %d has incorrect author. Got %x, expected %x",
 139  				i, ev.Pubkey, events[1].Pubkey,
 140  			)
 141  		}
 142  	}
 143  }
 144  
 145  func TestReplaceableEventsAndDeletion(t *testing.T) {
 146  	// Needs fresh database (modifies data)
 147  	db, ctx, cleanup := setupFreshTestDB(t)
 148  	defer cleanup()
 149  
 150  	// Seed with a few events for pubkey reference
 151  	events := GetSharedEvents(t)
 152  	if len(events) == 0 {
 153  		t.Fatal("Need at least 1 event for pubkey reference")
 154  	}
 155  
 156  	sign := p8k.MustNew()
 157  	if err := sign.Generate(); chk.E(err) {
 158  		t.Fatal(err)
 159  	}
 160  
 161  	// Create a replaceable event
 162  	replaceableEvent := event.New()
 163  	replaceableEvent.Kind = kind.ProfileMetadata.K
 164  	replaceableEvent.Pubkey = events[0].Pubkey
 165  	replaceableEvent.CreatedAt = timestamp.Now().V - 7200
 166  	replaceableEvent.Content = []byte("Original profile")
 167  	replaceableEvent.Tags = tag.NewS()
 168  	replaceableEvent.Sign(sign)
 169  
 170  	if _, err := db.SaveEvent(ctx, replaceableEvent); err != nil {
 171  		t.Errorf("Failed to save replaceable event: %v", err)
 172  	}
 173  
 174  	// Create a newer version
 175  	newerEvent := event.New()
 176  	newerEvent.Kind = kind.ProfileMetadata.K
 177  	newerEvent.Pubkey = replaceableEvent.Pubkey
 178  	newerEvent.CreatedAt = timestamp.Now().V - 3600
 179  	newerEvent.Content = []byte("Updated profile")
 180  	newerEvent.Tags = tag.NewS()
 181  	newerEvent.Sign(sign)
 182  
 183  	if _, err := db.SaveEvent(ctx, newerEvent); err != nil {
 184  		t.Errorf("Failed to save newer event: %v", err)
 185  	}
 186  
 187  	// Query for the original event by ID
 188  	evs, err := db.QueryEvents(
 189  		ctx, &filter.F{
 190  			Ids: tag.NewFromAny(replaceableEvent.ID),
 191  		},
 192  	)
 193  	if err != nil {
 194  		t.Errorf("Failed to query for replaced event by ID: %v", err)
 195  	}
 196  
 197  	if len(evs) != 1 {
 198  		t.Errorf("Expected 1 event when querying for replaced event by ID, got %d", len(evs))
 199  	}
 200  
 201  	if !utils.FastEqual(evs[0].ID, replaceableEvent.ID) {
 202  		t.Errorf(
 203  			"Event ID doesn't match when querying for replaced event. Got %x, expected %x",
 204  			evs[0].ID, replaceableEvent.ID,
 205  		)
 206  	}
 207  
 208  	// Query for all events of this kind and pubkey
 209  	kindFilter := kind.NewS(kind.ProfileMetadata)
 210  	authorFilter := tag.NewFromAny(replaceableEvent.Pubkey)
 211  
 212  	evs, err = db.QueryEvents(
 213  		ctx, &filter.F{
 214  			Kinds:   kindFilter,
 215  			Authors: authorFilter,
 216  		},
 217  	)
 218  	if err != nil {
 219  		t.Errorf("Failed to query for replaceable events: %v", err)
 220  	}
 221  
 222  	if len(evs) != 1 {
 223  		t.Errorf(
 224  			"Expected 1 event when querying for replaceable events, got %d",
 225  			len(evs),
 226  		)
 227  	}
 228  
 229  	if !utils.FastEqual(evs[0].ID, newerEvent.ID) {
 230  		t.Fatalf(
 231  			"Event ID doesn't match when querying for replaceable events. Got %x, expected %x",
 232  			evs[0].ID, newerEvent.ID,
 233  		)
 234  	}
 235  
 236  	// Test deletion events
 237  	deletionEvent := event.New()
 238  	deletionEvent.Kind = kind.Deletion.K
 239  	deletionEvent.Pubkey = replaceableEvent.Pubkey
 240  	deletionEvent.CreatedAt = timestamp.Now().V
 241  	deletionEvent.Content = []byte("Deleting the replaceable event")
 242  	deletionEvent.Tags = tag.NewS()
 243  	deletionEvent.Sign(sign)
 244  
 245  	*deletionEvent.Tags = append(
 246  		*deletionEvent.Tags,
 247  		tag.NewFromAny("e", hex.Enc(replaceableEvent.ID)),
 248  	)
 249  
 250  	if _, err = db.SaveEvent(ctx, deletionEvent); err != nil {
 251  		t.Fatalf("Failed to save deletion event: %v", err)
 252  	}
 253  
 254  	// Query for all events of this kind and pubkey again
 255  	evs, err = db.QueryEvents(
 256  		ctx, &filter.F{
 257  			Kinds:   kindFilter,
 258  			Authors: authorFilter,
 259  		},
 260  	)
 261  	if err != nil {
 262  		t.Errorf(
 263  			"Failed to query for replaceable events after deletion: %v", err,
 264  		)
 265  	}
 266  
 267  	if len(evs) != 1 {
 268  		t.Fatalf(
 269  			"Expected 1 event when querying for replaceable events after deletion, got %d",
 270  			len(evs),
 271  		)
 272  	}
 273  
 274  	if !utils.FastEqual(evs[0].ID, newerEvent.ID) {
 275  		t.Fatalf(
 276  			"Event ID doesn't match after deletion. Got %x, expected %x",
 277  			evs[0].ID, newerEvent.ID,
 278  		)
 279  	}
 280  
 281  	// Query for the original event by ID
 282  	evs, err = db.QueryEvents(
 283  		ctx, &filter.F{
 284  			Ids: tag.NewFromBytesSlice(replaceableEvent.ID),
 285  		},
 286  	)
 287  	if err != nil {
 288  		t.Errorf("Failed to query for deleted event by ID: %v", err)
 289  	}
 290  
 291  	if len(evs) != 0 {
 292  		t.Errorf("Expected 0 events when querying for deleted event by ID, got %d", len(evs))
 293  	}
 294  }
 295  
 296  func TestParameterizedReplaceableEventsAndDeletion(t *testing.T) {
 297  	// Needs fresh database (modifies data)
 298  	db, ctx, cleanup := setupFreshTestDB(t)
 299  	defer cleanup()
 300  
 301  	events := GetSharedEvents(t)
 302  	if len(events) == 0 {
 303  		t.Fatal("Need at least 1 event for pubkey reference")
 304  	}
 305  
 306  	sign := p8k.MustNew()
 307  	if err := sign.Generate(); chk.E(err) {
 308  		t.Fatal(err)
 309  	}
 310  
 311  	// Create a parameterized replaceable event
 312  	paramEvent := event.New()
 313  	paramEvent.Kind = 30000
 314  	paramEvent.Pubkey = events[0].Pubkey
 315  	paramEvent.CreatedAt = timestamp.Now().V - 7200
 316  	paramEvent.Content = []byte("Original parameterized event")
 317  	paramEvent.Tags = tag.NewS()
 318  	*paramEvent.Tags = append(
 319  		*paramEvent.Tags, tag.NewFromAny([]byte{'d'}, []byte("test-d-tag")),
 320  	)
 321  	paramEvent.Sign(sign)
 322  
 323  	if _, err := db.SaveEvent(ctx, paramEvent); err != nil {
 324  		t.Fatalf("Failed to save parameterized replaceable event: %v", err)
 325  	}
 326  
 327  	// Create a deletion event
 328  	paramDeletionEvent := event.New()
 329  	paramDeletionEvent.Kind = kind.Deletion.K
 330  	paramDeletionEvent.Pubkey = paramEvent.Pubkey
 331  	paramDeletionEvent.CreatedAt = timestamp.Now().V
 332  	paramDeletionEvent.Content = []byte("Deleting the parameterized replaceable event")
 333  	paramDeletionEvent.Tags = tag.NewS()
 334  	aTagValue := fmt.Sprintf(
 335  		"%d:%s:%s",
 336  		paramEvent.Kind,
 337  		hex.Enc(paramEvent.Pubkey),
 338  		"test-d-tag",
 339  	)
 340  	*paramDeletionEvent.Tags = append(
 341  		*paramDeletionEvent.Tags,
 342  		tag.NewFromAny([]byte{'a'}, []byte(aTagValue)),
 343  	)
 344  	paramDeletionEvent.Sign(sign)
 345  
 346  	if _, err := db.SaveEvent(ctx, paramDeletionEvent); err != nil {
 347  		t.Fatalf("Failed to save parameterized deletion event: %v", err)
 348  	}
 349  
 350  	// Create deletion with e-tag too
 351  	paramDeletionEvent2 := event.New()
 352  	paramDeletionEvent2.Kind = kind.Deletion.K
 353  	paramDeletionEvent2.Pubkey = paramEvent.Pubkey
 354  	paramDeletionEvent2.CreatedAt = timestamp.Now().V
 355  	paramDeletionEvent2.Content = []byte("Deleting with e-tag")
 356  	paramDeletionEvent2.Tags = tag.NewS()
 357  	*paramDeletionEvent2.Tags = append(
 358  		*paramDeletionEvent2.Tags,
 359  		tag.NewFromAny("e", []byte(hex.Enc(paramEvent.ID))),
 360  	)
 361  	paramDeletionEvent2.Sign(sign)
 362  
 363  	if _, err := db.SaveEvent(ctx, paramDeletionEvent2); err != nil {
 364  		t.Fatalf("Failed to save deletion event with e-tag: %v", err)
 365  	}
 366  
 367  	// Query for all events of this kind and pubkey
 368  	paramKindFilter := kind.NewS(kind.New(paramEvent.Kind))
 369  	paramAuthorFilter := tag.NewFromBytesSlice(paramEvent.Pubkey)
 370  
 371  	evs, err := db.QueryEvents(
 372  		ctx, &filter.F{
 373  			Kinds:   paramKindFilter,
 374  			Authors: paramAuthorFilter,
 375  		},
 376  	)
 377  	if err != nil {
 378  		t.Fatalf("Failed to query for parameterized events: %v", err)
 379  	}
 380  
 381  	if len(evs) != 0 {
 382  		t.Fatalf("Expected 0 events after deletion, got %d", len(evs))
 383  	}
 384  
 385  	// Query by ID
 386  	evs, err = db.QueryEvents(
 387  		ctx, &filter.F{
 388  			Ids: tag.NewFromBytesSlice(paramEvent.ID),
 389  		},
 390  	)
 391  	if err != nil {
 392  		t.Fatalf("Failed to query for deleted event by ID: %v", err)
 393  	}
 394  
 395  	if len(evs) != 0 {
 396  		t.Fatalf("Expected 0 events when querying deleted event by ID, got %d", len(evs))
 397  	}
 398  }
 399  
 400  func TestQueryEventsByTimeRange(t *testing.T) {
 401  	// Use shared database (read-only test)
 402  	db, ctx := GetSharedDB(t)
 403  	events := GetSharedEvents(t)
 404  
 405  	if len(events) < 10 {
 406  		t.Fatalf("Need at least 10 saved events, got %d", len(events))
 407  	}
 408  
 409  	middleIndex := len(events) / 2
 410  	middleEvent := events[middleIndex]
 411  
 412  	sinceTime := new(timestamp.T)
 413  	sinceTime.V = middleEvent.CreatedAt - 3600
 414  
 415  	untilTime := new(timestamp.T)
 416  	untilTime.V = middleEvent.CreatedAt + 3600
 417  
 418  	evs, err := db.QueryEvents(
 419  		ctx, &filter.F{
 420  			Since: sinceTime,
 421  			Until: untilTime,
 422  		},
 423  	)
 424  	if err != nil {
 425  		t.Fatalf("Failed to query events by time range: %v", err)
 426  	}
 427  
 428  	if len(evs) == 0 {
 429  		t.Fatal("Expected events in time range, but got none")
 430  	}
 431  
 432  	for i, ev := range evs {
 433  		if ev.CreatedAt < sinceTime.V || ev.CreatedAt > untilTime.V {
 434  			t.Fatalf(
 435  				"Event %d is outside the time range. Got %d, expected between %d and %d",
 436  				i, ev.CreatedAt, sinceTime.V, untilTime.V,
 437  			)
 438  		}
 439  	}
 440  }
 441  
 442  func TestQueryEventsByTag(t *testing.T) {
 443  	// Use shared database (read-only test)
 444  	db, ctx := GetSharedDB(t)
 445  	events := GetSharedEvents(t)
 446  
 447  	// Find an event with tags
 448  	var testTagEvent *event.E
 449  	for _, ev := range events {
 450  		if ev.Tags != nil && ev.Tags.Len() > 0 {
 451  			for _, tg := range *ev.Tags {
 452  				if tg.Len() >= 2 && len(tg.Key()) == 1 {
 453  					testTagEvent = ev
 454  					break
 455  				}
 456  			}
 457  			if testTagEvent != nil {
 458  				break
 459  			}
 460  		}
 461  	}
 462  
 463  	if testTagEvent == nil {
 464  		t.Skip("No suitable event with tags found for testing")
 465  		return
 466  	}
 467  
 468  	var testTag *tag.T
 469  	for _, tg := range *testTagEvent.Tags {
 470  		if tg.Len() >= 2 && len(tg.Key()) == 1 {
 471  			testTag = tg
 472  			break
 473  		}
 474  	}
 475  
 476  	tagsFilter := tag.NewS(testTag)
 477  
 478  	evs, err := db.QueryEvents(
 479  		ctx, &filter.F{
 480  			Tags: tagsFilter,
 481  		},
 482  	)
 483  	if err != nil {
 484  		t.Fatalf("Failed to query events by tag: %v", err)
 485  	}
 486  
 487  	if len(evs) == 0 {
 488  		t.Fatal("Expected events with tag, but got none")
 489  	}
 490  
 491  	for i, ev := range evs {
 492  		var hasTag bool
 493  		for _, tg := range *ev.Tags {
 494  			if tg.Len() >= 2 && len(tg.Key()) == 1 {
 495  				if utils.FastEqual(tg.Key(), testTag.Key()) &&
 496  					utils.FastEqual(tg.Value(), testTag.Value()) {
 497  					hasTag = true
 498  					break
 499  				}
 500  			}
 501  		}
 502  		if !hasTag {
 503  			t.Fatalf("Event %d does not have the expected tag", i)
 504  		}
 505  	}
 506  }
 507