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