github.com/go-kivik/kivik/v4@v4.3.2/replicate_test.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 // use this file except in compliance with the License. You may obtain a copy of 3 // the License at 4 // 5 // http://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 // License for the specific language governing permissions and limitations under 11 // the License. 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" // The filesystem driver 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 }