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 "fmt"
20 "net/http"
21 "testing"
22 "time"
23
24 "gitlab.com/flimzy/testy"
25
26 kivik "github.com/go-kivik/kivik/v4"
27 "github.com/go-kivik/kivik/v4/driver"
28 internal "github.com/go-kivik/kivik/v4/int/errors"
29 "github.com/go-kivik/kivik/v4/int/mock"
30 )
31
32 func TestReplicationError(t *testing.T) {
33 status := 404
34 reason := "not found"
35 err := &replicationError{status: status, reason: reason}
36 if d := internal.StatusErrorDiff(reason, status, err); d != "" {
37 t.Error(d)
38 }
39 }
40
41 func TestStateTime(t *testing.T) {
42 type stTest struct {
43 Name string
44 Input string
45 Error string
46 Expected string
47 }
48 tests := []stTest{
49 {
50 Name: "Blank",
51 Error: "unexpected end of JSON input",
52 Expected: "0001-01-01 00:00:00 +0000",
53 },
54 {
55 Name: "ValidRFC3339",
56 Input: `"2011-02-17T20:22:02+01:00"`,
57 Expected: "2011-02-17 20:22:02 +0100",
58 },
59 {
60 Name: "ValidUnixTimestamp",
61 Input: "1492543959",
62 Expected: "2017-04-18 19:32:39 +0000",
63 },
64 {
65 Name: "invalid timestamp",
66 Input: `"foo"`,
67 Error: `kivik: '"foo"' does not appear to be a valid timestamp`,
68 Expected: "0001-01-01 00:00:00 +0000",
69 },
70 }
71 for _, test := range tests {
72 func(test stTest) {
73 t.Run(test.Name, func(t *testing.T) {
74 var result replicationStateTime
75 err := json.Unmarshal([]byte(test.Input), &result)
76 if !testy.ErrorMatches(test.Error, err) {
77 t.Errorf("Unexpected error: %s", err)
78 }
79 if r := time.Time(result).Format("2006-01-02 15:04:05 -0700"); r != test.Expected {
80 t.Errorf("Result\nExpected: %s\n Actual: %s\n", test.Expected, r)
81 }
82 })
83 }(test)
84 }
85 }
86
87 func TestReplicationErrorUnmarshal(t *testing.T) {
88 tests := []struct {
89 name string
90 input string
91 expected *replicationError
92 err string
93 }{
94 {
95 name: "doc example 1",
96 input: `"db_not_found: could not open http://adm:*****@localhost:5984/missing/"`,
97 expected: &replicationError{
98 status: http.StatusNotFound,
99 reason: "db_not_found: could not open http://adm:*****@localhost:5984/missing/",
100 },
101 },
102 {
103 name: "timeout",
104 input: `"timeout: some timeout occurred"`,
105 expected: &replicationError{
106 status: http.StatusRequestTimeout,
107 reason: "timeout: some timeout occurred",
108 },
109 },
110 {
111 name: "unknown",
112 input: `"unknown error"`,
113 expected: &replicationError{
114 status: http.StatusInternalServerError,
115 reason: "unknown error",
116 },
117 },
118 {
119 name: "invalid JSON",
120 input: `"\C"`,
121 err: "invalid character 'C' in string escape code",
122 },
123 {
124 name: "Unauthorized",
125 input: `"unauthorized: unauthorized to access or create database foo"`,
126 expected: &replicationError{
127 status: http.StatusUnauthorized,
128 reason: "unauthorized: unauthorized to access or create database foo",
129 },
130 },
131 }
132 for _, test := range tests {
133 t.Run(test.name, func(t *testing.T) {
134 repErr := new(replicationError)
135 err := repErr.UnmarshalJSON([]byte(test.input))
136 if !testy.ErrorMatches(test.err, err) {
137 t.Errorf("Unexpected error: %s", err)
138 }
139 if err != nil {
140 return
141 }
142 if d := testy.DiffInterface(test.expected, repErr); d != nil {
143 t.Error(d)
144 }
145 })
146 }
147 }
148
149 func TestReplicate(t *testing.T) {
150 tests := []struct {
151 name string
152 target, source string
153 options kivik.Option
154 client *client
155 status int
156 err string
157 }{
158 {
159 name: "no target",
160 status: http.StatusBadRequest,
161 err: "kivik: targetDSN required",
162 },
163 {
164 name: "no source",
165 target: "foo",
166 status: http.StatusBadRequest,
167 err: "kivik: sourceDSN required",
168 },
169 {
170 name: "invalid options",
171 client: func() *client {
172 client := newTestClient(nil, errors.New("net error"))
173 b := false
174 client.schedulerDetected = &b
175 return client
176 }(),
177 target: "foo", source: "bar",
178 options: kivik.Param("foo", make(chan int)),
179 status: http.StatusBadRequest,
180 err: `^Post "?http://example.com/_replicator"?: json: unsupported type: chan int$`,
181 },
182 {
183 name: "network error",
184 target: "foo", source: "bar",
185 client: func() *client {
186 client := newTestClient(nil, errors.New("net error"))
187 b := false
188 client.schedulerDetected = &b
189 return client
190 }(),
191 status: http.StatusBadGateway,
192 err: `Post "?http://example.com/_replicator"?: net error`,
193 },
194 {
195 name: "1.6.1",
196 target: "foo", source: "bar",
197 client: func() *client {
198 client := newCustomClient(func(*http.Request) (*http.Response, error) {
199 return &http.Response{
200 StatusCode: 201,
201 Header: http.Header{
202 "Server": {"CouchDB/1.6.1 (Erlang OTP/17)"},
203 "Location": {"http://localhost:5984/_replicator/4ab99e4d7d4b5a6c5a6df0d0ed01221d"},
204 "ETag": {`"1-290800e5803500237075f9b08226cffd"`},
205 "Date": {"Mon, 30 Oct 2017 20:03:34 GMT"},
206 "Content-Type": {"application/json"},
207 "Content-Length": {"95"},
208 "Cache-Control": {"must-revalidate"},
209 },
210 Body: Body(`{"ok":true,"id":"4ab99e4d7d4b5a6c5a6df0d0ed01221d","rev":"1-290800e5803500237075f9b08226cffd"}`),
211 }, nil
212 })
213 b := false
214 client.schedulerDetected = &b
215 return client
216 }(),
217 },
218 {
219 name: "2.1.0",
220 target: "foo", source: "bar",
221 client: func() *client {
222 client := newCustomClient(func(req *http.Request) (*http.Response, error) {
223 switch req.URL.Path {
224 case "/_replicator":
225 return &http.Response{
226 StatusCode: 201,
227 Header: http.Header{
228 "Server": {"CouchDB/2.1.0 (Erlang OTP/17)"},
229 "Location": {"http://localhost:6002/_replicator/56d257bd2125c8f15870b3ddd2078b23"},
230 "Date": {"Sat, 18 Nov 2017 11:13:58 GMT"},
231 "Content-Type": {"application/json"},
232 "Content-Length": {"95"},
233 "Cache-Control": {"must-revalidate"},
234 "X-CouchDB-Body-Time": {"0"},
235 "X-Couch-Request-ID": {"a97b982715"},
236 },
237 Body: Body(`{"ok":true,"id":"56d257bd2125c8f15870b3ddd2078b23","rev":"1-290800e5803500237075f9b08226cffd"}`),
238 }, nil
239 case "/_scheduler/docs/_replicator/56d257bd2125c8f15870b3ddd2078b23":
240 return &http.Response{
241 StatusCode: 200,
242 Header: http.Header{
243 "Server": {"CouchDB/2.1.0 (Erlang OTP/17)"},
244 "Date": {"Sat, 18 Nov 2017 11:18:33 GMT"},
245 "Content-Type": {"application/json"},
246 "Content-Length": {"427"},
247 "Cache-Control": {"must-revalidate"},
248 },
249 Body: Body(fmt.Sprintf(`{"database":"_replicator","doc_id":"56d257bd2125c8f15870b3ddd2078b23","id":null,"source":"foo","target":"bar","state":"failed","error_count":1,"info":"Replication %s specified by document %s already started, triggered by document %s from db %s","start_time":"2017-11-18T11:13:58Z","last_updated":"2017-11-18T11:13:58Z"}`, "`c636d089fbdc3a9a937a466acf8f42c3`", "`56d257bd2125c8f15870b3ddd2078b23`", "`56d257bd2125c8f15870b3ddd2074759`", "`_replicator`")),
250 }, nil
251 default:
252 return nil, fmt.Errorf("Unexpected path: %s", req.URL.Path)
253 }
254 })
255 b := true
256 client.schedulerDetected = &b
257 return client
258 }(),
259 },
260 {
261 name: "scheduler detection error",
262 target: "foo", source: "bar",
263 client: newTestClient(nil, errors.New("sched err")),
264 status: http.StatusBadGateway,
265 err: `Head "?http://example.com/_scheduler/jobs"?: sched err`,
266 },
267 }
268 for _, test := range tests {
269 t.Run(test.name, func(t *testing.T) {
270 opts := test.options
271 if opts == nil {
272 opts = mock.NilOption
273 }
274 resp, err := test.client.Replicate(context.Background(), test.target, test.source, opts)
275 if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" {
276 t.Error(d)
277 }
278 if err != nil {
279 return
280 }
281 if _, ok := resp.(*replication); ok {
282 return
283 }
284 if _, ok := resp.(*schedulerReplication); ok {
285 return
286 }
287 t.Errorf("Unexpected response type: %T", resp)
288 })
289 }
290 }
291
292 func TestLegacyGetReplications(t *testing.T) {
293 tests := []struct {
294 name string
295 options map[string]interface{}
296 client *client
297 expected []*replication
298 status int
299 err string
300 }{
301 {
302 name: "invalid options",
303 options: map[string]interface{}{"foo": make(chan int)},
304 status: http.StatusBadRequest,
305 err: "kivik: invalid type chan int for options",
306 },
307 {
308 name: "network error",
309 client: newTestClient(nil, errors.New("net error")),
310 status: http.StatusBadGateway,
311 err: `^Get "?http://example.com/_replicator/_all_docs\?include_docs=true"?: net error$`,
312 },
313 {
314 name: "success, 1.6.1",
315 client: newTestClient(&http.Response{
316 StatusCode: 200,
317 Header: http.Header{
318 "Transfer-Encoding": {"chunked"},
319 "Server": {"CouchDB/1.6.1 (Erlang OTP/17)"},
320 "ETag": {`"97AGDUD7SV24L2PLSG3XG4MOY"`},
321 "Date": {"Mon, 30 Oct 2017 20:31:31 GMT"},
322 "Content-Type": {"application/json"},
323 "Cache-Control": {"must-revalidate"},
324 },
325 Body: Body(`{"total_rows":2,"offset":0,"rows":[
326 {"id":"4ab99e4d7d4b5a6c5a6df0d0ed01221d","key":"4ab99e4d7d4b5a6c5a6df0d0ed01221d","value":{"rev":"2-6419706e969050d8000efad07259de4f"},"doc":{"_id":"4ab99e4d7d4b5a6c5a6df0d0ed01221d","_rev":"2-6419706e969050d8000efad07259de4f","source":"foo","target":"bar","owner":"admin","_replication_state":"error","_replication_state_time":"2017-10-30T20:03:34+00:00","_replication_state_reason":"unauthorized: unauthorized to access or create database foo","_replication_id":"548507fbb9fb9fcd8a3b27050b9ba5bf"}},
327 {"id":"_design/_replicator","key":"_design/_replicator","value":{"rev":"1-5bfa2c99eefe2b2eb4962db50aa3cfd4"},"doc":{"_id":"_design/_replicator","_rev":"1-5bfa2c99eefe2b2eb4962db50aa3cfd4","language":"javascript","validate_doc_update":"..."}}
328 ]}`),
329 }, nil),
330 expected: []*replication{
331 {
332 docID: "4ab99e4d7d4b5a6c5a6df0d0ed01221d",
333 replicationID: "548507fbb9fb9fcd8a3b27050b9ba5bf",
334 source: "foo",
335 target: "bar",
336 endTime: parseTime(t, "2017-10-30T20:03:34+00:00"),
337 state: "error",
338 err: &replicationError{status: 401, reason: "unauthorized: unauthorized to access or create database foo"},
339 },
340 },
341 },
342 }
343 for _, test := range tests {
344 t.Run(test.name, func(t *testing.T) {
345 reps, err := test.client.legacyGetReplications(context.Background(), test.options)
346 if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" {
347 t.Error(d)
348 }
349 if err != nil {
350 return
351 }
352 result := make([]*replication, len(reps))
353 for i, rep := range reps {
354 result[i] = rep.(*replication)
355 result[i].db = nil
356 }
357 if d := testy.DiffInterface(test.expected, result); d != nil {
358 t.Error(d)
359 }
360 })
361 }
362 }
363
364 func TestGetReplications(t *testing.T) {
365 tests := []struct {
366 name string
367 client *client
368 status int
369 err string
370 }{
371 {
372 name: "network error",
373 client: newTestClient(nil, errors.New("net error")),
374 status: http.StatusBadGateway,
375 err: `Head "?http://example.com/_scheduler/jobs"?: net error`,
376 },
377 {
378 name: "no scheduler",
379 client: func() *client {
380 client := newCustomClient(func(req *http.Request) (*http.Response, error) {
381 if req.URL.Path != "/_replicator/_all_docs" {
382 return nil, fmt.Errorf("unexpected request path: %s", req.URL.Path)
383 }
384 return &http.Response{
385 StatusCode: 404,
386 Request: &http.Request{Method: "GET"},
387 Body: Body(""),
388 }, nil
389 })
390 b := false
391 client.schedulerDetected = &b
392 return client
393 }(),
394 status: http.StatusNotFound,
395 err: "Not Found",
396 },
397 {
398 name: "scheduler detected",
399 client: func() *client {
400 client := newCustomClient(func(req *http.Request) (*http.Response, error) {
401 if req.URL.Path != "/_scheduler/docs" {
402 return nil, fmt.Errorf("unexpected request path: %s", req.URL.Path)
403 }
404 return &http.Response{
405 StatusCode: 404,
406 Request: &http.Request{Method: "GET"},
407 Body: Body(""),
408 }, nil
409 })
410 b := true
411 client.schedulerDetected = &b
412 return client
413 }(),
414 status: http.StatusNotFound,
415 err: "Not Found",
416 },
417 }
418 for _, test := range tests {
419 t.Run(test.name, func(t *testing.T) {
420 _, err := test.client.GetReplications(context.Background(), mock.NilOption)
421 if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" {
422 t.Error(d)
423 }
424 })
425 }
426 }
427
428 func TestReplicationUpdate(t *testing.T) {
429 tests := []struct {
430 name string
431 rep *replication
432 expected *driver.ReplicationInfo
433 status int
434 err string
435 }{
436 {
437 name: "network error",
438 rep: &replication{
439 docID: "4ab99e4d7d4b5a6c5a6df0d0ed01221d",
440 db: newTestDB(nil, errors.New("net error")),
441 },
442 status: http.StatusBadGateway,
443 err: `Get "?http://example.com/testdb/4ab99e4d7d4b5a6c5a6df0d0ed01221d"?: net error`,
444 },
445 {
446 name: "no active reps 1.6.1",
447 rep: &replication{
448 docID: "4ab99e4d7d4b5a6c5a6df0d0ed01221d",
449 db: newCustomDB(func(req *http.Request) (*http.Response, error) {
450 switch req.URL.Path {
451 case "/testdb/4ab99e4d7d4b5a6c5a6df0d0ed01221d":
452 return &http.Response{
453 StatusCode: 200,
454 Header: http.Header{
455 "Server": {"CouchDB/1.6.1 (Erlang OTP/17)"},
456 "ETag": {`"2-6419706e969050d8000efad07259de4f"`},
457 "Date": {"Mon, 30 Oct 2017 20:57:15 GMT"},
458 "Content-Type": {"application/json"},
459 "Content-Length": {"359"},
460 "Cache-Control": {"must-revalidate"},
461 },
462 Body: Body(`{"_id":"4ab99e4d7d4b5a6c5a6df0d0ed01221d","_rev":"2-6419706e969050d8000efad07259de4f","source":"foo","target":"bar","owner":"admin","_replication_state":"error","_replication_state_time":"2017-10-30T20:03:34+00:00","_replication_state_reason":"unauthorized: unauthorized to access or create database foo","_replication_id":"548507fbb9fb9fcd8a3b27050b9ba5bf"}`),
463 }, nil
464 case "/_active_tasks":
465 return &http.Response{
466 StatusCode: 200,
467 Header: http.Header{
468 "Server": {"CouchDB/1.6.1 (Erlang OTP/17)"},
469 "Date": {"Mon, 30 Oct 2017 21:06:40 GMT"},
470 "Content-Type": {"application/json"},
471 "Content-Length": {"3"},
472 "Cache-Control": {"must-revalidate"},
473 },
474 Body: Body(`[]`),
475 }, nil
476 default:
477 panic("Unknown req path: " + req.URL.Path)
478 }
479 }),
480 },
481 expected: &driver.ReplicationInfo{},
482 },
483 }
484 for _, test := range tests {
485 t.Run(test.name, func(t *testing.T) {
486 result := new(driver.ReplicationInfo)
487 err := test.rep.Update(context.Background(), result)
488 if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" {
489 t.Error(d)
490 }
491 if err != nil {
492 return
493 }
494 if d := testy.DiffInterface(test.expected, result); d != nil {
495 t.Error(d)
496 }
497 })
498 }
499 }
500
501 func TestReplicationDelete(t *testing.T) {
502 tests := []struct {
503 name string
504 rep *replication
505 status int
506 err string
507 }{
508 {
509 name: "network error",
510 rep: &replication{
511 docID: "foo",
512 db: newTestDB(nil, errors.New("net error")),
513 },
514 status: http.StatusBadGateway,
515 err: `Head "?http://example.com/testdb/foo"?: net error`,
516 },
517 {
518 name: "delete network error",
519 rep: &replication{
520 docID: "4ab99e4d7d4b5a6c5a6df0d0ed01221d",
521 db: newCustomDB(func(req *http.Request) (*http.Response, error) {
522 if req.Method == "HEAD" {
523 return &http.Response{
524 StatusCode: 200,
525 Header: http.Header{
526 "Server": {"CouchDB/1.6.1 (Erlang OTP/17)"},
527 "ETag": {`"2-6419706e969050d8000efad07259de4f"`},
528 "Date": {"Mon, 30 Oct 2017 21:14:46 GMT"},
529 "Content-Type": {"application/json"},
530 "Content-Length": {"359"},
531 "Cache-Control": {"must-revalidate"},
532 },
533 Body: Body(""),
534 }, nil
535 }
536 return nil, errors.New("delete error")
537 }),
538 },
539 status: http.StatusBadGateway,
540 err: `^(Delete "?http://example.com/testdb/4ab99e4d7d4b5a6c5a6df0d0ed01221d\?rev=2-6419706e969050d8000efad07259de4f"?: )?delete error`,
541 },
542 {
543 name: "success, 1.6.1",
544 rep: &replication{
545 docID: "4ab99e4d7d4b5a6c5a6df0d0ed01221d",
546 db: newCustomDB(func(req *http.Request) (*http.Response, error) {
547 if req.Method == "HEAD" {
548 return &http.Response{
549 StatusCode: 200,
550 Header: http.Header{
551 "Server": {"CouchDB/1.6.1 (Erlang OTP/17)"},
552 "ETag": {`"2-6419706e969050d8000efad07259de4f"`},
553 "Date": {"Mon, 30 Oct 2017 21:14:46 GMT"},
554 "Content-Type": {"application/json"},
555 "Content-Length": {"359"},
556 "Cache-Control": {"must-revalidate"},
557 },
558 Body: Body(""),
559 }, nil
560 }
561 return &http.Response{
562 StatusCode: 200,
563 Header: http.Header{
564 "Server": {"CouchDB/1.6.1 (Erlang OTP/17)"},
565 "ETag": {`"3-2ae9fa6e1f8982a08c4a42b3943e67c5"`},
566 "Date": {"Mon, 30 Oct 2017 21:29:43 GMT"},
567 "Content-Type": {"application/json"},
568 "Content-Length": {"95"},
569 "Cache-Control": {"must-revalidate"},
570 },
571 Body: Body(`{"ok":true,"id":"4ab99e4d7d4b5a6c5a6df0d0ed01221d","rev":"3-2ae9fa6e1f8982a08c4a42b3943e67c5"}`),
572 }, nil
573 }),
574 },
575 },
576 }
577 for _, test := range tests {
578 t.Run(test.name, func(t *testing.T) {
579 err := test.rep.Delete(context.Background())
580 if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" {
581 t.Error(d)
582 }
583 })
584 }
585 }
586
587 func TestUpdateActiveTasks(t *testing.T) {
588 tests := []struct {
589 name string
590 rep *replication
591 expected *activeTask
592 status int
593 err string
594 }{
595 {
596 name: "network error",
597 rep: &replication{
598 db: newTestDB(nil, errors.New("net error")),
599 },
600 status: http.StatusBadGateway,
601 err: `Get "?http://example.com/_active_tasks"?: net error`,
602 },
603 {
604 name: "error response",
605 rep: &replication{
606 db: newTestDB(&http.Response{
607 StatusCode: 500,
608 Request: &http.Request{Method: "GET"},
609 Body: Body(""),
610 }, nil),
611 },
612 status: http.StatusInternalServerError,
613 err: "Internal Server Error",
614 },
615 {
616 name: "invalid json response",
617 rep: &replication{
618 db: newTestDB(&http.Response{
619 StatusCode: 200,
620 Body: Body("invalid json"),
621 }, nil),
622 },
623 status: http.StatusBadGateway,
624 err: "invalid character 'i' looking for beginning of value",
625 },
626 {
627 name: "rep not found",
628 rep: &replication{
629 replicationID: "foo",
630 db: newTestDB(&http.Response{
631 StatusCode: 200,
632 Body: Body("[]"),
633 }, nil),
634 },
635 status: http.StatusNotFound,
636 err: "task not found",
637 },
638 {
639 name: "rep found",
640 rep: &replication{
641 replicationID: "foo",
642 db: newTestDB(&http.Response{
643 StatusCode: 200,
644 Body: Body(`[
645 {"type":"foo"},
646 {"type":"replication","replication_id":"unf"},
647 {"type":"replication","replication_id":"foo","docs_written":1}
648 ]`),
649 }, nil),
650 },
651 expected: &activeTask{
652 Type: "replication",
653 ReplicationID: "foo",
654 DocsWritten: 1,
655 },
656 },
657 }
658 for _, test := range tests {
659 t.Run(test.name, func(t *testing.T) {
660 result, err := test.rep.updateActiveTasks(context.Background())
661 if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" {
662 t.Error(d)
663 }
664 if d := testy.DiffInterface(test.expected, result); d != nil {
665 t.Error(d)
666 }
667 })
668 }
669 }
670
671 func TestSetFromReplicatorDoc(t *testing.T) {
672 tests := []struct {
673 name string
674 rep *replication
675 doc *replicatorDoc
676 expected *replication
677 }{
678 {
679 name: "started",
680 rep: &replication{},
681 doc: &replicatorDoc{
682 State: string(kivik.ReplicationStarted),
683 StateTime: replicationStateTime(parseTime(t, "2017-01-01T01:01:01Z")),
684 },
685 expected: &replication{
686 state: "triggered",
687 startTime: parseTime(t, "2017-01-01T01:01:01Z"),
688 },
689 },
690 {
691 name: "errored",
692 rep: &replication{},
693 doc: &replicatorDoc{
694 State: string(kivik.ReplicationError),
695 StateTime: replicationStateTime(parseTime(t, "2017-01-01T01:01:01Z")),
696 },
697 expected: &replication{
698 state: "error",
699 endTime: parseTime(t, "2017-01-01T01:01:01Z"),
700 },
701 },
702 {
703 name: "completed",
704 rep: &replication{},
705 doc: &replicatorDoc{
706 State: string(kivik.ReplicationComplete),
707 StateTime: replicationStateTime(parseTime(t, "2017-01-01T01:01:01Z")),
708 },
709 expected: &replication{
710 state: "completed",
711 endTime: parseTime(t, "2017-01-01T01:01:01Z"),
712 },
713 },
714 {
715 name: "set fields",
716 rep: &replication{},
717 doc: &replicatorDoc{
718 Source: "foo",
719 Target: "bar",
720 ReplicationID: "oink",
721 Error: &replicationError{status: 500, reason: "unf"},
722 },
723 expected: &replication{
724 source: "foo",
725 target: "bar",
726 replicationID: "oink",
727 err: &replicationError{status: 500, reason: "unf"},
728 },
729 },
730 {
731 name: "validate that existing fields aren't re-set",
732 rep: &replication{source: "a", target: "b", replicationID: "c", err: errors.New("foo")},
733 doc: &replicatorDoc{
734 Source: "foo",
735 Target: "bar",
736 ReplicationID: "oink",
737 },
738 expected: &replication{
739 source: "a",
740 target: "b",
741 replicationID: "c",
742 },
743 },
744 }
745 for _, test := range tests {
746 t.Run(test.name, func(t *testing.T) {
747 test.rep.setFromReplicatorDoc(test.doc)
748 if d := testy.DiffInterface(test.expected, test.rep); d != nil {
749 t.Error(d)
750 }
751 })
752 }
753 }
754
755 func TestReplicationGetters(t *testing.T) {
756 const (
757 repID = "a"
758 source = "b"
759 target = "c"
760 state = "d"
761 wantErr = "e"
762 )
763 start := parseTime(t, "2017-01-01T01:01:01Z")
764 end := parseTime(t, "2017-01-01T01:01:02Z")
765 rep := &replication{
766 replicationID: repID,
767 source: source,
768 target: target,
769 startTime: start,
770 endTime: end,
771 state: state,
772 err: errors.New(wantErr),
773 }
774 if result := rep.ReplicationID(); result != repID {
775 t.Errorf("Unexpected replication ID: %s", result)
776 }
777 if result := rep.Source(); result != source {
778 t.Errorf("Unexpected source: %s", result)
779 }
780 if result := rep.Target(); result != target {
781 t.Errorf("Unexpected target: %s", result)
782 }
783 if result := rep.StartTime(); !result.Equal(start) {
784 t.Errorf("Unexpected start time: %v", result)
785 }
786 if result := rep.EndTime(); !result.Equal(end) {
787 t.Errorf("Unexpected end time: %v", result)
788 }
789 if result := rep.State(); result != state {
790 t.Errorf("Unexpected state: %s", result)
791 }
792 if err := rep.Err(); !testy.ErrorMatches(wantErr, err) {
793 t.Errorf("Unexpected error: %s", err)
794 }
795 }
796
View as plain text