github.com/weaviate/weaviate@v1.24.6/adapters/handlers/graphql/local/explore/concepts_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 explore
    13  
    14  import (
    15  	"testing"
    16  
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  	"github.com/weaviate/weaviate/entities/search"
    20  	"github.com/weaviate/weaviate/entities/searchparams"
    21  	helper "github.com/weaviate/weaviate/test/helper"
    22  	"github.com/weaviate/weaviate/usecases/traverser"
    23  )
    24  
    25  type testCase struct {
    26  	name                      string
    27  	query                     string
    28  	expectedParamsToTraverser traverser.ExploreParams
    29  	resolverReturn            []search.Result
    30  	expectedResults           []result
    31  }
    32  
    33  type testCases []testCase
    34  
    35  type result struct {
    36  	pathToField   []string
    37  	expectedValue interface{}
    38  }
    39  
    40  func Test_ResolveExplore(t *testing.T) {
    41  	t.Parallel()
    42  
    43  	testsNearText := testCases{
    44  		testCase{
    45  			name: "Resolve Explore with nearCustomText",
    46  			query: `
    47  			{
    48  					Explore(nearCustomText: {concepts: ["car", "best brand"]}) {
    49  							beacon className certainty distance
    50  					}
    51  			}`,
    52  			expectedParamsToTraverser: traverser.ExploreParams{
    53  				ModuleParams: map[string]interface{}{
    54  					"nearCustomText": extractNearCustomTextParam(map[string]interface{}{
    55  						"concepts": []interface{}{"car", "best brand"},
    56  					}),
    57  				},
    58  				WithCertaintyProp: true,
    59  			},
    60  			resolverReturn: []search.Result{
    61  				{
    62  					Beacon:    "weaviate://localhost/some-uuid",
    63  					ClassName: "bestClass",
    64  					Certainty: 0.7,
    65  					Dist:      helper.CertaintyToDist(t, 0.7),
    66  				},
    67  			},
    68  			expectedResults: []result{{
    69  				pathToField: []string{"Explore"},
    70  				expectedValue: []interface{}{
    71  					map[string]interface{}{
    72  						"beacon":    "weaviate://localhost/some-uuid",
    73  						"className": "bestClass",
    74  						"certainty": float32(0.7),
    75  						"distance":  helper.CertaintyToDist(t, 0.7),
    76  					},
    77  				},
    78  			}},
    79  		},
    80  
    81  		testCase{
    82  			name: "with nearCustomText with optional limit and distance set",
    83  			query: `
    84  			{
    85  					Explore(
    86  						nearCustomText: {concepts: ["car", "best brand"], distance: 0.4}, limit: 17
    87  						){
    88  							beacon className
    89  				}
    90  			}`,
    91  			expectedParamsToTraverser: traverser.ExploreParams{
    92  				ModuleParams: map[string]interface{}{
    93  					"nearCustomText": extractNearCustomTextParam(map[string]interface{}{
    94  						"concepts": []interface{}{"car", "best brand"},
    95  						"distance": float64(0.4),
    96  					}),
    97  				},
    98  				Limit: 17,
    99  			},
   100  			resolverReturn: []search.Result{
   101  				{
   102  					Beacon:    "weaviate://localhost/some-uuid",
   103  					ClassName: "bestClass",
   104  				},
   105  			},
   106  			expectedResults: []result{{
   107  				pathToField: []string{"Explore"},
   108  				expectedValue: []interface{}{
   109  					map[string]interface{}{
   110  						"beacon":    "weaviate://localhost/some-uuid",
   111  						"className": "bestClass",
   112  					},
   113  				},
   114  			}},
   115  		},
   116  
   117  		testCase{
   118  			name: "with nearCustomText with optional limit and certainty set",
   119  			query: `
   120  			{
   121  					Explore(
   122  						nearCustomText: {concepts: ["car", "best brand"], certainty: 0.6}, limit: 17
   123  						){
   124  							beacon className
   125  				}
   126  			}`,
   127  			expectedParamsToTraverser: traverser.ExploreParams{
   128  				ModuleParams: map[string]interface{}{
   129  					"nearCustomText": extractNearCustomTextParam(map[string]interface{}{
   130  						"concepts":  []interface{}{"car", "best brand"},
   131  						"certainty": float64(0.6),
   132  					}),
   133  				},
   134  				Limit: 17,
   135  			},
   136  			resolverReturn: []search.Result{
   137  				{
   138  					Beacon:    "weaviate://localhost/some-uuid",
   139  					ClassName: "bestClass",
   140  				},
   141  			},
   142  			expectedResults: []result{{
   143  				pathToField: []string{"Explore"},
   144  				expectedValue: []interface{}{
   145  					map[string]interface{}{
   146  						"beacon":    "weaviate://localhost/some-uuid",
   147  						"className": "bestClass",
   148  					},
   149  				},
   150  			}},
   151  		},
   152  
   153  		testCase{
   154  			name: "with moveTo set",
   155  			query: `
   156  			{
   157  					Explore(
   158  							limit: 17
   159  							nearCustomText: {
   160  								concepts: ["car", "best brand"]
   161  								moveTo: {
   162  									concepts: ["mercedes"]
   163  									force: 0.7
   164  								}
   165  							}
   166  							) {
   167  							beacon className
   168  						}
   169  			}`,
   170  			expectedParamsToTraverser: traverser.ExploreParams{
   171  				Limit: 17,
   172  				ModuleParams: map[string]interface{}{
   173  					"nearCustomText": extractNearCustomTextParam(map[string]interface{}{
   174  						"concepts": []interface{}{"car", "best brand"},
   175  						"moveTo": map[string]interface{}{
   176  							"concepts": []interface{}{"mercedes"},
   177  							"force":    float64(0.7),
   178  						},
   179  					}),
   180  				},
   181  			},
   182  			resolverReturn: []search.Result{
   183  				{
   184  					Beacon:    "weaviate://localhost/some-uuid",
   185  					ClassName: "bestClass",
   186  				},
   187  			},
   188  			expectedResults: []result{{
   189  				pathToField: []string{"Explore"},
   190  				expectedValue: []interface{}{
   191  					map[string]interface{}{
   192  						"beacon":    "weaviate://localhost/some-uuid",
   193  						"className": "bestClass",
   194  					},
   195  				},
   196  			}},
   197  		},
   198  
   199  		testCase{
   200  			name: "with moveTo and moveAwayFrom set",
   201  			query: `
   202  			{
   203  					Explore(
   204  							limit: 17
   205  							nearCustomText: {
   206  								concepts: ["car", "best brand"]
   207  								moveTo: {
   208  									concepts: ["mercedes"]
   209  									force: 0.7
   210  								}
   211  								moveAwayFrom: {
   212  									concepts: ["van"]
   213  									force: 0.7
   214  								}
   215  							}
   216  							) {
   217  							beacon className
   218  						}
   219  			}`,
   220  			expectedParamsToTraverser: traverser.ExploreParams{
   221  				Limit: 17,
   222  				ModuleParams: map[string]interface{}{
   223  					"nearCustomText": extractNearCustomTextParam(map[string]interface{}{
   224  						"concepts": []interface{}{"car", "best brand"},
   225  						"moveTo": map[string]interface{}{
   226  							"concepts": []interface{}{"mercedes"},
   227  							"force":    float64(0.7),
   228  						},
   229  						"moveAwayFrom": map[string]interface{}{
   230  							"concepts": []interface{}{"van"},
   231  							"force":    float64(0.7),
   232  						},
   233  					}),
   234  				},
   235  			},
   236  			resolverReturn: []search.Result{
   237  				{
   238  					Beacon:    "weaviate://localhost/some-uuid",
   239  					ClassName: "bestClass",
   240  				},
   241  			},
   242  			expectedResults: []result{{
   243  				pathToField: []string{"Explore"},
   244  				expectedValue: []interface{}{
   245  					map[string]interface{}{
   246  						"beacon":    "weaviate://localhost/some-uuid",
   247  						"className": "bestClass",
   248  					},
   249  				},
   250  			}},
   251  		},
   252  
   253  		testCase{
   254  			name: "with moveTo and objects set",
   255  			query: `
   256  			{
   257  					Explore(
   258  							limit: 17
   259  							nearCustomText: {
   260  								concepts: ["car", "best brand"]
   261  								moveTo: {
   262  									concepts: ["mercedes"]
   263  									force: 0.7
   264  									objects: [
   265  										{id: "moveto-uuid"},
   266  										{beacon: "weaviate://localhost/other-moveto-uuid"},
   267  									]
   268  								}
   269  							}
   270  							) {
   271  							beacon className
   272  						}
   273  			}`,
   274  			expectedParamsToTraverser: traverser.ExploreParams{
   275  				Limit: 17,
   276  				ModuleParams: map[string]interface{}{
   277  					"nearCustomText": extractNearCustomTextParam(map[string]interface{}{
   278  						"concepts": []interface{}{"car", "best brand"},
   279  						"moveTo": map[string]interface{}{
   280  							"concepts": []interface{}{"mercedes"},
   281  							"force":    float64(0.7),
   282  							"objects": []interface{}{
   283  								map[string]interface{}{
   284  									"id": "moveto-uuid",
   285  								},
   286  								map[string]interface{}{
   287  									"beacon": "weaviate://localhost/other-moveto-uuid",
   288  								},
   289  							},
   290  						},
   291  					}),
   292  				},
   293  			},
   294  			resolverReturn: []search.Result{
   295  				{
   296  					Beacon:    "weaviate://localhost/some-uuid",
   297  					ClassName: "bestClass",
   298  				},
   299  			},
   300  			expectedResults: []result{{
   301  				pathToField: []string{"Explore"},
   302  				expectedValue: []interface{}{
   303  					map[string]interface{}{
   304  						"beacon":    "weaviate://localhost/some-uuid",
   305  						"className": "bestClass",
   306  					},
   307  				},
   308  			}},
   309  		},
   310  
   311  		testCase{
   312  			name: "with moveTo and objects set",
   313  			query: `
   314  			{
   315  					Explore(
   316  							limit: 17
   317  							nearCustomText: {
   318  								concepts: ["car", "best brand"]
   319  								moveTo: {
   320  									concepts: ["mercedes"]
   321  									force: 0.7
   322  									objects: [
   323  										{id: "moveto-uuid1"},
   324  										{beacon: "weaviate://localhost/moveto-uuid2"},
   325  									]
   326  								}
   327  								moveAwayFrom: {
   328  									concepts: ["van"]
   329  									force: 0.7
   330  									objects: [
   331  										{id: "moveAway-uuid1"},
   332  										{beacon: "weaviate://localhost/moveAway-uuid2"},
   333  										{id: "moveAway-uuid3"},
   334  										{id: "moveAway-uuid4"},
   335  									]
   336  								}
   337  							}
   338  							) {
   339  							beacon className
   340  						}
   341  			}`,
   342  			expectedParamsToTraverser: traverser.ExploreParams{
   343  				Limit: 17,
   344  				ModuleParams: map[string]interface{}{
   345  					"nearCustomText": extractNearCustomTextParam(map[string]interface{}{
   346  						"concepts": []interface{}{"car", "best brand"},
   347  						"moveTo": map[string]interface{}{
   348  							"concepts": []interface{}{"mercedes"},
   349  							"force":    float64(0.7),
   350  							"objects": []interface{}{
   351  								map[string]interface{}{
   352  									"id": "moveto-uuid1",
   353  								},
   354  								map[string]interface{}{
   355  									"beacon": "weaviate://localhost/moveto-uuid2",
   356  								},
   357  							},
   358  						},
   359  						"moveAwayFrom": map[string]interface{}{
   360  							"concepts": []interface{}{"van"},
   361  							"force":    float64(0.7),
   362  							"objects": []interface{}{
   363  								map[string]interface{}{
   364  									"id": "moveAway-uuid1",
   365  								},
   366  								map[string]interface{}{
   367  									"beacon": "weaviate://localhost/moveAway-uuid2",
   368  								},
   369  								map[string]interface{}{
   370  									"id": "moveAway-uuid3",
   371  								},
   372  								map[string]interface{}{
   373  									"id": "moveAway-uuid4",
   374  								},
   375  							},
   376  						},
   377  					}),
   378  				},
   379  			},
   380  			resolverReturn: []search.Result{
   381  				{
   382  					Beacon:    "weaviate://localhost/some-uuid",
   383  					ClassName: "bestClass",
   384  				},
   385  			},
   386  			expectedResults: []result{{
   387  				pathToField: []string{"Explore"},
   388  				expectedValue: []interface{}{
   389  					map[string]interface{}{
   390  						"beacon":    "weaviate://localhost/some-uuid",
   391  						"className": "bestClass",
   392  					},
   393  				},
   394  			}},
   395  		},
   396  	}
   397  
   398  	tests := testCases{
   399  		testCase{
   400  			name: "Resolve Explore with nearVector",
   401  			query: `
   402  			{
   403  					Explore(nearVector: {vector: [0, 1, 0.8]}) {
   404  							beacon className certainty distance
   405  					}
   406  			}`,
   407  			expectedParamsToTraverser: traverser.ExploreParams{
   408  				NearVector: &searchparams.NearVector{
   409  					Vector: []float32{0, 1, 0.8},
   410  				},
   411  				WithCertaintyProp: true,
   412  			},
   413  			resolverReturn: []search.Result{
   414  				{
   415  					Beacon:    "weaviate://localhost/some-uuid",
   416  					ClassName: "bestClass",
   417  					Certainty: 0.7,
   418  					Dist:      helper.CertaintyToDist(t, 0.7),
   419  				},
   420  			},
   421  			expectedResults: []result{{
   422  				pathToField: []string{"Explore"},
   423  				expectedValue: []interface{}{
   424  					map[string]interface{}{
   425  						"beacon":    "weaviate://localhost/some-uuid",
   426  						"className": "bestClass",
   427  						"certainty": float32(0.7),
   428  						"distance":  helper.CertaintyToDist(t, 0.7),
   429  					},
   430  				},
   431  			}},
   432  		},
   433  
   434  		testCase{
   435  			name: "with nearVector with optional limit",
   436  			query: `
   437  			{
   438  					Explore(limit: 17, nearVector: {vector: [0, 1, 0.8]}) {
   439  							beacon className certainty distance
   440  					}
   441  			}`,
   442  			expectedParamsToTraverser: traverser.ExploreParams{
   443  				NearVector: &searchparams.NearVector{
   444  					Vector: []float32{0, 1, 0.8},
   445  				},
   446  				Limit:             17,
   447  				WithCertaintyProp: true,
   448  			},
   449  			resolverReturn: []search.Result{
   450  				{
   451  					Beacon:    "weaviate://localhost/some-uuid",
   452  					ClassName: "bestClass",
   453  					Certainty: 0.7,
   454  					Dist:      helper.CertaintyToDist(t, 0.7),
   455  				},
   456  			},
   457  			expectedResults: []result{{
   458  				pathToField: []string{"Explore"},
   459  				expectedValue: []interface{}{
   460  					map[string]interface{}{
   461  						"beacon":    "weaviate://localhost/some-uuid",
   462  						"className": "bestClass",
   463  						"certainty": float32(0.7),
   464  						"distance":  helper.CertaintyToDist(t, 0.7),
   465  					},
   466  				},
   467  			}},
   468  		},
   469  
   470  		testCase{
   471  			name: "Resolve Explore with nearObject, distance, and beacon set",
   472  			query: `
   473  			{
   474  				Explore(
   475  					nearObject: {
   476  						beacon: "weaviate://localhost/27b5213d-e152-4fea-bd63-2063d529024d"
   477  						distance: 0.3
   478  					}) {
   479  						beacon className certainty distance
   480  					}
   481  			}`,
   482  			expectedParamsToTraverser: traverser.ExploreParams{
   483  				NearObject: &searchparams.NearObject{
   484  					Beacon:       "weaviate://localhost/27b5213d-e152-4fea-bd63-2063d529024d",
   485  					Distance:     float64(0.3),
   486  					WithDistance: true,
   487  				},
   488  				WithCertaintyProp: true,
   489  			},
   490  			resolverReturn: []search.Result{
   491  				{
   492  					Beacon:    "weaviate://localhost/27b5213d-e152-4fea-bd63-2063d529024d",
   493  					ClassName: "bestClass",
   494  					Certainty: 0.7,
   495  					Dist:      helper.CertaintyToDist(t, 0.7),
   496  				},
   497  			},
   498  			expectedResults: []result{{
   499  				pathToField: []string{"Explore"},
   500  				expectedValue: []interface{}{
   501  					map[string]interface{}{
   502  						"beacon":    "weaviate://localhost/27b5213d-e152-4fea-bd63-2063d529024d",
   503  						"className": "bestClass",
   504  						"certainty": float32(0.7),
   505  						"distance":  helper.CertaintyToDist(t, 0.7),
   506  					},
   507  				},
   508  			}},
   509  		},
   510  
   511  		testCase{
   512  			name: "Resolve Explore with nearObject, certainty, and beacon set",
   513  			query: `
   514  			{
   515  				Explore(
   516  					nearObject: {
   517  						beacon: "weaviate://localhost/27b5213d-e152-4fea-bd63-2063d529024d"
   518  						certainty: 0.7
   519  					}) {
   520  						beacon className certainty distance
   521  					}
   522  			}`,
   523  			expectedParamsToTraverser: traverser.ExploreParams{
   524  				NearObject: &searchparams.NearObject{
   525  					Beacon:    "weaviate://localhost/27b5213d-e152-4fea-bd63-2063d529024d",
   526  					Certainty: 0.7,
   527  				},
   528  				WithCertaintyProp: true,
   529  			},
   530  			resolverReturn: []search.Result{
   531  				{
   532  					Beacon:    "weaviate://localhost/27b5213d-e152-4fea-bd63-2063d529024d",
   533  					ClassName: "bestClass",
   534  					Certainty: 0.7,
   535  					Dist:      helper.CertaintyToDist(t, 0.7),
   536  				},
   537  			},
   538  			expectedResults: []result{{
   539  				pathToField: []string{"Explore"},
   540  				expectedValue: []interface{}{
   541  					map[string]interface{}{
   542  						"beacon":    "weaviate://localhost/27b5213d-e152-4fea-bd63-2063d529024d",
   543  						"className": "bestClass",
   544  						"certainty": float32(0.7),
   545  						"distance":  helper.CertaintyToDist(t, 0.7),
   546  					},
   547  				},
   548  			}},
   549  		},
   550  
   551  		testCase{
   552  			name: "Resolve Explore with nearObject, distance and id set",
   553  			query: `
   554  			{
   555  					Explore(
   556  							limit: 17
   557  							nearObject: {
   558  								id: "27b5213d-e152-4fea-bd63-2063d529024d"
   559  								distance: 0.3
   560  							}
   561  							) {
   562  							beacon className
   563  						}
   564  			}`,
   565  			expectedParamsToTraverser: traverser.ExploreParams{
   566  				Limit: 17,
   567  				NearObject: &searchparams.NearObject{
   568  					ID:           "27b5213d-e152-4fea-bd63-2063d529024d",
   569  					Distance:     0.3,
   570  					WithDistance: true,
   571  				},
   572  			},
   573  			resolverReturn: []search.Result{
   574  				{
   575  					Beacon:    "weaviate://localhost/some-uuid",
   576  					ClassName: "bestClass",
   577  				},
   578  			},
   579  			expectedResults: []result{{
   580  				pathToField: []string{"Explore"},
   581  				expectedValue: []interface{}{
   582  					map[string]interface{}{
   583  						"beacon":    "weaviate://localhost/some-uuid",
   584  						"className": "bestClass",
   585  					},
   586  				},
   587  			}},
   588  		},
   589  
   590  		testCase{
   591  			name: "Resolve Explore with nearObject, certainty and id set",
   592  			query: `
   593  			{
   594  					Explore(
   595  							limit: 17
   596  							nearObject: {
   597  								id: "27b5213d-e152-4fea-bd63-2063d529024d"
   598  								certainty: 0.7
   599  							}
   600  							) {
   601  							beacon className
   602  						}
   603  			}`,
   604  			expectedParamsToTraverser: traverser.ExploreParams{
   605  				Limit: 17,
   606  				NearObject: &searchparams.NearObject{
   607  					ID:        "27b5213d-e152-4fea-bd63-2063d529024d",
   608  					Certainty: 0.7,
   609  				},
   610  			},
   611  			resolverReturn: []search.Result{
   612  				{
   613  					Beacon:    "weaviate://localhost/some-uuid",
   614  					ClassName: "bestClass",
   615  				},
   616  			},
   617  			expectedResults: []result{{
   618  				pathToField: []string{"Explore"},
   619  				expectedValue: []interface{}{
   620  					map[string]interface{}{
   621  						"beacon":    "weaviate://localhost/some-uuid",
   622  						"className": "bestClass",
   623  					},
   624  				},
   625  			}},
   626  		},
   627  	}
   628  
   629  	tests.AssertExtraction(t, newMockResolver())
   630  	testsNearText.AssertExtraction(t, newMockResolver())
   631  	tests.AssertExtraction(t, newMockResolverNoModules())
   632  }
   633  
   634  func Test_ExploreWithNoText2VecClasses(t *testing.T) {
   635  	t.Run("with distance", func(t *testing.T) {
   636  		resolver := newMockResolverEmptySchema()
   637  		query := `
   638  		{
   639  				Explore(
   640  					nearCustomText: {concepts: ["car", "best brand"], distance: 0.6}, limit: 17
   641  					){
   642  						beacon className
   643  			}
   644  		}`
   645  		res := resolver.Resolve(query)
   646  		require.Len(t, res.Errors, 1)
   647  		assert.Contains(t, res.Errors[0].Message, "Unknown argument \"nearCustomText\" on field \"Explore\"")
   648  	})
   649  
   650  	t.Run("with certainty", func(t *testing.T) {
   651  		resolver := newMockResolverEmptySchema()
   652  		query := `
   653  		{
   654  				Explore(
   655  					nearCustomText: {concepts: ["car", "best brand"], certainty: 0.6}, limit: 17
   656  					){
   657  						beacon className
   658  			}
   659  		}`
   660  		res := resolver.Resolve(query)
   661  		require.Len(t, res.Errors, 1)
   662  		assert.Contains(t, res.Errors[0].Message, "Unknown argument \"nearCustomText\" on field \"Explore\"")
   663  	})
   664  }
   665  
   666  func Test_ExploreWithNoModules(t *testing.T) {
   667  	t.Run("with distance", func(t *testing.T) {
   668  		resolver := newMockResolverNoModules()
   669  		query := `
   670  	{
   671  			Explore(
   672  				nearCustomText: {concepts: ["car", "best brand"], distance: 0.6}, limit: 17
   673  				){
   674  					beacon className
   675  		}
   676  	}`
   677  		res := resolver.Resolve(query)
   678  		require.Len(t, res.Errors, 1)
   679  		assert.Contains(t, res.Errors[0].Message, "Unknown argument \"nearCustomText\" on field \"Explore\"")
   680  	})
   681  
   682  	t.Run("with certainty", func(t *testing.T) {
   683  		resolver := newMockResolverNoModules()
   684  		query := `
   685  	{
   686  			Explore(
   687  				nearCustomText: {concepts: ["car", "best brand"], certainty: 0.6}, limit: 17
   688  				){
   689  					beacon className
   690  		}
   691  	}`
   692  		res := resolver.Resolve(query)
   693  		require.Len(t, res.Errors, 1)
   694  		assert.Contains(t, res.Errors[0].Message, "Unknown argument \"nearCustomText\" on field \"Explore\"")
   695  	})
   696  }
   697  
   698  func (tests testCases) AssertExtraction(t *testing.T, resolver *mockResolver) {
   699  	for _, testCase := range tests {
   700  		t.Run(testCase.name, func(t *testing.T) {
   701  			resolver.On("Explore", testCase.expectedParamsToTraverser).
   702  				Return(testCase.resolverReturn, nil).Once()
   703  
   704  			result := resolver.AssertResolve(t, testCase.query)
   705  
   706  			for _, expectedResult := range testCase.expectedResults {
   707  				value := result.Get(expectedResult.pathToField...).Result
   708  
   709  				assert.Equal(t, expectedResult.expectedValue, value)
   710  			}
   711  		})
   712  	}
   713  }