save-event_test.go raw
1 package database
2
3 import (
4 "bufio"
5 "bytes"
6 "context"
7 "os"
8 "sort"
9 "testing"
10 "time"
11
12 "next.orly.dev/pkg/nostr/encoders/event"
13 "next.orly.dev/pkg/nostr/encoders/event/examples"
14 "next.orly.dev/pkg/nostr/encoders/hex"
15 "next.orly.dev/pkg/nostr/encoders/kind"
16 "next.orly.dev/pkg/nostr/encoders/tag"
17 "next.orly.dev/pkg/nostr/encoders/timestamp"
18 "next.orly.dev/pkg/nostr/interfaces/signer/p8k"
19 "next.orly.dev/pkg/lol/chk"
20 "next.orly.dev/pkg/lol/errorf"
21 )
22
23 // TestSaveEvents tests saving all events from examples.Cache to the database
24 // to verify there are no errors during the saving process.
25 func TestSaveEvents(t *testing.T) {
26 // Create a temporary directory for the database
27 tempDir, err := os.MkdirTemp("", "test-db-*")
28 if err != nil {
29 t.Fatalf("Failed to create temporary directory: %v", err)
30 }
31 defer os.RemoveAll(tempDir) // Clean up after the test
32
33 // Create a context and cancel function for the database
34 ctx, cancel := context.WithCancel(context.Background())
35 defer cancel()
36
37 // Initialize the database
38 db, err := New(ctx, cancel, tempDir, "info")
39 if err != nil {
40 t.Fatalf("Failed to create database: %v", err)
41 }
42 defer db.Close()
43
44 // Create a scanner to read events from examples.Cache
45 scanner := bufio.NewScanner(bytes.NewBuffer(examples.Cache))
46 scanner.Buffer(make([]byte, 0, 1_000_000_000), 1_000_000_000)
47
48 // Collect all events first
49 var events []*event.E
50 var original int
51 for scanner.Scan() {
52 chk.E(scanner.Err())
53 b := scanner.Bytes()
54 original += len(b)
55 ev := event.New()
56
57 // Unmarshal the event
58 if _, err = ev.Unmarshal(b); chk.E(err) {
59 t.Fatal(err)
60 }
61
62 events = append(events, ev)
63 }
64
65 // Sort events by timestamp to ensure addressable events are processed in order
66 sort.Slice(events, func(i, j int) bool {
67 return events[i].CreatedAt < events[j].CreatedAt
68 })
69
70 // Count the number of events processed
71 eventCount := 0
72 skippedCount := 0
73 var kc, vc int
74 now := time.Now()
75 // Process each event in chronological order
76 for _, ev := range events {
77 // Save the event to the database
78 var k, v int
79 if _, err = db.SaveEvent(ctx, ev); err != nil {
80 // Skip events that fail validation (e.g., kind 3 without p tags)
81 skippedCount++
82 continue
83 }
84 kc += k
85 vc += v
86 eventCount++
87 }
88 _ = skippedCount // Used for logging below
89
90 // Check for scanner errors
91 if err = scanner.Err(); err != nil {
92 t.Fatalf("Scanner error: %v", err)
93 }
94 dur := time.Since(now)
95 t.Logf(
96 "Successfully saved %d events %d bytes to the database, %d bytes keys, %d bytes values in %v (%v/ev; %f ev/s)",
97 eventCount,
98 original,
99 kc, vc,
100 dur,
101 dur/time.Duration(eventCount),
102 float64(time.Second)/float64(dur/time.Duration(eventCount)),
103 )
104 }
105
106 // TestDeletionEventWithETagRejection tests that a deletion event with an "e" tag is rejected.
107 func TestDeletionEventWithETagRejection(t *testing.T) {
108 // Create a temporary directory for the database
109 tempDir, err := os.MkdirTemp("", "test-db-*")
110 if err != nil {
111 t.Fatalf("Failed to create temporary directory: %v", err)
112 }
113 defer os.RemoveAll(tempDir) // Clean up after the test
114
115 // Create a context and cancel function for the database
116 ctx, cancel := context.WithCancel(context.Background())
117 defer cancel()
118
119 // Initialize the database
120 db, err := New(ctx, cancel, tempDir, "info")
121 if err != nil {
122 t.Fatalf("Failed to create database: %v", err)
123 }
124 defer db.Close()
125
126 // Create a signer
127 sign := p8k.MustNew()
128 if err := sign.Generate(); chk.E(err) {
129 t.Fatal(err)
130 }
131
132 // Create a regular event
133 regularEvent := event.New()
134 regularEvent.Kind = kind.TextNote.K
135 regularEvent.Pubkey = sign.Pub()
136 regularEvent.CreatedAt = timestamp.Now().V - 3600 // 1 hour ago
137 regularEvent.Content = []byte("Regular event")
138 regularEvent.Tags = tag.NewS()
139 regularEvent.Sign(sign)
140
141 // Save the regular event
142 if _, err := db.SaveEvent(ctx, regularEvent); err != nil {
143 t.Fatalf("Failed to save regular event: %v", err)
144 }
145
146 // Create a deletion event with an "e" tag referencing the regular event
147 deletionEvent := event.New()
148 deletionEvent.Kind = kind.Deletion.K
149 deletionEvent.Pubkey = sign.Pub()
150 deletionEvent.CreatedAt = timestamp.Now().V // Current time
151 deletionEvent.Content = []byte("Deleting the regular event")
152 deletionEvent.Tags = tag.NewS()
153
154 // Add an e-tag referencing the regular event
155 *deletionEvent.Tags = append(
156 *deletionEvent.Tags,
157 tag.NewFromAny("e", hex.Enc(regularEvent.ID)),
158 )
159
160 deletionEvent.Sign(sign)
161
162 // Check if this is a deletion event with "e" tags
163 if deletionEvent.Kind == kind.Deletion.K && deletionEvent.Tags.GetFirst([]byte{'e'}) != nil {
164 // In this test, we want to reject deletion events with "e" tags
165 err = errorf.E("deletion events referencing other events with 'e' tag are not allowed")
166 } else {
167 // Try to save the deletion event
168 _, err = db.SaveEvent(ctx, deletionEvent)
169 }
170
171 if err == nil {
172 t.Fatal("Expected deletion event with e-tag to be rejected, but it was accepted")
173 }
174
175 // Verify the error message
176 expectedError := "deletion events referencing other events with 'e' tag are not allowed"
177 if err.Error() != expectedError {
178 t.Fatalf(
179 "Expected error message '%s', got '%s'", expectedError, err.Error(),
180 )
181 }
182 }
183
184 // TestSaveExistingEvent tests that attempting to save an event that already exists
185 // returns an error.
186 func TestSaveExistingEvent(t *testing.T) {
187 // Create a temporary directory for the database
188 tempDir, err := os.MkdirTemp("", "test-db-*")
189 if err != nil {
190 t.Fatalf("Failed to create temporary directory: %v", err)
191 }
192 defer os.RemoveAll(tempDir) // Clean up after the test
193
194 // Create a context and cancel function for the database
195 ctx, cancel := context.WithCancel(context.Background())
196 defer cancel()
197
198 // Initialize the database
199 db, err := New(ctx, cancel, tempDir, "info")
200 if err != nil {
201 t.Fatalf("Failed to create database: %v", err)
202 }
203 defer db.Close()
204
205 // Create a signer
206 sign := p8k.MustNew()
207 if err := sign.Generate(); chk.E(err) {
208 t.Fatal(err)
209 }
210
211 // Create an event
212 ev := event.New()
213 ev.Kind = kind.TextNote.K
214 ev.Pubkey = sign.Pub()
215 ev.CreatedAt = timestamp.Now().V
216 ev.Content = []byte("Test event")
217 ev.Tags = tag.NewS()
218 ev.Sign(sign)
219
220 // Save the event for the first time
221 if _, err := db.SaveEvent(ctx, ev); err != nil {
222 t.Fatalf("Failed to save event: %v", err)
223 }
224
225 // Try to save the same event again, it should be rejected
226 _, err = db.SaveEvent(ctx, ev)
227 if err == nil {
228 t.Fatal("Expected error when saving an existing event, but got nil")
229 }
230
231 // Verify the error message
232 expectedErrorPrefix := "blocked: event already exists: "
233 if !bytes.HasPrefix([]byte(err.Error()), []byte(expectedErrorPrefix)) {
234 t.Fatalf(
235 "Expected error message to start with '%s', got '%s'",
236 expectedErrorPrefix, err.Error(),
237 )
238 }
239 }
240