...

Source file src/github.com/go-kivik/kivik/v4/couchdb/rows_test.go

Documentation: github.com/go-kivik/kivik/v4/couchdb

     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  }
   296  

View as plain text