pipeline_test.mx raw

   1  package pipeline
   2  
   3  import (
   4  	"testing"
   5  	"time"
   6  
   7  	"smesh.lol/pkg/acl"
   8  	"smesh.lol/pkg/nostr/event"
   9  	"smesh.lol/pkg/nostr/kind"
  10  	"smesh.lol/pkg/nostr/signer/p8k"
  11  	"smesh.lol/pkg/nostr/tag"
  12  	"smesh.lol/pkg/relay/ratelimit"
  13  	"smesh.lol/pkg/store"
  14  )
  15  
  16  func setup(t *testing.T) (*Pipeline, *p8k.Signer) {
  17  	t.Helper()
  18  	eng, err := store.Open(t.TempDir())
  19  	if err != nil {
  20  		t.Fatal(err)
  21  	}
  22  	t.Cleanup(func() { eng.Close() })
  23  
  24  	s := p8k.MustNew()
  25  	if err := s.Generate(); err != nil {
  26  		t.Fatal(err)
  27  	}
  28  	p := New(eng, acl.Open{}, nil, DefaultConfig())
  29  	return p, s
  30  }
  31  
  32  func makeEv(t *testing.T, s *p8k.Signer, k uint16, ts int64, tags *tag.S, content string) *event.E {
  33  	t.Helper()
  34  	ev := &event.E{
  35  		CreatedAt: ts,
  36  		Kind:      k,
  37  		Tags:      tags,
  38  		Content:   []byte(content),
  39  	}
  40  	if err := ev.Sign(s); err != nil {
  41  		t.Fatal(err)
  42  	}
  43  	return ev
  44  }
  45  
  46  func TestIngestRegular(t *testing.T) {
  47  	p, s := setup(t)
  48  	ev := makeEv(t, s, 1, time.Now().Unix(), nil, "hello")
  49  	r := p.Ingest(ev)
  50  	if !r.OK {
  51  		t.Fatalf("expected OK, got: %s", r.Reason)
  52  	}
  53  }
  54  
  55  func TestIngestDuplicate(t *testing.T) {
  56  	p, s := setup(t)
  57  	ev := makeEv(t, s, 1, time.Now().Unix(), nil, "hello")
  58  	r := p.Ingest(ev)
  59  	if !r.OK {
  60  		t.Fatalf("first ingest failed: %s", r.Reason)
  61  	}
  62  	r = p.Ingest(ev)
  63  	if r.OK {
  64  		t.Fatal("expected duplicate rejection")
  65  	}
  66  	if string(r.Reason) != "duplicate: already have this event" {
  67  		t.Fatalf("unexpected reason: %s", r.Reason)
  68  	}
  69  }
  70  
  71  func TestIngestBadSignature(t *testing.T) {
  72  	p, s := setup(t)
  73  	ev := makeEv(t, s, 1, time.Now().Unix(), nil, "hello")
  74  	// Corrupt signature.
  75  	ev.Sig[0] ^= 0xFF
  76  	r := p.Ingest(ev)
  77  	if r.OK {
  78  		t.Fatal("expected rejection for bad sig")
  79  	}
  80  }
  81  
  82  func TestIngestACLBlocked(t *testing.T) {
  83  	eng, err := store.Open(t.TempDir())
  84  	if err != nil {
  85  		t.Fatal(err)
  86  	}
  87  	defer eng.Close()
  88  
  89  	s := p8k.MustNew()
  90  	if err := s.Generate(); err != nil {
  91  		t.Fatal(err)
  92  	}
  93  
  94  	// Whitelist with a different pubkey.
  95  	other := p8k.MustNew()
  96  	if err := other.Generate(); err != nil {
  97  		t.Fatal(err)
  98  	}
  99  	wl := &acl.Whitelist{Pubkeys: [][]byte{other.Pub()}}
 100  	p := New(eng, wl, nil, DefaultConfig())
 101  
 102  	ev := makeEv(t, s, 1, time.Now().Unix(), nil, "blocked")
 103  	r := p.Ingest(ev)
 104  	if r.OK {
 105  		t.Fatal("expected ACL rejection")
 106  	}
 107  	if string(r.Reason) != "blocked: pubkey not allowed" {
 108  		t.Fatalf("unexpected reason: %s", r.Reason)
 109  	}
 110  }
 111  
 112  func TestIngestRateLimit(t *testing.T) {
 113  	eng, err := store.Open(t.TempDir())
 114  	if err != nil {
 115  		t.Fatal(err)
 116  	}
 117  	defer eng.Close()
 118  
 119  	s := p8k.MustNew()
 120  	if err := s.Generate(); err != nil {
 121  		t.Fatal(err)
 122  	}
 123  
 124  	// 1 token/sec, burst 2.
 125  	lim := ratelimit.New(1.0, 2)
 126  	p := New(eng, acl.Open{}, lim, DefaultConfig())
 127  
 128  	now := time.Now().Unix()
 129  	for i := 0; i < 2; i++ {
 130  		ev := makeEv(t, s, 1, now+int64(i), nil, "msg")
 131  		r := p.Ingest(ev)
 132  		if !r.OK {
 133  			t.Fatalf("event %d should be allowed: %s", i, r.Reason)
 134  		}
 135  	}
 136  	// Third event should be rate-limited.
 137  	ev := makeEv(t, s, 1, now+2, nil, "msg")
 138  	r := p.Ingest(ev)
 139  	if r.OK {
 140  		t.Fatal("expected rate limit rejection")
 141  	}
 142  }
 143  
 144  func TestIngestEphemeral(t *testing.T) {
 145  	p, s := setup(t)
 146  	// Kind 20001 is ephemeral.
 147  	ev := makeEv(t, s, 20001, time.Now().Unix(), nil, "ephemeral")
 148  	r := p.Ingest(ev)
 149  	if !r.OK {
 150  		t.Fatalf("ephemeral should be accepted: %s", r.Reason)
 151  	}
 152  }
 153  
 154  func TestIngestReplaceable(t *testing.T) {
 155  	p, s := setup(t)
 156  	now := time.Now().Unix()
 157  
 158  	// Store initial kind-0 (metadata).
 159  	ev1 := makeEv(t, s, 0, now, nil, `{"name":"old"}`)
 160  	r := p.Ingest(ev1)
 161  	if !r.OK {
 162  		t.Fatalf("first replaceable failed: %s", r.Reason)
 163  	}
 164  
 165  	// Newer replaces it.
 166  	ev2 := makeEv(t, s, 0, now+1, nil, `{"name":"new"}`)
 167  	r = p.Ingest(ev2)
 168  	if !r.OK {
 169  		t.Fatalf("newer replaceable failed: %s", r.Reason)
 170  	}
 171  
 172  	// Older is rejected.
 173  	ev3 := makeEv(t, s, 0, now-1, nil, `{"name":"ancient"}`)
 174  	r = p.Ingest(ev3)
 175  	if r.OK {
 176  		t.Fatal("older replaceable should be rejected")
 177  	}
 178  }
 179  
 180  func TestIngestParamReplaceable(t *testing.T) {
 181  	p, s := setup(t)
 182  	now := time.Now().Unix()
 183  
 184  	mkTags := func(dval string) *tag.S {
 185  		return tag.NewS(tag.NewFromBytesSlice([]byte("d"), []byte(dval)))
 186  	}
 187  
 188  	ev1 := makeEv(t, s, 30023, now, mkTags("article-1"), "v1")
 189  	r := p.Ingest(ev1)
 190  	if !r.OK {
 191  		t.Fatalf("first param replaceable failed: %s", r.Reason)
 192  	}
 193  
 194  	// Same d-tag, newer — replaces.
 195  	ev2 := makeEv(t, s, 30023, now+1, mkTags("article-1"), "v2")
 196  	r = p.Ingest(ev2)
 197  	if !r.OK {
 198  		t.Fatalf("newer param replaceable failed: %s", r.Reason)
 199  	}
 200  
 201  	// Same d-tag, older — rejected.
 202  	ev3 := makeEv(t, s, 30023, now-1, mkTags("article-1"), "v0")
 203  	r = p.Ingest(ev3)
 204  	if r.OK {
 205  		t.Fatal("older param replaceable should be rejected")
 206  	}
 207  
 208  	// Different d-tag — accepted (independent).
 209  	ev4 := makeEv(t, s, 30023, now, mkTags("article-2"), "other")
 210  	r = p.Ingest(ev4)
 211  	if !r.OK {
 212  		t.Fatalf("different d-tag should be accepted: %s", r.Reason)
 213  	}
 214  }
 215  
 216  func TestIngestExpired(t *testing.T) {
 217  	p, s := setup(t)
 218  	past := time.Now().Unix() - 60
 219  	expTags := tag.NewS(tag.NewFromBytesSlice(
 220  		[]byte("expiration"), []byte("1000000000"),
 221  	))
 222  	ev := makeEv(t, s, 1, past, expTags, "expired")
 223  	r := p.Ingest(ev)
 224  	if r.OK {
 225  		t.Fatal("expired event should be rejected")
 226  	}
 227  }
 228  
 229  func TestIngestDeletion(t *testing.T) {
 230  	p, s := setup(t)
 231  	now := time.Now().Unix()
 232  
 233  	// Store a target event.
 234  	target := makeEv(t, s, 1, now, nil, "to be deleted")
 235  	r := p.Ingest(target)
 236  	if !r.OK {
 237  		t.Fatalf("target ingest failed: %s", r.Reason)
 238  	}
 239  
 240  	// Create deletion event referencing the target.
 241  	eTag := tag.NewFromBytesSlice([]byte("e"), target.ID)
 242  	delTags := tag.NewS(eTag)
 243  	del := makeEv(t, s, kind.EventDeletion.K, now+1, delTags, "")
 244  	r = p.Ingest(del)
 245  	if !r.OK {
 246  		t.Fatalf("deletion ingest failed: %s", r.Reason)
 247  	}
 248  }
 249  
 250  func TestIngestFutureTimestamp(t *testing.T) {
 251  	p, s := setup(t)
 252  	future := time.Now().Unix() + 3600 // 1 hour ahead
 253  	ev := makeEv(t, s, 1, future, nil, "future")
 254  	r := p.Ingest(ev)
 255  	if r.OK {
 256  		t.Fatal("event too far in future should be rejected")
 257  	}
 258  }
 259  
 260  func TestIngestIDMismatch(t *testing.T) {
 261  	p, s := setup(t)
 262  	ev := makeEv(t, s, 1, time.Now().Unix(), nil, "hello")
 263  	// Corrupt the ID without corrupting the sig.
 264  	ev.ID[0] ^= 0xFF
 265  	r := p.Ingest(ev)
 266  	if r.OK {
 267  		t.Fatal("event with mismatched ID should be rejected")
 268  	}
 269  }
 270