1
2
3
4
5
6
7
8
9
10
11
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 }
716
View as plain text