...

Source file src/github.com/go-kivik/kivik/v4/couchdb/scheduler_test.go

Documentation: github.com/go-kivik/kivik/v4/couchdb

     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  }
   716  

View as plain text