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 }