1
2
3
4
5
6
7
8
9
10
11
12
13 package kivik_test
14
15 import (
16 "context"
17 "io"
18 "net/http"
19 "os"
20 "runtime"
21 "strings"
22 "testing"
23 "time"
24
25 "github.com/google/go-cmp/cmp"
26 "gitlab.com/flimzy/testy"
27
28 "github.com/go-kivik/kivik/v4"
29 "github.com/go-kivik/kivik/v4/driver"
30 internal "github.com/go-kivik/kivik/v4/int/errors"
31 kivikmock "github.com/go-kivik/kivik/v4/mockdb"
32 _ "github.com/go-kivik/kivik/v4/x/fsdb"
33 )
34
35 const isGopherJS117 = runtime.GOARCH == "js"
36
37 func TestReplicateMock(t *testing.T) {
38 type tt struct {
39 mockT, mockS *kivikmock.Client
40 target, source *kivik.DB
41 options kivik.Option
42 status int
43 err string
44 result *kivik.ReplicationResult
45 }
46 tests := testy.NewTable()
47 tests.Add("no changes", func(t *testing.T) interface{} {
48 source, mock := kivikmock.NewT(t)
49 db := mock.NewDB()
50 mock.ExpectDB().WillReturn(db)
51 db.ExpectChanges().WillReturn(kivikmock.NewChanges())
52
53 return tt{
54 mockS: mock,
55 source: source.DB("src"),
56 result: &kivik.ReplicationResult{},
57 }
58 })
59 tests.Add("up to date", func(t *testing.T) interface{} {
60 source, smock := kivikmock.NewT(t)
61 sdb := smock.NewDB()
62 smock.ExpectDB().WillReturn(sdb)
63 sdb.ExpectChanges().WillReturn(kivikmock.NewChanges().
64 AddChange(&driver.Change{
65 ID: "foo",
66 Changes: []string{"2-7051cbe5c8faecd085a3fa619e6e6337"},
67 Seq: "3-g1AAAAG3eJzLYWBg4MhgTmHgz8tPSTV0MDQy1zMAQsMcoARTIkOS_P___7MSGXAqSVIAkkn2IFUZzIkMuUAee5pRqnGiuXkKA2dpXkpqWmZeagpu_Q4g_fGEbEkAqaqH2sIItsXAyMjM2NgUUwdOU_JYgCRDA5ACGjQfn30QlQsgKvcjfGaQZmaUmmZClM8gZhyAmHGfsG0PICrBPmQC22ZqbGRqamyIqSsLAAArcXo",
68 }))
69
70 target, tmock := kivikmock.NewT(t)
71 tdb := tmock.NewDB()
72 tmock.ExpectDB().WillReturn(tdb)
73 tdb.ExpectRevsDiff().
74 WithRevLookup(map[string][]string{
75 "foo": {"2-7051cbe5c8faecd085a3fa619e6e6337"},
76 }).
77 WillReturn(kivikmock.NewRows())
78
79 return tt{
80 mockS: smock,
81 mockT: tmock,
82 source: source.DB("src"),
83 target: target.DB("tgt"),
84 result: &kivik.ReplicationResult{},
85 }
86 })
87 tests.Add("one update", func(t *testing.T) interface{} {
88 source, smock := kivikmock.NewT(t)
89 sdb := smock.NewDB()
90 smock.ExpectDB().WillReturn(sdb)
91 sdb.ExpectChanges().WillReturn(kivikmock.NewChanges().
92 AddChange(&driver.Change{
93 ID: "foo",
94 Changes: []string{"2-7051cbe5c8faecd085a3fa619e6e6337"},
95 Seq: "3-g1AAAAG3eJzLYWBg4MhgTmHgz8tPSTV0MDQy1zMAQsMcoARTIkOS_P___7MSGXAqSVIAkkn2IFUZzIkMuUAee5pRqnGiuXkKA2dpXkpqWmZeagpu_Q4g_fGEbEkAqaqH2sIItsXAyMjM2NgUUwdOU_JYgCRDA5ACGjQfn30QlQsgKvcjfGaQZmaUmmZClM8gZhyAmHGfsG0PICrBPmQC22ZqbGRqamyIqSsLAAArcXo",
96 }))
97
98 target, tmock := kivikmock.NewT(t)
99 tdb := tmock.NewDB()
100 tmock.ExpectDB().WillReturn(tdb)
101 tdb.ExpectRevsDiff().
102 WithRevLookup(map[string][]string{
103 "foo": {"2-7051cbe5c8faecd085a3fa619e6e6337"},
104 }).
105 WillReturn(kivikmock.NewRows().
106 AddRow(&driver.Row{
107 ID: "foo",
108 Value: strings.NewReader(`{"missing":["2-7051cbe5c8faecd085a3fa619e6e6337"]}`),
109 }))
110 sdb.ExpectOpenRevs().WillReturnError(&internal.Error{Status: http.StatusNotImplemented})
111 sdb.ExpectGet().
112 WithDocID("foo").
113 WithOptions(kivik.Params(map[string]interface{}{
114 "rev": "2-7051cbe5c8faecd085a3fa619e6e6337",
115 "revs": true,
116 "attachments": true,
117 })).
118 WillReturn(&driver.Document{
119 Body: io.NopCloser(strings.NewReader(`{"_id":"foo","_rev":"2-7051cbe5c8faecd085a3fa619e6e6337","foo":"bar"}`)),
120 })
121 tdb.ExpectPut().
122 WithDocID("foo").
123 WithOptions(kivik.Param("new_edits", false)).
124 WillReturn("2-7051cbe5c8faecd085a3fa619e6e6337")
125
126 return tt{
127 mockS: smock,
128 mockT: tmock,
129 source: source.DB("src"),
130 target: target.DB("tgt"),
131 result: &kivik.ReplicationResult{
132 DocsRead: 1,
133 DocsWritten: 1,
134 MissingChecked: 1,
135 MissingFound: 1,
136 },
137 }
138 })
139 tests.Add("one update with OpenRevs", func(t *testing.T) interface{} {
140 source, smock := kivikmock.NewT(t)
141 sdb := smock.NewDB()
142 smock.ExpectDB().WillReturn(sdb)
143 sdb.ExpectChanges().WillReturn(kivikmock.NewChanges().
144 AddChange(&driver.Change{
145 ID: "foo",
146 Changes: []string{"2-7051cbe5c8faecd085a3fa619e6e6337"},
147 Seq: "3-g1AAAAG3eJzLYWBg4MhgTmHgz8tPSTV0MDQy1zMAQsMcoARTIkOS_P___7MSGXAqSVIAkkn2IFUZzIkMuUAee5pRqnGiuXkKA2dpXkpqWmZeagpu_Q4g_fGEbEkAqaqH2sIItsXAyMjM2NgUUwdOU_JYgCRDA5ACGjQfn30QlQsgKvcjfGaQZmaUmmZClM8gZhyAmHGfsG0PICrBPmQC22ZqbGRqamyIqSsLAAArcXo",
148 }))
149
150 target, tmock := kivikmock.NewT(t)
151 tdb := tmock.NewDB()
152 tmock.ExpectDB().WillReturn(tdb)
153 tdb.ExpectRevsDiff().
154 WithRevLookup(map[string][]string{
155 "foo": {"2-7051cbe5c8faecd085a3fa619e6e6337"},
156 }).
157 WillReturn(kivikmock.NewRows().
158 AddRow(&driver.Row{
159 ID: "foo",
160 Value: strings.NewReader(`{"missing":["2-7051cbe5c8faecd085a3fa619e6e6337"]}`),
161 }))
162 sdb.ExpectOpenRevs().
163 WithDocID("foo").
164 WillReturn(kivikmock.NewRows().AddRow(&driver.Row{
165 ID: "foo",
166 Rev: "2-7051cbe5c8faecd085a3fa619e6e6337",
167 Doc: strings.NewReader(`{"_id":"foo","_rev":"2-7051cbe5c8faecd085a3fa619e6e6337","foo":"bar"}`),
168 }))
169 tdb.ExpectPut().
170 WithDocID("foo").
171 WithOptions(kivik.Param("new_edits", false)).
172 WillReturn("2-7051cbe5c8faecd085a3fa619e6e6337")
173
174 return tt{
175 mockS: smock,
176 mockT: tmock,
177 source: source.DB("src"),
178 target: target.DB("tgt"),
179 result: &kivik.ReplicationResult{
180 DocsRead: 1,
181 DocsWritten: 1,
182 MissingChecked: 1,
183 MissingFound: 1,
184 },
185 }
186 })
187
188 tests.Run(t, func(t *testing.T, tt tt) {
189 result, err := kivik.Replicate(context.TODO(), tt.target, tt.source, tt.options)
190 if d := internal.StatusErrorDiff(tt.err, tt.status, err); d != "" {
191 t.Error(d)
192 }
193 if tt.mockT != nil {
194 if err := tt.mockT.ExpectationsWereMet(); !testy.ErrorMatches("", err) {
195 t.Errorf("Unexpected error: %s", err)
196 }
197 }
198 if tt.mockS != nil {
199 if err := tt.mockS.ExpectationsWereMet(); !testy.ErrorMatches("", err) {
200 t.Errorf("Unexpected error: %s", err)
201 }
202 }
203 result.StartTime = time.Time{}
204 result.EndTime = time.Time{}
205 if d := testy.DiffAsJSON(tt.result, result); d != nil {
206 t.Error(d)
207 }
208 })
209 }
210
211 func TestReplicate_with_callback(t *testing.T) {
212 source, smock := kivikmock.NewT(t)
213 sdb := smock.NewDB()
214 smock.ExpectDB().WillReturn(sdb)
215 sdb.ExpectChanges().WillReturn(kivikmock.NewChanges().
216 AddChange(&driver.Change{
217 ID: "foo",
218 Changes: []string{"2-7051cbe5c8faecd085a3fa619e6e6337"},
219 Seq: "3-g1AAAAG3eJzLYWBg4MhgTmHgz8tPSTV0MDQy1zMAQsMcoARTIkOS_P___7MSGXAqSVIAkkn2IFUZzIkMuUAee5pRqnGiuXkKA2dpXkpqWmZeagpu_Q4g_fGEbEkAqaqH2sIItsXAyMjM2NgUUwdOU_JYgCRDA5ACGjQfn30QlQsgKvcjfGaQZmaUmmZClM8gZhyAmHGfsG0PICrBPmQC22ZqbGRqamyIqSsLAAArcXo",
220 }))
221
222 target, tmock := kivikmock.NewT(t)
223 tdb := tmock.NewDB()
224 tmock.ExpectDB().WillReturn(tdb)
225 tdb.ExpectRevsDiff().
226 WithRevLookup(map[string][]string{
227 "foo": {"2-7051cbe5c8faecd085a3fa619e6e6337"},
228 }).
229 WillReturn(kivikmock.NewRows().
230 AddRow(&driver.Row{
231 ID: "foo",
232 Value: strings.NewReader(`{"missing":["2-7051cbe5c8faecd085a3fa619e6e6337"]}`),
233 }))
234 sdb.ExpectOpenRevs().WillReturnError(&internal.Error{Status: http.StatusNotImplemented})
235 sdb.ExpectGet().
236 WithDocID("foo").
237 WithOptions(kivik.Params(map[string]interface{}{
238 "rev": "2-7051cbe5c8faecd085a3fa619e6e6337",
239 "revs": true,
240 "attachments": true,
241 })).
242 WillReturn(&driver.Document{
243 Body: io.NopCloser(strings.NewReader(`{"_id":"foo","_rev":"2-7051cbe5c8faecd085a3fa619e6e6337","foo":"bar"}`)),
244 })
245 tdb.ExpectPut().
246 WithDocID("foo").
247 WithOptions(kivik.Param("new_edits", false)).
248 WillReturn("2-7051cbe5c8faecd085a3fa619e6e6337")
249
250 events := []kivik.ReplicationEvent{}
251
252 _, err := kivik.Replicate(context.TODO(), target.DB("tgt"), source.DB("src"), kivik.ReplicateCallback(func(e kivik.ReplicationEvent) {
253 events = append(events, e)
254 }))
255 if err != nil {
256 t.Fatal(err)
257 }
258
259 expected := []kivik.ReplicationEvent{
260 {
261 Type: "changes",
262 Read: true,
263 },
264 {
265 Type: "change",
266 Read: true,
267 DocID: "foo",
268 Changes: []string{"2-7051cbe5c8faecd085a3fa619e6e6337"},
269 },
270 {
271 Type: "revsdiff",
272 Read: true,
273 },
274 {
275 Type: "revsdiff",
276 Read: true,
277 DocID: "foo",
278 },
279 {
280 Type: "document",
281 Read: true,
282 DocID: "foo",
283 },
284 {
285 Type: "document",
286 DocID: "foo",
287 },
288 }
289 if d := cmp.Diff(expected, events); d != "" {
290 t.Error(d)
291 }
292 }
293
294 func TestReplicate(t *testing.T) {
295 if isGopherJS117 {
296 t.Skip("Replication doesn't work in GopherJS 1.17")
297 }
298 type tt struct {
299 path string
300 target, source *kivik.DB
301 options kivik.Option
302 status int
303 err string
304 }
305 tests := testy.NewTable()
306 tests.Add("fs to fs", func(t *testing.T) interface{} {
307 tmpdir := testy.CopyTempDir(t, "testdata/db4", 1)
308 tests.Cleanup(func() error {
309 return os.RemoveAll(tmpdir)
310 })
311
312 client, err := kivik.New("fs", tmpdir)
313 if err != nil {
314 t.Fatal(err)
315 }
316 if err := client.CreateDB(context.TODO(), "target"); err != nil {
317 t.Fatal(err)
318 }
319
320 return tt{
321 path: tmpdir,
322 target: client.DB("target"),
323 source: client.DB("db4"),
324 }
325 })
326
327 tests.Run(t, func(t *testing.T, tt tt) {
328 result, err := kivik.Replicate(context.TODO(), tt.target, tt.source, tt.options)
329 if d := internal.StatusErrorDiff(tt.err, tt.status, err); d != "" {
330 t.Error(d)
331 }
332 result.StartTime = time.Time{}
333 result.EndTime = time.Time{}
334 if d := testy.DiffAsJSON(testy.Snapshot(t), result); d != nil {
335 t.Error(d)
336 }
337 if d := testy.DiffAsJSON(testy.Snapshot(t, "fs"), testy.JSONDir{
338 Path: tt.path,
339 FileContent: true,
340 MaxContentSize: 100,
341 }); d != nil {
342 t.Error(d)
343 }
344 })
345 }
346
View as plain text