1
2
3
4
5
6
7
8
9
10
11
12
13 package couchdb
14
15 import (
16 "context"
17 "errors"
18 "io"
19 "net/http"
20 "strings"
21 "testing"
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 TestStats(t *testing.T) {
30 tests := []struct {
31 name string
32 db *db
33 expected *driver.DBStats
34 status int
35 err string
36 }{
37 {
38 name: "network error",
39 db: newTestDB(nil, errors.New("net error")),
40 status: http.StatusBadGateway,
41 err: `Get "?http://example.com/testdb"?: net error`,
42 },
43 {
44 name: "read error",
45 db: newTestDB(&http.Response{
46 StatusCode: http.StatusOK,
47 Body: &mockReadCloser{
48 ReadFunc: func(_ []byte) (int, error) {
49 return 0, errors.New("read error")
50 },
51 CloseFunc: func() error { return nil },
52 },
53 }, nil),
54 status: http.StatusBadGateway,
55 err: "read error",
56 },
57 {
58 name: "invalid JSON response",
59 db: newTestDB(&http.Response{
60 StatusCode: http.StatusOK,
61 Body: io.NopCloser(strings.NewReader(`invalid json`)),
62 }, nil),
63 status: http.StatusBadGateway,
64 err: "invalid character 'i' looking for beginning of value",
65 },
66 {
67 name: "error response",
68 db: newTestDB(&http.Response{
69 StatusCode: http.StatusBadRequest,
70 Body: io.NopCloser(strings.NewReader("")),
71 }, nil),
72 status: http.StatusBadRequest,
73 err: "Bad Request",
74 },
75 {
76 name: "1.6.1",
77 db: newTestDB(&http.Response{
78 StatusCode: http.StatusOK,
79 Header: http.Header{
80 "Server": {"CouchDB/1.6.1 (Erlang OTP/17)"},
81 "Date": {"Thu, 26 Oct 2017 12:58:14 GMT"},
82 "Content-Type": {"text/plain; charset=utf-8"},
83 "Content-Length": {"235"},
84 "Cache-Control": {"must-revalidate"},
85 },
86 Body: io.NopCloser(strings.NewReader(`{"db_name":"_users","doc_count":3,"doc_del_count":14,"update_seq":31,"purge_seq":0,"compact_running":false,"disk_size":127080,"data_size":6028,"instance_start_time":"1509022681259533","disk_format_version":6,"committed_update_seq":31}`)),
87 }, nil),
88 expected: &driver.DBStats{
89 Name: "_users",
90 DocCount: 3,
91 DeletedCount: 14,
92 UpdateSeq: "31",
93 DiskSize: 127080,
94 ActiveSize: 6028,
95 RawResponse: []byte(`{"db_name":"_users","doc_count":3,"doc_del_count":14,"update_seq":31,"purge_seq":0,"compact_running":false,"disk_size":127080,"data_size":6028,"instance_start_time":"1509022681259533","disk_format_version":6,"committed_update_seq":31}`),
96 },
97 },
98 {
99 name: "2.0.0",
100 db: newTestDB(&http.Response{
101 StatusCode: http.StatusOK,
102 Header: http.Header{
103 "Server": {"CouchDB/2.0.0 (Erlang OTP/17)"},
104 "Date": {"Thu, 26 Oct 2017 13:01:13 GMT"},
105 "Content-Type": {"application/json"},
106 "Content-Length": {"429"},
107 "Cache-Control": {"must-revalidate"},
108 "X-Couch-Request-ID": {"2486f27546"},
109 "X-CouchDB-Body-Time": {"0"},
110 },
111 Body: io.NopCloser(strings.NewReader(`{"db_name":"_users","update_seq":"13-g1AAAAEzeJzLYWBg4MhgTmHgzcvPy09JdcjLz8gvLskBCjMlMiTJ____PyuRAYeCJAUgmWQPVsOCS40DSE08WA0rLjUJIDX1eO3KYwGSDA1ACqhsPiF1CyDq9mclMuFVdwCi7j4hdQ8g6kDuywIAkRBjAw","sizes":{"file":87323,"external":2495,"active":6082},"purge_seq":0,"other":{"data_size":2495},"doc_del_count":6,"doc_count":1,"disk_size":87323,"disk_format_version":6,"data_size":6082,"compact_running":false,"instance_start_time":"0"}`)),
112 }, nil),
113 expected: &driver.DBStats{
114 Name: "_users",
115 DocCount: 1,
116 DeletedCount: 6,
117 UpdateSeq: "13-g1AAAAEzeJzLYWBg4MhgTmHgzcvPy09JdcjLz8gvLskBCjMlMiTJ____PyuRAYeCJAUgmWQPVsOCS40DSE08WA0rLjUJIDX1eO3KYwGSDA1ACqhsPiF1CyDq9mclMuFVdwCi7j4hdQ8g6kDuywIAkRBjAw",
118 DiskSize: 87323,
119 ActiveSize: 6082,
120 ExternalSize: 2495,
121 RawResponse: []byte(`{"db_name":"_users","update_seq":"13-g1AAAAEzeJzLYWBg4MhgTmHgzcvPy09JdcjLz8gvLskBCjMlMiTJ____PyuRAYeCJAUgmWQPVsOCS40DSE08WA0rLjUJIDX1eO3KYwGSDA1ACqhsPiF1CyDq9mclMuFVdwCi7j4hdQ8g6kDuywIAkRBjAw","sizes":{"file":87323,"external":2495,"active":6082},"purge_seq":0,"other":{"data_size":2495},"doc_del_count":6,"doc_count":1,"disk_size":87323,"disk_format_version":6,"data_size":6082,"compact_running":false,"instance_start_time":"0"}`),
122 },
123 },
124 {
125 name: "2.1.1",
126 db: newTestDB(&http.Response{
127 StatusCode: http.StatusOK,
128 Header: http.Header{
129 "Server": {"CouchDB/2.0.0 (Erlang OTP/17)"},
130 "Date": {"Thu, 26 Oct 2017 13:01:13 GMT"},
131 "Content-Type": {"application/json"},
132 "Content-Length": {"429"},
133 "Cache-Control": {"must-revalidate"},
134 "X-Couch-Request-ID": {"2486f27546"},
135 "X-CouchDB-Body-Time": {"0"},
136 },
137 Body: io.NopCloser(strings.NewReader(`{"db_name":"_users","update_seq":"13-g1AAAAEzeJzLYWBg4MhgTmHgzcvPy09JdcjLz8gvLskBCjMlMiTJ____PyuRAYeCJAUgmWQPVsOCS40DSE08WA0rLjUJIDX1eO3KYwGSDA1ACqhsPiF1CyDq9mclMuFVdwCi7j4hdQ8g6kDuywIAkRBjAw","sizes":{"file":87323,"external":2495,"active":6082},"purge_seq":0,"other":{"data_size":2495},"doc_del_count":6,"doc_count":1,"disk_size":87323,"disk_format_version":6,"data_size":6082,"compact_running":false,"instance_start_time":"0","cluster":{"n":1,"q":2,"r":3,"w":4}}`)),
138 }, nil),
139 expected: &driver.DBStats{
140 Name: "_users",
141 DocCount: 1,
142 DeletedCount: 6,
143 UpdateSeq: "13-g1AAAAEzeJzLYWBg4MhgTmHgzcvPy09JdcjLz8gvLskBCjMlMiTJ____PyuRAYeCJAUgmWQPVsOCS40DSE08WA0rLjUJIDX1eO3KYwGSDA1ACqhsPiF1CyDq9mclMuFVdwCi7j4hdQ8g6kDuywIAkRBjAw",
144 DiskSize: 87323,
145 ActiveSize: 6082,
146 ExternalSize: 2495,
147 Cluster: &driver.ClusterStats{
148 Replicas: 1,
149 Shards: 2,
150 ReadQuorum: 3,
151 WriteQuorum: 4,
152 },
153 RawResponse: []byte(`{"db_name":"_users","update_seq":"13-g1AAAAEzeJzLYWBg4MhgTmHgzcvPy09JdcjLz8gvLskBCjMlMiTJ____PyuRAYeCJAUgmWQPVsOCS40DSE08WA0rLjUJIDX1eO3KYwGSDA1ACqhsPiF1CyDq9mclMuFVdwCi7j4hdQ8g6kDuywIAkRBjAw","sizes":{"file":87323,"external":2495,"active":6082},"purge_seq":0,"other":{"data_size":2495},"doc_del_count":6,"doc_count":1,"disk_size":87323,"disk_format_version":6,"data_size":6082,"compact_running":false,"instance_start_time":"0","cluster":{"n":1,"q":2,"r":3,"w":4}}`),
154 },
155 },
156 }
157 for _, test := range tests {
158 t.Run(test.name, func(t *testing.T) {
159 result, err := test.db.Stats(context.Background())
160 if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" {
161 t.Error(d)
162 }
163 if d := testy.DiffInterface(test.expected, result); d != nil {
164 t.Error(d)
165 }
166 })
167 }
168 }
169
170 func TestDbsStats(t *testing.T) {
171 tests := []struct {
172 name string
173 client *client
174 dbnames []string
175 expected interface{}
176 status int
177 err string
178 }{
179 {
180 name: "network error",
181 client: newTestClient(nil, errors.New("net error")),
182 dbnames: []string{"foo", "bar"},
183 status: http.StatusBadGateway,
184 err: `Post "?http://example.com/_dbs_info"?: net error`,
185 },
186 {
187 name: "read error",
188 client: newTestClient(&http.Response{
189 StatusCode: http.StatusOK,
190 Body: &mockReadCloser{
191 ReadFunc: func(_ []byte) (int, error) {
192 return 0, errors.New("read error")
193 },
194 CloseFunc: func() error { return nil },
195 },
196 }, nil),
197 status: http.StatusBadGateway,
198 err: "read error",
199 },
200 {
201 name: "invalid JSON response",
202 client: newTestClient(&http.Response{
203 StatusCode: http.StatusOK,
204 Body: io.NopCloser(strings.NewReader(`invalid json`)),
205 }, nil),
206 status: http.StatusBadGateway,
207 err: "invalid character 'i' looking for beginning of value",
208 },
209 {
210 name: "error response",
211 client: newTestClient(&http.Response{
212 StatusCode: http.StatusBadRequest,
213 Body: io.NopCloser(strings.NewReader("")),
214 }, nil),
215 status: http.StatusBadRequest,
216 err: "Bad Request",
217 },
218 {
219 name: "2.1.2",
220 client: newTestClient(&http.Response{
221 StatusCode: http.StatusNotFound,
222 Header: http.Header{
223 "Server": {"CouchDB/2.1.2 (Erlang OTP/17)"},
224 "Date": {"Sat, 01 Sep 2018 15:42:53 GMT"},
225 "Content-Type": {"application/json"},
226 "Content-Length": {"58"},
227 "Cache-Control": {"must-revalidate"},
228 "X-Couch-Request-ID": {"e1264663f9"},
229 "X-CouchDB-Body-Time": {"0"},
230 },
231 Body: io.NopCloser(strings.NewReader(`{"error":"not_found","reason":"Database does not exist."}`)),
232 }, nil),
233 dbnames: []string{"foo", "bar"},
234 err: "Not Found",
235 status: http.StatusNotFound,
236 },
237 {
238 name: "2.2.0",
239 client: newTestClient(&http.Response{
240 StatusCode: http.StatusOK,
241 Header: http.Header{
242 "Server": {"CouchDB/2.2.0 (Erlang OTP/19)"},
243 "Date": {"Sat, 01 Sep 2018 15:50:56 GMT"},
244 "Content-Type": {"application/json"},
245 "Transfer-Encoding": {"chunked"},
246 "Cache-Control": {"must-revalidate"},
247 "X-Couch-Request-ID": {"1bf258cfbe"},
248 "X-CouchDB-Body-Time": {"0"},
249 },
250 Body: io.NopCloser(strings.NewReader(`[{"key":"foo","error":"not_found"},{"key":"bar","error":"not_found"},{"key":"_users","info":{"db_name":"_users","update_seq":"1-g1AAAAEzeJzLYWBg4MhgTmHgzcvPy09JdcjLz8gvLskBCjMlMiTJ____PyuRAYeCJAUgmWSPX40DSE08WA0jLjUJIDX1eM3JYwGSDA1ACqhsPiF1CyDq9hNSdwCi7j4hdQ8g6kDuywIAiVhi9w","sizes":{"file":24423,"external":5361,"active":2316},"purge_seq":0,"other":{"data_size":5361},"doc_del_count":0,"doc_count":1,"disk_size":24423,"disk_format_version":6,"data_size":2316,"compact_running":false,"cluster":{"q":8,"n":1,"w":1,"r":1},"instance_start_time":"0"}}]
251 `)),
252 }, nil),
253 expected: []*driver.DBStats{
254 nil,
255 nil,
256 {
257 Name: "_users",
258 DocCount: 1,
259 UpdateSeq: "1-g1AAAAEzeJzLYWBg4MhgTmHgzcvPy09JdcjLz8gvLskBCjMlMiTJ____PyuRAYeCJAUgmWSPX40DSE08WA0jLjUJIDX1eM3JYwGSDA1ACqhsPiF1CyDq9hNSdwCi7j4hdQ8g6kDuywIAiVhi9w",
260 DiskSize: 24423,
261 ActiveSize: 2316,
262 ExternalSize: 5361,
263 Cluster: &driver.ClusterStats{
264 Replicas: 1,
265 Shards: 8,
266 ReadQuorum: 1,
267 WriteQuorum: 1,
268 },
269 RawResponse: []byte(`{"db_name":"_users","update_seq":"1-g1AAAAEzeJzLYWBg4MhgTmHgzcvPy09JdcjLz8gvLskBCjMlMiTJ____PyuRAYeCJAUgmWSPX40DSE08WA0jLjUJIDX1eM3JYwGSDA1ACqhsPiF1CyDq9hNSdwCi7j4hdQ8g6kDuywIAiVhi9w","sizes":{"file":24423,"external":5361,"active":2316},"purge_seq":0,"other":{"data_size":5361},"doc_del_count":0,"doc_count":1,"disk_size":24423,"disk_format_version":6,"data_size":2316,"compact_running":false,"cluster":{"q":8,"n":1,"w":1,"r":1},"instance_start_time":"0"}`),
270 },
271 },
272 },
273 }
274 for _, test := range tests {
275 t.Run(test.name, func(t *testing.T) {
276 result, err := test.client.DBsStats(context.Background(), test.dbnames)
277 if d := internal.StatusErrorDiffRE(test.err, test.status, err); d != "" {
278 t.Error(d)
279 }
280 if err != nil {
281 return
282 }
283 if d := testy.DiffInterface(test.expected, result); d != nil {
284 t.Error(d)
285 }
286 })
287 }
288 }
289
290 func TestPartitionStats(t *testing.T) {
291 if isGopherJS {
292 t.Skip("https://github.com/gopherjs/gopherjs/issues/1228")
293 }
294 type tt struct {
295 db *db
296 name string
297 status int
298 err string
299 }
300
301 tests := testy.NewTable()
302 tests.Add("network error", tt{
303 db: newTestDB(nil, errors.New("net error")),
304 name: "partXX",
305 status: http.StatusBadGateway,
306 err: `Get "?http://example.com/testdb/_partition/partXX"?: net error`,
307 })
308 tests.Add("read error", tt{
309 db: newTestDB(&http.Response{
310 StatusCode: http.StatusOK,
311 Body: &mockReadCloser{
312 ReadFunc: func(_ []byte) (int, error) {
313 return 0, errors.New("read error")
314 },
315 CloseFunc: func() error { return nil },
316 },
317 }, nil),
318 status: http.StatusBadGateway,
319 err: "read error",
320 })
321 tests.Add("invalid JSON response", tt{
322 db: newTestDB(&http.Response{
323 StatusCode: http.StatusOK,
324 Body: io.NopCloser(strings.NewReader(`invalid json`)),
325 }, nil),
326 status: http.StatusBadGateway,
327 err: "invalid character 'i' looking for beginning of value",
328 })
329 tests.Add("error response", tt{
330 db: newTestDB(&http.Response{
331 StatusCode: http.StatusBadRequest,
332 Body: io.NopCloser(strings.NewReader("")),
333 }, nil),
334 status: http.StatusBadRequest,
335 err: "Bad Request",
336 })
337 tests.Add("3.0.0-pre", tt{
338 db: newTestDB(&http.Response{
339 StatusCode: http.StatusOK,
340 Header: http.Header{
341 "Server": {"CouchDB/2.3.0-a1e11cea9 (Erlang OTP/21)"},
342 "Date": {"Thu, 24 Jan 2019 17:19:59 GMT"},
343 "Content-Type": {"application/json"},
344 "Content-Length": {"119"},
345 "Cache-Control": {"must-revalidate"},
346 "X-Couch-Request-ID": {"2486f27546"},
347 "X-CouchDB-Body-Time": {"0"},
348 },
349 Body: io.NopCloser(strings.NewReader(`{"db_name":"my_new_db","doc_count":1,"doc_del_count":0,"partition":"sensor-260","sizes":{"active":244,"external":347}}
350 `)),
351 }, nil),
352 })
353
354 tests.Run(t, func(t *testing.T, tt tt) {
355 result, err := tt.db.PartitionStats(context.Background(), tt.name)
356 if d := internal.StatusErrorDiffRE(tt.err, tt.status, err); d != "" {
357 t.Error(d)
358 }
359 if err != nil {
360 return
361 }
362 if d := testy.DiffInterface(testy.Snapshot(t), result); d != nil {
363 t.Error(d)
364 }
365 })
366 }
367
View as plain text