github.com/go-kivik/kivik/v4@v4.3.2/couchdb/scheduler_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 couchdb 14 15 import ( 16 "context" 17 "encoding/json" 18 "errors" 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 ) 28 29 func TestSRUpdate(t *testing.T) { 30 tests := []struct { 31 name string 32 rep *schedulerReplication 33 status int 34 err string 35 expected *driver.ReplicationInfo 36 }{ 37 { 38 name: "network error", 39 rep: &schedulerReplication{ 40 database: "_replicator", 41 docID: "foo", 42 db: newTestDB(nil, errors.New("net error")), 43 }, 44 status: http.StatusBadGateway, 45 err: `Get "?http://example.com/_scheduler/docs/_replicator/foo"?: net error`, 46 }, 47 { 48 name: "real example", 49 rep: &schedulerReplication{ 50 database: "_replicator", 51 docID: "foo2", 52 db: newTestDB(&http.Response{ 53 StatusCode: 200, 54 Header: http.Header{ 55 "Server": {"CouchDB/2.1.0 (Erlang OTP/17)"}, 56 "Date": {"Thu, 09 Nov 2017 15:23:20 GMT"}, 57 "Content-Type": {"application/json"}, 58 "Content-Length": {"687"}, 59 "Cache-Control": {"must-revalidate"}, 60 }, 61 Body: Body(`{"database":"_replicator","doc_id":"foo2","id":null,"source":"http://localhost:5984/foo/","target":"http://localhost:5984/bar/","state":"completed","error_count":0,"info":{"revisions_checked":23,"missing_revisions_found":23,"docs_read":23,"docs_written":23,"changes_pending":null,"doc_write_failures":0,"checkpointed_source_seq":"27-g1AAAAIbeJyV0EsOgjAQBuAGMOLCM-gRSoUKK7mJ9kWQYLtQ13oTvYneRG-CfZAYSUjqZppM5v_SmRYAENchB3OppOKilKpWx1Or2wEBdNF1XVOHJD7oxnTFKMOcDYdH4nSpK930wsQKAmYIVdBXKI2w_RGQyFJYFb7CzgiXXgDuDywXKUk4mJ0lF9VeCj6SlpGu4KofDdyMEFoBk3QtMt87OOXulIdRAqvABHPO0F_K0ymv7zYU5UVe-W_zdoK9R2QFxhjBUAwzzQch86VT"},"start_time":"2017-11-01T21:05:03Z","last_updated":"2017-11-01T21:05:06Z"}`), 62 }, nil), 63 }, 64 expected: &driver.ReplicationInfo{ 65 DocsRead: 23, 66 DocsWritten: 23, 67 }, 68 }, 69 } 70 for _, test := range tests { 71 t.Run(test.name, func(t *testing.T) { 72 var result driver.ReplicationInfo 73 err := test.rep.Update(context.Background(), &result) 74 if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" { 75 t.Error(d) 76 } 77 if err != nil { 78 return 79 } 80 if d := testy.DiffInterface(test.expected, &result); d != nil { 81 t.Error(d) 82 } 83 }) 84 } 85 } 86 87 func TestRepInfoUnmarshalJSON(t *testing.T) { 88 tests := []struct { 89 name string 90 input string 91 expected *repInfo 92 err string 93 }{ 94 { 95 name: "null", 96 input: "null", 97 expected: &repInfo{}, 98 }, 99 { 100 name: "error string", 101 input: `"db_not_found: could not open foo"`, 102 expected: &repInfo{ 103 Error: &replicationError{ 104 status: 404, 105 reason: "db_not_found: could not open foo", 106 }, 107 }, 108 }, 109 { 110 name: "stats", 111 input: `{"revisions_checked":23,"missing_revisions_found":23,"docs_read":23,"docs_written":23,"changes_pending":null,"doc_write_failures":0,"checkpointed_source_seq":"27-g1AAAAIbeJyV0EsOgjAQBuAGMOLCM-gRSoUKK7mJ9kWQYLtQ13oTvYneRG-CfZAYSUjqZppM5v_SmRYAENchB3OppOKilKpWx1Or2wEBdNF1XVOHJD7oxnTFKMOcDYdH4nSpK930wsQKAmYIVdBXKI2w_RGQyFJYFb7CzgiXXgDuDywXKUk4mJ0lF9VeCj6SlpGu4KofDdyMEFoBk3QtMt87OOXulIdRAqvABHPO0F_K0ymv7zYU5UVe-W_zdoK9R2QFxhjBUAwzzQch86VT"}`, 112 expected: &repInfo{ 113 DocsRead: 23, 114 DocsWritten: 23, 115 DocWriteFailures: 0, 116 }, 117 }, 118 { 119 name: "invalid stats object", 120 input: `{"docs_written":"chicken"}`, 121 err: "^json: cannot unmarshal string into Go ", 122 }, 123 { 124 name: "CouchDB 3.0 error", 125 input: `{"error":"unauthorized: unauthorized to access or create database http://localhost:5984/foo/"}`, 126 expected: &repInfo{ 127 Error: &replicationError{ 128 status: http.StatusUnauthorized, 129 reason: "unauthorized: unauthorized to access or create database http://localhost:5984/foo/", 130 }, 131 }, 132 }, 133 { 134 name: "CouchDB 3.0 error bad JSON", 135 input: `{"error":123}`, 136 err: "cannot unmarshal number into Go struct field .error of type string", 137 }, 138 } 139 for _, test := range tests { 140 t.Run(test.name, func(t *testing.T) { 141 result := new(repInfo) 142 err := json.Unmarshal([]byte(test.input), result) 143 if !testy.ErrorMatchesRE(test.err, err) { 144 t.Errorf("Unexpected error: %s", err) 145 } 146 if err != nil { 147 return 148 } 149 if d := testy.DiffInterface(test.expected, result); d != nil { 150 t.Error(d) 151 } 152 }) 153 } 154 } 155 156 func TestGetReplicationsFromScheduler(t *testing.T) { 157 tests := []struct { 158 name string 159 options map[string]interface{} 160 client *client 161 expected []*schedulerReplication 162 status int 163 err string 164 }{ 165 { 166 name: "network error", 167 client: newTestClient(nil, errors.New("net error")), 168 status: http.StatusBadGateway, 169 err: `^Get "?http://example\.com/_scheduler/docs"?: net error$`, 170 }, 171 { 172 name: "invalid options", 173 options: map[string]interface{}{"foo": make(chan int)}, 174 status: http.StatusBadRequest, 175 err: "kivik: invalid type chan int for options", 176 }, 177 { 178 name: "valid response, 2.1.0", 179 client: newTestClient(&http.Response{ 180 StatusCode: 200, 181 Header: http.Header{ 182 "Server": {"CouchDB/2.1.0 (Erlang OTP/17)"}, 183 "Date": {"Wed, 08 Nov 2017 18:04:11 GMT"}, 184 "Content-Type": {"application/json"}, 185 "Transfer-Encoding": {"chunked"}, 186 "Cache-Control": {"must-revalidate"}, 187 "X-CouchDB-Body-Time": {"0"}, 188 "X-Couch-Request-ID": {"6d47891c37"}, 189 }, 190 Body: Body(`{"total_rows":2,"offset":0,"docs":[ 191 {"database":"_replicator","doc_id":"foo","id":"81cc3633ee8de1332e412ef9052c7b6f","node":"nonode@nohost","source":"foo","target":"bar","state":"crashing","info":"db_not_found: could not open foo","error_count":6,"last_updated":"2017-11-08T18:07:38Z","start_time":"2017-11-08T17:51:52Z","proxy":null}, 192 {"database":"_replicator","doc_id":"foo2","id":null,"source":"http://admin:*****@localhost:5984/foo/","target":"http://admin:*****@localhost:5984/bar/","state":"completed","error_count":0,"info":{"revisions_checked":23,"missing_revisions_found":23,"docs_read":23,"docs_written":23,"changes_pending":null,"doc_write_failures":0,"checkpointed_source_seq":"27-g1AAAAIbeJyV0EsOgjAQBuAGMOLCM-gRSoUKK7mJ9kWQYLtQ13oTvYneRG-CfZAYSUjqZppM5v_SmRYAENchB3OppOKilKpWx1Or2wEBdNF1XVOHJD7oxnTFKMOcDYdH4nSpK930wsQKAmYIVdBXKI2w_RGQyFJYFb7CzgiXXgDuDywXKUk4mJ0lF9VeCj6SlpGu4KofDdyMEFoBk3QtMt87OOXulIdRAqvABHPO0F_K0ymv7zYU5UVe-W_zdoK9R2QFxhjBUAwzzQch86VT"},"start_time":"2017-11-01T21:05:03Z","last_updated":"2017-11-01T21:05:06Z"} 193 ]}`), 194 }, nil), 195 expected: []*schedulerReplication{ 196 { 197 database: "_replicator", 198 docID: "foo", 199 replicationID: "81cc3633ee8de1332e412ef9052c7b6f", 200 state: "crashing", 201 source: "foo", 202 target: "bar", 203 startTime: parseTime(t, "2017-11-08T17:51:52Z"), 204 lastUpdated: parseTime(t, "2017-11-08T18:07:38Z"), 205 info: repInfo{ 206 Error: &replicationError{ 207 status: 404, 208 reason: "db_not_found: could not open foo", 209 }, 210 }, 211 }, 212 { 213 database: "_replicator", 214 docID: "foo2", 215 source: "http://admin:*****@localhost:5984/foo/", 216 target: "http://admin:*****@localhost:5984/bar/", 217 state: "completed", 218 startTime: parseTime(t, "2017-11-01T21:05:03Z"), 219 lastUpdated: parseTime(t, "2017-11-01T21:05:06Z"), 220 info: repInfo{ 221 DocsRead: 23, 222 DocsWritten: 23, 223 }, 224 }, 225 }, 226 }, 227 } 228 for _, test := range tests { 229 t.Run(test.name, func(t *testing.T) { 230 reps, err := test.client.getReplicationsFromScheduler(context.Background(), test.options) 231 if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" { 232 t.Error(d) 233 } 234 if err != nil { 235 return 236 } 237 result := make([]*schedulerReplication, len(reps)) 238 for i, rep := range reps { 239 result[i] = rep.(*schedulerReplication) 240 result[i].db = nil 241 } 242 if d := testy.DiffInterface(test.expected, result); d != nil { 243 t.Error(d) 244 } 245 }) 246 } 247 } 248 249 func TestSchedulerReplicationDelete(t *testing.T) { 250 tests := []struct { 251 name string 252 rep *schedulerReplication 253 status int 254 err string 255 }{ 256 { 257 name: "HEAD network error", 258 rep: &schedulerReplication{ 259 docID: "foo", 260 db: newTestDB(nil, errors.New("net error")), 261 }, 262 status: http.StatusBadGateway, 263 err: `Head "?http://example.com/testdb/foo"?: net error`, 264 }, 265 { 266 name: "DELETE network error", 267 rep: &schedulerReplication{ 268 docID: "foo", 269 db: newCustomDB(func(r *http.Request) (*http.Response, error) { 270 if r.Method == http.MethodHead { 271 return &http.Response{ 272 StatusCode: 200, 273 Header: http.Header{ 274 "ETag": {`"9-b38287cbde7623a328843f830f418c92"`}, 275 }, 276 Body: Body(""), 277 }, nil 278 } 279 return nil, errors.New("net error") 280 }), 281 }, 282 status: http.StatusBadGateway, 283 err: `(Delete "?http://example.com/testdb/foo?rev=9-b38287cbde7623a328843f830f418c92"?: )?net error`, 284 }, 285 { 286 name: "success", 287 rep: &schedulerReplication{ 288 docID: "foo", 289 db: newCustomDB(func(r *http.Request) (*http.Response, error) { 290 if r.Method == http.MethodHead { 291 return &http.Response{ 292 StatusCode: 200, 293 Header: http.Header{ 294 "ETag": {`"9-b38287cbde7623a328843f830f418c92"`}, 295 }, 296 Body: Body(""), 297 }, nil 298 } 299 expected := "http://example.com/testdb/foo?rev=9-b38287cbde7623a328843f830f418c92" 300 if r.URL.String() != expected { 301 panic("Unexpected url: " + r.URL.String()) 302 } 303 return &http.Response{ 304 StatusCode: 200, 305 Header: http.Header{ 306 "X-CouchDB-Body-Time": {"0"}, 307 "X-Couch-Request-ID": {"03b7ff8976"}, 308 "Server": {"CouchDB/2.1.0 (Erlang OTP/17)"}, 309 "ETag": {`"10-a4f1941d02a2bcc6b4fe8a463dbea746"`}, 310 "Date": {"Sat, 11 Nov 2017 16:28:26 GMT"}, 311 "Content-Type": {"application/json"}, 312 "Content-Length": {"67"}, 313 "Cache-Control": {"must-revalidate"}, 314 }, 315 Body: Body(`{"ok":true,"id":"foo","rev":"10-a4f1941d02a2bcc6b4fe8a463dbea746"}`), 316 }, nil 317 }), 318 }, 319 }, 320 } 321 for _, test := range tests { 322 t.Run(test.name, func(t *testing.T) { 323 err := test.rep.Delete(context.Background()) 324 if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" { 325 t.Error(d) 326 } 327 }) 328 } 329 } 330 331 func TestSchedulerReplicationGetters(t *testing.T) { 332 const ( 333 repID = "a" 334 source = "b" 335 target = "c" 336 state = "completed" 337 wantErr = "e" 338 ) 339 start := parseTime(t, "2017-01-01T01:01:01Z") 340 end := parseTime(t, "2017-01-01T01:01:02Z") 341 rep := &schedulerReplication{ 342 replicationID: repID, 343 source: source, 344 target: target, 345 startTime: start, 346 lastUpdated: end, 347 state: state, 348 info: repInfo{Error: errors.New(wantErr)}, 349 } 350 if result := rep.ReplicationID(); result != repID { 351 t.Errorf("Unexpected replication ID: %s", result) 352 } 353 if result := rep.Source(); result != source { 354 t.Errorf("Unexpected source: %s", result) 355 } 356 if result := rep.Target(); result != target { 357 t.Errorf("Unexpected target: %s", result) 358 } 359 if result := rep.StartTime(); !result.Equal(start) { 360 t.Errorf("Unexpected start time: %v", result) 361 } 362 if result := rep.EndTime(); !result.Equal(end) { 363 t.Errorf("Unexpected end time: %v", result) 364 } 365 if result := rep.State(); result != state { 366 t.Errorf("Unexpected state: %s", result) 367 } 368 if err := rep.Err(); !testy.ErrorMatches(wantErr, err) { 369 t.Errorf("Unexpected error: %s", err) 370 } 371 } 372 373 func TestSchedulerSupported(t *testing.T) { 374 supported := true 375 unsupported := false 376 tests := []struct { 377 name string 378 client *client 379 expected bool 380 expectedState *bool 381 status int 382 err string 383 }{ 384 { 385 name: "already set true", 386 client: &client{schedulerDetected: func() *bool { b := true; return &b }()}, 387 expected: true, 388 expectedState: &supported, 389 }, 390 { 391 name: "1.6.1, not supported", 392 client: newTestClient(&http.Response{ 393 StatusCode: 400, 394 Header: http.Header{ 395 "Server": {"CouchDB/1.6.1 (Erlang OTP/17)"}, 396 "Date": {"Thu, 16 Nov 2017 17:37:32 GMT"}, 397 "Content-Type": {"application/json"}, 398 "Content-Length": {"201"}, 399 "Cache-Control": {"must-revalidate"}, 400 }, 401 Request: &http.Request{Method: "HEAD"}, 402 Body: Body(""), 403 }, nil), 404 expected: false, 405 expectedState: &unsupported, 406 }, 407 { 408 name: "1.7.1, not supported", 409 client: newTestClient(&http.Response{ 410 StatusCode: 400, 411 Header: http.Header{ 412 "Server": {"CouchDB/1.7.1 (Erlang OTP/17)"}, 413 "Date": {"Thu, 16 Nov 2017 17:37:32 GMT"}, 414 "Content-Type": {"application/json"}, 415 "Content-Length": {"201"}, 416 "Cache-Control": {"must-revalidate"}, 417 }, 418 Request: &http.Request{Method: "HEAD"}, 419 Body: Body(""), 420 }, nil), 421 expected: false, 422 expectedState: &unsupported, 423 }, 424 { 425 name: "2.0.0, not supported", 426 client: newTestClient(&http.Response{ 427 StatusCode: 404, 428 Header: http.Header{ 429 "Cache-Control": {"must-revalidate"}, 430 "Content-Length": {"58"}, 431 "Content-Type": {"application/json"}, 432 "Date": {"Thu, 16 Nov 2017 17:45:34 GMT"}, 433 "Server": {"CouchDB/2.0.0 (Erlang OTP/17)"}, 434 "X-Couch-Request-ID": {"027c1e7ffe"}, 435 "X-CouchDB-Body-Time": {"0"}, 436 }, 437 Request: &http.Request{Method: "HEAD"}, 438 Body: Body(""), 439 }, nil), 440 expected: false, 441 expectedState: &unsupported, 442 }, 443 { 444 name: "2.1.1, supported", 445 client: newTestClient(&http.Response{ 446 StatusCode: 200, 447 Header: http.Header{ 448 "Server": {"CouchDB/2.1.0 (Erlang OTP/17)"}, 449 "Date": {"Thu, 16 Nov 2017 17:47:58 GMT"}, 450 "Content-Type": {"application/json"}, 451 "Content-Length": {"38"}, 452 "Cache-Control": {"must-revalidate"}, 453 }, 454 Request: &http.Request{Method: "HEAD"}, 455 Body: Body(""), 456 }, nil), 457 expected: true, 458 expectedState: &supported, 459 }, 460 { 461 name: "network error", 462 client: newTestClient(nil, errors.New("net error")), 463 expectedState: nil, 464 status: http.StatusBadGateway, 465 err: `Head "?http://example.com/_scheduler/jobs"?: net error`, 466 }, 467 { 468 name: "Unexpected response code", 469 client: newTestClient(&http.Response{ 470 StatusCode: 500, 471 Request: &http.Request{Method: "HEAD"}, 472 Body: Body(""), 473 }, nil), 474 expected: false, 475 expectedState: &unsupported, 476 }, 477 } 478 for _, test := range tests { 479 t.Run(test.name, func(t *testing.T) { 480 result, err := test.client.schedulerSupported(context.Background()) 481 if d := testy.DiffInterface(test.expectedState, test.client.schedulerDetected); d != nil { 482 t.Error(d) 483 } 484 if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" { 485 t.Error(d) 486 } 487 if result != test.expected { 488 t.Errorf("Unexpected result: %v", result) 489 } 490 }) 491 } 492 } 493 494 func TestSRinnerUpdate(t *testing.T) { 495 tests := []struct { 496 name string 497 r *schedulerReplication 498 status int 499 err string 500 expected *schedulerReplication 501 }{ 502 { 503 name: "network error", 504 r: &schedulerReplication{ 505 database: "_replicator", 506 docID: "foo", 507 db: newTestDB(nil, errors.New("net error")), 508 }, 509 status: http.StatusBadGateway, 510 err: `Get "?http://example.com/_scheduler/docs/_replicator/foo"?: net error`, 511 }, 512 { 513 name: "2.1.1 500 bug", 514 r: &schedulerReplication{ 515 database: "_replicator", 516 docID: "foo", 517 db: func() *db { 518 var count int 519 db := newCustomDB(func(*http.Request) (*http.Response, error) { 520 if count == 0 { 521 count++ 522 return &http.Response{ 523 StatusCode: 500, 524 Header: http.Header{ 525 "Content-Length": {"70"}, 526 "Cache-Control": {"must-revalidate"}, 527 "Content-Type": {"application/json"}, 528 "Date": {"Thu, 16 Nov 2017 20:14:25 GMT"}, 529 "Server": {"CouchDB/2.1.0 (Erlang OTP/17)"}, 530 "X-Couch-Request-Id": {"65913f4727"}, 531 "X-Couch-Stack-Hash": {"3194022798"}, 532 "X-Couchdb-Body-Time": {"0"}, 533 }, 534 Request: &http.Request{Method: "GET"}, 535 ContentLength: 70, 536 Body: Body(`{"error":"unknown_error","reason":"function_clause","ref":3194022798}`), 537 }, nil 538 } 539 return &http.Response{ 540 StatusCode: 200, 541 Header: http.Header{ 542 "Server": {"CouchDB/2.1.0 (Erlang OTP/17)"}, 543 "Date": {"Thu, 09 Nov 2017 15:23:20 GMT"}, 544 "Content-Type": {"application/json"}, 545 "Content-Length": {"687"}, 546 "Cache-Control": {"must-revalidate"}, 547 }, 548 Body: Body(`{"database":"_replicator","doc_id":"foo2","id":null,"source":"http://localhost:5984/foo/","target":"http://localhost:5984/bar/","state":"completed","error_count":0,"info":{"revisions_checked":23,"missing_revisions_found":23,"docs_read":23,"docs_written":23,"changes_pending":null,"doc_write_failures":0,"checkpointed_source_seq":"27-g1AAAAIbeJyV0EsOgjAQBuAGMOLCM-gRSoUKK7mJ9kWQYLtQ13oTvYneRG-CfZAYSUjqZppM5v_SmRYAENchB3OppOKilKpWx1Or2wEBdNF1XVOHJD7oxnTFKMOcDYdH4nSpK930wsQKAmYIVdBXKI2w_RGQyFJYFb7CzgiXXgDuDywXKUk4mJ0lF9VeCj6SlpGu4KofDdyMEFoBk3QtMt87OOXulIdRAqvABHPO0F_K0ymv7zYU5UVe-W_zdoK9R2QFxhjBUAwzzQch86VT"},"start_time":"2017-11-01T21:05:03Z","last_updated":"2017-11-01T21:05:06Z"}`), 549 }, nil 550 }) 551 return db 552 }(), 553 }, 554 expected: &schedulerReplication{ 555 docID: "foo2", 556 database: "_replicator", 557 source: "http://localhost:5984/foo/", 558 target: "http://localhost:5984/bar/", 559 startTime: parseTime(t, "2017-11-01T21:05:03Z"), 560 lastUpdated: parseTime(t, "2017-11-01T21:05:06Z"), 561 state: "completed", 562 info: repInfo{ 563 DocsRead: 23, 564 DocsWritten: 23, 565 }, 566 }, 567 }, 568 { 569 name: "db not found", 570 r: &schedulerReplication{ 571 database: "_replicator", 572 docID: "56d257bd2125c8f15870b3ddd202c4ca", 573 db: newTestDB(&http.Response{ 574 StatusCode: 200, 575 Header: http.Header{ 576 "Server": {"CouchDB/2.1.0 (Erlang OTP/17)"}, 577 "Date": {"Fri, 17 Nov 2017 13:05:52 GMT"}, 578 "Content-Type": {"application/json"}, 579 "Content-Length": {"328"}, 580 "Cache-Control": {"must-revalidate"}, 581 }, 582 Body: Body(`{"database":"_replicator","doc_id":"56d257bd2125c8f15870b3ddd202c4ca","id":"c636d089fbdc3a9a937a466acf8f42c3","node":"nonode@nohost","source":"foo","target":"bar","state":"crashing","info":"db_not_found: could not open foo","error_count":7,"last_updated":"2017-11-17T12:59:35Z","start_time":"2017-11-17T12:22:25Z","proxy":null}`), 583 }, nil), 584 }, 585 expected: &schedulerReplication{ 586 docID: "56d257bd2125c8f15870b3ddd202c4ca", 587 database: "_replicator", 588 replicationID: "c636d089fbdc3a9a937a466acf8f42c3", 589 source: "foo", 590 target: "bar", 591 startTime: parseTime(t, "2017-11-17T12:22:25Z"), 592 lastUpdated: parseTime(t, "2017-11-17T12:59:35Z"), 593 state: "crashing", 594 info: repInfo{ 595 Error: &replicationError{ 596 status: 404, 597 reason: "db_not_found: could not open foo", 598 }, 599 }, 600 }, 601 }, 602 { 603 name: "null time", 604 r: &schedulerReplication{ 605 database: "_replicator", 606 docID: "56d257bd2125c8f15870b3ddd202c4ca", 607 db: newTestDB(&http.Response{ 608 StatusCode: 200, 609 Header: http.Header{ 610 "Server": {"CouchDB/2.1.0 (Erlang OTP/17)"}, 611 "Date": {"Fri, 17 Nov 2017 13:05:52 GMT"}, 612 "Content-Type": {"application/json"}, 613 "Content-Length": {"275"}, 614 "Cache-Control": {"must-revalidate"}, 615 }, 616 Body: Body(`{"database":"_replicator","doc_id":"733c70a35768b7a8fc2e178bd9003f1b","id":null,"source":"http://localhost:5984/kivik$replicate_rw_admin$5fbcf68d8d9aaee0/","target":"http://localhost:5984/foo/","state":null,"error_count":0,"info":null,"start_time":null,"last_updated":null}`), 617 }, nil), 618 }, 619 expected: &schedulerReplication{ 620 docID: "733c70a35768b7a8fc2e178bd9003f1b", 621 database: "_replicator", 622 replicationID: "", 623 source: "http://localhost:5984/kivik$replicate_rw_admin$5fbcf68d8d9aaee0/", 624 target: "http://localhost:5984/foo/", 625 startTime: time.Time{}, 626 lastUpdated: time.Time{}, 627 state: "", 628 info: repInfo{}, 629 }, 630 }, 631 } 632 for _, test := range tests { 633 t.Run(test.name, func(t *testing.T) { 634 err := test.r.update(context.Background()) 635 if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" { 636 t.Error(d) 637 } 638 if err != nil { 639 return 640 } 641 test.r.db = nil 642 if d := testy.DiffInterface(test.expected, test.r); d != nil { 643 t.Error(d) 644 } 645 }) 646 } 647 } 648 649 func TestFetchSchedulerReplication(t *testing.T) { 650 tests := []struct { 651 name string 652 client *client 653 docID string 654 expected *schedulerReplication 655 status int 656 err string 657 }{ 658 { 659 name: "network error", 660 client: newTestClient(nil, errors.New("net error")), 661 status: http.StatusBadGateway, 662 err: `Get "?http://example.com/_scheduler/docs/_replicator/"?: net error`, 663 }, 664 { 665 name: "loop wait", 666 client: func() *client { 667 var count int 668 return newCustomClient(func(_ *http.Request) (*http.Response, error) { 669 if count < 2 { 670 count++ 671 return &http.Response{ 672 StatusCode: 200, 673 Body: Body(`{"database":"_replicator","doc_id":"56d257bd2125c8f15870b3ddd2074759","id":null,"state":"initializing","info":null,"error_count":0,"node":"nonode@nohost","last_updated":"2017-11-17T19:56:09Z","start_time":"2017-11-17T19:56:09Z"}`), 674 }, nil 675 } 676 return &http.Response{ 677 StatusCode: 200, 678 Body: Body(`{"database":"_replicator","doc_id":"56d257bd2125c8f15870b3ddd2074759","id":"c636d089fbdc3a9a937a466acf8f42c3","node":"nonode@nohost","source":"foo","target":"bar","state":"crashing","info":"db_not_found: could not open foo","error_count":1,"last_updated":"2017-11-17T19:57:09Z","start_time":"2017-11-17T19:56:09Z","proxy":null}`), 679 }, nil 680 }) 681 }(), 682 expected: &schedulerReplication{ 683 docID: "56d257bd2125c8f15870b3ddd2074759", 684 database: "_replicator", 685 replicationID: "c636d089fbdc3a9a937a466acf8f42c3", 686 source: "foo", 687 target: "bar", 688 startTime: parseTime(t, "2017-11-17T19:56:09Z"), 689 lastUpdated: parseTime(t, "2017-11-17T19:57:09Z"), 690 state: "crashing", 691 info: repInfo{ 692 Error: &replicationError{ 693 status: 404, 694 reason: "db_not_found: could not open foo", 695 }, 696 }, 697 }, 698 }, 699 } 700 for _, test := range tests { 701 t.Run(test.name, func(t *testing.T) { 702 result, err := test.client.fetchSchedulerReplication(context.Background(), test.docID) 703 if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" { 704 t.Error(d) 705 } 706 if err != nil { 707 return 708 } 709 result.db = nil 710 if d := testy.DiffInterface(test.expected, result); d != nil { 711 t.Error(d) 712 } 713 }) 714 } 715 }