1
2
3
4
5
6
7
8
9
10
11
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 }
296
View as plain text