github.com/go-kivik/kivik/v4@v4.3.2/couchdb/rows_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 "io" 18 "net/http" 19 "strings" 20 "testing" 21 22 "gitlab.com/flimzy/testy" 23 24 "github.com/go-kivik/kivik/v4/driver" 25 internal "github.com/go-kivik/kivik/v4/int/errors" 26 ) 27 28 const input = ` 29 { 30 "offset": 6, 31 "rows": [ 32 { 33 "id": "SpaghettiWithMeatballs", 34 "key": "meatballs", 35 "value": 1 36 }, 37 { 38 "id": "SpaghettiWithMeatballs", 39 "key": "spaghetti", 40 "value": 1 41 }, 42 { 43 "id": "SpaghettiWithMeatballs", 44 "key": "tomato sauce", 45 "value": 1 46 } 47 ], 48 "total_rows": 3 49 } 50 ` 51 52 var expectedKeys = []string{`"meatballs"`, `"spaghetti"`, `"tomato sauce"`} 53 54 func TestRowsIterator(t *testing.T) { 55 rows := newRows(context.TODO(), io.NopCloser(strings.NewReader(input))) 56 var count int 57 for { 58 row := &driver.Row{} 59 err := rows.Next(row) 60 if err == io.EOF { 61 break 62 } 63 if err != nil { 64 t.Fatalf("Next() failed: %s", err) 65 } 66 if string(row.Key) != expectedKeys[count] { 67 t.Errorf("Expected key #%d to be %s, got %s", count, expectedKeys[count], string(row.Key)) 68 } 69 if count++; count > 10 { 70 t.Fatalf("Ran too many iterations.") 71 } 72 } 73 if count != 3 { 74 t.Errorf("Expected 3 rows, got %d", count) 75 } 76 if rows.TotalRows() != 3 { 77 t.Errorf("Expected TotalRows of 3, got %d", rows.TotalRows()) 78 } 79 if rows.Offset() != 6 { 80 t.Errorf("Expected Offset of 6, got %d", rows.Offset()) 81 } 82 if err := rows.Next(&driver.Row{}); err != io.EOF { 83 t.Errorf("Calling Next() after end returned unexpected error: %s", err) 84 } 85 if err := rows.Close(); err != nil { 86 t.Errorf("Error closing rows iterator: %s", err) 87 } 88 } 89 90 const multipleQueries = `{ 91 "results" : [ 92 { 93 "offset": 0, 94 "rows": [ 95 { 96 "id": "SpaghettiWithMeatballs", 97 "key": "meatballs", 98 "value": 1 99 }, 100 { 101 "id": "SpaghettiWithMeatballs", 102 "key": "spaghetti", 103 "value": 1 104 }, 105 { 106 "id": "SpaghettiWithMeatballs", 107 "key": "tomato sauce", 108 "value": 1 109 } 110 ], 111 "total_rows": 3 112 }, 113 { 114 "offset" : 2, 115 "rows" : [ 116 { 117 "id" : "Adukiandorangecasserole-microwave", 118 "key" : "Aduki and orange casserole - microwave", 119 "value" : [ 120 null, 121 "Aduki and orange casserole - microwave" 122 ] 123 }, 124 { 125 "id" : "Aioli-garlicmayonnaise", 126 "key" : "Aioli - garlic mayonnaise", 127 "value" : [ 128 null, 129 "Aioli - garlic mayonnaise" 130 ] 131 }, 132 { 133 "id" : "Alabamapeanutchicken", 134 "key" : "Alabama peanut chicken", 135 "value" : [ 136 null, 137 "Alabama peanut chicken" 138 ] 139 } 140 ], 141 "total_rows" : 2667 142 } 143 ] 144 }` 145 146 func TestMultiQueriesRowsIterator(t *testing.T) { 147 rows := newMultiQueriesRows(context.TODO(), io.NopCloser(strings.NewReader(multipleQueries))) 148 results := make([]interface{}, 0, 8) 149 for { 150 row := &driver.Row{} 151 err := rows.Next(row) 152 if err == driver.EOQ { 153 results = append(results, map[string]interface{}{ 154 "EOQ": true, 155 "total_rows": rows.TotalRows(), 156 "offset": rows.Offset(), 157 }) 158 continue 159 } 160 if err == io.EOF { 161 results = append(results, map[string]interface{}{ 162 "EOF": true, 163 "total_rows": rows.TotalRows(), 164 "offset": rows.Offset(), 165 }) 166 break 167 } 168 if err != nil { 169 t.Fatalf("Next() failed: %s", err) 170 } 171 results = append(results, map[string]interface{}{ 172 "key": row.Key, 173 }) 174 } 175 if d := testy.DiffInterface(testy.Snapshot(t), results); d != nil { 176 t.Error(d) 177 } 178 if err := rows.Next(&driver.Row{}); err != io.EOF { 179 t.Errorf("Calling Next() after end returned unexpected error: %s", err) 180 } 181 if err := rows.Close(); err != nil { 182 t.Errorf("Error closing rows iterator: %s", err) 183 } 184 } 185 186 func TestRowsIteratorErrors(t *testing.T) { 187 tests := []struct { 188 name string 189 input string 190 status int 191 err string 192 }{ 193 { 194 name: "empty input", 195 input: "", 196 status: http.StatusBadGateway, 197 err: "EOF", 198 }, 199 { 200 name: "unexpected delimiter", 201 input: "[]", 202 status: http.StatusBadGateway, 203 err: "Unexpected JSON delimiter: [", 204 }, 205 { 206 name: "unexpected input", 207 input: `"foo"`, 208 status: http.StatusBadGateway, 209 err: "Unexpected token string: foo", 210 }, 211 { 212 name: "missing closing delimiter", 213 input: `{"rows":[{"id":"1","key":"1","value":1}`, 214 status: http.StatusBadGateway, 215 err: "EOF", 216 }, 217 { 218 name: "unexpected key", 219 input: `{"foo":"bar","rows":[]}`, 220 status: http.StatusInternalServerError, 221 err: "EOF", 222 }, 223 { 224 name: "unexpected key after valid row", 225 input: `{"rows":[{"id":"1","key":"1","value":1}],"foo":"bar"}`, 226 status: http.StatusInternalServerError, 227 err: "EOF", 228 }, 229 } 230 for _, test := range tests { 231 t.Run(test.name, func(t *testing.T) { 232 rows := newRows(context.TODO(), io.NopCloser(strings.NewReader(test.input))) 233 for i := 0; i < 10; i++ { 234 err := rows.Next(&driver.Row{}) 235 if err == nil { 236 continue 237 } 238 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" { 239 t.Error(d) 240 } 241 return 242 } 243 }) 244 } 245 } 246 247 const findInput = ` 248 {"warning":"no matching index found, create an index to optimize query time", 249 "docs":[ 250 {"id":"SpaghettiWithMeatballs","key":"meatballs","value":1}, 251 {"id":"SpaghettiWithMeatballs","key":"spaghetti","value":1}, 252 {"id":"SpaghettiWithMeatballs","key":"tomato sauce","value":1} 253 ], 254 "bookmark": "nil" 255 } 256 ` 257 258 type fullRows interface { 259 driver.Rows 260 driver.RowsWarner 261 driver.Bookmarker 262 } 263 264 func TestFindRowsIterator(t *testing.T) { 265 rows := newFindRows(context.TODO(), io.NopCloser(strings.NewReader(findInput))).(fullRows) 266 var count int 267 for { 268 row := &driver.Row{} 269 err := rows.Next(row) 270 if err == io.EOF { 271 break 272 } 273 if err != nil { 274 t.Fatalf("Next() failed: %s", err) 275 } 276 if count++; count > 10 { 277 t.Fatalf("Ran too many iterations.") 278 } 279 } 280 if count != 3 { 281 t.Errorf("Expected 3 rows, got %d", count) 282 } 283 if err := rows.Next(&driver.Row{}); err != io.EOF { 284 t.Errorf("Calling Next() after end returned unexpected error: %s", err) 285 } 286 if err := rows.Close(); err != nil { 287 t.Errorf("Error closing rows iterator: %s", err) 288 } 289 if rows.Warning() != "no matching index found, create an index to optimize query time" { 290 t.Errorf("Unexpected warning: %s", rows.Warning()) 291 } 292 if rows.Bookmark() != "nil" { 293 t.Errorf("Unexpected bookmark: %s", rows.Bookmark()) 294 } 295 }