summaryrefslogtreecommitdiffstats
path: root/internal/storage/deletions_test.go
diff options
context:
space:
mode:
authorbndw <ben@bdw.to>2026-02-13 19:12:28 -0800
committerbndw <ben@bdw.to>2026-02-13 19:12:28 -0800
commit656748ea286ff7eac6cbe1b241ad31212892ba61 (patch)
treee9685b4a585809463bdf51a4d1ecb7f7c5efaf70 /internal/storage/deletions_test.go
parent83876eae868bd1e4fb6b9a823a6e8173919f290d (diff)
feat: implement NIP-09 (deletions) and NIP-11 (relay info)
NIP-11 (Relay Information Document): - Serves relay metadata at GET / with Accept: application/nostr+json - Returns name, description, supported NIPs, limitations - CORS headers for browser compatibility NIP-09 (Event Deletion): - Kind 5 events delete events referenced in 'e' tags - Only authors can delete their own events - Soft delete (marks deleted=1) - Records deletion in deletions table - Works across all protocols (gRPC, Connect, WebSocket) Fixed deletions schema: - deleted_event_id as PRIMARY KEY (not deletion_event_id) - Allows one deletion event to delete multiple events 3 new tests, 44 total tests passing Supported NIPs now: 1, 9, 11
Diffstat (limited to 'internal/storage/deletions_test.go')
-rw-r--r--internal/storage/deletions_test.go184
1 files changed, 184 insertions, 0 deletions
diff --git a/internal/storage/deletions_test.go b/internal/storage/deletions_test.go
new file mode 100644
index 0000000..47e6547
--- /dev/null
+++ b/internal/storage/deletions_test.go
@@ -0,0 +1,184 @@
1package storage
2
3import (
4 "context"
5 "fmt"
6 "testing"
7 "time"
8
9 pb "northwest.io/nostr-grpc/api/nostr/v1"
10)
11
12func TestProcessDeletion(t *testing.T) {
13 store, err := New(":memory:")
14 if err != nil {
15 t.Fatalf("failed to create storage: %v", err)
16 }
17 defer store.Close()
18
19 ctx := context.Background()
20
21 event := &pb.Event{
22 Id: "event123",
23 Pubkey: "alice",
24 CreatedAt: time.Now().Unix(),
25 Kind: 1,
26 Tags: []*pb.Tag{},
27 Content: "to be deleted",
28 Sig: "sig1",
29 }
30
31 err = store.StoreEvent(ctx, &EventData{
32 Event: event,
33 CanonicalJSON: []byte(`[0,"alice",1234567890,1,[],"to be deleted"]`),
34 })
35 if err != nil {
36 t.Fatalf("failed to store event: %v", err)
37 }
38
39 retrieved, err := store.GetEvent(ctx, "event123")
40 if err != nil {
41 t.Fatalf("event should exist before deletion: %v", err)
42 }
43 if retrieved.Id != "event123" {
44 t.Errorf("expected event123, got %s", retrieved.Id)
45 }
46
47 deletionEvent := &pb.Event{
48 Id: "deletion123",
49 Pubkey: "alice",
50 CreatedAt: time.Now().Unix(),
51 Kind: KindDeletion,
52 Tags: []*pb.Tag{
53 {Values: []string{"e", "event123"}},
54 },
55 Content: "deleting my event",
56 Sig: "sig2",
57 }
58
59 err = store.ProcessDeletion(ctx, deletionEvent)
60 if err != nil {
61 t.Fatalf("failed to process deletion: %v", err)
62 }
63
64 _, err = store.GetEvent(ctx, "event123")
65 if err != ErrEventNotFound {
66 t.Errorf("event should be deleted, got error: %v", err)
67 }
68}
69
70func TestProcessDeletionWrongAuthor(t *testing.T) {
71 store, err := New(":memory:")
72 if err != nil {
73 t.Fatalf("failed to create storage: %v", err)
74 }
75 defer store.Close()
76
77 ctx := context.Background()
78
79 event := &pb.Event{
80 Id: "event456",
81 Pubkey: "alice",
82 CreatedAt: time.Now().Unix(),
83 Kind: 1,
84 Tags: []*pb.Tag{},
85 Content: "alice's event",
86 Sig: "sig1",
87 }
88
89 err = store.StoreEvent(ctx, &EventData{
90 Event: event,
91 CanonicalJSON: []byte(`[0,"alice",1234567890,1,[],"alice's event"]`),
92 })
93 if err != nil {
94 t.Fatalf("failed to store event: %v", err)
95 }
96
97 deletionEvent := &pb.Event{
98 Id: "deletion456",
99 Pubkey: "bob",
100 CreatedAt: time.Now().Unix(),
101 Kind: KindDeletion,
102 Tags: []*pb.Tag{
103 {Values: []string{"e", "event456"}},
104 },
105 Content: "trying to delete alice's event",
106 Sig: "sig2",
107 }
108
109 err = store.ProcessDeletion(ctx, deletionEvent)
110 if err != nil {
111 t.Fatalf("process deletion should succeed but not delete: %v", err)
112 }
113
114 retrieved, err := store.GetEvent(ctx, "event456")
115 if err != nil {
116 t.Fatalf("event should still exist: %v", err)
117 }
118 if retrieved.Id != "event456" {
119 t.Errorf("expected event456, got %s", retrieved.Id)
120 }
121}
122
123func TestProcessDeletionMultipleEvents(t *testing.T) {
124 store, err := New(":memory:")
125 if err != nil {
126 t.Fatalf("failed to create storage: %v", err)
127 }
128 defer store.Close()
129
130 ctx := context.Background()
131
132 for i := 1; i <= 3; i++ {
133 event := &pb.Event{
134 Id: fmt.Sprintf("event%d", i),
135 Pubkey: "alice",
136 CreatedAt: time.Now().Unix(),
137 Kind: 1,
138 Tags: []*pb.Tag{},
139 Content: fmt.Sprintf("event %d", i),
140 Sig: fmt.Sprintf("sig%d", i),
141 }
142
143 err = store.StoreEvent(ctx, &EventData{
144 Event: event,
145 CanonicalJSON: []byte(fmt.Sprintf(`[0,"alice",1234567890,1,[],"event %d"]`, i)),
146 })
147 if err != nil {
148 t.Fatalf("failed to store event: %v", err)
149 }
150 }
151
152 deletionEvent := &pb.Event{
153 Id: "deletion789",
154 Pubkey: "alice",
155 CreatedAt: time.Now().Unix(),
156 Kind: KindDeletion,
157 Tags: []*pb.Tag{
158 {Values: []string{"e", "event1"}},
159 {Values: []string{"e", "event2"}},
160 },
161 Content: "deleting multiple events",
162 Sig: "sig_del",
163 }
164
165 err = store.ProcessDeletion(ctx, deletionEvent)
166 if err != nil {
167 t.Fatalf("failed to process deletion: %v", err)
168 }
169
170 _, err = store.GetEvent(ctx, "event1")
171 if err != ErrEventNotFound {
172 t.Error("event1 should be deleted")
173 }
174
175 _, err = store.GetEvent(ctx, "event2")
176 if err != ErrEventNotFound {
177 t.Error("event2 should be deleted")
178 }
179
180 _, err = store.GetEvent(ctx, "event3")
181 if err != nil {
182 t.Error("event3 should still exist")
183 }
184}