...

Source file src/github.com/go-kivik/kivik/v4/couchdb/cluster_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  	"io"
    20  	"net/http"
    21  	"strings"
    22  	"testing"
    23  
    24  	"gitlab.com/flimzy/testy"
    25  
    26  	"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  const optionEnsureDBsExist = "ensure_dbs_exist"
    33  
    34  func TestClusterStatus(t *testing.T) {
    35  	type tst struct {
    36  		client   *client
    37  		options  kivik.Option
    38  		expected string
    39  		status   int
    40  		err      string
    41  	}
    42  	tests := testy.NewTable()
    43  	tests.Add("network error", tst{
    44  		client: newTestClient(nil, errors.New("network error")),
    45  		status: http.StatusBadGateway,
    46  		err:    `Get "?http://example.com/_cluster_setup"?: network error`,
    47  	})
    48  	tests.Add("finished", tst{
    49  		client: newTestClient(&http.Response{
    50  			StatusCode: http.StatusOK,
    51  			ProtoMajor: 1,
    52  			ProtoMinor: 1,
    53  			Header: http.Header{
    54  				"Content-Type": []string{"application/json"},
    55  			},
    56  			Body: io.NopCloser(strings.NewReader(`{"state":"cluster_finished"}`)),
    57  		}, nil),
    58  		expected: "cluster_finished",
    59  	})
    60  	tests.Add("invalid option", tst{
    61  		client: newCustomClient(func(*http.Request) (*http.Response, error) {
    62  			return nil, nil
    63  		}),
    64  		options: kivik.Param(optionEnsureDBsExist, 1.0),
    65  		status:  http.StatusBadRequest,
    66  		err:     "kivik: invalid type float64 for options",
    67  	})
    68  	tests.Add("invalid param", tst{
    69  		client: newCustomClient(func(r *http.Request) (*http.Response, error) {
    70  			result := []string{}
    71  			err := json.Unmarshal([]byte(r.URL.Query().Get(optionEnsureDBsExist)), &result)
    72  			return nil, &internal.Error{Status: http.StatusBadRequest, Err: err}
    73  		}),
    74  		options: kivik.Param(optionEnsureDBsExist, "foo,bar,baz"),
    75  		status:  http.StatusBadRequest,
    76  		err:     `Get "?http://example.com/_cluster_setup\?ensure_dbs_exist=foo%2Cbar%2Cbaz"?: invalid character 'o' in literal false \(expecting 'a'\)`,
    77  	})
    78  	tests.Add("ensure dbs", func(t *testing.T) interface{} {
    79  		return tst{
    80  			client: newCustomClient(func(r *http.Request) (*http.Response, error) {
    81  				input := r.URL.Query().Get(optionEnsureDBsExist)
    82  				expected := []string{"foo", "bar", "baz"}
    83  				result := []string{}
    84  				err := json.Unmarshal([]byte(input), &result)
    85  				if err != nil {
    86  					t.Fatalf("Failed to parse `%s`: %s\n", input, err)
    87  				}
    88  				if d := testy.DiffInterface(expected, result); d != nil {
    89  					t.Errorf("Unexpected db list:\n%s", d)
    90  				}
    91  				return &http.Response{
    92  					StatusCode: http.StatusOK,
    93  					ProtoMajor: 1,
    94  					ProtoMinor: 1,
    95  					Header: http.Header{
    96  						"Content-Type": []string{"application/json"},
    97  					},
    98  					Body: io.NopCloser(strings.NewReader(`{"state":"cluster_finished"}`)),
    99  				}, nil
   100  			}),
   101  			options:  kivik.Param(optionEnsureDBsExist, `["foo","bar","baz"]`),
   102  			expected: "cluster_finished",
   103  		}
   104  	})
   105  
   106  	tests.Run(t, func(t *testing.T, test tst) {
   107  		opts := test.options
   108  		if opts == nil {
   109  			opts = mock.NilOption
   110  		}
   111  		result, err := test.client.ClusterStatus(context.Background(), opts)
   112  		if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" {
   113  			t.Error(d)
   114  		}
   115  		if result != test.expected {
   116  			t.Errorf("Unexpected result:\nExpected: %s\n  Actual: %s\n", test.expected, result)
   117  		}
   118  	})
   119  }
   120  
   121  func TestClusterSetup(t *testing.T) {
   122  	type tst struct {
   123  		client *client
   124  		action interface{}
   125  		status int
   126  		err    string
   127  	}
   128  	tests := testy.NewTable()
   129  	tests.Add("network error", tst{
   130  		client: newTestClient(nil, errors.New("network error")),
   131  		status: http.StatusBadGateway,
   132  		err:    `Post "?http://example.com/_cluster_setup"?: network error`,
   133  	})
   134  	tests.Add("invalid action", tst{
   135  		client: newTestClient(nil, nil),
   136  		action: func() {},
   137  		status: http.StatusBadRequest,
   138  		err:    `Post "?http://example.com/_cluster_setup"?: json: unsupported type: func()`,
   139  	})
   140  	tests.Add("success", func(t *testing.T) interface{} {
   141  		return tst{
   142  			client: newCustomClient(func(r *http.Request) (*http.Response, error) {
   143  				expected := map[string]interface{}{
   144  					"action": "finish_cluster",
   145  				}
   146  				result := map[string]interface{}{}
   147  				if err := json.NewDecoder(r.Body).Decode(&result); err != nil {
   148  					t.Fatal(err)
   149  				}
   150  				if d := testy.DiffInterface(expected, result); d != nil {
   151  					t.Errorf("Unexpected request body:\n%s\n", d)
   152  				}
   153  				return &http.Response{
   154  					StatusCode: http.StatusOK,
   155  					ProtoMajor: 1,
   156  					ProtoMinor: 1,
   157  					Header: http.Header{
   158  						"Content-Type": []string{"application/json"},
   159  					},
   160  					Body: io.NopCloser(strings.NewReader(`{"ok":true}`)),
   161  				}, nil
   162  			}),
   163  			action: map[string]interface{}{
   164  				"action": "finish_cluster",
   165  			},
   166  		}
   167  	})
   168  	tests.Add("already finished", tst{
   169  		client: newTestClient(&http.Response{
   170  			StatusCode:    http.StatusBadRequest,
   171  			ProtoMajor:    1,
   172  			ProtoMinor:    1,
   173  			ContentLength: 63,
   174  			Header: http.Header{
   175  				"Content-Type": []string{"application/json"},
   176  			},
   177  			Body: io.NopCloser(strings.NewReader(`{"error":"bad_request","reason":"Cluster is already finished"}`)),
   178  		}, nil),
   179  		action: map[string]interface{}{
   180  			"action": "finish_cluster",
   181  		},
   182  		status: http.StatusBadRequest,
   183  		err:    "Bad Request: Cluster is already finished",
   184  	})
   185  
   186  	tests.Run(t, func(t *testing.T, test tst) {
   187  		err := test.client.ClusterSetup(context.Background(), test.action)
   188  		if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" {
   189  			t.Error(d)
   190  		}
   191  	})
   192  }
   193  
   194  func TestMembership(t *testing.T) {
   195  	type tt struct {
   196  		client *client
   197  		want   *driver.ClusterMembership
   198  		status int
   199  		err    string
   200  	}
   201  
   202  	tests := testy.NewTable()
   203  	tests.Add("network error", tt{
   204  		client: newTestClient(nil, errors.New("network error")),
   205  		status: http.StatusBadGateway,
   206  		err:    `Get "?http://example.com/_membership"?: network error`,
   207  	})
   208  	tests.Add("success 2.3.1", func(*testing.T) interface{} {
   209  		return tt{
   210  			client: newTestClient(&http.Response{
   211  				StatusCode: http.StatusOK,
   212  				Header: http.Header{
   213  					"Cache-Control":  []string{"must-revalidate"},
   214  					"Content-Length": []string{"382"},
   215  					"Content-Type":   []string{"application/json"},
   216  					"Date":           []string{"Fri, 10 Jul 2020 13:12:10 GMT"},
   217  					"Server":         []string{"CouchDB/2.3.1 (Erlang OTP/19)"},
   218  				},
   219  				Body: io.NopCloser(strings.NewReader(`{"all_nodes":["couchdb@b2c-couchdb-0.b2c-couchdb.b2c.svc.cluster.local","couchdb@b2c-couchdb-1.b2c-couchdb.b2c.svc.cluster.local","couchdb@b2c-couchdb-2.b2c-couchdb.b2c.svc.cluster.local"],"cluster_nodes":["couchdb@b2c-couchdb-0.b2c-couchdb.b2c.svc.cluster.local","couchdb@b2c-couchdb-1.b2c-couchdb.b2c.svc.cluster.local","couchdb@b2c-couchdb-2.b2c-couchdb.b2c.svc.cluster.local"]}
   220  				`)),
   221  			}, nil),
   222  			want: &driver.ClusterMembership{
   223  				AllNodes: []string{
   224  					"couchdb@b2c-couchdb-0.b2c-couchdb.b2c.svc.cluster.local",
   225  					"couchdb@b2c-couchdb-1.b2c-couchdb.b2c.svc.cluster.local",
   226  					"couchdb@b2c-couchdb-2.b2c-couchdb.b2c.svc.cluster.local",
   227  				},
   228  				ClusterNodes: []string{
   229  					"couchdb@b2c-couchdb-0.b2c-couchdb.b2c.svc.cluster.local",
   230  					"couchdb@b2c-couchdb-1.b2c-couchdb.b2c.svc.cluster.local",
   231  					"couchdb@b2c-couchdb-2.b2c-couchdb.b2c.svc.cluster.local",
   232  				},
   233  			},
   234  		}
   235  	})
   236  
   237  	tests.Run(t, func(t *testing.T, tt tt) {
   238  		got, err := tt.client.Membership(context.Background())
   239  		if d := internal.StatusErrorDiffRE(tt.err, tt.status, err); d != "" {
   240  			t.Error(d)
   241  		}
   242  		if err != nil {
   243  			return
   244  		}
   245  		if d := testy.DiffInterface(tt.want, got); d != nil {
   246  			t.Error(d)
   247  		}
   248  	})
   249  }
   250  

View as plain text