tinyrelayfile
tinyrelay/internal/storage/store_test.go/

internal/storage/store_test.go raw
1package storage
2
3import (
4 "context"
5 "database/sql"
6 "errors"
7 "path/filepath"
8 "testing"
9
10 "github.com/FelineStateMachine/tinyrelay/internal/event"
11)
12
13func openTestStore(t *testing.T) *Store {
14 t.Helper()
15 s, err := Open(context.Background(), filepath.Join(t.TempDir(), "relay.db"))
16 if err != nil {
17 t.Fatal(err)
18 }
19 t.Cleanup(func() {
20 if err := s.Close(); err != nil {
21 t.Error(err)
22 }
23 })
24 return s
25}
26
27func sampleEvent(t *testing.T, kind int, at int64, tags [][]string) event.Event {
28 t.Helper()
29 e := event.Event{Kind: kind, CreatedAt: at, Tags: tags, Content: "hello storage"}
30 if e.Tags == nil {
31 e.Tags = [][]string{}
32 }
33 if err := event.Sign(&e, "0000000000000000000000000000000000000000000000000000000000000001"); err != nil {
34 t.Fatal(err)
35 }
36 return e
37}
38
39func TestDurableAcceptanceIncludesIntents(t *testing.T) {
40 s := openTestStore(t)
41 ctx := context.Background()
42 e := sampleEvent(t, 1, 100, nil)
43 opts := SaveOptions{Now: 101, Intents: []Intent{{Kind: "delivery", EventID: e.ID, Target: "wss://example.com", Payload: `{}`}}}
44 if _, err := s.Save(ctx, e, opts); err != nil {
45 t.Fatal(err)
46 }
47 if _, err := s.Save(ctx, e, opts); !errors.Is(err, ErrDuplicate) {
48 t.Fatalf("duplicate: %v", err)
49 }
50 var n int
51 if err := s.DB().QueryRowContext(ctx, "SELECT count(*) FROM work_intents").Scan(&n); err != nil {
52 t.Fatal(err)
53 }
54 if n != 1 {
55 t.Fatalf("got %d intents", n)
56 }
57 got, err := s.Query(ctx, event.Filter{}, QueryOptions{Now: 101})
58 if err != nil || len(got.Events) != 1 {
59 t.Fatalf("query: %+v %v", got, err)
60 }
61}
62
63func TestDeletionArchivesAgentGrant(t *testing.T) {
64 s := openTestStore(t)
65 ctx := context.Background()
66 agent := "0000000000000000000000000000000000000000000000000000000000000002"
67 grant := sampleEvent(t, event.KIND_AGENT_GRANT, 100, [][]string{{"d", agent}, {"p", agent}, {"expiration", "200"}})
68 if _, err := s.Save(ctx, grant, SaveOptions{Now: 100}); err != nil {
69 t.Fatal(err)
70 }
71 del := sampleEvent(t, event.KIND_DELETION, 110, [][]string{{"e", grant.ID}})
72 del.PubKey = grant.PubKey
73 if err := event.Sign(&del, "0000000000000000000000000000000000000000000000000000000000000001"); err != nil {
74 t.Fatal(err)
75 }
76 if _, err := s.Save(ctx, del, SaveOptions{Now: 110}); err != nil {
77 t.Fatal(err)
78 }
79 var raw string
80 if err := s.DB().QueryRowContext(ctx, "SELECT raw FROM agent_grant_revisions WHERE event_id=?", grant.ID).Scan(&raw); err != nil {
81 t.Fatal(err)
82 }
83 if raw == "" {
84 t.Fatal("deleted grant revision is empty")
85 }
86}
87
88func TestEventAndIntentRollbackTogether(t *testing.T) {
89 s := openTestStore(t)
90 e := sampleEvent(t, 1, 100, nil)
91 opts := SaveOptions{Now: 101, BeforeCommit: func(context.Context, *sql.Tx) error { return errors.New("simulated disk failure") }}
92 if _, err := s.Save(context.Background(), e, opts); err == nil {
93 t.Fatal("expected failure")
94 }
95 got, err := s.Query(context.Background(), event.Filter{}, QueryOptions{Now: 101})
96 if err != nil || len(got.Events) != 0 {
97 t.Fatalf("uncommitted event visible: %+v %v", got, err)
98 }
99}
100
101func TestReplaceDeleteExpiryAndPrivacy(t *testing.T) {
102 ctx := context.Background()
103 t.Run("replacement and history", func(t *testing.T) {
104 s := openTestStore(t)
105 old := sampleEvent(t, 3, 100, [][]string{{"p", "old"}})
106 fresh := sampleEvent(t, 3, 102, [][]string{{"p", "new"}})
107 for _, e := range []event.Event{old, fresh} {
108 if _, err := s.Save(ctx, e, SaveOptions{Now: 103}); err != nil {
109 t.Fatal(err)
110 }
111 }
112 if _, err := s.Save(ctx, old, SaveOptions{Now: 103}); !errors.Is(err, ErrReplaced) {
113 t.Fatalf("got %v", err)
114 }
115 got, err := s.Query(ctx, event.Filter{}, QueryOptions{Now: 103})
116 if err != nil || len(got.Events) != 1 || got.Events[0].ID != fresh.ID {
117 t.Fatalf("replace: %+v %v", got, err)
118 }
119 rows, err := s.ListHistory(ctx, old.PubKey, 103)
120 if err != nil || len(rows) != 1 {
121 t.Fatalf("history: %+v %v", rows, err)
122 }
123 })
124 t.Run("deletion arrives first", func(t *testing.T) {
125 s := openTestStore(t)
126 e := sampleEvent(t, 1, 100, nil)
127 d := sampleEvent(t, 5, 102, [][]string{{"e", e.ID}})
128 if _, err := s.Save(ctx, d, SaveOptions{Now: 103}); err != nil {
129 t.Fatal(err)
130 }
131 if _, err := s.Save(ctx, e, SaveOptions{Now: 103}); !errors.Is(err, ErrDeleted) {
132 t.Fatalf("got %v", err)
133 }
134 })
135 t.Run("private count and query agree", func(t *testing.T) {
136 s := openTestStore(t)
137 e := sampleEvent(t, 1059, 100, [][]string{{"p", "recipient"}})
138 if _, err := s.Save(ctx, e, SaveOptions{Now: 101}); err != nil {
139 t.Fatal(err)
140 }
141 for _, tc := range []struct {
142 who Access
143 want int
144 }{{Access{}, 0}, {Access{PubKeys: []string{"recipient"}}, 1}, {Access{PubKeys: []string{"stranger"}}, 0}} {
145 opts := QueryOptions{Now: 101, Access: tc.who}
146 got, err := s.Query(ctx, event.Filter{}, opts)
147 if err != nil || len(got.Events) != tc.want {
148 t.Fatalf("privacy: %+v %v", got, err)
149 }
150 n, err := s.Count(ctx, []event.Filter{{}}, opts)
151 if err != nil || n != int64(tc.want) {
152 t.Fatalf("count: %d %v", n, err)
153 }
154 }
155 })
156 t.Run("nip59 seals are visible only to their author", func(t *testing.T) {
157 s := openTestStore(t)
158 e := sampleEvent(t, 13, 100, [][]string{})
159 if _, err := s.Save(ctx, e, SaveOptions{Now: 101}); err != nil {
160 t.Fatal(err)
161 }
162 for _, tc := range []struct {
163 who Access
164 want int
165 }{{Access{}, 0}, {Access{PubKeys: []string{e.PubKey}}, 1}, {Access{PubKeys: []string{"stranger"}}, 0}} {
166 got, err := s.Query(ctx, event.Filter{Kinds: []int{13}}, QueryOptions{Now: 101, Access: tc.who})
167 if err != nil || len(got.Events) != tc.want {
168 t.Fatalf("seal query: %+v %v", got, err)
169 }
170 n, err := s.Count(ctx, []event.Filter{{Kinds: []int{13}}}, QueryOptions{Now: 101, Access: tc.who})
171 if err != nil || n != int64(tc.want) {
172 t.Fatalf("seal count: %d %v", n, err)
173 }
174 }
175 })
176 t.Run("expired and ephemeral", func(t *testing.T) {
177 s := openTestStore(t)
178 for _, e := range []event.Event{sampleEvent(t, 1, 100, [][]string{{"expiration", "102"}}), sampleEvent(t, 20001, 100, nil)} {
179 if _, err := s.Save(ctx, e, SaveOptions{Now: 101}); err != nil {
180 t.Fatal(err)
181 }
182 }
183 got, err := s.Query(ctx, event.Filter{}, QueryOptions{Now: 103})
184 if err != nil || len(got.Events) != 0 {
185 t.Fatalf("expiration: %+v %v", got, err)
186 }
187 })
188}
189
190func TestEmptyFilterArraysMatchNothing(t *testing.T) {
191 s := openTestStore(t)
192 if _, err := s.Save(context.Background(), sampleEvent(t, 1, 100, nil), SaveOptions{Now: 101}); err != nil {
193 t.Fatal(err)
194 }
195 for _, f := range []event.Filter{{IDs: []string{}}, {Authors: []string{}}, {Kinds: []int{}}, {Tags: map[string][]string{"p": {}}}} {
196 got, err := s.Query(context.Background(), f, QueryOptions{Now: 101})
197 if err != nil || len(got.Events) != 0 {
198 t.Fatalf("filter: %+v %v", got, err)
199 }
200 }
201}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs