github.com/weaviate/weaviate@v1.24.6/test/acceptance/schema/add_class_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 test 13 14 import ( 15 "fmt" 16 "testing" 17 18 "github.com/stretchr/testify/assert" 19 "github.com/stretchr/testify/require" 20 21 clschema "github.com/weaviate/weaviate/client/schema" 22 "github.com/weaviate/weaviate/entities/models" 23 "github.com/weaviate/weaviate/entities/schema" 24 "github.com/weaviate/weaviate/test/helper" 25 ) 26 27 // this test prevents a regression on 28 // https://github.com/weaviate/weaviate/issues/981 29 func TestInvalidDataTypeInProperty(t *testing.T) { 30 t.Parallel() 31 className := "WrongPropertyClass" 32 33 t.Run("asserting that this class does not exist yet", func(t *testing.T) { 34 assert.NotContains(t, GetObjectClassNames(t), className) 35 }) 36 37 t.Run("trying to import empty string as data type", func(t *testing.T) { 38 c := &models.Class{ 39 Class: className, 40 Properties: []*models.Property{ 41 { 42 Name: "someProperty", 43 DataType: []string{""}, 44 }, 45 }, 46 } 47 48 params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(c) 49 resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) 50 helper.AssertRequestFail(t, resp, err, func() { 51 parsed, ok := err.(*clschema.SchemaObjectsCreateUnprocessableEntity) 52 require.True(t, ok, "error should be unprocessable entity") 53 assert.Equal(t, "property 'someProperty': invalid dataType: dataType cannot be an empty string", 54 parsed.Payload.Error[0].Message) 55 }) 56 }) 57 } 58 59 func TestInvalidPropertyName(t *testing.T) { 60 t.Parallel() 61 className := "WrongPropertyClass" 62 63 t.Run("asserting that this class does not exist yet", func(t *testing.T) { 64 assert.NotContains(t, GetObjectClassNames(t), className) 65 }) 66 67 t.Run("trying to create class with invalid property name", func(t *testing.T) { 68 c := &models.Class{ 69 Class: className, 70 Properties: []*models.Property{ 71 { 72 Name: "some-property", 73 DataType: schema.DataTypeText.PropString(), 74 Tokenization: models.PropertyTokenizationWhitespace, 75 }, 76 }, 77 } 78 79 params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(c) 80 resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) 81 helper.AssertRequestFail(t, resp, err, func() { 82 parsed, ok := err.(*clschema.SchemaObjectsCreateUnprocessableEntity) 83 require.True(t, ok, "error should be unprocessable entity") 84 assert.Equal(t, "'some-property' is not a valid property name. Property names in Weaviate "+ 85 "are restricted to valid GraphQL names, which must be “/[_A-Za-z][_0-9A-Za-z]{0,230}/”.", 86 parsed.Payload.Error[0].Message) 87 }) 88 }) 89 } 90 91 func TestAddAndRemoveObjectClass(t *testing.T) { 92 randomObjectClassName := "YellowCars" 93 94 // Ensure that this name is not in the schema yet. 95 t.Log("Asserting that this class does not exist yet") 96 assert.NotContains(t, GetObjectClassNames(t), randomObjectClassName) 97 98 tc := &models.Class{ 99 Class: randomObjectClassName, 100 ModuleConfig: map[string]interface{}{ 101 "text2vec-contextionary": map[string]interface{}{ 102 "vectorizeClassName": true, 103 }, 104 }, 105 } 106 107 t.Log("Creating class") 108 params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(tc) 109 resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) 110 helper.AssertRequestOk(t, resp, err, nil) 111 112 t.Log("Asserting that this class is now created") 113 assert.Contains(t, GetObjectClassNames(t), randomObjectClassName) 114 115 t.Run("pure http - without the auto-generated client", testGetSchemaWithoutClient) 116 117 // Now clean up this class. 118 t.Log("Remove the class") 119 delParams := clschema.NewSchemaObjectsDeleteParams().WithClassName(randomObjectClassName) 120 delResp, err := helper.Client(t).Schema.SchemaObjectsDelete(delParams, nil) 121 helper.AssertRequestOk(t, delResp, err, nil) 122 123 // And verify that the class does not exist anymore. 124 assert.NotContains(t, GetObjectClassNames(t), randomObjectClassName) 125 126 t.Log("Verify schema cluster status") 127 statusResp, err := helper.Client(t).Schema.SchemaClusterStatus( 128 clschema.NewSchemaClusterStatusParams(), nil, 129 ) 130 require.Nil(t, err) 131 assert.Equal(t, "", statusResp.Payload.Error) 132 assert.True(t, statusResp.Payload.Healthy) 133 } 134 135 // This test prevents a regression on 136 // https://github.com/weaviate/weaviate/issues/1799 137 // 138 // This was related to adding ref props. For example in the case of a circular 139 // dependency (A<>B), users would typically add A without refs, then add B with 140 // a reference back to A, finally update A with a ref to B. 141 // 142 // This last update that would set the ref prop on an existing class was missing 143 // module-specific defaults. So when comparing to-be-updated to existing we would 144 // find differences in the properties, thus triggering the above error. 145 func TestUpdateHNSWSettingsAfterAddingRefProps(t *testing.T) { 146 className := "RefUpdateIssueClass" 147 148 t.Run("asserting that this class does not exist yet", func(t *testing.T) { 149 assert.NotContains(t, GetObjectClassNames(t), className) 150 }) 151 152 defer func(t *testing.T) { 153 params := clschema.NewSchemaObjectsDeleteParams().WithClassName(className) 154 _, err := helper.Client(t).Schema.SchemaObjectsDelete(params, nil) 155 assert.Nil(t, err) 156 if err != nil { 157 if typed, ok := err.(*clschema.SchemaObjectsDeleteBadRequest); ok { 158 fmt.Println(typed.Payload.Error[0].Message) 159 } 160 } 161 }(t) 162 163 t.Run("initially creating the class", func(t *testing.T) { 164 c := &models.Class{ 165 Class: className, 166 Properties: []*models.Property{ 167 { 168 Name: "string_prop", 169 DataType: schema.DataTypeText.PropString(), 170 Tokenization: models.PropertyTokenizationWhitespace, 171 }, 172 }, 173 } 174 175 params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(c) 176 _, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) 177 assert.Nil(t, err) 178 }) 179 180 t.Run("adding a ref prop after the fact", func(t *testing.T) { 181 params := clschema.NewSchemaObjectsPropertiesAddParams(). 182 WithClassName(className). 183 WithBody(&models.Property{ 184 DataType: []string{className}, 185 Name: "ref_prop", 186 }) 187 _, err := helper.Client(t).Schema.SchemaObjectsPropertiesAdd(params, nil) 188 assert.Nil(t, err) 189 }) 190 191 t.Run("obtaining the class, making an innocent change and trying to update it", func(t *testing.T) { 192 params := clschema.NewSchemaObjectsGetParams(). 193 WithClassName(className) 194 res, err := helper.Client(t).Schema.SchemaObjectsGet(params, nil) 195 require.Nil(t, err) 196 197 class := res.Payload 198 199 class.VectorIndexConfig.(map[string]interface{})["ef"] = float64(1234) 200 201 updateParams := clschema.NewSchemaObjectsUpdateParams(). 202 WithClassName(className). 203 WithObjectClass(class) 204 _, err = helper.Client(t).Schema.SchemaObjectsUpdate(updateParams, nil) 205 assert.Nil(t, err) 206 }) 207 208 t.Run("obtaining the class, making a change to IndexNullState (immutable) property and update", func(t *testing.T) { 209 params := clschema.NewSchemaObjectsGetParams(). 210 WithClassName(className) 211 res, err := helper.Client(t).Schema.SchemaObjectsGet(params, nil) 212 require.Nil(t, err) 213 214 class := res.Payload 215 216 // IndexNullState cannot be updated during runtime 217 class.InvertedIndexConfig.IndexNullState = true 218 updateParams := clschema.NewSchemaObjectsUpdateParams(). 219 WithClassName(className). 220 WithObjectClass(class) 221 _, err = helper.Client(t).Schema.SchemaObjectsUpdate(updateParams, nil) 222 assert.NotNil(t, err) 223 }) 224 225 t.Run("obtaining the class, making a change to IndexPropertyLength (immutable) property and update", func(t *testing.T) { 226 params := clschema.NewSchemaObjectsGetParams(). 227 WithClassName(className) 228 res, err := helper.Client(t).Schema.SchemaObjectsGet(params, nil) 229 require.Nil(t, err) 230 231 class := res.Payload 232 233 // IndexPropertyLength cannot be updated during runtime 234 class.InvertedIndexConfig.IndexPropertyLength = true 235 updateParams := clschema.NewSchemaObjectsUpdateParams(). 236 WithClassName(className). 237 WithObjectClass(class) 238 _, err = helper.Client(t).Schema.SchemaObjectsUpdate(updateParams, nil) 239 assert.NotNil(t, err) 240 }) 241 } 242 243 // This test prevents a regression of 244 // https://github.com/weaviate/weaviate/issues/2692 245 // 246 // In this issue, any time a class had no vector index set, any other update to 247 // the class would be blocked 248 func TestUpdateClassWithoutVectorIndex(t *testing.T) { 249 className := "IAintGotNoVectorIndex" 250 251 t.Run("asserting that this class does not exist yet", func(t *testing.T) { 252 assert.NotContains(t, GetObjectClassNames(t), className) 253 }) 254 255 defer func(t *testing.T) { 256 params := clschema.NewSchemaObjectsDeleteParams().WithClassName(className) 257 _, err := helper.Client(t).Schema.SchemaObjectsDelete(params, nil) 258 assert.Nil(t, err) 259 if err != nil { 260 if typed, ok := err.(*clschema.SchemaObjectsDeleteBadRequest); ok { 261 fmt.Println(typed.Payload.Error[0].Message) 262 } 263 } 264 }(t) 265 266 t.Run("initially creating the class", func(t *testing.T) { 267 c := &models.Class{ 268 Class: className, 269 InvertedIndexConfig: &models.InvertedIndexConfig{ 270 Stopwords: &models.StopwordConfig{ 271 Preset: "en", 272 }, 273 }, 274 Properties: []*models.Property{ 275 { 276 Name: "text_prop", 277 DataType: []string{"text"}, 278 }, 279 }, 280 VectorIndexConfig: map[string]interface{}{ 281 "skip": true, 282 }, 283 } 284 285 params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(c) 286 _, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) 287 assert.Nil(t, err) 288 }) 289 290 t.Run("obtaining the class, making an innocent change and trying to update it", func(t *testing.T) { 291 params := clschema.NewSchemaObjectsGetParams(). 292 WithClassName(className) 293 res, err := helper.Client(t).Schema.SchemaObjectsGet(params, nil) 294 require.Nil(t, err) 295 296 class := res.Payload 297 298 class.InvertedIndexConfig.Stopwords.Preset = "none" 299 300 updateParams := clschema.NewSchemaObjectsUpdateParams(). 301 WithClassName(className). 302 WithObjectClass(class) 303 _, err = helper.Client(t).Schema.SchemaObjectsUpdate(updateParams, nil) 304 assert.Nil(t, err) 305 }) 306 } 307 308 // This test prevents a regression of 309 // https://github.com/weaviate/weaviate/issues//3177 310 // 311 // This test ensures that distance belongs to the immutable properties, i.e. no changes to it are possible after creating the class. 312 func TestUpdateDistanceSettings(t *testing.T) { 313 className := "Cosine_Class" 314 315 t.Run("asserting that this class does not exist yet", func(t *testing.T) { 316 assert.NotContains(t, GetObjectClassNames(t), className) 317 }) 318 319 defer func(t *testing.T) { 320 params := clschema.NewSchemaObjectsDeleteParams().WithClassName(className) 321 _, err := helper.Client(t).Schema.SchemaObjectsDelete(params, nil) 322 assert.Nil(t, err) 323 if err != nil { 324 if typed, ok := err.(*clschema.SchemaObjectsDeleteBadRequest); ok { 325 fmt.Println(typed.Payload.Error[0].Message) 326 } 327 } 328 }(t) 329 330 t.Run("initially creating the class", func(t *testing.T) { 331 c := &models.Class{ 332 Class: className, 333 Vectorizer: "none", 334 Properties: []*models.Property{ 335 { 336 Name: "name", 337 DataType: schema.DataTypeText.PropString(), 338 Tokenization: models.PropertyTokenizationWhitespace, 339 }, 340 }, 341 VectorIndexConfig: map[string]interface{}{ 342 "distance": "cosine", 343 }, 344 } 345 346 params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(c) 347 _, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) 348 assert.Nil(t, err) 349 }) 350 351 t.Run("Trying to change the distance measurement", func(t *testing.T) { 352 params := clschema.NewSchemaObjectsGetParams(). 353 WithClassName(className) 354 res, err := helper.Client(t).Schema.SchemaObjectsGet(params, nil) 355 require.Nil(t, err) 356 357 class := res.Payload 358 359 class.VectorIndexConfig.(map[string]interface{})["distance"] = "l2-squared" 360 361 updateParams := clschema.NewSchemaObjectsUpdateParams(). 362 WithClassName(className). 363 WithObjectClass(class) 364 _, err = helper.Client(t).Schema.SchemaObjectsUpdate(updateParams, nil) 365 assert.NotNil(t, err) 366 }) 367 } 368 369 // TODO: https://github.com/weaviate/weaviate/issues/973 370 // // This test prevents a regression on the fix for this bug: 371 // // https://github.com/weaviate/weaviate/issues/831 372 // func TestDeleteSingleProperties(t *testing.T) { 373 // t.Parallel() 374 375 // randomObjectClassName := "RedShip" 376 377 // // Ensure that this name is not in the schema yet. 378 // t.Log("Asserting that this class does not exist yet") 379 // assert.NotContains(t, GetThingClassNames(t), randomThingClassName) 380 381 // tc := &models.Class{ 382 // Class: randomThingClassName, 383 // Properties: []*models.Property{ 384 // &models.Property{ 385 // DataType: schema.DataTypeText.PropString(), 386 // Tokenization: models.PropertyTokenizationWhitespace, 387 // Name: "name", 388 // }, 389 // &models.Property{ 390 // DataType: schema.DataTypeText.PropString(), 391 // Tokenization: models.PropertyTokenizationWhitespace, 392 // Name: "description", 393 // }, 394 // }, 395 // } 396 397 // t.Log("Creating class") 398 // params := clschema.NewSchemaThingsCreateParams().WithThingClass(tc) 399 // resp, err := helper.Client(t).Schema.SchemaThingsCreate(params, nil) 400 // helper.AssertRequestOk(t, resp, err, nil) 401 402 // t.Log("Asserting that this class is now created") 403 // assert.Contains(t, GetThingClassNames(t), randomThingClassName) 404 405 // t.Log("adding an instance of this particular class that uses both properties") 406 // instanceParams := things.NewThingsCreateParams().WithBody( 407 // &models.Thing{ 408 // Class: randomThingClassName, 409 // Schema: map[string]interface{}{ 410 // "name": "my name", 411 // "description": "my description", 412 // }, 413 // }) 414 // instanceRes, err := helper.Client(t).Things.ThingsCreate(instanceParams, nil) 415 // assert.Nil(t, err, "adding a class instance should not error") 416 417 // t.Log("delete a single property of the class") 418 // deleteParams := clschema.NewSchemaThingsPropertiesDeleteParams(). 419 // WithClassName(randomThingClassName). 420 // WithPropertyName("description") 421 // _, err = helper.Client(t).Schema.SchemaThingsPropertiesDelete(deleteParams, nil) 422 // assert.Nil(t, err, "deleting the property should not error") 423 424 // t.Log("retrieve the class and make sure the property is gone") 425 // thing := assertGetThingEventually(t, instanceRes.Payload.ID) 426 // expectedSchema := map[string]interface{}{ 427 // "name": "my name", 428 // } 429 // assert.Equal(t, expectedSchema, thing.Schema) 430 431 // t.Log("verifying that we can still retrieve the thing through graphQL") 432 // result := gql.AssertGraphQL(t, helper.RootAuth, "{ Get { Things { RedShip { name } } } }") 433 // ships := result.Get("Get", "Things", "RedShip").AsSlice() 434 // expectedShip := map[string]interface{}{ 435 // "name": "my name", 436 // } 437 // assert.Contains(t, ships, expectedShip) 438 439 // t.Log("verifying other GQL/REST queries still work") 440 // gql.AssertGraphQL(t, helper.RootAuth, "{ Meta { Things { RedShip { name { count } } } } }") 441 // gql.AssertGraphQL(t, helper.RootAuth, `{ Aggregate { Things { RedShip(groupBy: ["name"]) { name { count } } } } }`) 442 // _, err = helper.Client(t).Things.ThingsList(things.NewThingsListParams(), nil) 443 // assert.Nil(t, err, "listing things should not error") 444 445 // t.Log("verifying we could re-add the property with the same name") 446 // readdParams := clschema.NewSchemaThingsPropertiesAddParams(). 447 // WithClassName(randomThingClassName). 448 // WithBody(&models.Property{ 449 // Name: "description", 450 // DataType: schema.DataTypeText.PropString(), 451 // Tokenization: models.PropertyTokenizationWhitespace, 452 // }) 453 454 // _, err = helper.Client(t).Schema.SchemaThingsPropertiesAdd(readdParams, nil) 455 // assert.Nil(t, err, "adding the previously deleted property again should not error") 456 457 // // Now clean up this class. 458 // t.Log("Remove the class") 459 // delParams := clschema.NewSchemaThingsDeleteParams().WithClassName(randomThingClassName) 460 // delResp, err := helper.Client(t).Schema.SchemaThingsDelete(delParams, nil) 461 // helper.AssertRequestOk(t, delResp, err, nil) 462 463 // // And verify that the class does not exist anymore. 464 // assert.NotContains(t, GetThingClassNames(t), randomThingClassName) 465 // }