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 "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