github.com/go-kivik/kivik/v4@v4.3.2/replication_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 14 15 import ( 16 "context" 17 "errors" 18 "fmt" 19 "net/http" 20 "testing" 21 "time" 22 23 "gitlab.com/flimzy/testy" 24 25 "github.com/go-kivik/kivik/v4/driver" 26 internal "github.com/go-kivik/kivik/v4/int/errors" 27 "github.com/go-kivik/kivik/v4/int/mock" 28 ) 29 30 func TestReplicationDocsWritten(t *testing.T) { 31 t.Run("No Info", func(t *testing.T) { 32 r := &Replication{} 33 result := r.DocsWritten() 34 if result != 0 { 35 t.Errorf("Unexpected doc count: %d", result) 36 } 37 }) 38 t.Run("With Info", func(t *testing.T) { 39 r := &Replication{ 40 info: &driver.ReplicationInfo{ 41 DocsWritten: 123, 42 }, 43 } 44 result := r.DocsWritten() 45 if result != 123 { 46 t.Errorf("Unexpected doc count: %d", result) 47 } 48 }) 49 t.Run("Nil", func(t *testing.T) { 50 var r *Replication 51 result := r.DocsWritten() 52 if result != 0 { 53 t.Errorf("Unexpected doc count: %d", result) 54 } 55 }) 56 } 57 58 func TestDocsRead(t *testing.T) { 59 t.Run("No Info", func(t *testing.T) { 60 r := &Replication{} 61 result := r.DocsRead() 62 if result != 0 { 63 t.Errorf("Unexpected doc count: %d", result) 64 } 65 }) 66 t.Run("With Info", func(t *testing.T) { 67 r := &Replication{ 68 info: &driver.ReplicationInfo{ 69 DocsRead: 123, 70 }, 71 } 72 result := r.DocsRead() 73 if result != 123 { 74 t.Errorf("Unexpected doc count: %d", result) 75 } 76 }) 77 t.Run("Nil", func(t *testing.T) { 78 var r *Replication 79 result := r.DocsRead() 80 if result != 0 { 81 t.Errorf("Unexpected doc count: %d", result) 82 } 83 }) 84 } 85 86 func TestDocWriteFailures(t *testing.T) { 87 t.Run("No Info", func(t *testing.T) { 88 r := &Replication{} 89 result := r.DocWriteFailures() 90 if result != 0 { 91 t.Errorf("Unexpected doc count: %d", result) 92 } 93 }) 94 t.Run("With Info", func(t *testing.T) { 95 r := &Replication{ 96 info: &driver.ReplicationInfo{ 97 DocWriteFailures: 123, 98 }, 99 } 100 result := r.DocWriteFailures() 101 if result != 123 { 102 t.Errorf("Unexpected doc count: %d", result) 103 } 104 }) 105 t.Run("Nil", func(t *testing.T) { 106 var r *Replication 107 result := r.DocWriteFailures() 108 if result != 0 { 109 t.Errorf("Unexpected doc count: %d", result) 110 } 111 }) 112 } 113 114 func TestProgress(t *testing.T) { 115 t.Run("No Info", func(t *testing.T) { 116 r := &Replication{} 117 result := r.Progress() 118 if result != 0 { 119 t.Errorf("Unexpected doc count: %v", result) 120 } 121 }) 122 t.Run("With Info", func(t *testing.T) { 123 r := &Replication{ 124 info: &driver.ReplicationInfo{ 125 Progress: 123, 126 }, 127 } 128 result := r.Progress() 129 if result != 123 { 130 t.Errorf("Unexpected doc count: %v", result) 131 } 132 }) 133 t.Run("Nil", func(t *testing.T) { 134 var r *Replication 135 result := r.Progress() 136 if result != 0 { 137 t.Errorf("Unexpected doc count: %v", result) 138 } 139 }) 140 } 141 142 func TestNewReplication(t *testing.T) { 143 source := "foo" 144 target := "bar" 145 rep := &mock.Replication{ 146 SourceFunc: func() string { return source }, 147 TargetFunc: func() string { return target }, 148 } 149 expected := &Replication{ 150 Source: source, 151 Target: target, 152 irep: rep, 153 } 154 result := newReplication(rep) 155 if d := testy.DiffInterface(expected, result); d != nil { 156 t.Error(d) 157 } 158 } 159 160 func TestReplicationGetters(t *testing.T) { 161 repID := "repID" 162 start := parseTime(t, "2018-01-01T00:00:00Z") 163 end := parseTime(t, "2019-01-01T00:00:00Z") 164 state := "confusion" 165 r := &Replication{ 166 irep: &mock.Replication{ 167 ReplicationIDFunc: func() string { return repID }, 168 StartTimeFunc: func() time.Time { return start }, 169 EndTimeFunc: func() time.Time { return end }, 170 StateFunc: func() string { return state }, 171 }, 172 } 173 174 t.Run("ReplicationID", func(t *testing.T) { 175 result := r.ReplicationID() 176 if result != repID { 177 t.Errorf("Unexpected result: %v", result) 178 } 179 }) 180 181 t.Run("StartTime", func(t *testing.T) { 182 result := r.StartTime() 183 if !result.Equal(start) { 184 t.Errorf("Unexpected result: %v", result) 185 } 186 }) 187 188 t.Run("EndTime", func(t *testing.T) { 189 result := r.EndTime() 190 if !result.Equal(end) { 191 t.Errorf("Unexpected result: %v", result) 192 } 193 }) 194 195 t.Run("State", func(t *testing.T) { 196 result := r.State() 197 if result != ReplicationState(state) { 198 t.Errorf("Unexpected result: %v", result) 199 } 200 }) 201 } 202 203 func TestReplicationErr(t *testing.T) { 204 t.Run("No error", func(t *testing.T) { 205 r := &Replication{ 206 irep: &mock.Replication{ 207 ErrFunc: func() error { return nil }, 208 }, 209 } 210 if err := r.Err(); err != nil { 211 t.Errorf("Unexpected error: %s", err) 212 } 213 }) 214 t.Run("Error", func(t *testing.T) { 215 r := &Replication{ 216 irep: &mock.Replication{ 217 ErrFunc: func() error { 218 return errors.New("rep error") 219 }, 220 }, 221 } 222 if err := r.Err(); err == nil || err.Error() != "rep error" { 223 t.Errorf("Unexpected error: %s", err) 224 } 225 }) 226 t.Run("Nil", func(t *testing.T) { 227 var r *Replication 228 if err := r.Err(); err != nil { 229 t.Errorf("Unexpected error: %s", err) 230 } 231 }) 232 } 233 234 func TestReplicationIsActive(t *testing.T) { 235 t.Run("Active", func(t *testing.T) { 236 r := &Replication{ 237 irep: &mock.Replication{ 238 StateFunc: func() string { 239 return "active" 240 }, 241 }, 242 } 243 if !r.IsActive() { 244 t.Errorf("Expected active") 245 } 246 }) 247 t.Run("Complete", func(t *testing.T) { 248 r := &Replication{ 249 irep: &mock.Replication{ 250 StateFunc: func() string { 251 return string(ReplicationComplete) 252 }, 253 }, 254 } 255 if r.IsActive() { 256 t.Errorf("Expected not active") 257 } 258 }) 259 t.Run("Nil", func(t *testing.T) { 260 var r *Replication 261 if r.IsActive() { 262 t.Errorf("Expected not active") 263 } 264 }) 265 } 266 267 func TestReplicationDelete(t *testing.T) { 268 expected := "delete error" 269 r := &Replication{ 270 irep: &mock.Replication{ 271 DeleteFunc: func(context.Context) error { return errors.New(expected) }, 272 }, 273 } 274 err := r.Delete(context.Background()) 275 if !testy.ErrorMatches(expected, err) { 276 t.Errorf("Unexpected error: %s", err) 277 } 278 } 279 280 func TestReplicationUpdate(t *testing.T) { 281 t.Run("update error", func(t *testing.T) { 282 expected := "rep error" 283 r := &Replication{ 284 irep: &mock.Replication{ 285 UpdateFunc: func(context.Context, *driver.ReplicationInfo) error { 286 return errors.New(expected) 287 }, 288 }, 289 } 290 err := r.Update(context.Background()) 291 if !testy.ErrorMatches(expected, err) { 292 t.Errorf("Unexpected error: %s", err) 293 } 294 }) 295 296 t.Run("success", func(t *testing.T) { 297 expected := driver.ReplicationInfo{ 298 DocsRead: 123, 299 } 300 r := &Replication{ 301 irep: &mock.Replication{ 302 UpdateFunc: func(_ context.Context, i *driver.ReplicationInfo) error { 303 *i = driver.ReplicationInfo{ 304 DocsRead: 123, 305 } 306 return nil 307 }, 308 }, 309 } 310 err := r.Update(context.Background()) 311 if !testy.ErrorMatches("", err) { 312 t.Errorf("Unexpected error: %s", err) 313 } 314 if d := testy.DiffInterface(&expected, r.info); d != nil { 315 t.Error(d) 316 } 317 }) 318 } 319 320 func TestGetReplications(t *testing.T) { 321 tests := []struct { 322 name string 323 client *Client 324 options Option 325 expected []*Replication 326 status int 327 err string 328 }{ 329 { 330 name: "non-replicator", 331 client: &Client{ 332 driverClient: &mock.Client{}, 333 }, 334 status: http.StatusNotImplemented, 335 err: "kivik: driver does not support replication", 336 }, 337 { 338 name: "db error", 339 client: &Client{ 340 driverClient: &mock.ClientReplicator{ 341 GetReplicationsFunc: func(context.Context, driver.Options) ([]driver.Replication, error) { 342 return nil, errors.New("db error") 343 }, 344 }, 345 }, 346 status: http.StatusInternalServerError, 347 err: "db error", 348 }, 349 { 350 name: "success", 351 client: &Client{ 352 driverClient: &mock.ClientReplicator{ 353 GetReplicationsFunc: func(_ context.Context, options driver.Options) ([]driver.Replication, error) { 354 gotOpts := map[string]interface{}{} 355 options.Apply(gotOpts) 356 wantOpts := map[string]interface{}{"foo": 123} 357 if d := testy.DiffInterface(wantOpts, gotOpts); d != nil { 358 return nil, fmt.Errorf("Unexpected options:\n%v", d) 359 } 360 return []driver.Replication{ 361 &mock.Replication{ID: "1"}, 362 &mock.Replication{ID: "2"}, 363 }, nil 364 }, 365 }, 366 }, 367 options: Param("foo", 123), 368 expected: []*Replication{ 369 { 370 Source: "1-source", 371 Target: "1-target", 372 irep: &mock.Replication{ID: "1"}, 373 }, 374 { 375 Source: "2-source", 376 Target: "2-target", 377 irep: &mock.Replication{ID: "2"}, 378 }, 379 }, 380 }, 381 { 382 name: "closed", 383 client: &Client{ 384 closed: true, 385 }, 386 status: http.StatusServiceUnavailable, 387 err: "kivik: client closed", 388 }, 389 } 390 for _, test := range tests { 391 t.Run(test.name, func(t *testing.T) { 392 result, err := test.client.GetReplications(context.Background(), test.options) 393 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" { 394 t.Error(d) 395 } 396 if d := testy.DiffInterface(test.expected, result); d != nil { 397 t.Error(d) 398 } 399 }) 400 } 401 } 402 403 func TestReplicate(t *testing.T) { 404 tests := []struct { 405 name string 406 client *Client 407 target, source string 408 options Option 409 expected *Replication 410 status int 411 err string 412 }{ 413 { 414 name: "non-replicator", 415 client: &Client{ 416 driverClient: &mock.Client{}, 417 }, 418 status: http.StatusNotImplemented, 419 err: "kivik: driver does not support replication", 420 }, 421 { 422 name: "db error", 423 client: &Client{ 424 driverClient: &mock.ClientReplicator{ 425 ReplicateFunc: func(context.Context, string, string, driver.Options) (driver.Replication, error) { 426 return nil, errors.New("db error") 427 }, 428 }, 429 }, 430 status: http.StatusInternalServerError, 431 err: "db error", 432 }, 433 { 434 name: "success", 435 client: &Client{ 436 driverClient: &mock.ClientReplicator{ 437 ReplicateFunc: func(_ context.Context, target, source string, options driver.Options) (driver.Replication, error) { 438 expectedTarget := "foo" 439 expectedSource := "bar" 440 gotOpts := map[string]interface{}{} 441 options.Apply(gotOpts) 442 wantOpts := map[string]interface{}{"foo": 123} 443 if target != expectedTarget { 444 return nil, fmt.Errorf("Unexpected target: %s", target) 445 } 446 if source != expectedSource { 447 return nil, fmt.Errorf("Unexpected source: %s", source) 448 } 449 if d := testy.DiffInterface(wantOpts, gotOpts); d != nil { 450 return nil, fmt.Errorf("Unexpected options:\n%v", d) 451 } 452 return &mock.Replication{ID: "a"}, nil 453 }, 454 }, 455 }, 456 target: "foo", 457 source: "bar", 458 options: Param("foo", 123), 459 expected: &Replication{ 460 Source: "a-source", 461 Target: "a-target", 462 irep: &mock.Replication{ID: "a"}, 463 }, 464 }, 465 { 466 name: "closed", 467 client: &Client{ 468 closed: true, 469 }, 470 status: http.StatusServiceUnavailable, 471 err: "kivik: client closed", 472 }, 473 } 474 for _, test := range tests { 475 t.Run(test.name, func(t *testing.T) { 476 result, err := test.client.Replicate(context.Background(), test.target, test.source, test.options) 477 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" { 478 t.Error(d) 479 } 480 if d := testy.DiffInterface(test.expected, result); d != nil { 481 t.Error(d) 482 } 483 }) 484 } 485 }