dual-storage_test.go raw

   1  package database
   2  
   3  import (
   4  	"context"
   5  	"os"
   6  	"testing"
   7  
   8  	"github.com/stretchr/testify/assert"
   9  	"github.com/stretchr/testify/require"
  10  	"next.orly.dev/pkg/nostr/encoders/event"
  11  	"next.orly.dev/pkg/nostr/encoders/kind"
  12  	"next.orly.dev/pkg/nostr/encoders/tag"
  13  	"next.orly.dev/pkg/nostr/encoders/timestamp"
  14  	"next.orly.dev/pkg/nostr/interfaces/signer/p8k"
  15  )
  16  
  17  func TestDualStorageForReplaceableEvents(t *testing.T) {
  18  	// Create a temporary directory for the database
  19  	tempDir, err := os.MkdirTemp("", "test-dual-db-*")
  20  	require.NoError(t, err)
  21  	defer os.RemoveAll(tempDir)
  22  
  23  	// Create a context and cancel function for the database
  24  	ctx, cancel := context.WithCancel(context.Background())
  25  	defer cancel()
  26  
  27  	// Initialize the database
  28  	db, err := New(ctx, cancel, tempDir, "info")
  29  	require.NoError(t, err)
  30  	defer db.Close()
  31  
  32  	// Create a signing key
  33  	sign := p8k.MustNew()
  34  	require.NoError(t, sign.Generate())
  35  
  36  	t.Run("SmallReplaceableEvent", func(t *testing.T) {
  37  		// Create a small replaceable event (kind 0 - profile metadata)
  38  		ev := event.New()
  39  		ev.Pubkey = sign.Pub()
  40  		ev.CreatedAt = timestamp.Now().V
  41  		ev.Kind = kind.ProfileMetadata.K
  42  		ev.Tags = tag.NewS()
  43  		ev.Content = []byte(`{"name":"Alice","about":"Test user"}`)
  44  
  45  		require.NoError(t, ev.Sign(sign))
  46  
  47  		// Save the event
  48  		replaced, err := db.SaveEvent(ctx, ev)
  49  		require.NoError(t, err)
  50  		assert.False(t, replaced)
  51  
  52  		// Fetch by serial - should work via sev key
  53  		ser, err := db.GetSerialById(ev.ID)
  54  		require.NoError(t, err)
  55  		require.NotNil(t, ser)
  56  
  57  		fetched, err := db.FetchEventBySerial(ser)
  58  		require.NoError(t, err)
  59  		require.NotNil(t, fetched)
  60  
  61  		// Verify event contents
  62  		assert.Equal(t, ev.ID, fetched.ID)
  63  		assert.Equal(t, ev.Pubkey, fetched.Pubkey)
  64  		assert.Equal(t, ev.Kind, fetched.Kind)
  65  		assert.Equal(t, ev.Content, fetched.Content)
  66  	})
  67  
  68  	t.Run("LargeReplaceableEvent", func(t *testing.T) {
  69  		// Create a large replaceable event (> 384 bytes)
  70  		largeContent := make([]byte, 500)
  71  		for i := range largeContent {
  72  			largeContent[i] = 'x'
  73  		}
  74  
  75  		ev := event.New()
  76  		ev.Pubkey = sign.Pub()
  77  		ev.CreatedAt = timestamp.Now().V + 1
  78  		ev.Kind = kind.ProfileMetadata.K
  79  		ev.Tags = tag.NewS()
  80  		ev.Content = largeContent
  81  
  82  		require.NoError(t, ev.Sign(sign))
  83  
  84  		// Save the event
  85  		replaced, err := db.SaveEvent(ctx, ev)
  86  		require.NoError(t, err)
  87  		assert.True(t, replaced) // Should replace the previous profile
  88  
  89  		// Fetch by serial - should work via evt key
  90  		ser, err := db.GetSerialById(ev.ID)
  91  		require.NoError(t, err)
  92  		require.NotNil(t, ser)
  93  
  94  		fetched, err := db.FetchEventBySerial(ser)
  95  		require.NoError(t, err)
  96  		require.NotNil(t, fetched)
  97  
  98  		// Verify event contents
  99  		assert.Equal(t, ev.ID, fetched.ID)
 100  		assert.Equal(t, ev.Content, fetched.Content)
 101  	})
 102  }
 103  
 104  func TestDualStorageForAddressableEvents(t *testing.T) {
 105  	// Create a temporary directory for the database
 106  	tempDir, err := os.MkdirTemp("", "test-addressable-db-*")
 107  	require.NoError(t, err)
 108  	defer os.RemoveAll(tempDir)
 109  
 110  	// Create a context and cancel function for the database
 111  	ctx, cancel := context.WithCancel(context.Background())
 112  	defer cancel()
 113  
 114  	// Initialize the database
 115  	db, err := New(ctx, cancel, tempDir, "info")
 116  	require.NoError(t, err)
 117  	defer db.Close()
 118  
 119  	// Create a signing key
 120  	sign := p8k.MustNew()
 121  	require.NoError(t, sign.Generate())
 122  
 123  	t.Run("SmallAddressableEvent", func(t *testing.T) {
 124  		// Create a small addressable event (kind 30023 - long-form content)
 125  		ev := event.New()
 126  		ev.Pubkey = sign.Pub()
 127  		ev.CreatedAt = timestamp.Now().V
 128  		ev.Kind = 30023
 129  		ev.Tags = tag.NewS(
 130  			tag.NewFromAny("d", []byte("my-article")),
 131  			tag.NewFromAny("title", []byte("Test Article")),
 132  		)
 133  		ev.Content = []byte("This is a short article.")
 134  
 135  		require.NoError(t, ev.Sign(sign))
 136  
 137  		// Save the event
 138  		replaced, err := db.SaveEvent(ctx, ev)
 139  		require.NoError(t, err)
 140  		assert.False(t, replaced)
 141  
 142  		// Fetch by serial - should work via sev key
 143  		ser, err := db.GetSerialById(ev.ID)
 144  		require.NoError(t, err)
 145  		require.NotNil(t, ser)
 146  
 147  		fetched, err := db.FetchEventBySerial(ser)
 148  		require.NoError(t, err)
 149  		require.NotNil(t, fetched)
 150  
 151  		// Verify event contents
 152  		assert.Equal(t, ev.ID, fetched.ID)
 153  		assert.Equal(t, ev.Pubkey, fetched.Pubkey)
 154  		assert.Equal(t, ev.Kind, fetched.Kind)
 155  		assert.Equal(t, ev.Content, fetched.Content)
 156  
 157  		// Verify d tag
 158  		dTag := fetched.Tags.GetFirst([]byte("d"))
 159  		require.NotNil(t, dTag)
 160  		assert.Equal(t, []byte("my-article"), dTag.Value())
 161  	})
 162  
 163  	t.Run("AddressableEventWithoutDTag", func(t *testing.T) {
 164  		// Create an addressable event without d tag (should be treated as regular event)
 165  		ev := event.New()
 166  		ev.Pubkey = sign.Pub()
 167  		ev.CreatedAt = timestamp.Now().V + 1
 168  		ev.Kind = 30023
 169  		ev.Tags = tag.NewS()
 170  		ev.Content = []byte("Article without d tag")
 171  
 172  		require.NoError(t, ev.Sign(sign))
 173  
 174  		// Save should fail with missing d tag error
 175  		_, err := db.SaveEvent(ctx, ev)
 176  		assert.Error(t, err)
 177  		assert.Contains(t, err.Error(), "missing a d tag")
 178  	})
 179  
 180  	t.Run("ReplaceAddressableEvent", func(t *testing.T) {
 181  		// Create first version
 182  		ev1 := event.New()
 183  		ev1.Pubkey = sign.Pub()
 184  		ev1.CreatedAt = timestamp.Now().V
 185  		ev1.Kind = 30023
 186  		ev1.Tags = tag.NewS(
 187  			tag.NewFromAny("d", []byte("replaceable-article")),
 188  		)
 189  		ev1.Content = []byte("Version 1")
 190  
 191  		require.NoError(t, ev1.Sign(sign))
 192  
 193  		replaced, err := db.SaveEvent(ctx, ev1)
 194  		require.NoError(t, err)
 195  		assert.False(t, replaced)
 196  
 197  		// Create second version (newer)
 198  		ev2 := event.New()
 199  		ev2.Pubkey = sign.Pub()
 200  		ev2.CreatedAt = ev1.CreatedAt + 10
 201  		ev2.Kind = 30023
 202  		ev2.Tags = tag.NewS(
 203  			tag.NewFromAny("d", []byte("replaceable-article")),
 204  		)
 205  		ev2.Content = []byte("Version 2")
 206  
 207  		require.NoError(t, ev2.Sign(sign))
 208  
 209  		replaced, err = db.SaveEvent(ctx, ev2)
 210  		require.NoError(t, err)
 211  		assert.True(t, replaced)
 212  
 213  		// Try to save older version (should fail)
 214  		ev0 := event.New()
 215  		ev0.Pubkey = sign.Pub()
 216  		ev0.CreatedAt = ev1.CreatedAt - 10
 217  		ev0.Kind = 30023
 218  		ev0.Tags = tag.NewS(
 219  			tag.NewFromAny("d", []byte("replaceable-article")),
 220  		)
 221  		ev0.Content = []byte("Version 0 (old)")
 222  
 223  		require.NoError(t, ev0.Sign(sign))
 224  
 225  		replaced, err = db.SaveEvent(ctx, ev0)
 226  		assert.Error(t, err)
 227  		assert.Contains(t, err.Error(), "older than existing")
 228  	})
 229  }
 230  
 231  func TestDualStorageRegularEvents(t *testing.T) {
 232  	// Create a temporary directory for the database
 233  	tempDir, err := os.MkdirTemp("", "test-regular-db-*")
 234  	require.NoError(t, err)
 235  	defer os.RemoveAll(tempDir)
 236  
 237  	// Create a context and cancel function for the database
 238  	ctx, cancel := context.WithCancel(context.Background())
 239  	defer cancel()
 240  
 241  	// Initialize the database
 242  	db, err := New(ctx, cancel, tempDir, "info")
 243  	require.NoError(t, err)
 244  	defer db.Close()
 245  
 246  	// Create a signing key
 247  	sign := p8k.MustNew()
 248  	require.NoError(t, sign.Generate())
 249  
 250  	t.Run("SmallRegularEvent", func(t *testing.T) {
 251  		// Create a small regular event (kind 1 - note)
 252  		ev := event.New()
 253  		ev.Pubkey = sign.Pub()
 254  		ev.CreatedAt = timestamp.Now().V
 255  		ev.Kind = kind.TextNote.K
 256  		ev.Tags = tag.NewS()
 257  		ev.Content = []byte("Hello, Nostr!")
 258  
 259  		require.NoError(t, ev.Sign(sign))
 260  
 261  		// Save the event
 262  		replaced, err := db.SaveEvent(ctx, ev)
 263  		require.NoError(t, err)
 264  		assert.False(t, replaced)
 265  
 266  		// Fetch by serial - should work via sev key
 267  		ser, err := db.GetSerialById(ev.ID)
 268  		require.NoError(t, err)
 269  		require.NotNil(t, ser)
 270  
 271  		fetched, err := db.FetchEventBySerial(ser)
 272  		require.NoError(t, err)
 273  		require.NotNil(t, fetched)
 274  
 275  		// Verify event contents
 276  		assert.Equal(t, ev.ID, fetched.ID)
 277  		assert.Equal(t, ev.Content, fetched.Content)
 278  	})
 279  }
 280