diff options
author | Christopher Speller <crspeller@gmail.com> | 2018-04-16 05:37:14 -0700 |
---|---|---|
committer | Joram Wilander <jwawilander@gmail.com> | 2018-04-16 08:37:14 -0400 |
commit | 6e2cb00008cbf09e556b00f87603797fcaa47e09 (patch) | |
tree | 3c0eb55ff4226a3f024aad373140d1fb860a6404 /vendor/google.golang.org/appengine/search/search_test.go | |
parent | bf24f51c4e1cc6286885460672f7f449e8c6f5ef (diff) | |
download | chat-6e2cb00008cbf09e556b00f87603797fcaa47e09.tar.gz chat-6e2cb00008cbf09e556b00f87603797fcaa47e09.tar.bz2 chat-6e2cb00008cbf09e556b00f87603797fcaa47e09.zip |
Depenancy upgrades and movign to dep. (#8630)
Diffstat (limited to 'vendor/google.golang.org/appengine/search/search_test.go')
-rw-r--r-- | vendor/google.golang.org/appengine/search/search_test.go | 1270 |
1 files changed, 0 insertions, 1270 deletions
diff --git a/vendor/google.golang.org/appengine/search/search_test.go b/vendor/google.golang.org/appengine/search/search_test.go deleted file mode 100644 index ef1409c19..000000000 --- a/vendor/google.golang.org/appengine/search/search_test.go +++ /dev/null @@ -1,1270 +0,0 @@ -// Copyright 2012 Google Inc. All rights reserved. -// Use of this source code is governed by the Apache 2.0 -// license that can be found in the LICENSE file. - -package search - -import ( - "errors" - "fmt" - "reflect" - "strings" - "testing" - "time" - - "github.com/golang/protobuf/proto" - - "google.golang.org/appengine" - "google.golang.org/appengine/internal/aetesting" - pb "google.golang.org/appengine/internal/search" -) - -type TestDoc struct { - String string - Atom Atom - HTML HTML - Float float64 - Location appengine.GeoPoint - Time time.Time -} - -type FieldListWithMeta struct { - Fields FieldList - Meta *DocumentMetadata -} - -func (f *FieldListWithMeta) Load(fields []Field, meta *DocumentMetadata) error { - f.Meta = meta - return f.Fields.Load(fields, nil) -} - -func (f *FieldListWithMeta) Save() ([]Field, *DocumentMetadata, error) { - fields, _, err := f.Fields.Save() - return fields, f.Meta, err -} - -// Assert that FieldListWithMeta satisfies FieldLoadSaver -var _ FieldLoadSaver = &FieldListWithMeta{} - -var ( - float = 3.14159 - floatOut = "3.14159e+00" - latitude = 37.3894 - longitude = 122.0819 - testGeo = appengine.GeoPoint{latitude, longitude} - testString = "foo<b>bar" - testTime = time.Unix(1337324400, 0) - testTimeOut = "1337324400000" - searchMeta = &DocumentMetadata{ - Rank: 42, - } - searchDoc = TestDoc{ - String: testString, - Atom: Atom(testString), - HTML: HTML(testString), - Float: float, - Location: testGeo, - Time: testTime, - } - searchFields = FieldList{ - Field{Name: "String", Value: testString}, - Field{Name: "Atom", Value: Atom(testString)}, - Field{Name: "HTML", Value: HTML(testString)}, - Field{Name: "Float", Value: float}, - Field{Name: "Location", Value: testGeo}, - Field{Name: "Time", Value: testTime}, - } - // searchFieldsWithLang is a copy of the searchFields with the Language field - // set on text/HTML Fields. - searchFieldsWithLang = FieldList{} - protoFields = []*pb.Field{ - newStringValueField("String", testString, pb.FieldValue_TEXT), - newStringValueField("Atom", testString, pb.FieldValue_ATOM), - newStringValueField("HTML", testString, pb.FieldValue_HTML), - newStringValueField("Float", floatOut, pb.FieldValue_NUMBER), - { - Name: proto.String("Location"), - Value: &pb.FieldValue{ - Geo: &pb.FieldValue_Geo{ - Lat: proto.Float64(latitude), - Lng: proto.Float64(longitude), - }, - Type: pb.FieldValue_GEO.Enum(), - }, - }, - newStringValueField("Time", testTimeOut, pb.FieldValue_DATE), - } -) - -func init() { - for _, f := range searchFields { - if f.Name == "String" || f.Name == "HTML" { - f.Language = "en" - } - searchFieldsWithLang = append(searchFieldsWithLang, f) - } -} - -func newStringValueField(name, value string, valueType pb.FieldValue_ContentType) *pb.Field { - return &pb.Field{ - Name: proto.String(name), - Value: &pb.FieldValue{ - StringValue: proto.String(value), - Type: valueType.Enum(), - }, - } -} - -func newFacet(name, value string, valueType pb.FacetValue_ContentType) *pb.Facet { - return &pb.Facet{ - Name: proto.String(name), - Value: &pb.FacetValue{ - StringValue: proto.String(value), - Type: valueType.Enum(), - }, - } -} - -func TestValidIndexNameOrDocID(t *testing.T) { - testCases := []struct { - s string - want bool - }{ - {"", true}, - {"!", false}, - {"$", true}, - {"!bad", false}, - {"good!", true}, - {"alsoGood", true}, - {"has spaces", false}, - {"is_inva\xffid_UTF-8", false}, - {"is_non-ASCïI", false}, - {"underscores_are_ok", true}, - } - for _, tc := range testCases { - if got := validIndexNameOrDocID(tc.s); got != tc.want { - t.Errorf("%q: got %v, want %v", tc.s, got, tc.want) - } - } -} - -func TestLoadDoc(t *testing.T) { - got, want := TestDoc{}, searchDoc - if err := loadDoc(&got, &pb.Document{Field: protoFields}, nil); err != nil { - t.Fatalf("loadDoc: %v", err) - } - if got != want { - t.Errorf("loadDoc: got %v, wanted %v", got, want) - } -} - -func TestSaveDoc(t *testing.T) { - got, err := saveDoc(&searchDoc) - if err != nil { - t.Fatalf("saveDoc: %v", err) - } - want := protoFields - if !reflect.DeepEqual(got.Field, want) { - t.Errorf("\ngot %v\nwant %v", got, want) - } -} - -func TestSaveDocUsesDefaultedRankIfNotSpecified(t *testing.T) { - got, err := saveDoc(&searchDoc) - if err != nil { - t.Fatalf("saveDoc: %v", err) - } - orderIdSource := got.GetOrderIdSource() - if orderIdSource != pb.Document_DEFAULTED { - t.Errorf("OrderIdSource: got %v, wanted DEFAULTED", orderIdSource) - } -} - -func TestLoadFieldList(t *testing.T) { - var got FieldList - want := searchFieldsWithLang - if err := loadDoc(&got, &pb.Document{Field: protoFields}, nil); err != nil { - t.Fatalf("loadDoc: %v", err) - } - if !reflect.DeepEqual(got, want) { - t.Errorf("\ngot %v\nwant %v", got, want) - } -} - -func TestLangFields(t *testing.T) { - fl := &FieldList{ - {Name: "Foo", Value: "I am English", Language: "en"}, - {Name: "Bar", Value: "私は日本人だ", Language: "jp"}, - } - var got FieldList - doc, err := saveDoc(fl) - if err != nil { - t.Fatalf("saveDoc: %v", err) - } - if err := loadDoc(&got, doc, nil); err != nil { - t.Fatalf("loadDoc: %v", err) - } - if want := fl; !reflect.DeepEqual(&got, want) { - t.Errorf("got %v\nwant %v", got, want) - } -} - -func TestSaveFieldList(t *testing.T) { - got, err := saveDoc(&searchFields) - if err != nil { - t.Fatalf("saveDoc: %v", err) - } - want := protoFields - if !reflect.DeepEqual(got.Field, want) { - t.Errorf("\ngot %v\nwant %v", got, want) - } -} - -func TestLoadFieldAndExprList(t *testing.T) { - var got, want FieldList - for i, f := range searchFieldsWithLang { - f.Derived = (i >= 2) // First 2 elements are "fields", next are "expressions". - want = append(want, f) - } - doc, expr := &pb.Document{Field: protoFields[:2]}, protoFields[2:] - if err := loadDoc(&got, doc, expr); err != nil { - t.Fatalf("loadDoc: %v", err) - } - if !reflect.DeepEqual(got, want) { - t.Errorf("got %v\nwant %v", got, want) - } -} - -func TestLoadMeta(t *testing.T) { - var got FieldListWithMeta - want := FieldListWithMeta{ - Meta: searchMeta, - Fields: searchFieldsWithLang, - } - doc := &pb.Document{ - Field: protoFields, - OrderId: proto.Int32(42), - OrderIdSource: pb.Document_SUPPLIED.Enum(), - } - if err := loadDoc(&got, doc, nil); err != nil { - t.Fatalf("loadDoc: %v", err) - } - if !reflect.DeepEqual(got, want) { - t.Errorf("\ngot %v\nwant %v", got, want) - } -} - -func TestSaveMeta(t *testing.T) { - got, err := saveDoc(&FieldListWithMeta{ - Meta: searchMeta, - Fields: searchFields, - }) - if err != nil { - t.Fatalf("saveDoc: %v", err) - } - want := &pb.Document{ - Field: protoFields, - OrderId: proto.Int32(42), - OrderIdSource: pb.Document_SUPPLIED.Enum(), - } - if !proto.Equal(got, want) { - t.Errorf("\ngot %v\nwant %v", got, want) - } -} - -func TestSaveMetaWithDefaultedRank(t *testing.T) { - metaWithoutRank := &DocumentMetadata{ - Rank: 0, - } - got, err := saveDoc(&FieldListWithMeta{ - Meta: metaWithoutRank, - Fields: searchFields, - }) - if err != nil { - t.Fatalf("saveDoc: %v", err) - } - want := &pb.Document{ - Field: protoFields, - OrderId: got.OrderId, - OrderIdSource: pb.Document_DEFAULTED.Enum(), - } - if !proto.Equal(got, want) { - t.Errorf("\ngot %v\nwant %v", got, want) - } -} - -func TestSaveWithoutMetaUsesDefaultedRank(t *testing.T) { - got, err := saveDoc(&FieldListWithMeta{ - Fields: searchFields, - }) - if err != nil { - t.Fatalf("saveDoc: %v", err) - } - want := &pb.Document{ - Field: protoFields, - OrderId: got.OrderId, - OrderIdSource: pb.Document_DEFAULTED.Enum(), - } - if !proto.Equal(got, want) { - t.Errorf("\ngot %v\nwant %v", got, want) - } -} - -func TestLoadSaveWithStruct(t *testing.T) { - type gopher struct { - Name string - Info string `search:"about"` - Legs float64 `search:",facet"` - Fuzz Atom `search:"Fur,facet"` - } - - doc := gopher{"Gopher", "Likes slide rules.", 4, Atom("furry")} - pb := &pb.Document{ - Field: []*pb.Field{ - newStringValueField("Name", "Gopher", pb.FieldValue_TEXT), - newStringValueField("about", "Likes slide rules.", pb.FieldValue_TEXT), - }, - Facet: []*pb.Facet{ - newFacet("Legs", "4e+00", pb.FacetValue_NUMBER), - newFacet("Fur", "furry", pb.FacetValue_ATOM), - }, - } - - var gotDoc gopher - if err := loadDoc(&gotDoc, pb, nil); err != nil { - t.Fatalf("loadDoc: %v", err) - } - if !reflect.DeepEqual(gotDoc, doc) { - t.Errorf("loading doc\ngot %v\nwant %v", gotDoc, doc) - } - - gotPB, err := saveDoc(&doc) - if err != nil { - t.Fatalf("saveDoc: %v", err) - } - gotPB.OrderId = nil // Don't test: it's time dependent. - gotPB.OrderIdSource = nil // Don't test because it's contingent on OrderId. - if !proto.Equal(gotPB, pb) { - t.Errorf("saving doc\ngot %v\nwant %v", gotPB, pb) - } -} - -func TestValidFieldNames(t *testing.T) { - testCases := []struct { - name string - valid bool - }{ - {"Normal", true}, - {"Also_OK_123", true}, - {"Not so great", false}, - {"lower_case", true}, - {"Exclaim!", false}, - {"Hello세상아 안녕", false}, - {"", false}, - {"Hεllo", false}, - {strings.Repeat("A", 500), true}, - {strings.Repeat("A", 501), false}, - } - - for _, tc := range testCases { - _, err := saveDoc(&FieldList{ - Field{Name: tc.name, Value: "val"}, - }) - if err != nil && !strings.Contains(err.Error(), "invalid field name") { - t.Errorf("unexpected err %q for field name %q", err, tc.name) - } - if (err == nil) != tc.valid { - t.Errorf("field %q: expected valid %t, received err %v", tc.name, tc.valid, err) - } - } -} - -func TestValidLangs(t *testing.T) { - testCases := []struct { - field Field - valid bool - }{ - {Field{Name: "Foo", Value: "String", Language: ""}, true}, - {Field{Name: "Foo", Value: "String", Language: "en"}, true}, - {Field{Name: "Foo", Value: "String", Language: "aussie"}, false}, - {Field{Name: "Foo", Value: "String", Language: "12"}, false}, - {Field{Name: "Foo", Value: HTML("String"), Language: "en"}, true}, - {Field{Name: "Foo", Value: Atom("String"), Language: "en"}, false}, - {Field{Name: "Foo", Value: 42, Language: "en"}, false}, - } - - for _, tt := range testCases { - _, err := saveDoc(&FieldList{tt.field}) - if err == nil != tt.valid { - t.Errorf("Field %v, got error %v, wanted valid %t", tt.field, err, tt.valid) - } - } -} - -func TestDuplicateFields(t *testing.T) { - testCases := []struct { - desc string - fields FieldList - errMsg string // Non-empty if we expect an error - }{ - { - desc: "multi string", - fields: FieldList{{Name: "FieldA", Value: "val1"}, {Name: "FieldA", Value: "val2"}, {Name: "FieldA", Value: "val3"}}, - }, - { - desc: "multi atom", - fields: FieldList{{Name: "FieldA", Value: Atom("val1")}, {Name: "FieldA", Value: Atom("val2")}, {Name: "FieldA", Value: Atom("val3")}}, - }, - { - desc: "mixed", - fields: FieldList{{Name: "FieldA", Value: testString}, {Name: "FieldA", Value: testTime}, {Name: "FieldA", Value: float}}, - }, - { - desc: "multi time", - fields: FieldList{{Name: "FieldA", Value: testTime}, {Name: "FieldA", Value: testTime}}, - errMsg: `duplicate time field "FieldA"`, - }, - { - desc: "multi num", - fields: FieldList{{Name: "FieldA", Value: float}, {Name: "FieldA", Value: float}}, - errMsg: `duplicate numeric field "FieldA"`, - }, - } - for _, tc := range testCases { - _, err := saveDoc(&tc.fields) - if (err == nil) != (tc.errMsg == "") || (err != nil && !strings.Contains(err.Error(), tc.errMsg)) { - t.Errorf("%s: got err %v, wanted %q", tc.desc, err, tc.errMsg) - } - } -} - -func TestLoadErrFieldMismatch(t *testing.T) { - testCases := []struct { - desc string - dst interface{} - src []*pb.Field - err error - }{ - { - desc: "missing", - dst: &struct{ One string }{}, - src: []*pb.Field{newStringValueField("Two", "woop!", pb.FieldValue_TEXT)}, - err: &ErrFieldMismatch{ - FieldName: "Two", - Reason: "no such struct field", - }, - }, - { - desc: "wrong type", - dst: &struct{ Num float64 }{}, - src: []*pb.Field{newStringValueField("Num", "woop!", pb.FieldValue_TEXT)}, - err: &ErrFieldMismatch{ - FieldName: "Num", - Reason: "type mismatch: float64 for string data", - }, - }, - { - desc: "unsettable", - dst: &struct{ lower string }{}, - src: []*pb.Field{newStringValueField("lower", "woop!", pb.FieldValue_TEXT)}, - err: &ErrFieldMismatch{ - FieldName: "lower", - Reason: "cannot set struct field", - }, - }, - } - for _, tc := range testCases { - err := loadDoc(tc.dst, &pb.Document{Field: tc.src}, nil) - if !reflect.DeepEqual(err, tc.err) { - t.Errorf("%s, got err %v, wanted %v", tc.desc, err, tc.err) - } - } -} - -func TestLimit(t *testing.T) { - index, err := Open("Doc") - if err != nil { - t.Fatalf("err from Open: %v", err) - } - c := aetesting.FakeSingleContext(t, "search", "Search", func(req *pb.SearchRequest, res *pb.SearchResponse) error { - limit := 20 // Default per page. - if req.Params.Limit != nil { - limit = int(*req.Params.Limit) - } - res.Status = &pb.RequestStatus{Code: pb.SearchServiceError_OK.Enum()} - res.MatchedCount = proto.Int64(int64(limit)) - for i := 0; i < limit; i++ { - res.Result = append(res.Result, &pb.SearchResult{Document: &pb.Document{}}) - res.Cursor = proto.String("moreresults") - } - return nil - }) - - const maxDocs = 500 // Limit maximum number of docs. - testCases := []struct { - limit, want int - }{ - {limit: 0, want: maxDocs}, - {limit: 42, want: 42}, - {limit: 100, want: 100}, - {limit: 1000, want: maxDocs}, - } - - for _, tt := range testCases { - it := index.Search(c, "gopher", &SearchOptions{Limit: tt.limit, IDsOnly: true}) - count := 0 - for ; count < maxDocs; count++ { - _, err := it.Next(nil) - if err == Done { - break - } - if err != nil { - t.Fatalf("err after %d: %v", count, err) - } - } - if count != tt.want { - t.Errorf("got %d results, expected %d", count, tt.want) - } - } -} - -func TestPut(t *testing.T) { - index, err := Open("Doc") - if err != nil { - t.Fatalf("err from Open: %v", err) - } - - c := aetesting.FakeSingleContext(t, "search", "IndexDocument", func(in *pb.IndexDocumentRequest, out *pb.IndexDocumentResponse) error { - expectedIn := &pb.IndexDocumentRequest{ - Params: &pb.IndexDocumentParams{ - Document: []*pb.Document{ - {Field: protoFields, OrderId: proto.Int32(42), OrderIdSource: pb.Document_SUPPLIED.Enum()}, - }, - IndexSpec: &pb.IndexSpec{ - Name: proto.String("Doc"), - }, - }, - } - if !proto.Equal(in, expectedIn) { - return fmt.Errorf("unsupported argument:\ngot %v\nwant %v", in, expectedIn) - } - *out = pb.IndexDocumentResponse{ - Status: []*pb.RequestStatus{ - {Code: pb.SearchServiceError_OK.Enum()}, - }, - DocId: []string{ - "doc_id", - }, - } - return nil - }) - - id, err := index.Put(c, "", &FieldListWithMeta{ - Meta: searchMeta, - Fields: searchFields, - }) - if err != nil { - t.Fatal(err) - } - if want := "doc_id"; id != want { - t.Errorf("Got doc ID %q, want %q", id, want) - } -} - -func TestPutAutoOrderID(t *testing.T) { - index, err := Open("Doc") - if err != nil { - t.Fatalf("err from Open: %v", err) - } - - c := aetesting.FakeSingleContext(t, "search", "IndexDocument", func(in *pb.IndexDocumentRequest, out *pb.IndexDocumentResponse) error { - if len(in.Params.GetDocument()) < 1 { - return fmt.Errorf("expected at least one Document, got %v", in) - } - got, want := in.Params.Document[0].GetOrderId(), int32(time.Since(orderIDEpoch).Seconds()) - if d := got - want; -5 > d || d > 5 { - return fmt.Errorf("got OrderId %d, want near %d", got, want) - } - *out = pb.IndexDocumentResponse{ - Status: []*pb.RequestStatus{ - {Code: pb.SearchServiceError_OK.Enum()}, - }, - DocId: []string{ - "doc_id", - }, - } - return nil - }) - - if _, err := index.Put(c, "", &searchFields); err != nil { - t.Fatal(err) - } -} - -func TestPutBadStatus(t *testing.T) { - index, err := Open("Doc") - if err != nil { - t.Fatalf("err from Open: %v", err) - } - - c := aetesting.FakeSingleContext(t, "search", "IndexDocument", func(_ *pb.IndexDocumentRequest, out *pb.IndexDocumentResponse) error { - *out = pb.IndexDocumentResponse{ - Status: []*pb.RequestStatus{ - { - Code: pb.SearchServiceError_INVALID_REQUEST.Enum(), - ErrorDetail: proto.String("insufficient gophers"), - }, - }, - } - return nil - }) - - wantErr := "search: INVALID_REQUEST: insufficient gophers" - if _, err := index.Put(c, "", &searchFields); err == nil || err.Error() != wantErr { - t.Fatalf("Put: got %v error, want %q", err, wantErr) - } -} - -func TestPutMultiNilIDSlice(t *testing.T) { - index, err := Open("Doc") - if err != nil { - t.Fatalf("err from Open: %v", err) - } - - c := aetesting.FakeSingleContext(t, "search", "IndexDocument", func(in *pb.IndexDocumentRequest, out *pb.IndexDocumentResponse) error { - if len(in.Params.GetDocument()) < 1 { - return fmt.Errorf("got %v, want at least 1 document", in) - } - got, want := in.Params.Document[0].GetOrderId(), int32(time.Since(orderIDEpoch).Seconds()) - if d := got - want; -5 > d || d > 5 { - return fmt.Errorf("got OrderId %d, want near %d", got, want) - } - *out = pb.IndexDocumentResponse{ - Status: []*pb.RequestStatus{ - {Code: pb.SearchServiceError_OK.Enum()}, - }, - DocId: []string{ - "doc_id", - }, - } - return nil - }) - - if _, err := index.PutMulti(c, nil, []interface{}{&searchFields}); err != nil { - t.Fatal(err) - } -} - -func TestPutMultiError(t *testing.T) { - index, err := Open("Doc") - if err != nil { - t.Fatalf("err from Open: %v", err) - } - - c := aetesting.FakeSingleContext(t, "search", "IndexDocument", func(in *pb.IndexDocumentRequest, out *pb.IndexDocumentResponse) error { - *out = pb.IndexDocumentResponse{ - Status: []*pb.RequestStatus{ - {Code: pb.SearchServiceError_OK.Enum()}, - {Code: pb.SearchServiceError_PERMISSION_DENIED.Enum(), ErrorDetail: proto.String("foo")}, - }, - DocId: []string{ - "id1", - "", - }, - } - return nil - }) - - switch _, err := index.PutMulti(c, nil, []interface{}{&searchFields, &searchFields}); { - case err == nil: - t.Fatalf("got nil, want error") - case err.(appengine.MultiError)[0] != nil: - t.Fatalf("got %v, want nil MultiError[0]", err.(appengine.MultiError)[0]) - case err.(appengine.MultiError)[1] == nil: - t.Fatalf("got nil, want not-nill MultiError[1]") - } -} - -func TestPutMultiWrongNumberOfIDs(t *testing.T) { - index, err := Open("Doc") - if err != nil { - t.Fatalf("err from Open: %v", err) - } - - c := aetesting.FakeSingleContext(t, "search", "IndexDocument", func(in *pb.IndexDocumentRequest, out *pb.IndexDocumentResponse) error { - return nil - }) - - if _, err := index.PutMulti(c, []string{"a"}, []interface{}{&searchFields, &searchFields}); err == nil { - t.Fatal("got success, want error") - } -} - -func TestPutMultiTooManyDocs(t *testing.T) { - index, err := Open("Doc") - if err != nil { - t.Fatalf("err from Open: %v", err) - } - - c := aetesting.FakeSingleContext(t, "search", "IndexDocument", func(in *pb.IndexDocumentRequest, out *pb.IndexDocumentResponse) error { - return nil - }) - - srcs := make([]interface{}, 201) - for i, _ := range srcs { - srcs[i] = &searchFields - } - - if _, err := index.PutMulti(c, nil, srcs); err != ErrTooManyDocuments { - t.Fatalf("got %v, want ErrTooManyDocuments", err) - } -} - -func TestSortOptions(t *testing.T) { - index, err := Open("Doc") - if err != nil { - t.Fatalf("err from Open: %v", err) - } - - noErr := errors.New("") // Sentinel err to return to prevent sending request. - - testCases := []struct { - desc string - sort *SortOptions - wantSort []*pb.SortSpec - wantScorer *pb.ScorerSpec - wantErr string - }{ - { - desc: "No SortOptions", - }, - { - desc: "Basic", - sort: &SortOptions{ - Expressions: []SortExpression{ - {Expr: "dog"}, - {Expr: "cat", Reverse: true}, - {Expr: "gopher", Default: "blue"}, - {Expr: "fish", Default: 2.0}, - }, - Limit: 42, - Scorer: MatchScorer, - }, - wantSort: []*pb.SortSpec{ - {SortExpression: proto.String("dog")}, - {SortExpression: proto.String("cat"), SortDescending: proto.Bool(false)}, - {SortExpression: proto.String("gopher"), DefaultValueText: proto.String("blue")}, - {SortExpression: proto.String("fish"), DefaultValueNumeric: proto.Float64(2)}, - }, - wantScorer: &pb.ScorerSpec{ - Limit: proto.Int32(42), - Scorer: pb.ScorerSpec_MATCH_SCORER.Enum(), - }, - }, - { - desc: "Bad expression default", - sort: &SortOptions{ - Expressions: []SortExpression{ - {Expr: "dog", Default: true}, - }, - }, - wantErr: `search: invalid Default type bool for expression "dog"`, - }, - { - desc: "RescoringMatchScorer", - sort: &SortOptions{Scorer: RescoringMatchScorer}, - wantScorer: &pb.ScorerSpec{Scorer: pb.ScorerSpec_RESCORING_MATCH_SCORER.Enum()}, - }, - } - - for _, tt := range testCases { - c := aetesting.FakeSingleContext(t, "search", "Search", func(req *pb.SearchRequest, _ *pb.SearchResponse) error { - params := req.Params - if !reflect.DeepEqual(params.SortSpec, tt.wantSort) { - t.Errorf("%s: params.SortSpec=%v; want %v", tt.desc, params.SortSpec, tt.wantSort) - } - if !reflect.DeepEqual(params.ScorerSpec, tt.wantScorer) { - t.Errorf("%s: params.ScorerSpec=%v; want %v", tt.desc, params.ScorerSpec, tt.wantScorer) - } - return noErr // Always return some error to prevent response parsing. - }) - - it := index.Search(c, "gopher", &SearchOptions{Sort: tt.sort}) - _, err := it.Next(nil) - if err == nil { - t.Fatalf("%s: err==nil; should not happen", tt.desc) - } - if err.Error() != tt.wantErr { - t.Errorf("%s: got error %q, want %q", tt.desc, err, tt.wantErr) - } - } -} - -func TestFieldSpec(t *testing.T) { - index, err := Open("Doc") - if err != nil { - t.Fatalf("err from Open: %v", err) - } - - errFoo := errors.New("foo") // sentinel error when there isn't one. - - testCases := []struct { - desc string - opts *SearchOptions - want *pb.FieldSpec - }{ - { - desc: "No options", - want: &pb.FieldSpec{}, - }, - { - desc: "Fields", - opts: &SearchOptions{ - Fields: []string{"one", "two"}, - }, - want: &pb.FieldSpec{ - Name: []string{"one", "two"}, - }, - }, - { - desc: "Expressions", - opts: &SearchOptions{ - Expressions: []FieldExpression{ - {Name: "one", Expr: "price * quantity"}, - {Name: "two", Expr: "min(daily_use, 10) * rate"}, - }, - }, - want: &pb.FieldSpec{ - Expression: []*pb.FieldSpec_Expression{ - {Name: proto.String("one"), Expression: proto.String("price * quantity")}, - {Name: proto.String("two"), Expression: proto.String("min(daily_use, 10) * rate")}, - }, - }, - }, - } - - for _, tt := range testCases { - c := aetesting.FakeSingleContext(t, "search", "Search", func(req *pb.SearchRequest, _ *pb.SearchResponse) error { - params := req.Params - if !reflect.DeepEqual(params.FieldSpec, tt.want) { - t.Errorf("%s: params.FieldSpec=%v; want %v", tt.desc, params.FieldSpec, tt.want) - } - return errFoo // Always return some error to prevent response parsing. - }) - - it := index.Search(c, "gopher", tt.opts) - if _, err := it.Next(nil); err != errFoo { - t.Fatalf("%s: got error %v; want %v", tt.desc, err, errFoo) - } - } -} - -func TestBasicSearchOpts(t *testing.T) { - index, err := Open("Doc") - if err != nil { - t.Fatalf("err from Open: %v", err) - } - - noErr := errors.New("") // Sentinel err to return to prevent sending request. - - testCases := []struct { - desc string - facetOpts []FacetSearchOption - cursor Cursor - offset int - countAccuracy int - want *pb.SearchParams - wantErr string - }{ - { - desc: "No options", - want: &pb.SearchParams{}, - }, - { - desc: "Default auto discovery", - facetOpts: []FacetSearchOption{ - AutoFacetDiscovery(0, 0), - }, - want: &pb.SearchParams{ - AutoDiscoverFacetCount: proto.Int32(10), - }, - }, - { - desc: "Auto discovery", - facetOpts: []FacetSearchOption{ - AutoFacetDiscovery(7, 12), - }, - want: &pb.SearchParams{ - AutoDiscoverFacetCount: proto.Int32(7), - FacetAutoDetectParam: &pb.FacetAutoDetectParam{ - ValueLimit: proto.Int32(12), - }, - }, - }, - { - desc: "Param Depth", - facetOpts: []FacetSearchOption{ - AutoFacetDiscovery(7, 12), - }, - want: &pb.SearchParams{ - AutoDiscoverFacetCount: proto.Int32(7), - FacetAutoDetectParam: &pb.FacetAutoDetectParam{ - ValueLimit: proto.Int32(12), - }, - }, - }, - { - desc: "Doc depth", - facetOpts: []FacetSearchOption{ - FacetDocumentDepth(123), - }, - want: &pb.SearchParams{ - FacetDepth: proto.Int32(123), - }, - }, - { - desc: "Facet discovery", - facetOpts: []FacetSearchOption{ - FacetDiscovery("colour"), - FacetDiscovery("size", Atom("M"), Atom("L")), - FacetDiscovery("price", LessThan(7), Range{7, 14}, AtLeast(14)), - }, - want: &pb.SearchParams{ - IncludeFacet: []*pb.FacetRequest{ - {Name: proto.String("colour")}, - {Name: proto.String("size"), Params: &pb.FacetRequestParam{ - ValueConstraint: []string{"M", "L"}, - }}, - {Name: proto.String("price"), Params: &pb.FacetRequestParam{ - Range: []*pb.FacetRange{ - {End: proto.String("7e+00")}, - {Start: proto.String("7e+00"), End: proto.String("1.4e+01")}, - {Start: proto.String("1.4e+01")}, - }, - }}, - }, - }, - }, - { - desc: "Facet discovery - bad value", - facetOpts: []FacetSearchOption{ - FacetDiscovery("colour", true), - }, - wantErr: "bad FacetSearchOption: unsupported value type bool", - }, - { - desc: "Facet discovery - mix value types", - facetOpts: []FacetSearchOption{ - FacetDiscovery("colour", Atom("blue"), AtLeast(7)), - }, - wantErr: "bad FacetSearchOption: values must all be Atom, or must all be Range", - }, - { - desc: "Facet discovery - invalid range", - facetOpts: []FacetSearchOption{ - FacetDiscovery("colour", Range{negInf, posInf}), - }, - wantErr: "bad FacetSearchOption: invalid range: either Start or End must be finite", - }, - { - desc: "Cursor", - cursor: Cursor("mycursor"), - want: &pb.SearchParams{ - Cursor: proto.String("mycursor"), - }, - }, - { - desc: "Offset", - offset: 121, - want: &pb.SearchParams{ - Offset: proto.Int32(121), - }, - }, - { - desc: "Cursor and Offset set", - cursor: Cursor("mycursor"), - offset: 121, - wantErr: "at most one of Cursor and Offset may be specified", - }, - { - desc: "Count accuracy", - countAccuracy: 100, - want: &pb.SearchParams{ - MatchedCountAccuracy: proto.Int32(100), - }, - }, - } - - for _, tt := range testCases { - c := aetesting.FakeSingleContext(t, "search", "Search", func(req *pb.SearchRequest, _ *pb.SearchResponse) error { - if tt.want == nil { - t.Errorf("%s: expected call to fail", tt.desc) - return nil - } - // Set default fields. - tt.want.Query = proto.String("gopher") - tt.want.IndexSpec = &pb.IndexSpec{Name: proto.String("Doc")} - tt.want.CursorType = pb.SearchParams_PER_RESULT.Enum() - tt.want.FieldSpec = &pb.FieldSpec{} - if got := req.Params; !reflect.DeepEqual(got, tt.want) { - t.Errorf("%s: params=%v; want %v", tt.desc, got, tt.want) - } - return noErr // Always return some error to prevent response parsing. - }) - - it := index.Search(c, "gopher", &SearchOptions{ - Facets: tt.facetOpts, - Cursor: tt.cursor, - Offset: tt.offset, - CountAccuracy: tt.countAccuracy, - }) - _, err := it.Next(nil) - if err == nil { - t.Fatalf("%s: err==nil; should not happen", tt.desc) - } - if err.Error() != tt.wantErr { - t.Errorf("%s: got error %q, want %q", tt.desc, err, tt.wantErr) - } - } -} - -func TestFacetRefinements(t *testing.T) { - index, err := Open("Doc") - if err != nil { - t.Fatalf("err from Open: %v", err) - } - - noErr := errors.New("") // Sentinel err to return to prevent sending request. - - testCases := []struct { - desc string - refine []Facet - want []*pb.FacetRefinement - wantErr string - }{ - { - desc: "No refinements", - }, - { - desc: "Basic", - refine: []Facet{ - {Name: "fur", Value: Atom("fluffy")}, - {Name: "age", Value: LessThan(123)}, - {Name: "age", Value: AtLeast(0)}, - {Name: "legs", Value: Range{Start: 3, End: 5}}, - }, - want: []*pb.FacetRefinement{ - {Name: proto.String("fur"), Value: proto.String("fluffy")}, - {Name: proto.String("age"), Range: &pb.FacetRefinement_Range{End: proto.String("1.23e+02")}}, - {Name: proto.String("age"), Range: &pb.FacetRefinement_Range{Start: proto.String("0e+00")}}, - {Name: proto.String("legs"), Range: &pb.FacetRefinement_Range{Start: proto.String("3e+00"), End: proto.String("5e+00")}}, - }, - }, - { - desc: "Infinite range", - refine: []Facet{ - {Name: "age", Value: Range{Start: negInf, End: posInf}}, - }, - wantErr: `search: refinement for facet "age": either Start or End must be finite`, - }, - { - desc: "Bad End value in range", - refine: []Facet{ - {Name: "age", Value: LessThan(2147483648)}, - }, - wantErr: `search: refinement for facet "age": invalid value for End`, - }, - { - desc: "Bad Start value in range", - refine: []Facet{ - {Name: "age", Value: AtLeast(-2147483649)}, - }, - wantErr: `search: refinement for facet "age": invalid value for Start`, - }, - { - desc: "Unknown value type", - refine: []Facet{ - {Name: "age", Value: "you can't use strings!"}, - }, - wantErr: `search: unsupported refinement for facet "age" of type string`, - }, - } - - for _, tt := range testCases { - c := aetesting.FakeSingleContext(t, "search", "Search", func(req *pb.SearchRequest, _ *pb.SearchResponse) error { - if got := req.Params.FacetRefinement; !reflect.DeepEqual(got, tt.want) { - t.Errorf("%s: params.FacetRefinement=%v; want %v", tt.desc, got, tt.want) - } - return noErr // Always return some error to prevent response parsing. - }) - - it := index.Search(c, "gopher", &SearchOptions{Refinements: tt.refine}) - _, err := it.Next(nil) - if err == nil { - t.Fatalf("%s: err==nil; should not happen", tt.desc) - } - if err.Error() != tt.wantErr { - t.Errorf("%s: got error %q, want %q", tt.desc, err, tt.wantErr) - } - } -} - -func TestNamespaceResetting(t *testing.T) { - namec := make(chan *string, 1) - c0 := aetesting.FakeSingleContext(t, "search", "IndexDocument", func(req *pb.IndexDocumentRequest, res *pb.IndexDocumentResponse) error { - namec <- req.Params.IndexSpec.Namespace - return fmt.Errorf("RPC error") - }) - - // Check that wrapping c0 in a namespace twice works correctly. - c1, err := appengine.Namespace(c0, "A") - if err != nil { - t.Fatalf("appengine.Namespace: %v", err) - } - c2, err := appengine.Namespace(c1, "") // should act as the original context - if err != nil { - t.Fatalf("appengine.Namespace: %v", err) - } - - i := (&Index{}) - - i.Put(c0, "something", &searchDoc) - if ns := <-namec; ns != nil { - t.Errorf(`Put with c0: ns = %q, want nil`, *ns) - } - - i.Put(c1, "something", &searchDoc) - if ns := <-namec; ns == nil { - t.Error(`Put with c1: ns = nil, want "A"`) - } else if *ns != "A" { - t.Errorf(`Put with c1: ns = %q, want "A"`, *ns) - } - - i.Put(c2, "something", &searchDoc) - if ns := <-namec; ns != nil { - t.Errorf(`Put with c2: ns = %q, want nil`, *ns) - } -} - -func TestDelete(t *testing.T) { - index, err := Open("Doc") - if err != nil { - t.Fatalf("err from Open: %v", err) - } - - c := aetesting.FakeSingleContext(t, "search", "DeleteDocument", func(in *pb.DeleteDocumentRequest, out *pb.DeleteDocumentResponse) error { - expectedIn := &pb.DeleteDocumentRequest{ - Params: &pb.DeleteDocumentParams{ - DocId: []string{"id"}, - IndexSpec: &pb.IndexSpec{Name: proto.String("Doc")}, - }, - } - if !proto.Equal(in, expectedIn) { - return fmt.Errorf("unsupported argument:\ngot %v\nwant %v", in, expectedIn) - } - *out = pb.DeleteDocumentResponse{ - Status: []*pb.RequestStatus{ - {Code: pb.SearchServiceError_OK.Enum()}, - }, - } - return nil - }) - - if err := index.Delete(c, "id"); err != nil { - t.Fatal(err) - } -} - -func TestDeleteMulti(t *testing.T) { - index, err := Open("Doc") - if err != nil { - t.Fatalf("err from Open: %v", err) - } - - c := aetesting.FakeSingleContext(t, "search", "DeleteDocument", func(in *pb.DeleteDocumentRequest, out *pb.DeleteDocumentResponse) error { - expectedIn := &pb.DeleteDocumentRequest{ - Params: &pb.DeleteDocumentParams{ - DocId: []string{"id1", "id2"}, - IndexSpec: &pb.IndexSpec{Name: proto.String("Doc")}, - }, - } - if !proto.Equal(in, expectedIn) { - return fmt.Errorf("unsupported argument:\ngot %v\nwant %v", in, expectedIn) - } - *out = pb.DeleteDocumentResponse{ - Status: []*pb.RequestStatus{ - {Code: pb.SearchServiceError_OK.Enum()}, - {Code: pb.SearchServiceError_OK.Enum()}, - }, - } - return nil - }) - - if err := index.DeleteMulti(c, []string{"id1", "id2"}); err != nil { - t.Fatal(err) - } -} - -func TestDeleteWrongNumberOfResults(t *testing.T) { - index, err := Open("Doc") - if err != nil { - t.Fatalf("err from Open: %v", err) - } - - c := aetesting.FakeSingleContext(t, "search", "DeleteDocument", func(in *pb.DeleteDocumentRequest, out *pb.DeleteDocumentResponse) error { - expectedIn := &pb.DeleteDocumentRequest{ - Params: &pb.DeleteDocumentParams{ - DocId: []string{"id1", "id2"}, - IndexSpec: &pb.IndexSpec{Name: proto.String("Doc")}, - }, - } - if !proto.Equal(in, expectedIn) { - return fmt.Errorf("unsupported argument:\ngot %v\nwant %v", in, expectedIn) - } - *out = pb.DeleteDocumentResponse{ - Status: []*pb.RequestStatus{ - {Code: pb.SearchServiceError_OK.Enum()}, - }, - } - return nil - }) - - if err := index.DeleteMulti(c, []string{"id1", "id2"}); err == nil { - t.Fatalf("got nil, want error") - } -} - -func TestDeleteMultiError(t *testing.T) { - index, err := Open("Doc") - if err != nil { - t.Fatalf("err from Open: %v", err) - } - - c := aetesting.FakeSingleContext(t, "search", "DeleteDocument", func(in *pb.DeleteDocumentRequest, out *pb.DeleteDocumentResponse) error { - expectedIn := &pb.DeleteDocumentRequest{ - Params: &pb.DeleteDocumentParams{ - DocId: []string{"id1", "id2"}, - IndexSpec: &pb.IndexSpec{Name: proto.String("Doc")}, - }, - } - if !proto.Equal(in, expectedIn) { - return fmt.Errorf("unsupported argument:\ngot %v\nwant %v", in, expectedIn) - } - *out = pb.DeleteDocumentResponse{ - Status: []*pb.RequestStatus{ - {Code: pb.SearchServiceError_OK.Enum()}, - {Code: pb.SearchServiceError_PERMISSION_DENIED.Enum(), ErrorDetail: proto.String("foo")}, - }, - } - return nil - }) - - switch err := index.DeleteMulti(c, []string{"id1", "id2"}); { - case err == nil: - t.Fatalf("got nil, want error") - case err.(appengine.MultiError)[0] != nil: - t.Fatalf("got %v, want nil MultiError[0]", err.(appengine.MultiError)[0]) - case err.(appengine.MultiError)[1] == nil: - t.Fatalf("got nil, want not-nill MultiError[1]") - } -} |