1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package kivik_test
18
19 import (
20 "context"
21 "net/http"
22 "os"
23 "testing"
24 "time"
25
26 "gitlab.com/flimzy/testy"
27
28 "github.com/go-kivik/kivik/v4"
29 _ "github.com/go-kivik/kivik/v4/couchdb"
30 internal "github.com/go-kivik/kivik/v4/int/errors"
31 "github.com/go-kivik/kivik/v4/kiviktest/kt"
32 _ "github.com/go-kivik/kivik/v4/x/fsdb"
33 )
34
35 func TestReplicate_live(t *testing.T) {
36 if isGopherJS117 {
37 t.Skip("Replication doesn't work in GopherJS 1.17")
38 }
39 type tt struct {
40 source, target *kivik.DB
41 options kivik.Option
42 status int
43 err string
44 result *kivik.ReplicationResult
45 }
46 tests := testy.NewTable()
47 tests.Add("couch to couch", func(t *testing.T) interface{} {
48 dsn := kt.DSN3(t)
49 client, err := kivik.New("couch", dsn)
50 if err != nil {
51 t.Fatal(err)
52 }
53 sourceName := kt.TestDBName(t)
54 targetName := kt.TestDBName(t)
55 ctx := context.Background()
56 if err := client.CreateDB(ctx, sourceName); err != nil {
57 t.Fatal(err)
58 }
59 tests.Cleanup(func() {
60 _ = client.DestroyDB(ctx, sourceName)
61 })
62 if err := client.CreateDB(ctx, targetName); err != nil {
63 t.Fatal(err)
64 }
65 tests.Cleanup(func() {
66 _ = client.DestroyDB(ctx, targetName)
67 })
68 source := client.DB(sourceName)
69 target := client.DB(targetName)
70 doc := map[string]string{"foo": "bar"}
71 if _, err := source.Put(ctx, "foo", doc); err != nil {
72 t.Fatal(err)
73 }
74
75 return tt{
76 source: source,
77 target: target,
78 result: &kivik.ReplicationResult{
79 DocsRead: 1,
80 DocsWritten: 1,
81 MissingChecked: 1,
82 MissingFound: 1,
83 },
84 }
85 })
86 tests.Add("fs to couch", func(t *testing.T) interface{} {
87 fsclient, err := kivik.New("fs", "testdata/")
88 if err != nil {
89 t.Fatal(err)
90 }
91 dsn := kt.DSN3(t)
92 client, err := kivik.New("couch", dsn)
93 if err != nil {
94 t.Fatal(err)
95 }
96 ctx := context.Background()
97 source := fsclient.DB("db1")
98 targetName := kt.TestDBName(t)
99 if err := client.CreateDB(ctx, targetName); err != nil {
100 t.Fatal(err)
101 }
102 tests.Cleanup(func() {
103 _ = client.DestroyDB(ctx, targetName)
104 })
105 target := client.DB(targetName)
106
107 return tt{
108 source: source,
109 target: target,
110 result: &kivik.ReplicationResult{
111 DocsRead: 1,
112 DocsWritten: 1,
113 MissingChecked: 1,
114 MissingFound: 1,
115 },
116 }
117 })
118 tests.Add("fs to couch, no shared history", func(t *testing.T) interface{} {
119 fsclient, err := kivik.New("fs", "testdata/")
120 if err != nil {
121 t.Fatal(err)
122 }
123 dsn := kt.DSN3(t)
124 client, err := kivik.New("couch", dsn)
125 if err != nil {
126 t.Fatal(err)
127 }
128 ctx := context.Background()
129 source := fsclient.DB("db1")
130 targetName := kt.TestDBName(t)
131 if err := client.CreateDB(ctx, targetName); err != nil {
132 t.Fatal(err)
133 }
134 tests.Cleanup(func() {
135 _ = client.DestroyDB(ctx, targetName)
136 })
137 target := client.DB(targetName)
138
139 if _, err := kivik.Replicate(ctx, target, source); err != nil {
140 t.Fatalf("setup replication failed: %s", err)
141 }
142
143 return tt{
144 source: fsclient.DB("db2"),
145 target: target,
146 result: &kivik.ReplicationResult{
147 DocsRead: 1,
148 DocsWritten: 1,
149 MissingChecked: 1,
150 MissingFound: 1,
151 },
152 }
153 })
154 tests.Add("couch to couch with sec", func(t *testing.T) interface{} {
155 dsn := kt.DSN3(t)
156 client, err := kivik.New("couch", dsn)
157 if err != nil {
158 t.Fatal(err)
159 }
160 sourceName := kt.TestDBName(t)
161 targetName := kt.TestDBName(t)
162 ctx := context.Background()
163 if err := client.CreateDB(ctx, sourceName); err != nil {
164 t.Fatal(err)
165 }
166 tests.Cleanup(func() {
167 _ = client.DestroyDB(ctx, sourceName)
168 })
169 if err := client.CreateDB(ctx, targetName); err != nil {
170 t.Fatal(err)
171 }
172 tests.Cleanup(func() {
173 _ = client.DestroyDB(ctx, targetName)
174 })
175 source := client.DB(sourceName)
176 target := client.DB(targetName)
177 doc := map[string]string{"foo": "bar"}
178 if _, err := source.Put(ctx, "foo", doc); err != nil {
179 t.Fatal(err)
180 }
181 err = source.SetSecurity(ctx, &kivik.Security{
182 Members: kivik.Members{
183 Names: []string{"bob"},
184 },
185 })
186 if err != nil {
187 t.Fatal(err)
188 }
189
190 return tt{
191 source: source,
192 target: target,
193 options: kivik.ReplicateCopySecurity(),
194 result: &kivik.ReplicationResult{
195 DocsRead: 1,
196 DocsWritten: 1,
197 MissingChecked: 1,
198 MissingFound: 1,
199 },
200 }
201 })
202 tests.Add("fs to couch, bad put", func(t *testing.T) interface{} {
203 fsclient, err := kivik.New("fs", "testdata/")
204 if err != nil {
205 t.Fatal(err)
206 }
207 dsn := kt.DSN3(t)
208 client, err := kivik.New("couch", dsn)
209 if err != nil {
210 t.Fatal(err)
211 }
212 ctx := context.Background()
213 targetName := kt.TestDBName(t)
214 if err := client.CreateDB(ctx, targetName); err != nil {
215 t.Fatal(err)
216 }
217 tests.Cleanup(func() {
218 _ = client.DestroyDB(ctx, targetName)
219 })
220 target := client.DB(targetName)
221
222 return tt{
223 source: fsclient.DB("db3"),
224 target: target,
225 result: &kivik.ReplicationResult{
226 DocsRead: 1,
227 DocsWritten: 1,
228 MissingChecked: 1,
229 MissingFound: 1,
230 },
231 status: http.StatusBadRequest,
232 err: "store doc note--XkWjFv13acvjJTt-CGJJ8hXlWE: Bad Request: Bad special document member: _invalid",
233 }
234 })
235 tests.Add("fs to couch with attachment", func(t *testing.T) interface{} {
236 fsclient, err := kivik.New("fs", "testdata/")
237 if err != nil {
238 t.Fatal(err)
239 }
240 dsn := kt.DSN3(t)
241 client, err := kivik.New("couch", dsn)
242 if err != nil {
243 t.Fatal(err)
244 }
245 ctx := context.Background()
246 source := fsclient.DB("db4")
247 targetName := kt.TestDBName(t)
248 if err := client.CreateDB(ctx, targetName); err != nil {
249 t.Fatal(err)
250 }
251 tests.Cleanup(func() {
252 _ = client.DestroyDB(ctx, targetName)
253 })
254 target := client.DB(targetName)
255
256 return tt{
257 source: source,
258 target: target,
259 result: &kivik.ReplicationResult{
260 DocsRead: 1,
261 DocsWritten: 1,
262 MissingChecked: 1,
263 MissingFound: 1,
264 },
265 }
266 })
267 tests.Add("couch to fs", func(t *testing.T) interface{} {
268 tempDir, err := os.MkdirTemp("", "kivik.test.")
269 if err != nil {
270 t.Fatal(err)
271 }
272 tests.Cleanup(func() error {
273 return os.RemoveAll(tempDir)
274 })
275 tClient, err := kivik.New("fs", tempDir)
276 if err != nil {
277 t.Fatal(err)
278 }
279
280 dsn := kt.DSN3(t)
281 client, err := kivik.New("couch", dsn)
282 if err != nil {
283 t.Fatal(err)
284 }
285 dbName := kt.TestDBName(t)
286 ctx := context.Background()
287 if err := client.CreateDB(ctx, dbName); err != nil {
288 t.Fatal(err)
289 }
290 tests.Cleanup(func() {
291 _ = client.DestroyDB(ctx, dbName)
292 })
293 if err := tClient.CreateDB(ctx, dbName); err != nil {
294 t.Fatal(err)
295 }
296 source := client.DB(dbName)
297 target := tClient.DB(dbName)
298 doc := map[string]interface{}{
299 "foo": "bar",
300 "_attachments": map[string]interface{}{
301 "foo.txt": map[string]interface{}{
302 "content_type": "application/octet-stream",
303 "data": []byte("Test content"),
304 },
305 },
306 }
307 if _, err := source.Put(ctx, "foo", doc); err != nil {
308 t.Fatal(err)
309 }
310
311 return tt{
312 source: source,
313 target: target,
314 result: &kivik.ReplicationResult{
315 DocsRead: 1,
316 DocsWritten: 1,
317 MissingChecked: 1,
318 MissingFound: 1,
319 },
320 }
321 })
322 tests.Add("fs to couch with deleted document", func(t *testing.T) interface{} {
323 fsclient, err := kivik.New("fs", "testdata/")
324 if err != nil {
325 t.Fatal(err)
326 }
327 dsn := kt.DSN3(t)
328 client, err := kivik.New("couch", dsn)
329 if err != nil {
330 t.Fatal(err)
331 }
332 ctx := context.Background()
333 source := fsclient.DB("dbdelete")
334 targetName := kt.TestDBName(t)
335 if err := client.CreateDB(ctx, targetName); err != nil {
336 t.Fatal(err)
337 }
338 tests.Cleanup(func() {
339 _ = client.DestroyDB(ctx, targetName)
340 })
341 target := client.DB(targetName)
342 if _, err := target.Put(ctx, "foo", map[string]string{"still": "here"}); err != nil {
343 t.Fatal(err)
344 }
345
346 return tt{
347 source: source,
348 target: target,
349 result: &kivik.ReplicationResult{
350 DocsRead: 1,
351 DocsWritten: 1,
352 MissingChecked: 1,
353 MissingFound: 1,
354 },
355 }
356 })
357 tests.Run(t, func(t *testing.T, tt tt) {
358 ctx := context.TODO()
359 result, err := kivik.Replicate(ctx, tt.target, tt.source, tt.options)
360 if d := internal.StatusErrorDiff(tt.err, tt.status, err); d != "" {
361 t.Error(d)
362 }
363 if err != nil {
364 return
365 }
366
367 verifyDoc(ctx, t, tt.target, tt.source, "foo")
368 verifySec(ctx, t, tt.target)
369 result.StartTime = time.Time{}
370 result.EndTime = time.Time{}
371 if d := testy.DiffAsJSON(tt.result, result); d != nil {
372 t.Error(d)
373 }
374 })
375 }
376
377 func verifyDoc(ctx context.Context, t *testing.T, target, source *kivik.DB, docID string) {
378 t.Helper()
379 var targetDoc, sourceDoc interface{}
380 notFound := false
381 if err := source.Get(ctx, docID).ScanDoc(&sourceDoc); err != nil {
382 if kivik.HTTPStatus(err) == http.StatusNotFound {
383 notFound = true
384 } else {
385 t.Fatalf("get %s from source failed: %s", docID, err)
386 }
387 }
388 if err := target.Get(ctx, docID).ScanDoc(&targetDoc); err != nil {
389 if notFound && kivik.HTTPStatus(err) == http.StatusNotFound {
390 return
391 }
392 t.Fatalf("get %s from target failed: %s", docID, err)
393 }
394 if d := testy.DiffAsJSON(sourceDoc, targetDoc); d != nil {
395 t.Error(d)
396 }
397 }
398
399 func verifySec(ctx context.Context, t *testing.T, target *kivik.DB) {
400 t.Helper()
401 sec, err := target.Security(ctx)
402 if err != nil {
403 t.Fatal(err)
404 }
405 if d := testy.DiffAsJSON(&testy.File{Path: "testdata/" + testy.Stub(t) + ".security"}, sec); d != nil {
406 t.Errorf("Security object:\n%s", d)
407 }
408 }
409
View as plain text