github.com/go-kivik/kivik/v4@v4.3.2/couchdb/cluster_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 "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 }