etag-graph_test.go raw

   1  //go:build !(js && wasm)
   2  
   3  package database
   4  
   5  import (
   6  	"bytes"
   7  	"context"
   8  	"testing"
   9  
  10  	"github.com/dgraph-io/badger/v4"
  11  	"next.orly.dev/pkg/database/indexes"
  12  	"next.orly.dev/pkg/database/indexes/types"
  13  	"next.orly.dev/pkg/nostr/encoders/event"
  14  	"next.orly.dev/pkg/nostr/encoders/hex"
  15  	"next.orly.dev/pkg/nostr/encoders/tag"
  16  )
  17  
  18  func TestETagGraphEdgeCreation(t *testing.T) {
  19  	ctx, cancel := context.WithCancel(context.Background())
  20  	defer cancel()
  21  
  22  	db, err := New(ctx, cancel, t.TempDir(), "info")
  23  	if err != nil {
  24  		t.Fatalf("Failed to create database: %v", err)
  25  	}
  26  	defer db.Close()
  27  
  28  	// Create a parent event (the post being replied to)
  29  	parentPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
  30  	parentID := make([]byte, 32)
  31  	parentID[0] = 0x10
  32  	parentSig := make([]byte, 64)
  33  	parentSig[0] = 0x10
  34  
  35  	parentEvent := &event.E{
  36  		ID:        parentID,
  37  		Pubkey:    parentPubkey,
  38  		CreatedAt: 1234567890,
  39  		Kind:      1,
  40  		Content:   []byte("This is the parent post"),
  41  		Sig:       parentSig,
  42  		Tags:      &tag.S{},
  43  	}
  44  	_, err = db.SaveEvent(ctx, parentEvent)
  45  	if err != nil {
  46  		t.Fatalf("Failed to save parent event: %v", err)
  47  	}
  48  
  49  	// Create a reply event with e-tag pointing to parent
  50  	replyPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
  51  	replyID := make([]byte, 32)
  52  	replyID[0] = 0x20
  53  	replySig := make([]byte, 64)
  54  	replySig[0] = 0x20
  55  
  56  	replyEvent := &event.E{
  57  		ID:        replyID,
  58  		Pubkey:    replyPubkey,
  59  		CreatedAt: 1234567891,
  60  		Kind:      1,
  61  		Content:   []byte("This is a reply"),
  62  		Sig:       replySig,
  63  		Tags: tag.NewS(
  64  			tag.NewFromAny("e", hex.Enc(parentID)),
  65  		),
  66  	}
  67  	_, err = db.SaveEvent(ctx, replyEvent)
  68  	if err != nil {
  69  		t.Fatalf("Failed to save reply event: %v", err)
  70  	}
  71  
  72  	// Get serials for both events
  73  	parentSerial, err := db.GetSerialById(parentID)
  74  	if err != nil {
  75  		t.Fatalf("Failed to get parent serial: %v", err)
  76  	}
  77  	replySerial, err := db.GetSerialById(replyID)
  78  	if err != nil {
  79  		t.Fatalf("Failed to get reply serial: %v", err)
  80  	}
  81  
  82  	t.Logf("Parent serial: %d, Reply serial: %d", parentSerial.Get(), replySerial.Get())
  83  
  84  	// Verify forward edge exists (reply -> parent)
  85  	forwardFound := false
  86  	prefix := []byte(indexes.EventEventGraphPrefix)
  87  
  88  	err = db.View(func(txn *badger.Txn) error {
  89  		it := txn.NewIterator(badger.DefaultIteratorOptions)
  90  		defer it.Close()
  91  
  92  		for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
  93  			item := it.Item()
  94  			key := item.KeyCopy(nil)
  95  
  96  			// Decode the key
  97  			srcSer, tgtSer, kind, direction := indexes.EventEventGraphVars()
  98  			keyReader := bytes.NewReader(key)
  99  			if err := indexes.EventEventGraphDec(srcSer, tgtSer, kind, direction).UnmarshalRead(keyReader); err != nil {
 100  				t.Logf("Failed to decode key: %v", err)
 101  				continue
 102  			}
 103  
 104  			// Check if this is our edge
 105  			if srcSer.Get() == replySerial.Get() && tgtSer.Get() == parentSerial.Get() {
 106  				forwardFound = true
 107  				if direction.Letter() != types.EdgeDirectionETagOut {
 108  					t.Errorf("Expected direction %d, got %d", types.EdgeDirectionETagOut, direction.Letter())
 109  				}
 110  				if kind.Get() != 1 {
 111  					t.Errorf("Expected kind 1, got %d", kind.Get())
 112  				}
 113  			}
 114  		}
 115  		return nil
 116  	})
 117  	if err != nil {
 118  		t.Fatalf("View failed: %v", err)
 119  	}
 120  	if !forwardFound {
 121  		t.Error("Forward edge (reply -> parent) should exist")
 122  	}
 123  
 124  	// Verify reverse edge exists (parent <- reply)
 125  	reverseFound := false
 126  	prefix = []byte(indexes.GraphEventEventPrefix)
 127  
 128  	err = db.View(func(txn *badger.Txn) error {
 129  		it := txn.NewIterator(badger.DefaultIteratorOptions)
 130  		defer it.Close()
 131  
 132  		for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
 133  			item := it.Item()
 134  			key := item.KeyCopy(nil)
 135  
 136  			// Decode the key
 137  			tgtSer, kind, direction, srcSer := indexes.GraphEventEventVars()
 138  			keyReader := bytes.NewReader(key)
 139  			if err := indexes.GraphEventEventDec(tgtSer, kind, direction, srcSer).UnmarshalRead(keyReader); err != nil {
 140  				t.Logf("Failed to decode key: %v", err)
 141  				continue
 142  			}
 143  
 144  			t.Logf("Found gee edge: tgt=%d kind=%d dir=%d src=%d",
 145  				tgtSer.Get(), kind.Get(), direction.Letter(), srcSer.Get())
 146  
 147  			// Check if this is our edge
 148  			if tgtSer.Get() == parentSerial.Get() && srcSer.Get() == replySerial.Get() {
 149  				reverseFound = true
 150  				if direction.Letter() != types.EdgeDirectionETagIn {
 151  					t.Errorf("Expected direction %d, got %d", types.EdgeDirectionETagIn, direction.Letter())
 152  				}
 153  				if kind.Get() != 1 {
 154  					t.Errorf("Expected kind 1, got %d", kind.Get())
 155  				}
 156  			}
 157  		}
 158  		return nil
 159  	})
 160  	if err != nil {
 161  		t.Fatalf("View failed: %v", err)
 162  	}
 163  	if !reverseFound {
 164  		t.Error("Reverse edge (parent <- reply) should exist")
 165  	}
 166  }
 167  
 168  func TestETagGraphMultipleReplies(t *testing.T) {
 169  	ctx, cancel := context.WithCancel(context.Background())
 170  	defer cancel()
 171  
 172  	db, err := New(ctx, cancel, t.TempDir(), "info")
 173  	if err != nil {
 174  		t.Fatalf("Failed to create database: %v", err)
 175  	}
 176  	defer db.Close()
 177  
 178  	// Create a parent event
 179  	parentPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
 180  	parentID := make([]byte, 32)
 181  	parentID[0] = 0x10
 182  	parentSig := make([]byte, 64)
 183  	parentSig[0] = 0x10
 184  
 185  	parentEvent := &event.E{
 186  		ID:        parentID,
 187  		Pubkey:    parentPubkey,
 188  		CreatedAt: 1234567890,
 189  		Kind:      1,
 190  		Content:   []byte("Parent post"),
 191  		Sig:       parentSig,
 192  		Tags:      &tag.S{},
 193  	}
 194  	_, err = db.SaveEvent(ctx, parentEvent)
 195  	if err != nil {
 196  		t.Fatalf("Failed to save parent: %v", err)
 197  	}
 198  
 199  	// Create multiple replies
 200  	numReplies := 5
 201  	for i := 0; i < numReplies; i++ {
 202  		replyPubkey := make([]byte, 32)
 203  		replyPubkey[0] = byte(i + 0x20)
 204  		replyID := make([]byte, 32)
 205  		replyID[0] = byte(i + 0x30)
 206  		replySig := make([]byte, 64)
 207  		replySig[0] = byte(i + 0x30)
 208  
 209  		replyEvent := &event.E{
 210  			ID:        replyID,
 211  			Pubkey:    replyPubkey,
 212  			CreatedAt: int64(1234567891 + i),
 213  			Kind:      1,
 214  			Content:   []byte("Reply"),
 215  			Sig:       replySig,
 216  			Tags: tag.NewS(
 217  				tag.NewFromAny("e", hex.Enc(parentID)),
 218  			),
 219  		}
 220  		_, err := db.SaveEvent(ctx, replyEvent)
 221  		if err != nil {
 222  			t.Fatalf("Failed to save reply %d: %v", i, err)
 223  		}
 224  	}
 225  
 226  	// Count inbound edges to parent
 227  	parentSerial, err := db.GetSerialById(parentID)
 228  	if err != nil {
 229  		t.Fatalf("Failed to get parent serial: %v", err)
 230  	}
 231  
 232  	inboundCount := 0
 233  	prefix := []byte(indexes.GraphEventEventPrefix)
 234  
 235  	err = db.View(func(txn *badger.Txn) error {
 236  		it := txn.NewIterator(badger.DefaultIteratorOptions)
 237  		defer it.Close()
 238  
 239  		for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
 240  			item := it.Item()
 241  			key := item.KeyCopy(nil)
 242  
 243  			tgtSer, kind, direction, srcSer := indexes.GraphEventEventVars()
 244  			keyReader := bytes.NewReader(key)
 245  			if err := indexes.GraphEventEventDec(tgtSer, kind, direction, srcSer).UnmarshalRead(keyReader); err != nil {
 246  				continue
 247  			}
 248  
 249  			if tgtSer.Get() == parentSerial.Get() {
 250  				inboundCount++
 251  			}
 252  		}
 253  		return nil
 254  	})
 255  	if err != nil {
 256  		t.Fatalf("View failed: %v", err)
 257  	}
 258  
 259  	if inboundCount != numReplies {
 260  		t.Errorf("Expected %d inbound edges, got %d", numReplies, inboundCount)
 261  	}
 262  }
 263  
 264  func TestETagGraphDifferentKinds(t *testing.T) {
 265  	ctx, cancel := context.WithCancel(context.Background())
 266  	defer cancel()
 267  
 268  	db, err := New(ctx, cancel, t.TempDir(), "info")
 269  	if err != nil {
 270  		t.Fatalf("Failed to create database: %v", err)
 271  	}
 272  	defer db.Close()
 273  
 274  	// Create a parent event (kind 1 - note)
 275  	parentPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
 276  	parentID := make([]byte, 32)
 277  	parentID[0] = 0x10
 278  	parentSig := make([]byte, 64)
 279  	parentSig[0] = 0x10
 280  
 281  	parentEvent := &event.E{
 282  		ID:        parentID,
 283  		Pubkey:    parentPubkey,
 284  		CreatedAt: 1234567890,
 285  		Kind:      1,
 286  		Content:   []byte("A note"),
 287  		Sig:       parentSig,
 288  		Tags:      &tag.S{},
 289  	}
 290  	_, err = db.SaveEvent(ctx, parentEvent)
 291  	if err != nil {
 292  		t.Fatalf("Failed to save parent: %v", err)
 293  	}
 294  
 295  	// Create a reaction (kind 7)
 296  	reactionPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
 297  	reactionID := make([]byte, 32)
 298  	reactionID[0] = 0x20
 299  	reactionSig := make([]byte, 64)
 300  	reactionSig[0] = 0x20
 301  
 302  	reactionEvent := &event.E{
 303  		ID:        reactionID,
 304  		Pubkey:    reactionPubkey,
 305  		CreatedAt: 1234567891,
 306  		Kind:      7,
 307  		Content:   []byte("+"),
 308  		Sig:       reactionSig,
 309  		Tags: tag.NewS(
 310  			tag.NewFromAny("e", hex.Enc(parentID)),
 311  		),
 312  	}
 313  	_, err = db.SaveEvent(ctx, reactionEvent)
 314  	if err != nil {
 315  		t.Fatalf("Failed to save reaction: %v", err)
 316  	}
 317  
 318  	// Create a repost (kind 6)
 319  	repostPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003")
 320  	repostID := make([]byte, 32)
 321  	repostID[0] = 0x30
 322  	repostSig := make([]byte, 64)
 323  	repostSig[0] = 0x30
 324  
 325  	repostEvent := &event.E{
 326  		ID:        repostID,
 327  		Pubkey:    repostPubkey,
 328  		CreatedAt: 1234567892,
 329  		Kind:      6,
 330  		Content:   []byte(""),
 331  		Sig:       repostSig,
 332  		Tags: tag.NewS(
 333  			tag.NewFromAny("e", hex.Enc(parentID)),
 334  		),
 335  	}
 336  	_, err = db.SaveEvent(ctx, repostEvent)
 337  	if err != nil {
 338  		t.Fatalf("Failed to save repost: %v", err)
 339  	}
 340  
 341  	// Query inbound edges by kind
 342  	parentSerial, err := db.GetSerialById(parentID)
 343  	if err != nil {
 344  		t.Fatalf("Failed to get parent serial: %v", err)
 345  	}
 346  
 347  	kindCounts := make(map[uint16]int)
 348  	prefix := []byte(indexes.GraphEventEventPrefix)
 349  
 350  	err = db.View(func(txn *badger.Txn) error {
 351  		it := txn.NewIterator(badger.DefaultIteratorOptions)
 352  		defer it.Close()
 353  
 354  		for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
 355  			item := it.Item()
 356  			key := item.KeyCopy(nil)
 357  
 358  			tgtSer, kind, direction, srcSer := indexes.GraphEventEventVars()
 359  			keyReader := bytes.NewReader(key)
 360  			if err := indexes.GraphEventEventDec(tgtSer, kind, direction, srcSer).UnmarshalRead(keyReader); err != nil {
 361  				continue
 362  			}
 363  
 364  			if tgtSer.Get() == parentSerial.Get() {
 365  				kindCounts[kind.Get()]++
 366  			}
 367  		}
 368  		return nil
 369  	})
 370  	if err != nil {
 371  		t.Fatalf("View failed: %v", err)
 372  	}
 373  
 374  	// Verify we have edges for each kind
 375  	if kindCounts[7] != 1 {
 376  		t.Errorf("Expected 1 kind-7 (reaction) edge, got %d", kindCounts[7])
 377  	}
 378  	if kindCounts[6] != 1 {
 379  		t.Errorf("Expected 1 kind-6 (repost) edge, got %d", kindCounts[6])
 380  	}
 381  }
 382  
 383  func TestETagGraphUnknownTarget(t *testing.T) {
 384  	ctx, cancel := context.WithCancel(context.Background())
 385  	defer cancel()
 386  
 387  	db, err := New(ctx, cancel, t.TempDir(), "info")
 388  	if err != nil {
 389  		t.Fatalf("Failed to create database: %v", err)
 390  	}
 391  	defer db.Close()
 392  
 393  	// Create an event with e-tag pointing to non-existent event
 394  	unknownID := make([]byte, 32)
 395  	unknownID[0] = 0xFF
 396  	unknownID[31] = 0xFF
 397  
 398  	replyPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
 399  	replyID := make([]byte, 32)
 400  	replyID[0] = 0x10
 401  	replySig := make([]byte, 64)
 402  	replySig[0] = 0x10
 403  
 404  	replyEvent := &event.E{
 405  		ID:        replyID,
 406  		Pubkey:    replyPubkey,
 407  		CreatedAt: 1234567890,
 408  		Kind:      1,
 409  		Content:   []byte("Reply to unknown"),
 410  		Sig:       replySig,
 411  		Tags: tag.NewS(
 412  			tag.NewFromAny("e", hex.Enc(unknownID)),
 413  		),
 414  	}
 415  	_, err = db.SaveEvent(ctx, replyEvent)
 416  	if err != nil {
 417  		t.Fatalf("Failed to save reply: %v", err)
 418  	}
 419  
 420  	// Verify event was saved
 421  	replySerial, err := db.GetSerialById(replyID)
 422  	if err != nil {
 423  		t.Fatalf("Failed to get reply serial: %v", err)
 424  	}
 425  	if replySerial == nil {
 426  		t.Fatal("Reply serial should exist")
 427  	}
 428  
 429  	// Verify no forward edge was created (since target doesn't exist)
 430  	edgeCount := 0
 431  	prefix := []byte(indexes.EventEventGraphPrefix)
 432  
 433  	err = db.View(func(txn *badger.Txn) error {
 434  		it := txn.NewIterator(badger.DefaultIteratorOptions)
 435  		defer it.Close()
 436  
 437  		for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
 438  			item := it.Item()
 439  			key := item.KeyCopy(nil)
 440  
 441  			srcSer, _, _, _ := indexes.EventEventGraphVars()
 442  			keyReader := bytes.NewReader(key)
 443  			if err := indexes.EventEventGraphDec(srcSer, new(types.Uint40), new(types.Uint16), new(types.Letter)).UnmarshalRead(keyReader); err != nil {
 444  				continue
 445  			}
 446  
 447  			if srcSer.Get() == replySerial.Get() {
 448  				edgeCount++
 449  			}
 450  		}
 451  		return nil
 452  	})
 453  	if err != nil {
 454  		t.Fatalf("View failed: %v", err)
 455  	}
 456  
 457  	if edgeCount != 0 {
 458  		t.Errorf("Expected no edges for unknown target, got %d", edgeCount)
 459  	}
 460  }
 461