github.com/weaviate/weaviate@v1.24.6/modules/text2vec-contextionary/module_graphql_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 modcontextionary
    13  
    14  import (
    15  	"testing"
    16  
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  	"github.com/tailor-inc/graphql/language/ast"
    20  	"github.com/weaviate/weaviate/entities/additional"
    21  	"github.com/weaviate/weaviate/entities/dto"
    22  	"github.com/weaviate/weaviate/entities/filters"
    23  	"github.com/weaviate/weaviate/entities/search"
    24  	"github.com/weaviate/weaviate/modules/text2vec-contextionary/additional/models"
    25  	helper "github.com/weaviate/weaviate/test/helper"
    26  	"github.com/weaviate/weaviate/usecases/traverser"
    27  )
    28  
    29  type testCase struct {
    30  	name                      string
    31  	query                     string
    32  	expectedParamsToTraverser traverser.ExploreParams
    33  	resolverReturn            []search.Result
    34  	expectedResults           []result
    35  }
    36  
    37  type testCases []testCase
    38  
    39  type result struct {
    40  	pathToField   []string
    41  	expectedValue interface{}
    42  }
    43  
    44  func TestExtractAdditionalFields(t *testing.T) {
    45  	// We don't need to explicitly test every subselection as we did on
    46  	// phoneNumber as these fields have fixed keys. So we can simply check for
    47  	// the prop
    48  
    49  	type test struct {
    50  		name           string
    51  		query          string
    52  		expectedParams dto.GetParams
    53  		resolverReturn interface{}
    54  		expectedResult interface{}
    55  	}
    56  
    57  	tests := []test{
    58  		{
    59  			name:  "with _additional certainty",
    60  			query: "{ Get { SomeAction { _additional { certainty distance } } } }",
    61  			expectedParams: dto.GetParams{
    62  				ClassName: "SomeAction",
    63  				AdditionalProperties: additional.Properties{
    64  					Certainty: true,
    65  					Distance:  true,
    66  				},
    67  			},
    68  			resolverReturn: []interface{}{
    69  				map[string]interface{}{
    70  					"_additional": map[string]interface{}{
    71  						"certainty": 0.69,
    72  						"distance":  helper.CertaintyToDist(t, 0.69),
    73  					},
    74  				},
    75  			},
    76  			expectedResult: map[string]interface{}{
    77  				"_additional": map[string]interface{}{
    78  					"certainty": 0.69,
    79  					"distance":  helper.CertaintyToDist(t, 0.69),
    80  				},
    81  			},
    82  		},
    83  		{
    84  			name:  "with _additional interpretation",
    85  			query: "{ Get { SomeAction { _additional { interpretation { source { concept weight occurrence } }  } } } }",
    86  			expectedParams: dto.GetParams{
    87  				ClassName: "SomeAction",
    88  				AdditionalProperties: additional.Properties{
    89  					ModuleParams: map[string]interface{}{
    90  						"interpretation": true,
    91  					},
    92  				},
    93  			},
    94  			resolverReturn: []interface{}{
    95  				map[string]interface{}{
    96  					"_additional": map[string]interface{}{
    97  						"interpretation": &models.Interpretation{
    98  							Source: []*models.InterpretationSource{
    99  								{
   100  									Concept:    "foo",
   101  									Weight:     0.6,
   102  									Occurrence: 1200,
   103  								},
   104  								{
   105  									Concept:    "bar",
   106  									Weight:     0.9,
   107  									Occurrence: 800,
   108  								},
   109  							},
   110  						},
   111  					},
   112  				},
   113  			},
   114  			expectedResult: map[string]interface{}{
   115  				"_additional": map[string]interface{}{
   116  					"interpretation": map[string]interface{}{
   117  						"source": []interface{}{
   118  							map[string]interface{}{
   119  								"concept":    "foo",
   120  								"weight":     0.6,
   121  								"occurrence": 1200,
   122  							},
   123  							map[string]interface{}{
   124  								"concept":    "bar",
   125  								"weight":     0.9,
   126  								"occurrence": 800,
   127  							},
   128  						},
   129  					},
   130  				},
   131  			},
   132  		},
   133  		{
   134  			name:  "with _additional nearestNeighbors",
   135  			query: "{ Get { SomeAction { _additional { nearestNeighbors { neighbors { concept distance } }  } } } }",
   136  			expectedParams: dto.GetParams{
   137  				ClassName: "SomeAction",
   138  				AdditionalProperties: additional.Properties{
   139  					ModuleParams: map[string]interface{}{
   140  						"nearestNeighbors": true,
   141  					},
   142  				},
   143  			},
   144  			resolverReturn: []interface{}{
   145  				map[string]interface{}{
   146  					"_additional": map[string]interface{}{
   147  						"nearestNeighbors": &models.NearestNeighbors{
   148  							Neighbors: []*models.NearestNeighbor{
   149  								{
   150  									Concept:  "foo",
   151  									Distance: 0.1,
   152  								},
   153  								{
   154  									Concept:  "bar",
   155  									Distance: 0.2,
   156  								},
   157  							},
   158  						},
   159  					},
   160  				},
   161  			},
   162  			expectedResult: map[string]interface{}{
   163  				"_additional": map[string]interface{}{
   164  					"nearestNeighbors": map[string]interface{}{
   165  						"neighbors": []interface{}{
   166  							map[string]interface{}{
   167  								"concept":  "foo",
   168  								"distance": float32(0.1),
   169  							},
   170  							map[string]interface{}{
   171  								"concept":  "bar",
   172  								"distance": float32(0.2),
   173  							},
   174  						},
   175  					},
   176  				},
   177  			},
   178  		},
   179  		{
   180  			name:  "with _additional featureProjection without any optional parameters",
   181  			query: "{ Get { SomeAction { _additional { featureProjection { vector }  } } } }",
   182  			expectedParams: dto.GetParams{
   183  				ClassName: "SomeAction",
   184  				AdditionalProperties: additional.Properties{
   185  					ModuleParams: map[string]interface{}{
   186  						"featureProjection": extractAdditionalParam("featureProjection", nil),
   187  					},
   188  				},
   189  			},
   190  			resolverReturn: []interface{}{
   191  				map[string]interface{}{
   192  					"_additional": map[string]interface{}{
   193  						"featureProjection": &models.FeatureProjection{
   194  							Vector: []float32{0.0, 1.1, 2.2},
   195  						},
   196  					},
   197  				},
   198  			},
   199  			expectedResult: map[string]interface{}{
   200  				"_additional": map[string]interface{}{
   201  					"featureProjection": map[string]interface{}{
   202  						"vector": []interface{}{float32(0.0), float32(1.1), float32(2.2)},
   203  					},
   204  				},
   205  			},
   206  		},
   207  		{
   208  			name:  "with _additional featureProjection with optional parameters",
   209  			query: `{ Get { SomeAction { _additional { featureProjection(algorithm: "tsne", dimensions: 3, learningRate: 15, iterations: 100, perplexity: 10) { vector }  } } } }`,
   210  			expectedParams: dto.GetParams{
   211  				ClassName: "SomeAction",
   212  				AdditionalProperties: additional.Properties{
   213  					ModuleParams: map[string]interface{}{
   214  						"featureProjection": extractAdditionalParam("featureProjection",
   215  							[]*ast.Argument{
   216  								createArg("algorithm", "tsne"),
   217  								createArg("dimensions", "3"),
   218  								createArg("iterations", "100"),
   219  								createArg("learningRate", "15"),
   220  								createArg("perplexity", "10"),
   221  							},
   222  						),
   223  					},
   224  				},
   225  			},
   226  			resolverReturn: []interface{}{
   227  				map[string]interface{}{
   228  					"_additional": map[string]interface{}{
   229  						"featureProjection": &models.FeatureProjection{
   230  							Vector: []float32{0.0, 1.1, 2.2},
   231  						},
   232  					},
   233  				},
   234  			},
   235  			expectedResult: map[string]interface{}{
   236  				"_additional": map[string]interface{}{
   237  					"featureProjection": map[string]interface{}{
   238  						"vector": []interface{}{float32(0.0), float32(1.1), float32(2.2)},
   239  					},
   240  				},
   241  			},
   242  		},
   243  		{
   244  			name:  "with _additional semanticPath set",
   245  			query: `{ Get { SomeAction { _additional { semanticPath { path { concept distanceToQuery distanceToResult distanceToPrevious distanceToNext } } } } } }`,
   246  			expectedParams: dto.GetParams{
   247  				ClassName: "SomeAction",
   248  				AdditionalProperties: additional.Properties{
   249  					ModuleParams: map[string]interface{}{
   250  						"semanticPath": extractAdditionalParam("semanticPath", nil),
   251  					},
   252  				},
   253  			},
   254  			resolverReturn: []interface{}{
   255  				map[string]interface{}{
   256  					"_additional": map[string]interface{}{
   257  						"semanticPath": &models.SemanticPath{
   258  							Path: []*models.SemanticPathElement{
   259  								{
   260  									Concept:            "foo",
   261  									DistanceToNext:     ptFloat32(0.5),
   262  									DistanceToPrevious: nil,
   263  									DistanceToQuery:    0.1,
   264  									DistanceToResult:   0.1,
   265  								},
   266  								{
   267  									Concept:            "bar",
   268  									DistanceToPrevious: ptFloat32(0.5),
   269  									DistanceToNext:     nil,
   270  									DistanceToQuery:    0.1,
   271  									DistanceToResult:   0.1,
   272  								},
   273  							},
   274  						},
   275  					},
   276  				},
   277  			},
   278  			expectedResult: map[string]interface{}{
   279  				"_additional": map[string]interface{}{
   280  					"semanticPath": map[string]interface{}{
   281  						"path": []interface{}{
   282  							map[string]interface{}{
   283  								"concept":            "foo",
   284  								"distanceToNext":     float32(0.5),
   285  								"distanceToPrevious": nil,
   286  								"distanceToQuery":    float32(0.1),
   287  								"distanceToResult":   float32(0.1),
   288  							},
   289  							map[string]interface{}{
   290  								"concept":            "bar",
   291  								"distanceToPrevious": float32(0.5),
   292  								"distanceToNext":     nil,
   293  								"distanceToQuery":    float32(0.1),
   294  								"distanceToResult":   float32(0.1),
   295  							},
   296  						},
   297  					},
   298  				},
   299  			},
   300  		},
   301  	}
   302  
   303  	for _, test := range tests {
   304  		t.Run(test.name, func(t *testing.T) {
   305  			resolver := newMockResolver()
   306  
   307  			resolver.On("GetClass", test.expectedParams).
   308  				Return(test.resolverReturn, nil).Once()
   309  			result := resolver.AssertResolve(t, test.query)
   310  			assert.Equal(t, test.expectedResult, result.Get("Get", "SomeAction").Result.([]interface{})[0])
   311  		})
   312  	}
   313  }
   314  
   315  func TestNearTextRanker(t *testing.T) {
   316  	t.Parallel()
   317  
   318  	resolver := newMockResolver()
   319  
   320  	t.Run("for actions", func(t *testing.T) {
   321  		query := `{ Get { SomeAction(nearText: {
   322                  concepts: ["c1", "c2", "c3"],
   323  								moveTo: {
   324  									concepts:["positive"],
   325  									force: 0.5
   326  								},
   327  								moveAwayFrom: {
   328  									concepts:["epic"],
   329  									force: 0.25
   330  								}
   331  							}) { intField } } }`
   332  
   333  		expectedParams := dto.GetParams{
   334  			ClassName:  "SomeAction",
   335  			Properties: []search.SelectProperty{{Name: "intField", IsPrimitive: true}},
   336  			ModuleParams: map[string]interface{}{
   337  				"nearText": extractNearTextParam(map[string]interface{}{
   338  					"concepts": []interface{}{"c1", "c2", "c3"},
   339  					"moveTo": map[string]interface{}{
   340  						"concepts": []interface{}{"positive"},
   341  						"force":    float64(0.5),
   342  					},
   343  					"moveAwayFrom": map[string]interface{}{
   344  						"concepts": []interface{}{"epic"},
   345  						"force":    float64(0.25),
   346  					},
   347  				}),
   348  			},
   349  		}
   350  
   351  		resolver.On("GetClass", expectedParams).
   352  			Return([]interface{}{}, nil).Once()
   353  
   354  		resolver.AssertResolve(t, query)
   355  	})
   356  
   357  	t.Run("for a class that does not have a text2vec module", func(t *testing.T) {
   358  		query := `{ Get { CustomVectorClass(nearText: {
   359                  concepts: ["c1", "c2", "c3"],
   360  								moveTo: {
   361  									concepts:["positive"],
   362  									force: 0.5
   363  								},
   364  								moveAwayFrom: {
   365  									concepts:["epic"],
   366  									force: 0.25
   367  								}
   368  							}) { intField } } }`
   369  
   370  		res := resolver.Resolve(query)
   371  		require.Len(t, res.Errors, 1)
   372  		assert.Contains(t, res.Errors[0].Message, "Unknown argument \"nearText\" on field \"CustomVectorClass\"")
   373  	})
   374  
   375  	t.Run("for things with optional distance set", func(t *testing.T) {
   376  		query := `{ Get { SomeThing(nearText: {
   377                  concepts: ["c1", "c2", "c3"],
   378  								distance: 0.6,
   379  								moveTo: {
   380  									concepts:["positive"],
   381  									force: 0.5
   382  								},
   383  								moveAwayFrom: {
   384  									concepts:["epic"],
   385  									force: 0.25
   386  								}
   387  							}) { intField } } }`
   388  
   389  		expectedParams := dto.GetParams{
   390  			ClassName:  "SomeThing",
   391  			Properties: []search.SelectProperty{{Name: "intField", IsPrimitive: true}},
   392  			Pagination: &filters.Pagination{Limit: filters.LimitFlagSearchByDist},
   393  			ModuleParams: map[string]interface{}{
   394  				"nearText": extractNearTextParam(map[string]interface{}{
   395  					"concepts": []interface{}{"c1", "c2", "c3"},
   396  					"distance": float64(0.6),
   397  					"moveTo": map[string]interface{}{
   398  						"concepts": []interface{}{"positive"},
   399  						"force":    float64(0.5),
   400  					},
   401  					"moveAwayFrom": map[string]interface{}{
   402  						"concepts": []interface{}{"epic"},
   403  						"force":    float64(0.25),
   404  					},
   405  				}),
   406  			},
   407  		}
   408  		resolver.On("GetClass", expectedParams).
   409  			Return([]interface{}{}, nil).Once()
   410  
   411  		resolver.AssertResolve(t, query)
   412  	})
   413  
   414  	t.Run("for things with optional certainty set", func(t *testing.T) {
   415  		query := `{ Get { SomeThing(nearText: {
   416                  concepts: ["c1", "c2", "c3"],
   417  								certainty: 0.4,
   418  								moveTo: {
   419  									concepts:["positive"],
   420  									force: 0.5
   421  								},
   422  								moveAwayFrom: {
   423  									concepts:["epic"],
   424  									force: 0.25
   425  								}
   426  							}) { intField } } }`
   427  
   428  		expectedParams := dto.GetParams{
   429  			ClassName:  "SomeThing",
   430  			Properties: []search.SelectProperty{{Name: "intField", IsPrimitive: true}},
   431  			Pagination: &filters.Pagination{Limit: filters.LimitFlagSearchByDist},
   432  			ModuleParams: map[string]interface{}{
   433  				"nearText": extractNearTextParam(map[string]interface{}{
   434  					"concepts":  []interface{}{"c1", "c2", "c3"},
   435  					"certainty": float64(0.4),
   436  					"moveTo": map[string]interface{}{
   437  						"concepts": []interface{}{"positive"},
   438  						"force":    float64(0.5),
   439  					},
   440  					"moveAwayFrom": map[string]interface{}{
   441  						"concepts": []interface{}{"epic"},
   442  						"force":    float64(0.25),
   443  					},
   444  				}),
   445  			},
   446  		}
   447  		resolver.On("GetClass", expectedParams).
   448  			Return([]interface{}{}, nil).Once()
   449  
   450  		resolver.AssertResolve(t, query)
   451  	})
   452  
   453  	t.Run("for things with optional distance and objects set", func(t *testing.T) {
   454  		query := `{ Get { SomeThing(nearText: {
   455  								concepts: ["c1", "c2", "c3"],
   456  								distance: 0.4,
   457  								moveTo: {
   458  									concepts:["positive"],
   459  									force: 0.5
   460  									objects: [
   461  										{ id: "moveTo-uuid1" }
   462  										{ beacon: "weaviate://localhost/moveTo-uuid3" }
   463  									]
   464  								},
   465  								moveAwayFrom: {
   466  									concepts:["epic"],
   467  									force: 0.25
   468  									objects: [
   469  										{ id: "moveAway-uuid1" }
   470  										{ beacon: "weaviate://localhost/moveAway-uuid2" }
   471  										{ beacon: "weaviate://localhost/moveAway-uuid3" }
   472  									]
   473  								}
   474  							}) { intField } } }`
   475  
   476  		expectedParams := dto.GetParams{
   477  			ClassName:  "SomeThing",
   478  			Properties: []search.SelectProperty{{Name: "intField", IsPrimitive: true}},
   479  			Pagination: &filters.Pagination{Limit: filters.LimitFlagSearchByDist},
   480  			ModuleParams: map[string]interface{}{
   481  				"nearText": extractNearTextParam(map[string]interface{}{
   482  					"concepts": []interface{}{"c1", "c2", "c3"},
   483  					"distance": float64(0.4),
   484  					"moveTo": map[string]interface{}{
   485  						"concepts": []interface{}{"positive"},
   486  						"force":    float64(0.5),
   487  						"objects": []interface{}{
   488  							map[string]interface{}{
   489  								"id": "moveTo-uuid1",
   490  							},
   491  							map[string]interface{}{
   492  								"beacon": "weaviate://localhost/moveTo-uuid3",
   493  							},
   494  						},
   495  					},
   496  					"moveAwayFrom": map[string]interface{}{
   497  						"concepts": []interface{}{"epic"},
   498  						"force":    float64(0.25),
   499  						"objects": []interface{}{
   500  							map[string]interface{}{
   501  								"id": "moveAway-uuid1",
   502  							},
   503  							map[string]interface{}{
   504  								"beacon": "weaviate://localhost/moveAway-uuid2",
   505  							},
   506  							map[string]interface{}{
   507  								"beacon": "weaviate://localhost/moveAway-uuid3",
   508  							},
   509  						},
   510  					},
   511  				}),
   512  			},
   513  		}
   514  		resolver.On("GetClass", expectedParams).
   515  			Return([]interface{}{}, nil).Once()
   516  
   517  		resolver.AssertResolve(t, query)
   518  	})
   519  
   520  	t.Run("for things with optional certainty and objects set", func(t *testing.T) {
   521  		query := `{ Get { SomeThing(nearText: {
   522  								concepts: ["c1", "c2", "c3"],
   523  								certainty: 0.4,
   524  								moveTo: {
   525  									concepts:["positive"],
   526  									force: 0.5
   527  									objects: [
   528  										{ id: "moveTo-uuid1" }
   529  										{ beacon: "weaviate://localhost/moveTo-uuid3" }
   530  									]
   531  								},
   532  								moveAwayFrom: {
   533  									concepts:["epic"],
   534  									force: 0.25
   535  									objects: [
   536  										{ id: "moveAway-uuid1" }
   537  										{ beacon: "weaviate://localhost/moveAway-uuid2" }
   538  										{ beacon: "weaviate://localhost/moveAway-uuid3" }
   539  									]
   540  								}
   541  							}) { intField } } }`
   542  
   543  		expectedParams := dto.GetParams{
   544  			ClassName:  "SomeThing",
   545  			Properties: []search.SelectProperty{{Name: "intField", IsPrimitive: true}},
   546  			Pagination: &filters.Pagination{Limit: filters.LimitFlagSearchByDist},
   547  			ModuleParams: map[string]interface{}{
   548  				"nearText": extractNearTextParam(map[string]interface{}{
   549  					"concepts":  []interface{}{"c1", "c2", "c3"},
   550  					"certainty": float64(0.4),
   551  					"moveTo": map[string]interface{}{
   552  						"concepts": []interface{}{"positive"},
   553  						"force":    float64(0.5),
   554  						"objects": []interface{}{
   555  							map[string]interface{}{
   556  								"id": "moveTo-uuid1",
   557  							},
   558  							map[string]interface{}{
   559  								"beacon": "weaviate://localhost/moveTo-uuid3",
   560  							},
   561  						},
   562  					},
   563  					"moveAwayFrom": map[string]interface{}{
   564  						"concepts": []interface{}{"epic"},
   565  						"force":    float64(0.25),
   566  						"objects": []interface{}{
   567  							map[string]interface{}{
   568  								"id": "moveAway-uuid1",
   569  							},
   570  							map[string]interface{}{
   571  								"beacon": "weaviate://localhost/moveAway-uuid2",
   572  							},
   573  							map[string]interface{}{
   574  								"beacon": "weaviate://localhost/moveAway-uuid3",
   575  							},
   576  						},
   577  					},
   578  				}),
   579  			},
   580  		}
   581  		resolver.On("GetClass", expectedParams).
   582  			Return([]interface{}{}, nil).Once()
   583  
   584  		resolver.AssertResolve(t, query)
   585  	})
   586  }
   587  
   588  func Test_ResolveExplore(t *testing.T) {
   589  	t.Parallel()
   590  
   591  	testsNearText := testCases{
   592  		testCase{
   593  			name: "Resolve Explore with nearText",
   594  			query: `
   595  			{
   596  					Explore(nearText: {concepts: ["car", "best brand"]}) {
   597  							beacon className certainty distance
   598  					}
   599  			}`,
   600  			expectedParamsToTraverser: traverser.ExploreParams{
   601  				ModuleParams: map[string]interface{}{
   602  					"nearText": extractNearTextParam(map[string]interface{}{
   603  						"concepts": []interface{}{"car", "best brand"},
   604  					}),
   605  				},
   606  				WithCertaintyProp: true,
   607  			},
   608  			resolverReturn: []search.Result{
   609  				{
   610  					Beacon:    "weaviate://localhost/some-uuid",
   611  					ClassName: "bestClass",
   612  					Certainty: 0.7,
   613  					Dist:      helper.CertaintyToDist(t, 0.7),
   614  				},
   615  			},
   616  			expectedResults: []result{{
   617  				pathToField: []string{"Explore"},
   618  				expectedValue: []interface{}{
   619  					map[string]interface{}{
   620  						"beacon":    "weaviate://localhost/some-uuid",
   621  						"className": "bestClass",
   622  						"certainty": float32(0.7),
   623  						"distance":  helper.CertaintyToDist(t, 0.7),
   624  					},
   625  				},
   626  			}},
   627  		},
   628  
   629  		testCase{
   630  			name: "with nearText with optional limit and distance set",
   631  			query: `
   632  			{
   633  					Explore(
   634  						nearText: {concepts: ["car", "best brand"], distance: 0.6}, limit: 17
   635  						){
   636  							beacon className
   637  				}
   638  			}`,
   639  			expectedParamsToTraverser: traverser.ExploreParams{
   640  				ModuleParams: map[string]interface{}{
   641  					"nearText": extractNearTextParam(map[string]interface{}{
   642  						"concepts": []interface{}{"car", "best brand"},
   643  						"distance": float64(0.6),
   644  					}),
   645  				},
   646  				Limit: 17,
   647  			},
   648  			resolverReturn: []search.Result{
   649  				{
   650  					Beacon:    "weaviate://localhost/some-uuid",
   651  					ClassName: "bestClass",
   652  					Dist:      0.6,
   653  				},
   654  			},
   655  			expectedResults: []result{{
   656  				pathToField: []string{"Explore"},
   657  				expectedValue: []interface{}{
   658  					map[string]interface{}{
   659  						"beacon":    "weaviate://localhost/some-uuid",
   660  						"className": "bestClass",
   661  					},
   662  				},
   663  			}},
   664  		},
   665  
   666  		testCase{
   667  			name: "with nearText with optional limit and certainty set",
   668  			query: `
   669  			{
   670  					Explore(
   671  						nearText: {concepts: ["car", "best brand"], certainty: 0.6}, limit: 17
   672  						){
   673  							beacon className
   674  				}
   675  			}`,
   676  			expectedParamsToTraverser: traverser.ExploreParams{
   677  				ModuleParams: map[string]interface{}{
   678  					"nearText": extractNearTextParam(map[string]interface{}{
   679  						"concepts":  []interface{}{"car", "best brand"},
   680  						"certainty": float64(0.6),
   681  					}),
   682  				},
   683  				Limit: 17,
   684  			},
   685  			resolverReturn: []search.Result{
   686  				{
   687  					Beacon:    "weaviate://localhost/some-uuid",
   688  					ClassName: "bestClass",
   689  				},
   690  			},
   691  			expectedResults: []result{{
   692  				pathToField: []string{"Explore"},
   693  				expectedValue: []interface{}{
   694  					map[string]interface{}{
   695  						"beacon":    "weaviate://localhost/some-uuid",
   696  						"className": "bestClass",
   697  					},
   698  				},
   699  			}},
   700  		},
   701  
   702  		testCase{
   703  			name: "with moveTo set",
   704  			query: `
   705  			{
   706  					Explore(
   707  							limit: 17
   708  							nearText: {
   709  								concepts: ["car", "best brand"]
   710  								moveTo: {
   711  									concepts: ["mercedes"]
   712  									force: 0.7
   713  								}
   714  							}
   715  							) {
   716  							beacon className
   717  						}
   718  			}`,
   719  			expectedParamsToTraverser: traverser.ExploreParams{
   720  				Limit: 17,
   721  				ModuleParams: map[string]interface{}{
   722  					"nearText": extractNearTextParam(map[string]interface{}{
   723  						"concepts": []interface{}{"car", "best brand"},
   724  						"moveTo": map[string]interface{}{
   725  							"concepts": []interface{}{"mercedes"},
   726  							"force":    float64(0.7),
   727  						},
   728  					}),
   729  				},
   730  			},
   731  			resolverReturn: []search.Result{
   732  				{
   733  					Beacon:    "weaviate://localhost/some-uuid",
   734  					ClassName: "bestClass",
   735  				},
   736  			},
   737  			expectedResults: []result{{
   738  				pathToField: []string{"Explore"},
   739  				expectedValue: []interface{}{
   740  					map[string]interface{}{
   741  						"beacon":    "weaviate://localhost/some-uuid",
   742  						"className": "bestClass",
   743  					},
   744  				},
   745  			}},
   746  		},
   747  
   748  		testCase{
   749  			name: "with moveTo and moveAwayFrom set",
   750  			query: `
   751  			{
   752  					Explore(
   753  							limit: 17
   754  							nearText: {
   755  								concepts: ["car", "best brand"]
   756  								moveTo: {
   757  									concepts: ["mercedes"]
   758  									force: 0.7
   759  								}
   760  								moveAwayFrom: {
   761  									concepts: ["van"]
   762  									force: 0.7
   763  								}
   764  							}
   765  							) {
   766  							beacon className
   767  						}
   768  			}`,
   769  			expectedParamsToTraverser: traverser.ExploreParams{
   770  				Limit: 17,
   771  				ModuleParams: map[string]interface{}{
   772  					"nearText": extractNearTextParam(map[string]interface{}{
   773  						"concepts": []interface{}{"car", "best brand"},
   774  						"moveTo": map[string]interface{}{
   775  							"concepts": []interface{}{"mercedes"},
   776  							"force":    float64(0.7),
   777  						},
   778  						"moveAwayFrom": map[string]interface{}{
   779  							"concepts": []interface{}{"van"},
   780  							"force":    float64(0.7),
   781  						},
   782  					}),
   783  				},
   784  			},
   785  			resolverReturn: []search.Result{
   786  				{
   787  					Beacon:    "weaviate://localhost/some-uuid",
   788  					ClassName: "bestClass",
   789  				},
   790  			},
   791  			expectedResults: []result{{
   792  				pathToField: []string{"Explore"},
   793  				expectedValue: []interface{}{
   794  					map[string]interface{}{
   795  						"beacon":    "weaviate://localhost/some-uuid",
   796  						"className": "bestClass",
   797  					},
   798  				},
   799  			}},
   800  		},
   801  
   802  		testCase{
   803  			name: "with moveTo and objects set",
   804  			query: `
   805  			{
   806  					Explore(
   807  							limit: 17
   808  							nearText: {
   809  								concepts: ["car", "best brand"]
   810  								moveTo: {
   811  									concepts: ["mercedes"]
   812  									force: 0.7
   813  									objects: [
   814  										{id: "moveto-uuid"},
   815  										{beacon: "weaviate://localhost/other-moveto-uuid"},
   816  									]
   817  								}
   818  							}
   819  							) {
   820  							beacon className
   821  						}
   822  			}`,
   823  			expectedParamsToTraverser: traverser.ExploreParams{
   824  				Limit: 17,
   825  				ModuleParams: map[string]interface{}{
   826  					"nearText": extractNearTextParam(map[string]interface{}{
   827  						"concepts": []interface{}{"car", "best brand"},
   828  						"moveTo": map[string]interface{}{
   829  							"concepts": []interface{}{"mercedes"},
   830  							"force":    float64(0.7),
   831  							"objects": []interface{}{
   832  								map[string]interface{}{
   833  									"id": "moveto-uuid",
   834  								},
   835  								map[string]interface{}{
   836  									"beacon": "weaviate://localhost/other-moveto-uuid",
   837  								},
   838  							},
   839  						},
   840  					}),
   841  				},
   842  			},
   843  			resolverReturn: []search.Result{
   844  				{
   845  					Beacon:    "weaviate://localhost/some-uuid",
   846  					ClassName: "bestClass",
   847  				},
   848  			},
   849  			expectedResults: []result{{
   850  				pathToField: []string{"Explore"},
   851  				expectedValue: []interface{}{
   852  					map[string]interface{}{
   853  						"beacon":    "weaviate://localhost/some-uuid",
   854  						"className": "bestClass",
   855  					},
   856  				},
   857  			}},
   858  		},
   859  
   860  		testCase{
   861  			name: "with moveTo and moveAwayFrom and objects set",
   862  			query: `
   863  			{
   864  					Explore(
   865  							limit: 17
   866  							nearText: {
   867  								concepts: ["car", "best brand"]
   868  								moveTo: {
   869  									concepts: ["mercedes"]
   870  									force: 0.7
   871  									objects: [
   872  										{id: "moveto-uuid1"},
   873  										{beacon: "weaviate://localhost/moveto-uuid2"},
   874  									]
   875  								}
   876  								moveAwayFrom: {
   877  									concepts: ["van"]
   878  									force: 0.7
   879  									objects: [
   880  										{id: "moveAway-uuid1"},
   881  										{beacon: "weaviate://localhost/moveAway-uuid2"},
   882  										{id: "moveAway-uuid3"},
   883  										{id: "moveAway-uuid4"},
   884  									]
   885  								}
   886  							}
   887  							) {
   888  							beacon className
   889  						}
   890  			}`,
   891  			expectedParamsToTraverser: traverser.ExploreParams{
   892  				Limit: 17,
   893  				ModuleParams: map[string]interface{}{
   894  					"nearText": extractNearTextParam(map[string]interface{}{
   895  						"concepts": []interface{}{"car", "best brand"},
   896  						"moveTo": map[string]interface{}{
   897  							"concepts": []interface{}{"mercedes"},
   898  							"force":    float64(0.7),
   899  							"objects": []interface{}{
   900  								map[string]interface{}{
   901  									"id": "moveto-uuid1",
   902  								},
   903  								map[string]interface{}{
   904  									"beacon": "weaviate://localhost/moveto-uuid2",
   905  								},
   906  							},
   907  						},
   908  						"moveAwayFrom": map[string]interface{}{
   909  							"concepts": []interface{}{"van"},
   910  							"force":    float64(0.7),
   911  							"objects": []interface{}{
   912  								map[string]interface{}{
   913  									"id": "moveAway-uuid1",
   914  								},
   915  								map[string]interface{}{
   916  									"beacon": "weaviate://localhost/moveAway-uuid2",
   917  								},
   918  								map[string]interface{}{
   919  									"id": "moveAway-uuid3",
   920  								},
   921  								map[string]interface{}{
   922  									"id": "moveAway-uuid4",
   923  								},
   924  							},
   925  						},
   926  					}),
   927  				},
   928  			},
   929  			resolverReturn: []search.Result{
   930  				{
   931  					Beacon:    "weaviate://localhost/some-uuid",
   932  					ClassName: "bestClass",
   933  				},
   934  			},
   935  			expectedResults: []result{{
   936  				pathToField: []string{"Explore"},
   937  				expectedValue: []interface{}{
   938  					map[string]interface{}{
   939  						"beacon":    "weaviate://localhost/some-uuid",
   940  						"className": "bestClass",
   941  					},
   942  				},
   943  			}},
   944  		},
   945  	}
   946  
   947  	testsNearText.AssertExtraction(t, newExploreMockResolver())
   948  }
   949  
   950  func (tests testCases) AssertExtraction(t *testing.T, resolver *mockResolver) {
   951  	for _, testCase := range tests {
   952  		t.Run(testCase.name, func(t *testing.T) {
   953  			resolver.On("Explore", testCase.expectedParamsToTraverser).
   954  				Return(testCase.resolverReturn, nil).Once()
   955  
   956  			result := resolver.AssertResolve(t, testCase.query)
   957  
   958  			for _, expectedResult := range testCase.expectedResults {
   959  				value := result.Get(expectedResult.pathToField...).Result
   960  
   961  				assert.Equal(t, expectedResult.expectedValue, value)
   962  			}
   963  		})
   964  	}
   965  }
   966  
   967  func ptFloat32(in float32) *float32 {
   968  	return &in
   969  }