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