1// Copyright (c) 2014 Couchbase, Inc. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package bleve 16 17import ( 18 "encoding/json" 19 "fmt" 20 "reflect" 21 "strings" 22 "testing" 23 "time" 24 25 "github.com/blevesearch/bleve/search" 26) 27 28func TestSearchResultString(t *testing.T) { 29 30 tests := []struct { 31 result *SearchResult 32 str string 33 }{ 34 { 35 result: &SearchResult{ 36 Request: &SearchRequest{ 37 Size: 10, 38 }, 39 Total: 5, 40 Took: 1 * time.Second, 41 Hits: search.DocumentMatchCollection{ 42 &search.DocumentMatch{}, 43 &search.DocumentMatch{}, 44 &search.DocumentMatch{}, 45 &search.DocumentMatch{}, 46 &search.DocumentMatch{}, 47 }, 48 }, 49 str: "5 matches, showing 1 through 5, took 1s", 50 }, 51 { 52 result: &SearchResult{ 53 Request: &SearchRequest{ 54 Size: 0, 55 }, 56 Total: 5, 57 Hits: search.DocumentMatchCollection{}, 58 }, 59 str: "5 matches", 60 }, 61 { 62 result: &SearchResult{ 63 Request: &SearchRequest{ 64 Size: 10, 65 }, 66 Total: 0, 67 Hits: search.DocumentMatchCollection{}, 68 }, 69 str: "No matches", 70 }, 71 } 72 73 for _, test := range tests { 74 srstring := test.result.String() 75 if !strings.HasPrefix(srstring, test.str) { 76 t.Errorf("expected to start %s, got %s", test.str, srstring) 77 } 78 } 79} 80 81func TestSearchResultMerge(t *testing.T) { 82 l := &SearchResult{ 83 Status: &SearchStatus{ 84 Total: 1, 85 Successful: 1, 86 Errors: make(map[string]error), 87 }, 88 Total: 1, 89 MaxScore: 1, 90 Hits: search.DocumentMatchCollection{ 91 &search.DocumentMatch{ 92 ID: "a", 93 Score: 1, 94 }, 95 }, 96 } 97 98 r := &SearchResult{ 99 Status: &SearchStatus{ 100 Total: 1, 101 Successful: 1, 102 Errors: make(map[string]error), 103 }, 104 Total: 1, 105 MaxScore: 2, 106 Hits: search.DocumentMatchCollection{ 107 &search.DocumentMatch{ 108 ID: "b", 109 Score: 2, 110 }, 111 }, 112 } 113 114 expected := &SearchResult{ 115 Status: &SearchStatus{ 116 Total: 2, 117 Successful: 2, 118 Errors: make(map[string]error), 119 }, 120 Total: 2, 121 MaxScore: 2, 122 Hits: search.DocumentMatchCollection{ 123 &search.DocumentMatch{ 124 ID: "a", 125 Score: 1, 126 }, 127 &search.DocumentMatch{ 128 ID: "b", 129 Score: 2, 130 }, 131 }, 132 } 133 134 l.Merge(r) 135 136 if !reflect.DeepEqual(l, expected) { 137 t.Errorf("expected %#v, got %#v", expected, l) 138 } 139} 140 141func TestUnmarshalingSearchResult(t *testing.T) { 142 143 searchResponse := []byte(`{ 144 "status":{ 145 "total":1, 146 "failed":1, 147 "successful":0, 148 "errors":{ 149 "default_index_362ce020b3d62b13_348f5c3c":"context deadline exceeded" 150 } 151 }, 152 "request":{ 153 "query":{ 154 "match":"emp", 155 "field":"type", 156 "boost":1, 157 "prefix_length":0, 158 "fuzziness":0 159 }, 160 "size":10000000, 161 "from":0, 162 "highlight":null, 163 "fields":[], 164 "facets":null, 165 "explain":false 166 }, 167 "hits":null, 168 "total_hits":0, 169 "max_score":0, 170 "took":0, 171 "facets":null 172}`) 173 174 rv := &SearchResult{ 175 Status: &SearchStatus{ 176 Errors: make(map[string]error), 177 }, 178 } 179 err = json.Unmarshal(searchResponse, rv) 180 if err != nil { 181 t.Error(err) 182 } 183 if len(rv.Status.Errors) != 1 { 184 t.Errorf("expected 1 error, got %d", len(rv.Status.Errors)) 185 } 186} 187 188func TestFacetNumericDateRangeRequests(t *testing.T) { 189 var drMissingErr = fmt.Errorf("date range query must specify either start, end or both for range name 'testName'") 190 var nrMissingErr = fmt.Errorf("numeric range query must specify either min, max or both for range name 'testName'") 191 var drNrErr = fmt.Errorf("facet can only conain numeric ranges or date ranges, not both") 192 var drNameDupErr = fmt.Errorf("date ranges contains duplicate name 'testName'") 193 var nrNameDupErr = fmt.Errorf("numeric ranges contains duplicate name 'testName'") 194 value := float64(5) 195 196 tests := []struct { 197 facet *FacetRequest 198 result error 199 }{ 200 { 201 facet: &FacetRequest{ 202 Field: "Date_Range_Success_With_StartEnd", 203 Size: 1, 204 DateTimeRanges: []*dateTimeRange{ 205 &dateTimeRange{Name: "testName", Start: time.Unix(0, 0), End: time.Now()}, 206 }, 207 }, 208 result: nil, 209 }, 210 { 211 facet: &FacetRequest{ 212 Field: "Date_Range_Success_With_Start", 213 Size: 1, 214 DateTimeRanges: []*dateTimeRange{ 215 &dateTimeRange{Name: "testName", Start: time.Unix(0, 0)}, 216 }, 217 }, 218 result: nil, 219 }, 220 { 221 facet: &FacetRequest{ 222 Field: "Date_Range_Success_With_End", 223 Size: 1, 224 DateTimeRanges: []*dateTimeRange{ 225 &dateTimeRange{Name: "testName", End: time.Now()}, 226 }, 227 }, 228 result: nil, 229 }, 230 { 231 facet: &FacetRequest{ 232 Field: "Numeric_Range_Success_With_MinMax", 233 Size: 1, 234 NumericRanges: []*numericRange{ 235 &numericRange{Name: "testName", Min: &value, Max: &value}, 236 }, 237 }, 238 result: nil, 239 }, 240 { 241 facet: &FacetRequest{ 242 Field: "Numeric_Range_Success_With_Min", 243 Size: 1, 244 NumericRanges: []*numericRange{ 245 &numericRange{Name: "testName", Min: &value}, 246 }, 247 }, 248 result: nil, 249 }, 250 { 251 facet: &FacetRequest{ 252 Field: "Numeric_Range_Success_With_Max", 253 Size: 1, 254 NumericRanges: []*numericRange{ 255 &numericRange{Name: "testName", Max: &value}, 256 }, 257 }, 258 result: nil, 259 }, 260 { 261 facet: &FacetRequest{ 262 Field: "Date_Range_Missing_Failure", 263 Size: 1, 264 DateTimeRanges: []*dateTimeRange{ 265 &dateTimeRange{Name: "testName2", Start: time.Unix(0, 0)}, 266 &dateTimeRange{Name: "testName1", End: time.Now()}, 267 &dateTimeRange{Name: "testName"}, 268 }, 269 }, 270 result: drMissingErr, 271 }, 272 { 273 facet: &FacetRequest{ 274 Field: "Numeric_Range_Missing_Failure", 275 Size: 1, 276 NumericRanges: []*numericRange{ 277 &numericRange{Name: "testName2", Min: &value}, 278 &numericRange{Name: "testName1", Max: &value}, 279 &numericRange{Name: "testName"}, 280 }, 281 }, 282 result: nrMissingErr, 283 }, 284 { 285 facet: &FacetRequest{ 286 Field: "Numeric_And_DateRanges_Failure", 287 Size: 1, 288 NumericRanges: []*numericRange{ 289 &numericRange{Name: "testName", Max: &value}, 290 }, 291 DateTimeRanges: []*dateTimeRange{ 292 &dateTimeRange{Name: "testName", End: time.Now()}, 293 }, 294 }, 295 result: drNrErr, 296 }, 297 { 298 facet: &FacetRequest{ 299 Field: "Numeric_Range_Name_Repeat_Failure", 300 Size: 1, 301 NumericRanges: []*numericRange{ 302 &numericRange{Name: "testName", Min: &value}, 303 &numericRange{Name: "testName", Max: &value}, 304 }, 305 }, 306 result: nrNameDupErr, 307 }, 308 { 309 facet: &FacetRequest{ 310 Field: "Date_Range_Name_Repeat_Failure", 311 Size: 1, 312 DateTimeRanges: []*dateTimeRange{ 313 &dateTimeRange{Name: "testName", Start: time.Unix(0, 0)}, 314 &dateTimeRange{Name: "testName", End: time.Now()}, 315 }, 316 }, 317 result: drNameDupErr, 318 }, 319 } 320 321 for _, test := range tests { 322 result := test.facet.Validate() 323 if !reflect.DeepEqual(result, test.result) { 324 t.Errorf("expected %#v, got %#v", test.result, result) 325 } 326 } 327 328} 329 330func TestSearchResultFacetsMerge(t *testing.T) { 331 lowmed := "2010-01-01" 332 medhi := "2011-01-01" 333 hihigher := "2012-01-01" 334 335 fr := &search.FacetResult{ 336 Field: "birthday", 337 Total: 100, 338 Missing: 25, 339 Other: 25, 340 DateRanges: []*search.DateRangeFacet{ 341 { 342 Name: "low", 343 End: &lowmed, 344 Count: 25, 345 }, 346 { 347 Name: "med", 348 Count: 24, 349 Start: &lowmed, 350 End: &medhi, 351 }, 352 { 353 Name: "hi", 354 Count: 1, 355 Start: &medhi, 356 End: &hihigher, 357 }, 358 }, 359 } 360 frs := search.FacetResults{ 361 "birthdays": fr, 362 } 363 364 l := &SearchResult{ 365 Status: &SearchStatus{ 366 Total: 10, 367 Successful: 1, 368 Errors: make(map[string]error), 369 }, 370 Total: 10, 371 MaxScore: 1, 372 } 373 374 r := &SearchResult{ 375 Status: &SearchStatus{ 376 Total: 1, 377 Successful: 1, 378 Errors: make(map[string]error), 379 }, 380 Total: 1, 381 MaxScore: 2, 382 Facets: frs, 383 } 384 385 expected := &SearchResult{ 386 Status: &SearchStatus{ 387 Total: 11, 388 Successful: 2, 389 Errors: make(map[string]error), 390 }, 391 Total: 11, 392 MaxScore: 2, 393 Facets: frs, 394 } 395 396 l.Merge(r) 397 398 if !reflect.DeepEqual(l, expected) { 399 t.Errorf("expected %#v, got %#v", expected, l) 400 } 401} 402