github.com/weaviate/weaviate@v1.24.6/usecases/traverser/explorer_validate_sort_test.go (about)

     1  //                           _       _
     2  // __      _____  __ ___   ___  __ _| |_ ___
     3  // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \
     4  //  \ V  V /  __/ (_| |\ V /| | (_| | ||  __/
     5  //   \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___|
     6  //
     7  //  Copyright © 2016 - 2024 Weaviate B.V. All rights reserved.
     8  //
     9  //  CONTACT: hello@weaviate.io
    10  //
    11  
    12  package traverser
    13  
    14  import (
    15  	"context"
    16  	"errors"
    17  	"testing"
    18  
    19  	testLogger "github.com/sirupsen/logrus/hooks/test"
    20  	"github.com/stretchr/testify/assert"
    21  	"github.com/stretchr/testify/mock"
    22  	"github.com/stretchr/testify/require"
    23  	"github.com/weaviate/weaviate/entities/dto"
    24  	"github.com/weaviate/weaviate/entities/filters"
    25  	"github.com/weaviate/weaviate/entities/search"
    26  	"github.com/weaviate/weaviate/entities/searchparams"
    27  )
    28  
    29  func Test_Explorer_GetClass_WithSort(t *testing.T) {
    30  	type testData struct {
    31  		name          string
    32  		params        dto.GetParams
    33  		expectedError error
    34  	}
    35  
    36  	oneSortFilter := []testData{
    37  		{
    38  			name: "invalid order parameter",
    39  			params: dto.GetParams{
    40  				ClassName: "ClassOne",
    41  				Sort:      []filters.Sort{{Path: nil, Order: "asce"}},
    42  			},
    43  			expectedError: errors.New(`invalid 'sort' parameter: sort parameter at position 0: ` +
    44  				`invalid order parameter, possible values are: ["asc", "desc"] not: "asce"`),
    45  		},
    46  		{
    47  			name: "empty path",
    48  			params: dto.GetParams{
    49  				ClassName: "ClassOne",
    50  				Sort:      []filters.Sort{{Path: nil, Order: "asc"}},
    51  			},
    52  			expectedError: errors.New("invalid 'sort' parameter: sort parameter at position 0: " +
    53  				"path parameter cannot be empty"),
    54  		},
    55  		{
    56  			name: "non-existent class",
    57  			params: dto.GetParams{
    58  				ClassName: "NonExistentClass",
    59  				Sort:      []filters.Sort{{Path: []string{"property"}, Order: "asc"}},
    60  			},
    61  			expectedError: errors.New("invalid 'sort' parameter: sort parameter at position 0: " +
    62  				"class \"NonExistentClass\" does not exist in schema"),
    63  		},
    64  		{
    65  			name: "non-existent property in class",
    66  			params: dto.GetParams{
    67  				ClassName: "ClassOne",
    68  				Sort:      []filters.Sort{{Path: []string{"nonexistentproperty"}, Order: "asc"}},
    69  			},
    70  			expectedError: errors.New("invalid 'sort' parameter: sort parameter at position 0: " +
    71  				"no such prop with name 'nonexistentproperty' found in class 'ClassOne' in the schema. " +
    72  				"Check your schema files for which properties in this class are available"),
    73  		},
    74  		{
    75  			name: "reference property in class",
    76  			params: dto.GetParams{
    77  				ClassName: "ClassOne",
    78  				Sort:      []filters.Sort{{Path: []string{"ref_prop"}, Order: "asc"}},
    79  			},
    80  			expectedError: errors.New("invalid 'sort' parameter: sort parameter at position 0: " +
    81  				"sorting by reference not supported, " +
    82  				"property \"ref_prop\" is a ref prop to the class \"ClassTwo\""),
    83  		},
    84  		{
    85  			name: "reference property path",
    86  			params: dto.GetParams{
    87  				ClassName: "ClassOne",
    88  				Sort:      []filters.Sort{{Path: []string{"ref", "prop"}, Order: "asc"}},
    89  			},
    90  			expectedError: errors.New("invalid 'sort' parameter: sort parameter at position 0: " +
    91  				"sorting by reference not supported, " +
    92  				"path must have exactly one argument"),
    93  		},
    94  		{
    95  			name: "invalid order parameter",
    96  			params: dto.GetParams{
    97  				ClassName: "ClassOne",
    98  				Sort:      []filters.Sort{{Path: nil, Order: "asce"}},
    99  			},
   100  			expectedError: errors.New(`invalid 'sort' parameter: sort parameter at position 0: ` +
   101  				`invalid order parameter, possible values are: ["asc", "desc"] not: "asce"`),
   102  		},
   103  	}
   104  
   105  	twoSortFilters := []testData{
   106  		{
   107  			name: "invalid order parameter",
   108  			params: dto.GetParams{
   109  				ClassName: "ClassOne",
   110  				Sort: []filters.Sort{
   111  					{Path: nil, Order: "asce"},
   112  					{Path: nil, Order: "desce"},
   113  				},
   114  			},
   115  			expectedError: errors.New(`invalid 'sort' parameter: ` +
   116  				`sort parameter at position 0: ` +
   117  				`invalid order parameter, possible values are: ["asc", "desc"] not: "asce", ` +
   118  				`sort parameter at position 1: ` +
   119  				`invalid order parameter, possible values are: ["asc", "desc"] not: "desce"`),
   120  		},
   121  		{
   122  			name: "empty path",
   123  			params: dto.GetParams{
   124  				ClassName: "ClassOne",
   125  				Sort: []filters.Sort{
   126  					{Path: nil, Order: "asc"},
   127  					{Path: []string{}, Order: "asc"},
   128  				},
   129  			},
   130  			expectedError: errors.New("invalid 'sort' parameter: " +
   131  				"sort parameter at position 0: path parameter cannot be empty, " +
   132  				"sort parameter at position 1: path parameter cannot be empty"),
   133  		},
   134  		{
   135  			name: "non-existent class",
   136  			params: dto.GetParams{
   137  				ClassName: "NonExistentClass",
   138  				Sort: []filters.Sort{
   139  					{Path: []string{"property"}, Order: "asc"},
   140  					{Path: []string{"property"}, Order: "asc"},
   141  				},
   142  			},
   143  			expectedError: errors.New("invalid 'sort' parameter: " +
   144  				"sort parameter at position 0: " +
   145  				"class \"NonExistentClass\" does not exist in schema, " +
   146  				"sort parameter at position 1: " +
   147  				"class \"NonExistentClass\" does not exist in schema"),
   148  		},
   149  		{
   150  			name: "non-existent property in class",
   151  			params: dto.GetParams{
   152  				ClassName: "ClassOne",
   153  				Sort: []filters.Sort{
   154  					{Path: []string{"nonexistentproperty1"}, Order: "asc"},
   155  					{Path: []string{"nonexistentproperty2"}, Order: "asc"},
   156  				},
   157  			},
   158  			expectedError: errors.New("invalid 'sort' parameter: " +
   159  				"sort parameter at position 0: " +
   160  				"no such prop with name 'nonexistentproperty1' found in class 'ClassOne' in the schema. " +
   161  				"Check your schema files for which properties in this class are available, " +
   162  				"sort parameter at position 1: " +
   163  				"no such prop with name 'nonexistentproperty2' found in class 'ClassOne' in the schema. " +
   164  				"Check your schema files for which properties in this class are available"),
   165  		},
   166  		{
   167  			name: "reference property in class",
   168  			params: dto.GetParams{
   169  				ClassName: "ClassOne",
   170  				Sort: []filters.Sort{
   171  					{Path: []string{"ref_prop"}, Order: "asc"},
   172  					{Path: []string{"ref_prop"}, Order: "desc"},
   173  				},
   174  			},
   175  			expectedError: errors.New("invalid 'sort' parameter: " +
   176  				"sort parameter at position 0: " +
   177  				"sorting by reference not supported, " +
   178  				"property \"ref_prop\" is a ref prop to the class \"ClassTwo\", " +
   179  				"sort parameter at position 1: " +
   180  				"sorting by reference not supported, " +
   181  				"property \"ref_prop\" is a ref prop to the class \"ClassTwo\""),
   182  		},
   183  		{
   184  			name: "reference property path",
   185  			params: dto.GetParams{
   186  				ClassName: "ClassOne",
   187  				Sort: []filters.Sort{
   188  					{Path: []string{"ref", "prop"}, Order: "asc"},
   189  					{Path: []string{"ref", "prop"}, Order: "asc"},
   190  				},
   191  			},
   192  			expectedError: errors.New("invalid 'sort' parameter: " +
   193  				"sort parameter at position 0: " +
   194  				"sorting by reference not supported, " +
   195  				"path must have exactly one argument, " +
   196  				"sort parameter at position 1: " +
   197  				"sorting by reference not supported, " +
   198  				"path must have exactly one argument"),
   199  		},
   200  		{
   201  			name: "reference properties path",
   202  			params: dto.GetParams{
   203  				ClassName: "ClassOne",
   204  				Sort: []filters.Sort{
   205  					{Path: []string{"ref_prop"}, Order: "asc"},
   206  					{Path: []string{"ref", "prop"}, Order: "asc"},
   207  				},
   208  			},
   209  			expectedError: errors.New("invalid 'sort' parameter: " +
   210  				"sort parameter at position 0: " +
   211  				"sorting by reference not supported, " +
   212  				"property \"ref_prop\" is a ref prop to the class \"ClassTwo\", " +
   213  				"sort parameter at position 1: " +
   214  				"sorting by reference not supported, " +
   215  				"path must have exactly one argument"),
   216  		},
   217  		{
   218  			name: "reference properties path",
   219  			params: dto.GetParams{
   220  				ClassName: "ClassOne",
   221  				Sort: []filters.Sort{
   222  					{Path: []string{"ref_prop"}, Order: "asc"},
   223  					{Path: []string{"ref", "prop"}, Order: "asc"},
   224  				},
   225  			},
   226  			expectedError: errors.New("invalid 'sort' parameter: " +
   227  				"sort parameter at position 0: " +
   228  				"sorting by reference not supported, " +
   229  				"property \"ref_prop\" is a ref prop to the class \"ClassTwo\", " +
   230  				"sort parameter at position 1: " +
   231  				"sorting by reference not supported, " +
   232  				"path must have exactly one argument"),
   233  		},
   234  	}
   235  
   236  	oneOfTwoSortFilters := []testData{
   237  		{
   238  			name: "invalid order parameter",
   239  			params: dto.GetParams{
   240  				ClassName: "ClassOne",
   241  				Sort: []filters.Sort{
   242  					{Path: []string{"text_prop"}, Order: "asc"},
   243  					{Path: nil, Order: "desce"},
   244  				},
   245  			},
   246  			expectedError: errors.New(`invalid 'sort' parameter: ` +
   247  				`sort parameter at position 1: ` +
   248  				`invalid order parameter, possible values are: ["asc", "desc"] not: "desce"`),
   249  		},
   250  		{
   251  			name: "empty path",
   252  			params: dto.GetParams{
   253  				ClassName: "ClassOne",
   254  				Sort: []filters.Sort{
   255  					{Path: []string{"text_prop"}, Order: "asc"},
   256  					{Path: []string{}, Order: "asc"},
   257  				},
   258  			},
   259  			expectedError: errors.New("invalid 'sort' parameter: " +
   260  				"sort parameter at position 1: path parameter cannot be empty"),
   261  		},
   262  		{
   263  			name: "non-existent property in class",
   264  			params: dto.GetParams{
   265  				ClassName: "ClassOne",
   266  				Sort: []filters.Sort{
   267  					{Path: []string{"text_prop"}, Order: "asc"},
   268  					{Path: []string{"nonexistentproperty2"}, Order: "asc"},
   269  				},
   270  			},
   271  			expectedError: errors.New("invalid 'sort' parameter: " +
   272  				"sort parameter at position 1: " +
   273  				"no such prop with name 'nonexistentproperty2' found in class 'ClassOne' in the schema. " +
   274  				"Check your schema files for which properties in this class are available"),
   275  		},
   276  		{
   277  			name: "reference property in class",
   278  			params: dto.GetParams{
   279  				ClassName: "ClassOne",
   280  				Sort: []filters.Sort{
   281  					{Path: []string{"text_prop"}, Order: "asc"},
   282  					{Path: []string{"ref_prop"}, Order: "desc"},
   283  				},
   284  			},
   285  			expectedError: errors.New("invalid 'sort' parameter: " +
   286  				"sort parameter at position 1: " +
   287  				"sorting by reference not supported, " +
   288  				"property \"ref_prop\" is a ref prop to the class \"ClassTwo\""),
   289  		},
   290  		{
   291  			name: "reference property path",
   292  			params: dto.GetParams{
   293  				ClassName: "ClassOne",
   294  				Sort: []filters.Sort{
   295  					{Path: []string{"text_prop"}, Order: "asc"},
   296  					{Path: []string{"ref", "prop"}, Order: "asc"},
   297  				},
   298  			},
   299  			expectedError: errors.New("invalid 'sort' parameter: " +
   300  				"sort parameter at position 1: " +
   301  				"sorting by reference not supported, " +
   302  				"path must have exactly one argument"),
   303  		},
   304  		{
   305  			name: "reference properties path",
   306  			params: dto.GetParams{
   307  				ClassName: "ClassOne",
   308  				Sort: []filters.Sort{
   309  					{Path: []string{"text_prop"}, Order: "asc"},
   310  					{Path: []string{"ref_prop"}, Order: "asc"},
   311  					{Path: []string{"ref", "prop"}, Order: "asc"},
   312  				},
   313  			},
   314  			expectedError: errors.New("invalid 'sort' parameter: " +
   315  				"sort parameter at position 1: " +
   316  				"sorting by reference not supported, " +
   317  				"property \"ref_prop\" is a ref prop to the class \"ClassTwo\", " +
   318  				"sort parameter at position 2: " +
   319  				"sorting by reference not supported, " +
   320  				"path must have exactly one argument"),
   321  		},
   322  	}
   323  
   324  	properSortFilters := []testData{
   325  		{
   326  			name: "sort by text_prop",
   327  			params: dto.GetParams{
   328  				ClassName: "ClassOne",
   329  				NearVector: &searchparams.NearVector{
   330  					Vector: []float32{0.8, 0.2, 0.7},
   331  				},
   332  				Sort: []filters.Sort{
   333  					{Path: []string{"text_prop"}, Order: "asc"},
   334  				},
   335  			},
   336  		},
   337  	}
   338  
   339  	testCases := []struct {
   340  		name     string
   341  		testData []testData
   342  	}{
   343  		{
   344  			name:     "one sort filter broken",
   345  			testData: oneSortFilter,
   346  		},
   347  		{
   348  			name:     "two sort filters broken",
   349  			testData: twoSortFilters,
   350  		},
   351  		{
   352  			name:     "one of two sort filters broken",
   353  			testData: oneOfTwoSortFilters,
   354  		},
   355  		{
   356  			name:     "proper sort filters",
   357  			testData: properSortFilters,
   358  		},
   359  	}
   360  
   361  	for _, tc := range testCases {
   362  		t.Run(tc.name, func(t *testing.T) {
   363  			for _, td := range tc.testData {
   364  				t.Run(td.name, func(t *testing.T) {
   365  					params := td.params
   366  					searchResults := []search.Result{
   367  						{
   368  							ID: "id1",
   369  							Schema: map[string]interface{}{
   370  								"name": "Foo",
   371  							},
   372  						},
   373  					}
   374  
   375  					search := &fakeVectorSearcher{}
   376  					sg := &fakeSchemaGetter{
   377  						schema: schemaForFiltersValidation(),
   378  					}
   379  					log, _ := testLogger.NewNullLogger()
   380  					metrics := &fakeMetrics{}
   381  					metrics.On("AddUsageDimensions", mock.Anything, mock.Anything, mock.Anything,
   382  						mock.Anything)
   383  					explorer := NewExplorer(search, log, getFakeModulesProvider(), metrics, defaultConfig)
   384  					explorer.SetSchemaGetter(sg)
   385  
   386  					if td.expectedError == nil {
   387  						search.
   388  							On("VectorSearch", mock.Anything).
   389  							Return(searchResults, nil)
   390  						res, err := explorer.GetClass(context.Background(), params)
   391  						assert.Nil(t, err)
   392  						search.AssertExpectations(t)
   393  						require.Len(t, res, 1)
   394  					} else {
   395  						_, err := explorer.GetClass(context.Background(), params)
   396  						require.NotNil(t, err)
   397  						assert.Equal(t, err.Error(), td.expectedError.Error())
   398  					}
   399  				})
   400  			}
   401  		})
   402  	}
   403  }