1
2
3
4
5
6
7
8
9
10
11
12
13 package kivik
14
15 import (
16 "context"
17 "errors"
18 "fmt"
19 "io"
20 "net/http"
21 "strconv"
22 "testing"
23
24 "github.com/google/go-cmp/cmp"
25 "gitlab.com/flimzy/testy"
26
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 func TestChangesNext(t *testing.T) {
33 tests := []struct {
34 name string
35 changes *Changes
36 expected bool
37 }{
38 {
39 name: "nothing more",
40 changes: &Changes{
41 iter: &iter{state: stateClosed},
42 },
43 expected: false,
44 },
45 {
46 name: "more",
47 changes: &Changes{
48 iter: &iter{
49 feed: &mockIterator{
50 NextFunc: func(_ interface{}) error { return nil },
51 },
52 },
53 },
54 expected: true,
55 },
56 }
57 for _, test := range tests {
58 t.Run(test.name, func(t *testing.T) {
59 result := test.changes.Next()
60 if result != test.expected {
61 t.Errorf("Unexpected result: %v", result)
62 }
63 })
64 }
65 }
66
67 func TestChangesErr(t *testing.T) {
68 const expected = "foo error"
69 c := &Changes{
70 iter: &iter{err: errors.New(expected)},
71 }
72 err := c.Err()
73 if !testy.ErrorMatches(expected, err) {
74 t.Errorf("Unexpected error: %s", err)
75 }
76 }
77
78 func TestChangesClose(t *testing.T) {
79 const expected = "close error"
80 c := &Changes{
81 iter: &iter{
82 feed: &mockIterator{CloseFunc: func() error { return errors.New(expected) }},
83 },
84 }
85 err := c.Close()
86 if !testy.ErrorMatches(expected, err) {
87 t.Errorf("Unexpected error: %s", err)
88 }
89 }
90
91 func TestChangesIteratorNext(t *testing.T) {
92 const expected = "foo error"
93 c := &changesIterator{
94 Changes: &mock.Changes{
95 NextFunc: func(_ *driver.Change) error { return errors.New(expected) },
96 },
97 }
98 var i driver.Change
99 err := c.Next(&i)
100 if !testy.ErrorMatches(expected, err) {
101 t.Errorf("Unexpected error: %s", err)
102 }
103 }
104
105 func TestChangesIteratorNew(t *testing.T) {
106 ch := newChanges(context.Background(), nil, &mock.Changes{})
107 expected := &Changes{
108 iter: &iter{
109 feed: &changesIterator{
110 Changes: &mock.Changes{},
111 },
112 curVal: &driver.Change{},
113 },
114 changesi: &mock.Changes{},
115 }
116 ch.cancel = nil
117 if d := testy.DiffInterface(expected, ch); d != nil {
118 t.Error(d)
119 }
120 }
121
122 func TestChangesGetters(t *testing.T) {
123 changes := []*driver.Change{
124 {
125 ID: "foo",
126 Deleted: true,
127 Changes: []string{"1", "2", "3"},
128 Seq: "2-foo",
129 },
130 }
131 c := newChanges(context.Background(), nil, &mock.Changes{
132 NextFunc: func(c *driver.Change) error {
133 if len(changes) == 0 {
134 return io.EOF
135 }
136 change := changes[0]
137 changes = changes[1:]
138 *c = *change
139 return nil
140 },
141 PendingFunc: func() int64 { return 123 },
142 LastSeqFunc: func() string { return "3-bar" },
143 ETagFunc: func() string { return "etag-foo" },
144 })
145 _ = c.Next()
146
147 t.Run("Changes", func(t *testing.T) {
148 expected := []string{"1", "2", "3"}
149 result := c.Changes()
150 if d := testy.DiffInterface(expected, result); d != nil {
151 t.Error(d)
152 }
153 })
154
155 t.Run("Deleted", func(t *testing.T) {
156 expected := true
157 result := c.Deleted()
158 if expected != result {
159 t.Errorf("Unexpected result: %v", result)
160 }
161 })
162
163 t.Run("ID", func(t *testing.T) {
164 expected := "foo"
165 result := c.ID()
166 if expected != result {
167 t.Errorf("Unexpected result: %v", result)
168 }
169 })
170 t.Run("Seq", func(t *testing.T) {
171 expected := "2-foo"
172 result := c.Seq()
173 if expected != result {
174 t.Errorf("Unexpected result: %v", result)
175 }
176 })
177 t.Run("ETag", func(t *testing.T) {
178 expected := "etag-foo"
179 result := c.ETag()
180 if expected != result {
181 t.Errorf("Unexpected result: %v", result)
182 }
183 })
184 t.Run("Metadata", func(t *testing.T) {
185 _ = c.Next()
186 t.Run("LastSeq", func(t *testing.T) {
187 expected := "3-bar"
188 meta, err := c.Metadata()
189 if err != nil {
190 t.Fatal(err)
191 }
192 if expected != meta.LastSeq {
193 t.Errorf("Unexpected LastSeq: %v", meta.LastSeq)
194 }
195 })
196 t.Run("Pending", func(t *testing.T) {
197 expected := int64(123)
198 meta, err := c.Metadata()
199 if err != nil {
200 t.Fatal(err)
201 }
202 if expected != meta.Pending {
203 t.Errorf("Unexpected Pending: %v", meta.Pending)
204 }
205 })
206 })
207 }
208
209 func TestChangesScanDoc(t *testing.T) {
210 tests := []struct {
211 name string
212 changes *Changes
213 expected interface{}
214 status int
215 err string
216 }{
217 {
218 name: "success",
219 changes: &Changes{
220 iter: &iter{
221 state: stateRowReady,
222 curVal: &driver.Change{
223 Doc: []byte(`{"foo":123.4}`),
224 },
225 },
226 },
227 expected: map[string]interface{}{"foo": 123.4},
228 },
229 {
230 name: "closed",
231 changes: &Changes{
232 iter: &iter{
233 state: stateClosed,
234 },
235 },
236 status: http.StatusBadRequest,
237 err: "kivik: Iterator is closed",
238 },
239 }
240 for _, test := range tests {
241 t.Run(test.name, func(t *testing.T) {
242 var result interface{}
243 err := test.changes.ScanDoc(&result)
244 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
245 t.Error(d)
246 }
247 if d := testy.DiffInterface(test.expected, result); d != nil {
248 t.Error(d)
249 }
250 })
251 }
252 }
253
254 func TestChanges(t *testing.T) {
255 tests := []struct {
256 name string
257 db *DB
258 opts Option
259 expected *Changes
260 status int
261 err string
262 }{
263 {
264 name: "db error",
265 db: &DB{
266 client: &Client{},
267 driverDB: &mock.DB{
268 ChangesFunc: func(context.Context, driver.Options) (driver.Changes, error) {
269 return nil, errors.New("db error")
270 },
271 },
272 },
273 status: 500,
274 err: "db error",
275 },
276 {
277 name: "success",
278 db: &DB{
279 client: &Client{},
280 driverDB: &mock.DB{
281 ChangesFunc: func(_ context.Context, options driver.Options) (driver.Changes, error) {
282 expectedOpts := map[string]interface{}{"foo": 123.4}
283 gotOpts := map[string]interface{}{}
284 options.Apply(gotOpts)
285 if d := testy.DiffInterface(expectedOpts, gotOpts); d != nil {
286 return nil, fmt.Errorf("Unexpected options:\n%s", d)
287 }
288 return &mock.Changes{}, nil
289 },
290 },
291 },
292 opts: Param("foo", 123.4),
293 expected: &Changes{
294 iter: &iter{
295 feed: &changesIterator{
296 Changes: &mock.Changes{},
297 },
298 curVal: &driver.Change{},
299 },
300 changesi: &mock.Changes{},
301 },
302 },
303 {
304 name: "client closed",
305 db: &DB{
306 client: &Client{
307 closed: true,
308 },
309 },
310 status: http.StatusServiceUnavailable,
311 err: "kivik: client closed",
312 },
313 {
314 name: "db error",
315 db: &DB{
316 err: errors.New("db error"),
317 },
318 status: http.StatusInternalServerError,
319 err: "db error",
320 },
321 }
322 for _, test := range tests {
323 t.Run(test.name, func(t *testing.T) {
324 result := test.db.Changes(context.Background(), test.opts)
325 err := result.Err()
326 if d := internal.StatusErrorDiff(test.err, test.status, err); d != "" {
327 t.Error(d)
328 }
329 if err != nil {
330 return
331 }
332 result.cancel = nil
333 result.onClose = nil
334 if d := testy.DiffInterface(test.expected, result); d != nil {
335 t.Error(d)
336 }
337 })
338 }
339 t.Run("standalone", func(t *testing.T) {
340 t.Run("after err, close doesn't block", func(t *testing.T) {
341 db := &DB{
342 client: &Client{},
343 driverDB: &mock.DB{
344 ChangesFunc: func(context.Context, driver.Options) (driver.Changes, error) {
345 return nil, errors.New("unf")
346 },
347 },
348 }
349 rows := db.Changes(context.Background())
350 if err := rows.Err(); err == nil {
351 t.Fatal("expected an error, got none")
352 }
353 _ = db.Close()
354 })
355 })
356 }
357
358 func TestChanges_uninitialized_should_not_panic(*testing.T) {
359
360
361 c := &Changes{}
362 _, _ = c.Metadata()
363 _ = c.ETag()
364 }
365
366 func TestChanges_Next_resets_iterator_value(t *testing.T) {
367 idx := 0
368 db := &DB{
369 client: &Client{},
370 driverDB: &mock.DB{
371 ChangesFunc: func(context.Context, driver.Options) (driver.Changes, error) {
372 return &mock.Changes{
373 NextFunc: func(change *driver.Change) error {
374 idx++
375 switch idx {
376 case 1:
377 change.ID = strconv.Itoa(idx)
378 return nil
379 case 2:
380 return nil
381 }
382 return io.EOF
383 },
384 }, nil
385 },
386 },
387 }
388
389 changes := db.Changes(context.Background())
390
391 wantIDs := []string{"1", ""}
392 gotIDs := []string{}
393 for changes.Next() {
394 gotIDs = append(gotIDs, changes.ID())
395 }
396 if d := cmp.Diff(wantIDs, gotIDs); d != "" {
397 t.Error(d)
398 }
399 }
400
View as plain text