engine_test.mx raw

   1  package store
   2  
   3  import (
   4  	"bytes"
   5  	"encoding/hex"
   6  	"fmt"
   7  	"os"
   8  	"testing"
   9  
  10  	"smesh.lol/pkg/nostr/event"
  11  	"smesh.lol/pkg/nostr/filter"
  12  	"smesh.lol/pkg/nostr/kind"
  13  	"smesh.lol/pkg/nostr/signer/p8k"
  14  	"smesh.lol/pkg/nostr/tag"
  15  	"smesh.lol/pkg/nostr/timestamp"
  16  )
  17  
  18  func makeSigner(t *testing.T) *p8k.Signer {
  19  	t.Helper()
  20  	s := p8k.MustNew()
  21  	if err := s.Generate(); err != nil {
  22  		t.Fatal(err)
  23  	}
  24  	return s
  25  }
  26  
  27  func makeEvent(t *testing.T, s *p8k.Signer, k uint16, ts int64, tags *tag.S, content string) *event.E {
  28  	t.Helper()
  29  	ev := &event.E{
  30  		CreatedAt: ts,
  31  		Kind:      k,
  32  		Tags:      tags,
  33  		Content:   []byte(content),
  34  	}
  35  	if err := ev.Sign(s); err != nil {
  36  		t.Fatal(err)
  37  	}
  38  	return ev
  39  }
  40  
  41  func openTmp(t *testing.T) *Engine {
  42  	t.Helper()
  43  	dir := t.TempDir()
  44  	eng, err := Open(dir)
  45  	if err != nil {
  46  		t.Fatal(err)
  47  	}
  48  	return eng
  49  }
  50  
  51  func TestSaveAndGetBySerial(t *testing.T) {
  52  	eng := openTmp(t)
  53  	defer eng.Close()
  54  
  55  	s := makeSigner(t)
  56  	ev := makeEvent(t, s, 1, 1700000000, nil, "hello")
  57  
  58  	if err := eng.SaveEvent(ev); err != nil {
  59  		t.Fatal(err)
  60  	}
  61  	// Duplicate should fail.
  62  	if err := eng.SaveEvent(ev); err == nil {
  63  		t.Fatal("expected duplicate error")
  64  	}
  65  }
  66  
  67  func TestQueryByID(t *testing.T) {
  68  	eng := openTmp(t)
  69  	defer eng.Close()
  70  
  71  	s := makeSigner(t)
  72  	ev := makeEvent(t, s, 1, 1700000000, nil, "find me")
  73  	eng.SaveEvent(ev)
  74  
  75  	f := &filter.F{Ids: tag.NewFromBytesSlice(ev.ID)}
  76  	results, err := eng.QueryEvents(f)
  77  	if err != nil {
  78  		t.Fatal(err)
  79  	}
  80  	if len(results) != 1 {
  81  		t.Fatalf("expected 1 result, got %d", len(results))
  82  	}
  83  	if !bytes.Equal(results[0].ID, ev.ID) {
  84  		t.Fatal("ID mismatch")
  85  	}
  86  }
  87  
  88  func TestQueryByKind(t *testing.T) {
  89  	eng := openTmp(t)
  90  	defer eng.Close()
  91  
  92  	s := makeSigner(t)
  93  	for i := 0; i < 10; i++ {
  94  		k := uint16(1)
  95  		if i%3 == 0 {
  96  			k = 7
  97  		}
  98  		eng.SaveEvent(makeEvent(t, s, k, int64(1700000000+i), nil, fmt.Sprintf("msg %d", i)))
  99  	}
 100  
 101  	f := &filter.F{Kinds: kind.NewS(kind.New(uint16(7)))}
 102  	results, err := eng.QueryEvents(f)
 103  	if err != nil {
 104  		t.Fatal(err)
 105  	}
 106  	if len(results) != 4 { // i=0,3,6,9
 107  		t.Fatalf("expected 4 kind-7 events, got %d", len(results))
 108  	}
 109  	for _, r := range results {
 110  		if r.Kind != 7 {
 111  			t.Fatalf("expected kind 7, got %d", r.Kind)
 112  		}
 113  	}
 114  }
 115  
 116  func TestQueryByAuthor(t *testing.T) {
 117  	eng := openTmp(t)
 118  	defer eng.Close()
 119  
 120  	s1 := makeSigner(t)
 121  	s2 := makeSigner(t)
 122  	for i := 0; i < 10; i++ {
 123  		signer := s1
 124  		if i%2 == 0 {
 125  			signer = s2
 126  		}
 127  		eng.SaveEvent(makeEvent(t, signer, 1, int64(1700000000+i), nil, fmt.Sprintf("msg %d", i)))
 128  	}
 129  
 130  	f := &filter.F{Authors: tag.NewFromBytesSlice(s1.Pub())}
 131  	results, err := eng.QueryEvents(f)
 132  	if err != nil {
 133  		t.Fatal(err)
 134  	}
 135  	if len(results) != 5 {
 136  		t.Fatalf("expected 5 events by s1, got %d", len(results))
 137  	}
 138  	for _, r := range results {
 139  		if !bytes.Equal(r.Pubkey, s1.Pub()) {
 140  			t.Fatal("pubkey mismatch")
 141  		}
 142  	}
 143  }
 144  
 145  func TestQueryByTag(t *testing.T) {
 146  	eng := openTmp(t)
 147  	defer eng.Close()
 148  
 149  	s := makeSigner(t)
 150  	for i := 0; i < 10; i++ {
 151  		var tags *tag.S
 152  		if i%2 == 0 {
 153  			tags = tag.NewS(tag.NewFromBytesSlice([]byte("t"), []byte("alpha")))
 154  		} else {
 155  			tags = tag.NewS(tag.NewFromBytesSlice([]byte("t"), []byte("beta")))
 156  		}
 157  		eng.SaveEvent(makeEvent(t, s, 1, int64(1700000000+i), tags, fmt.Sprintf("msg %d", i)))
 158  	}
 159  
 160  	f := &filter.F{
 161  		Tags: tag.NewS(tag.NewFromBytesSlice([]byte("t"), []byte("alpha"))),
 162  	}
 163  	results, err := eng.QueryEvents(f)
 164  	if err != nil {
 165  		t.Fatal(err)
 166  	}
 167  	if len(results) != 5 {
 168  		t.Fatalf("expected 5 events with t=alpha, got %d", len(results))
 169  	}
 170  }
 171  
 172  func TestQueryByPTag(t *testing.T) {
 173  	eng := openTmp(t)
 174  	defer eng.Close()
 175  
 176  	s := makeSigner(t)
 177  	target := makeSigner(t)
 178  	targetHex := hex.EncodeToString(target.Pub())
 179  
 180  	for i := 0; i < 5; i++ {
 181  		tags := tag.NewS(tag.NewFromBytesSlice([]byte("p"), []byte(targetHex)))
 182  		eng.SaveEvent(makeEvent(t, s, 1, int64(1700000000+i), tags, fmt.Sprintf("mention %d", i)))
 183  	}
 184  	for i := 5; i < 10; i++ {
 185  		eng.SaveEvent(makeEvent(t, s, 1, int64(1700000000+i), nil, fmt.Sprintf("no mention %d", i)))
 186  	}
 187  
 188  	f := &filter.F{
 189  		Tags: tag.NewS(tag.NewFromBytesSlice([]byte("p"), []byte(targetHex))),
 190  	}
 191  	results, err := eng.QueryEvents(f)
 192  	if err != nil {
 193  		t.Fatal(err)
 194  	}
 195  	if len(results) != 5 {
 196  		t.Fatalf("expected 5 events mentioning target, got %d", len(results))
 197  	}
 198  }
 199  
 200  func TestQueryByTimeRange(t *testing.T) {
 201  	eng := openTmp(t)
 202  	defer eng.Close()
 203  
 204  	s := makeSigner(t)
 205  	for i := 0; i < 20; i++ {
 206  		eng.SaveEvent(makeEvent(t, s, 1, int64(1700000000+i*100), nil, fmt.Sprintf("msg %d", i)))
 207  	}
 208  
 209  	since := int64(1700000500)
 210  	until := int64(1700001500)
 211  	f := &filter.F{
 212  		Since: timestamp.FromUnix(since),
 213  		Until: timestamp.FromUnix(until),
 214  	}
 215  	results, err := eng.QueryEvents(f)
 216  	if err != nil {
 217  		t.Fatal(err)
 218  	}
 219  	for _, r := range results {
 220  		if r.CreatedAt < since || r.CreatedAt > until {
 221  			t.Fatalf("event %d out of range [%d, %d]", r.CreatedAt, since, until)
 222  		}
 223  	}
 224  	// Events at 500,600,...1500 → 11 events.
 225  	if len(results) != 11 {
 226  		t.Fatalf("expected 11 events in range, got %d", len(results))
 227  	}
 228  }
 229  
 230  func TestQueryWithLimit(t *testing.T) {
 231  	eng := openTmp(t)
 232  	defer eng.Close()
 233  
 234  	s := makeSigner(t)
 235  	for i := 0; i < 50; i++ {
 236  		eng.SaveEvent(makeEvent(t, s, 1, int64(1700000000+i), nil, fmt.Sprintf("msg %d", i)))
 237  	}
 238  
 239  	limit := uint(10)
 240  	f := &filter.F{
 241  		Kinds: kind.NewS(kind.New(uint16(1))),
 242  		Limit: &limit,
 243  	}
 244  	results, err := eng.QueryEvents(f)
 245  	if err != nil {
 246  		t.Fatal(err)
 247  	}
 248  	if len(results) != 10 {
 249  		t.Fatalf("expected 10, got %d", len(results))
 250  	}
 251  	// Should be newest first.
 252  	for i := 1; i < len(results); i++ {
 253  		if results[i].CreatedAt > results[i-1].CreatedAt {
 254  			t.Fatal("results not in reverse chronological order")
 255  		}
 256  	}
 257  }
 258  
 259  func TestBulk10k(t *testing.T) {
 260  	if testing.Short() {
 261  		t.Skip("skipping bulk test in short mode")
 262  	}
 263  	dir, err := os.MkdirTemp("", "store-bulk-*")
 264  	if err != nil {
 265  		t.Fatal(err)
 266  	}
 267  	defer os.RemoveAll(dir)
 268  
 269  	eng, err := Open(dir)
 270  	if err != nil {
 271  		t.Fatal(err)
 272  	}
 273  
 274  	const nSigners = 10
 275  	const nEvents = 10000
 276  	signers := []*p8k.Signer{:nSigners}
 277  	for i := range signers {
 278  		signers[i] = makeSigner(t)
 279  	}
 280  
 281  	kinds := []uint16{1, 7, 30023, 10002}
 282  	tagValues := []string{"nostr", "bitcoin", "lightning", "zap", "relay"}
 283  
 284  	for i := 0; i < nEvents; i++ {
 285  		s := signers[i%nSigners]
 286  		k := kinds[i%len(kinds)]
 287  		ts := int64(1700000000 + i)
 288  		tags := tag.NewS(
 289  			tag.NewFromBytesSlice([]byte("t"), []byte(tagValues[i%len(tagValues)])),
 290  		)
 291  		if i%5 == 0 {
 292  			other := signers[(i+1)%nSigners]
 293  			otherHex := hex.EncodeToString(other.Pub())
 294  			tags.Append(tag.NewFromBytesSlice([]byte("p"), []byte(otherHex)))
 295  		}
 296  		ev := makeEvent(t, s, k, ts, tags, fmt.Sprintf("event %d", i))
 297  		if err := eng.SaveEvent(ev); err != nil {
 298  			t.Fatalf("save event %d: %v", i, err)
 299  		}
 300  	}
 301  
 302  	if err := eng.Flush(); err != nil {
 303  		t.Fatal(err)
 304  	}
 305  
 306  	// Query by author.
 307  	f := &filter.F{Authors: tag.NewFromBytesSlice(signers[0].Pub())}
 308  	results, err := eng.QueryEvents(f)
 309  	if err != nil {
 310  		t.Fatal(err)
 311  	}
 312  	if len(results) != nEvents/nSigners {
 313  		t.Fatalf("author query: expected %d, got %d", nEvents/nSigners, len(results))
 314  	}
 315  
 316  	// Query by kind.
 317  	f = &filter.F{Kinds: kind.NewS(kind.New(uint16(7)))}
 318  	results, err = eng.QueryEvents(f)
 319  	if err != nil {
 320  		t.Fatal(err)
 321  	}
 322  	expected := 0
 323  	for i := 0; i < nEvents; i++ {
 324  		if kinds[i%len(kinds)] == 7 {
 325  			expected++
 326  		}
 327  	}
 328  	if len(results) != expected {
 329  		t.Fatalf("kind query: expected %d, got %d", expected, len(results))
 330  	}
 331  
 332  	// Query by tag.
 333  	f = &filter.F{
 334  		Tags: tag.NewS(tag.NewFromBytesSlice([]byte("t"), []byte("nostr"))),
 335  	}
 336  	results, err = eng.QueryEvents(f)
 337  	if err != nil {
 338  		t.Fatal(err)
 339  	}
 340  	expected = 0
 341  	for i := 0; i < nEvents; i++ {
 342  		if tagValues[i%len(tagValues)] == "nostr" {
 343  			expected++
 344  		}
 345  	}
 346  	if len(results) != expected {
 347  		t.Fatalf("tag query: expected %d, got %d", expected, len(results))
 348  	}
 349  
 350  	// Query by time range.
 351  	since := int64(1700005000)
 352  	until := int64(1700005099)
 353  	f = &filter.F{
 354  		Since: timestamp.FromUnix(since),
 355  		Until: timestamp.FromUnix(until),
 356  	}
 357  	results, err = eng.QueryEvents(f)
 358  	if err != nil {
 359  		t.Fatal(err)
 360  	}
 361  	if len(results) != 100 {
 362  		t.Fatalf("time range query: expected 100, got %d", len(results))
 363  	}
 364  
 365  	eng.Close()
 366  }
 367