github.com/weaviate/weaviate@v1.24.6/adapters/handlers/graphql/local/local_component_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 local
    13  
    14  import (
    15  	"fmt"
    16  	"runtime/debug"
    17  	"testing"
    18  
    19  	logrus "github.com/sirupsen/logrus/hooks/test"
    20  	"github.com/stretchr/testify/assert"
    21  	"github.com/stretchr/testify/require"
    22  	"github.com/tailor-inc/graphql"
    23  	"github.com/weaviate/weaviate/entities/models"
    24  	"github.com/weaviate/weaviate/entities/schema"
    25  	"github.com/weaviate/weaviate/usecases/config"
    26  	"github.com/weaviate/weaviate/usecases/modules"
    27  )
    28  
    29  // These tests are component tests for the local package including all its
    30  // subpackages, such as get, getmeta, etc.. However, they only assert that the
    31  // graphql tree can be built under certain circumstances. This helps us to
    32  // catch errors on edge cases like empty schemas, classes with empty
    33  // properties, empty peer lists, peers with empty schemas, etc. However, we
    34  // don't get any guarantee of whether the individual queries resolve
    35  // correctly. For those cases we have unit tests in die individual subpackages
    36  // (i.e. get, getmeta, aggregate, etc.).  Additionally we have (a few) e2e
    37  // tests.
    38  
    39  func TestBuild_GraphQLNetwork(t *testing.T) {
    40  	tests := testCases{
    41  		// This tests asserts that an action-only schema doesn't lead to errors.
    42  		testCase{
    43  			name: "with only objects locally",
    44  			localSchema: schema.Schema{
    45  				Objects: &models.Schema{
    46  					Classes: []*models.Class{
    47  						{
    48  							Class: "BestLocalAction",
    49  							Properties: []*models.Property{
    50  								{
    51  									DataType:     schema.DataTypeText.PropString(),
    52  									Name:         "myStringProp",
    53  									Tokenization: models.PropertyTokenizationWhitespace,
    54  								},
    55  							},
    56  						},
    57  					},
    58  				},
    59  			},
    60  		},
    61  
    62  		// This tests asserts that a things-only schema doesn't lead to errors.
    63  		testCase{
    64  			name: "with only objects locally",
    65  			localSchema: schema.Schema{
    66  				Objects: &models.Schema{
    67  					Classes: []*models.Class{
    68  						{
    69  							Class: "BestLocalThing",
    70  							Properties: []*models.Property{
    71  								{
    72  									DataType:     schema.DataTypeText.PropString(),
    73  									Name:         "myStringProp",
    74  									Tokenization: models.PropertyTokenizationWhitespace,
    75  								},
    76  							},
    77  						},
    78  					},
    79  				},
    80  			},
    81  		},
    82  
    83  		// // This tests asserts that a class without any properties doesn't lead to
    84  		// // errors.
    85  		testCase{
    86  			name: "with things without properties locally",
    87  			localSchema: schema.Schema{
    88  				Objects: &models.Schema{
    89  					Classes: []*models.Class{
    90  						{
    91  							Class:      "BestLocalThing",
    92  							Properties: []*models.Property{},
    93  						},
    94  					},
    95  				},
    96  			},
    97  		},
    98  
    99  		testCase{
   100  			name:        "without any peers",
   101  			localSchema: validSchema(),
   102  		},
   103  	}
   104  
   105  	tests.AssertNoError(t)
   106  }
   107  
   108  func TestBuild_RefProps(t *testing.T) {
   109  	t.Run("expected error logs", func(t *testing.T) {
   110  		tests := testCases{
   111  			{
   112  				name: "build class with nonexistent ref prop",
   113  				localSchema: schema.Schema{
   114  					Objects: &models.Schema{
   115  						Classes: []*models.Class{
   116  							{
   117  								Class: "ThisClassExists",
   118  								Properties: []*models.Property{
   119  									{
   120  										DataType: []string{"ThisClassDoesNotExist"},
   121  										Name:     "ofNonexistentClass",
   122  									},
   123  								},
   124  							},
   125  						},
   126  					},
   127  				},
   128  			},
   129  		}
   130  
   131  		expectedLogMsg := "ignoring ref prop \"ofNonexistentClass\" on class \"ThisClassExists\", " +
   132  			"because it contains reference to nonexistent class [\"ThisClassDoesNotExist\"]"
   133  
   134  		tests.AssertErrorLogs(t, expectedLogMsg)
   135  	})
   136  
   137  	t.Run("expected success", func(t *testing.T) {
   138  		tests := testCases{
   139  			{
   140  				name: "build class with existing non-circular ref prop",
   141  				localSchema: schema.Schema{
   142  					Objects: &models.Schema{
   143  						Classes: []*models.Class{
   144  							{
   145  								Class: "ThisClassExists",
   146  								Properties: []*models.Property{
   147  									{
   148  										DataType: []string{"ThisClassAlsoExists"},
   149  										Name:     "ofExistingClass",
   150  									},
   151  								},
   152  							},
   153  							{
   154  								Class: "ThisClassAlsoExists",
   155  								Properties: []*models.Property{
   156  									{
   157  										DataType:     schema.DataTypeText.PropString(),
   158  										Name:         "stringProp",
   159  										Tokenization: models.PropertyTokenizationWhitespace,
   160  									},
   161  								},
   162  							},
   163  						},
   164  					},
   165  				},
   166  			},
   167  			{
   168  				name: "build class with existing circular ref prop",
   169  				localSchema: schema.Schema{
   170  					Objects: &models.Schema{
   171  						Classes: []*models.Class{
   172  							{
   173  								Class: "ThisClassExists",
   174  								Properties: []*models.Property{
   175  									{
   176  										DataType: []string{"ThisClassAlsoExists"},
   177  										Name:     "ofExistingClass",
   178  									},
   179  								},
   180  							},
   181  							{
   182  								Class: "ThisClassAlsoExists",
   183  								Properties: []*models.Property{
   184  									{
   185  										DataType: []string{"ThisClassExists"},
   186  										Name:     "ofExistingClass",
   187  									},
   188  								},
   189  							},
   190  						},
   191  					},
   192  				},
   193  			},
   194  		}
   195  
   196  		tests.AssertNoError(t)
   197  	})
   198  }
   199  
   200  type testCase struct {
   201  	name        string
   202  	localSchema schema.Schema
   203  }
   204  
   205  type testCases []testCase
   206  
   207  func (tests testCases) AssertNoError(t *testing.T) {
   208  	for _, test := range tests {
   209  		t.Run(test.name, func(t *testing.T) {
   210  			modules := modules.NewProvider()
   211  			localSchema, err := Build(&test.localSchema, nil, config.Config{}, modules)
   212  			require.Nil(t, err, test.name)
   213  
   214  			schemaObject := graphql.ObjectConfig{
   215  				Name:        "WeaviateObj",
   216  				Description: "Location of the root query",
   217  				Fields:      localSchema,
   218  			}
   219  
   220  			func() {
   221  				defer func() {
   222  					if r := recover(); r != nil {
   223  						err = fmt.Errorf("%v at %s", r, debug.Stack())
   224  					}
   225  				}()
   226  
   227  				_, err = graphql.NewSchema(graphql.SchemaConfig{
   228  					Query: graphql.NewObject(schemaObject),
   229  				})
   230  			}()
   231  
   232  			assert.Nil(t, err, test.name)
   233  		})
   234  	}
   235  }
   236  
   237  // AssertErrorLogs still expects the test to pass without errors,
   238  // but does expect the Build logger to contain errors messages
   239  // from the GQL schema rebuilding thunk
   240  func (tests testCases) AssertErrorLogs(t *testing.T, expectedMsg string) {
   241  	for _, test := range tests {
   242  		t.Run(test.name, func(t *testing.T) {
   243  			modules := modules.NewProvider()
   244  			logger, logsHook := logrus.NewNullLogger()
   245  			localSchema, err := Build(&test.localSchema, logger, config.Config{}, modules)
   246  			require.Nil(t, err, test.name)
   247  
   248  			schemaObject := graphql.ObjectConfig{
   249  				Name:        "WeaviateObj",
   250  				Description: "Location of the root query",
   251  				Fields:      localSchema,
   252  			}
   253  
   254  			func() {
   255  				defer func() {
   256  					if r := recover(); r != nil {
   257  						err = fmt.Errorf("%v at %s", r, debug.Stack())
   258  					}
   259  				}()
   260  
   261  				_, err = graphql.NewSchema(graphql.SchemaConfig{
   262  					Query: graphql.NewObject(schemaObject),
   263  				})
   264  			}()
   265  
   266  			last := logsHook.LastEntry()
   267  			assert.Contains(t, last.Message, expectedMsg)
   268  			assert.Nil(t, err)
   269  		})
   270  	}
   271  }
   272  
   273  func validSchema() schema.Schema {
   274  	return schema.Schema{
   275  		Objects: &models.Schema{
   276  			Classes: []*models.Class{
   277  				{
   278  					Class: "BestLocalThing",
   279  					Properties: []*models.Property{
   280  						{
   281  							DataType:     schema.DataTypeText.PropString(),
   282  							Name:         "myStringProp",
   283  							Tokenization: models.PropertyTokenizationWhitespace,
   284  						},
   285  					},
   286  				},
   287  			},
   288  		},
   289  	}
   290  }