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