1package repomgr
2
3import (
4 "bytes"
5 "context"
6 "fmt"
7 "os"
8 "path/filepath"
9 "strings"
10 "testing"
11
12 atproto "github.com/bluesky-social/indigo/api/atproto"
13 bsky "github.com/bluesky-social/indigo/api/bsky"
14 "github.com/bluesky-social/indigo/carstore"
15 "github.com/bluesky-social/indigo/models"
16 "github.com/bluesky-social/indigo/repo"
17 "github.com/bluesky-social/indigo/util"
18 "github.com/ipfs/go-cid"
19 "gorm.io/driver/sqlite"
20 "gorm.io/gorm"
21)
22
23func skipIfNoFile(t *testing.T, f string) {
24 t.Helper()
25 _, err := os.Stat(f)
26 if err != nil {
27 if os.IsNotExist(err) {
28 t.Skipf("test vector %s not present, skipping for now", f)
29 }
30
31 t.Fatal(err)
32 }
33}
34
35func TestLoadNewRepo(t *testing.T) {
36 skipIfNoFile(t, "testrepo.car")
37
38 dir, err := os.MkdirTemp("", "integtest")
39 if err != nil {
40 t.Fatal(err)
41 }
42
43 cardb, err := gorm.Open(sqlite.Open(filepath.Join(dir, "car.sqlite")))
44 if err != nil {
45 t.Fatal(err)
46 }
47
48 cspath := filepath.Join(dir, "carstore")
49 if err := os.Mkdir(cspath, 0775); err != nil {
50 t.Fatal(err)
51 }
52
53 cs, err := carstore.NewCarStore(cardb, []string{cspath})
54 if err != nil {
55 t.Fatal(err)
56 }
57
58 repoman := NewRepoManager(cs, &util.FakeKeyManager{})
59
60 fi, err := os.Open("../testing/testdata/divy.repo")
61 if err != nil {
62 t.Fatal(err)
63 }
64 defer fi.Close()
65
66 ctx := context.TODO()
67 if err := repoman.ImportNewRepo(ctx, 2, "", fi, nil); err != nil {
68 t.Fatal(err)
69 }
70}
71
72func testCarstore(t *testing.T, dir string, archive bool) carstore.CarStore {
73 cardb, err := gorm.Open(sqlite.Open(filepath.Join(dir, "car.sqlite")))
74 if err != nil {
75 t.Fatal(err)
76 }
77
78 cspath := filepath.Join(dir, "carstore")
79 if err := os.Mkdir(cspath, 0775); err != nil {
80 t.Fatal(err)
81 }
82
83 if archive {
84 cs, err := carstore.NewCarStore(cardb, []string{cspath})
85 if err != nil {
86 t.Fatal(err)
87 }
88 return cs
89 } else {
90 cs, err := carstore.NewNonArchivalCarstore(cardb)
91 if err != nil {
92 t.Fatal(err)
93 }
94
95 return cs
96 }
97}
98
99func TestIngestWithGap(t *testing.T) {
100 dir, err := os.MkdirTemp("", "integtest")
101 if err != nil {
102 t.Fatal(err)
103 }
104
105 maindb, err := gorm.Open(sqlite.Open(filepath.Join(dir, "test.sqlite")))
106 if err != nil {
107 t.Fatal(err)
108 }
109 maindb.AutoMigrate(models.ActorInfo{})
110
111 did := "did:plc:beepboop"
112 maindb.Create(&models.ActorInfo{
113 Did: did,
114 Uid: 1,
115 })
116
117 cs := testCarstore(t, dir, true)
118
119 repoman := NewRepoManager(cs, &util.FakeKeyManager{})
120
121 dir2, err := os.MkdirTemp("", "integtest")
122 if err != nil {
123 t.Fatal(err)
124 }
125 cs2 := testCarstore(t, dir2, true)
126
127 var since *string
128 ctx := context.TODO()
129 for i := 0; i < 5; i++ {
130 slice, _, nrev, tid := doPost(t, cs2, did, since, i)
131
132 ops := []*atproto.SyncSubscribeRepos_RepoOp{
133 {
134 Action: "create",
135 Path: "app.bsky.feed.post/" + tid,
136 },
137 }
138
139 if err := repoman.HandleExternalUserEvent(ctx, 1, 1, did, since, nrev, slice, ops); err != nil {
140 t.Fatal(err)
141 }
142
143 since = &nrev
144 }
145
146 // now do a few outside of the standard event stream flow
147 for i := 0; i < 5; i++ {
148 _, _, nrev, _ := doPost(t, cs2, did, since, i)
149 since = &nrev
150 }
151
152 buf := new(bytes.Buffer)
153 if err := cs2.ReadUserCar(ctx, 1, "", true, buf); err != nil {
154 t.Fatal(err)
155 }
156
157 if err := repoman.ImportNewRepo(ctx, 1, did, buf, nil); err != nil {
158 t.Fatal(err)
159 }
160}
161
162func doPost(t *testing.T, cs carstore.CarStore, did string, prev *string, postid int) ([]byte, cid.Cid, string, string) {
163 ctx := context.TODO()
164 ds, err := cs.NewDeltaSession(ctx, 1, prev)
165 if err != nil {
166 t.Fatal(err)
167 }
168
169 r := repo.NewRepo(ctx, did, ds)
170
171 _, tid, err := r.CreateRecord(ctx, "app.bsky.feed.post", &bsky.FeedPost{
172 Text: fmt.Sprintf("hello friend %d", postid),
173 })
174 if err != nil {
175 t.Fatal(err)
176 }
177
178 root, nrev, err := r.Commit(ctx, func(context.Context, string, []byte) ([]byte, error) { return nil, nil })
179 if err != nil {
180 t.Fatal(err)
181 }
182
183 slice, err := ds.CloseWithRoot(ctx, root, nrev)
184 if err != nil {
185 t.Fatal(err)
186 }
187
188 return slice, root, nrev, tid
189}
190
191func TestDuplicateRecord(t *testing.T) {
192 dir, err := os.MkdirTemp("", "integtest")
193 if err != nil {
194 t.Fatal(err)
195 }
196
197 maindb, err := gorm.Open(sqlite.Open(filepath.Join(dir, "test.sqlite")))
198 if err != nil {
199 t.Fatal(err)
200 }
201 maindb.AutoMigrate(models.ActorInfo{})
202
203 did := "did:plc:beepboop"
204 maindb.Create(&models.ActorInfo{
205 Did: did,
206 Uid: 1,
207 })
208
209 cs := testCarstore(t, dir, true)
210
211 repoman := NewRepoManager(cs, &util.FakeKeyManager{})
212
213 ctx := context.TODO()
214 if err := repoman.InitNewActor(ctx, 1, "hello.world", "did:plc:foobar", "", "", ""); err != nil {
215 t.Fatal(err)
216 }
217
218 p1, _, err := repoman.CreateRecord(ctx, 1, "app.bsky.feed.post", &bsky.FeedPost{
219 Text: "hello friend",
220 })
221 if err != nil {
222 t.Fatal(err)
223 }
224
225 p2, _, err := repoman.CreateRecord(ctx, 1, "app.bsky.feed.post", &bsky.FeedPost{
226 Text: "hello friend",
227 })
228 if err != nil {
229 t.Fatal(err)
230 }
231
232 rkey2 := strings.Split(p2, "/")[1]
233 if err := repoman.DeleteRecord(ctx, 1, "app.bsky.feed.post", rkey2); err != nil {
234 t.Fatal(err)
235 }
236
237 rkey1 := strings.Split(p1, "/")[1]
238 c, rec, err := repoman.GetRecord(ctx, 1, "app.bsky.feed.post", rkey1, cid.Undef)
239 if err != nil {
240 t.Fatal(err)
241 }
242
243 _ = c
244 _ = rec
245}