// _ _ // __ _____ __ ___ ___ __ _| |_ ___ // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ // \ V V / __/ (_| |\ V /| | (_| | || __/ // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| // // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. // // CONTACT: hello@weaviate.io // package test import ( "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" clschema "github.com/weaviate/weaviate/client/schema" "github.com/weaviate/weaviate/entities/models" "github.com/weaviate/weaviate/entities/schema" "github.com/weaviate/weaviate/test/helper" ) // this test prevents a regression on // https://github.com/weaviate/weaviate/issues/981 func TestInvalidDataTypeInProperty(t *testing.T) { t.Parallel() className := "WrongPropertyClass" t.Run("asserting that this class does not exist yet", func(t *testing.T) { assert.NotContains(t, GetObjectClassNames(t), className) }) t.Run("trying to import empty string as data type", func(t *testing.T) { c := &models.Class{ Class: className, Properties: []*models.Property{ { Name: "someProperty", DataType: []string{""}, }, }, } params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(c) resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) helper.AssertRequestFail(t, resp, err, func() { parsed, ok := err.(*clschema.SchemaObjectsCreateUnprocessableEntity) require.True(t, ok, "error should be unprocessable entity") assert.Equal(t, "property 'someProperty': invalid dataType: dataType cannot be an empty string", parsed.Payload.Error[0].Message) }) }) } func TestInvalidPropertyName(t *testing.T) { t.Parallel() className := "WrongPropertyClass" t.Run("asserting that this class does not exist yet", func(t *testing.T) { assert.NotContains(t, GetObjectClassNames(t), className) }) t.Run("trying to create class with invalid property name", func(t *testing.T) { c := &models.Class{ Class: className, Properties: []*models.Property{ { Name: "some-property", DataType: schema.DataTypeText.PropString(), Tokenization: models.PropertyTokenizationWhitespace, }, }, } params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(c) resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) helper.AssertRequestFail(t, resp, err, func() { parsed, ok := err.(*clschema.SchemaObjectsCreateUnprocessableEntity) require.True(t, ok, "error should be unprocessable entity") assert.Equal(t, "'some-property' is not a valid property name. Property names in Weaviate "+ "are restricted to valid GraphQL names, which must be “/[_A-Za-z][_0-9A-Za-z]*/”.", parsed.Payload.Error[0].Message) }) }) } func TestAddAndRemoveObjectClass(t *testing.T) { randomObjectClassName := "YellowCars" // Ensure that this name is not in the schema yet. t.Log("Asserting that this class does not exist yet") assert.NotContains(t, GetObjectClassNames(t), randomObjectClassName) tc := &models.Class{ Class: randomObjectClassName, ModuleConfig: map[string]interface{}{ "text2vec-contextionary": map[string]interface{}{ "vectorizeClassName": true, }, }, } t.Log("Creating class") params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(tc) resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) helper.AssertRequestOk(t, resp, err, nil) t.Log("Asserting that this class is now created") assert.Contains(t, GetObjectClassNames(t), randomObjectClassName) t.Run("pure http - without the auto-generated client", testGetSchemaWithoutClient) // Now clean up this class. t.Log("Remove the class") delParams := clschema.NewSchemaObjectsDeleteParams().WithClassName(randomObjectClassName) delResp, err := helper.Client(t).Schema.SchemaObjectsDelete(delParams, nil) helper.AssertRequestOk(t, delResp, err, nil) // And verify that the class does not exist anymore. assert.NotContains(t, GetObjectClassNames(t), randomObjectClassName) t.Log("Verify schema cluster status") statusResp, err := helper.Client(t).Schema.SchemaClusterStatus( clschema.NewSchemaClusterStatusParams(), nil, ) require.Nil(t, err) assert.Equal(t, "", statusResp.Payload.Error) assert.True(t, statusResp.Payload.Healthy) } // This test prevents a regression on // https://github.com/weaviate/weaviate/issues/1799 // // This was related to adding ref props. For example in the case of a circular // dependency (A<>B), users would typically add A without refs, then add B with // a reference back to A, finally update A with a ref to B. // // This last update that would set the ref prop on an existing class was missing // module-specific defaults. So when comparing to-be-updated to existing we would // find differences in the properties, thus triggering the above error. func TestUpdateHNSWSettingsAfterAddingRefProps(t *testing.T) { className := "RefUpdateIssueClass" t.Run("asserting that this class does not exist yet", func(t *testing.T) { assert.NotContains(t, GetObjectClassNames(t), className) }) defer func(t *testing.T) { params := clschema.NewSchemaObjectsDeleteParams().WithClassName(className) _, err := helper.Client(t).Schema.SchemaObjectsDelete(params, nil) assert.Nil(t, err) if err != nil { if typed, ok := err.(*clschema.SchemaObjectsDeleteBadRequest); ok { fmt.Println(typed.Payload.Error[0].Message) } } }(t) t.Run("initially creating the class", func(t *testing.T) { c := &models.Class{ Class: className, Properties: []*models.Property{ { Name: "string_prop", DataType: schema.DataTypeText.PropString(), Tokenization: models.PropertyTokenizationWhitespace, }, }, } params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(c) _, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) assert.Nil(t, err) }) t.Run("adding a ref prop after the fact", func(t *testing.T) { params := clschema.NewSchemaObjectsPropertiesAddParams(). WithClassName(className). WithBody(&models.Property{ DataType: []string{className}, Name: "ref_prop", }) _, err := helper.Client(t).Schema.SchemaObjectsPropertiesAdd(params, nil) assert.Nil(t, err) }) t.Run("obtaining the class, making an innocent change and trying to update it", func(t *testing.T) { params := clschema.NewSchemaObjectsGetParams(). WithClassName(className) res, err := helper.Client(t).Schema.SchemaObjectsGet(params, nil) require.Nil(t, err) class := res.Payload class.VectorIndexConfig.(map[string]interface{})["ef"] = float64(1234) updateParams := clschema.NewSchemaObjectsUpdateParams(). WithClassName(className). WithObjectClass(class) _, err = helper.Client(t).Schema.SchemaObjectsUpdate(updateParams, nil) assert.Nil(t, err) }) t.Run("obtaining the class, making a change to IndexNullState (immutable) property and update", func(t *testing.T) { params := clschema.NewSchemaObjectsGetParams(). WithClassName(className) res, err := helper.Client(t).Schema.SchemaObjectsGet(params, nil) require.Nil(t, err) class := res.Payload // IndexNullState cannot be updated during runtime class.InvertedIndexConfig.IndexNullState = true updateParams := clschema.NewSchemaObjectsUpdateParams(). WithClassName(className). WithObjectClass(class) _, err = helper.Client(t).Schema.SchemaObjectsUpdate(updateParams, nil) assert.NotNil(t, err) }) t.Run("obtaining the class, making a change to IndexPropertyLength (immutable) property and update", func(t *testing.T) { params := clschema.NewSchemaObjectsGetParams(). WithClassName(className) res, err := helper.Client(t).Schema.SchemaObjectsGet(params, nil) require.Nil(t, err) class := res.Payload // IndexPropertyLength cannot be updated during runtime class.InvertedIndexConfig.IndexPropertyLength = true updateParams := clschema.NewSchemaObjectsUpdateParams(). WithClassName(className). WithObjectClass(class) _, err = helper.Client(t).Schema.SchemaObjectsUpdate(updateParams, nil) assert.NotNil(t, err) }) } // This test prevents a regression of // https://github.com/weaviate/weaviate/issues/2692 // // In this issue, any time a class had no vector index set, any other update to // the class would be blocked func TestUpdateClassWithoutVectorIndex(t *testing.T) { className := "IAintGotNoVectorIndex" t.Run("asserting that this class does not exist yet", func(t *testing.T) { assert.NotContains(t, GetObjectClassNames(t), className) }) defer func(t *testing.T) { params := clschema.NewSchemaObjectsDeleteParams().WithClassName(className) _, err := helper.Client(t).Schema.SchemaObjectsDelete(params, nil) assert.Nil(t, err) if err != nil { if typed, ok := err.(*clschema.SchemaObjectsDeleteBadRequest); ok { fmt.Println(typed.Payload.Error[0].Message) } } }(t) t.Run("initially creating the class", func(t *testing.T) { c := &models.Class{ Class: className, InvertedIndexConfig: &models.InvertedIndexConfig{ Stopwords: &models.StopwordConfig{ Preset: "en", }, }, Properties: []*models.Property{ { Name: "text_prop", DataType: []string{"text"}, }, }, VectorIndexConfig: map[string]interface{}{ "skip": true, }, } params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(c) _, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) assert.Nil(t, err) }) t.Run("obtaining the class, making an innocent change and trying to update it", func(t *testing.T) { params := clschema.NewSchemaObjectsGetParams(). WithClassName(className) res, err := helper.Client(t).Schema.SchemaObjectsGet(params, nil) require.Nil(t, err) class := res.Payload class.InvertedIndexConfig.Stopwords.Preset = "none" updateParams := clschema.NewSchemaObjectsUpdateParams(). WithClassName(className). WithObjectClass(class) _, err = helper.Client(t).Schema.SchemaObjectsUpdate(updateParams, nil) assert.Nil(t, err) }) } // This test prevents a regression of // https://github.com/weaviate/weaviate/issues//3177 // // This test ensures that distance belongs to the immutable properties, i.e. no changes to it are possible after creating the class. func TestUpdateDistanceSettings(t *testing.T) { className := "Cosine_Class" t.Run("asserting that this class does not exist yet", func(t *testing.T) { assert.NotContains(t, GetObjectClassNames(t), className) }) defer func(t *testing.T) { params := clschema.NewSchemaObjectsDeleteParams().WithClassName(className) _, err := helper.Client(t).Schema.SchemaObjectsDelete(params, nil) assert.Nil(t, err) if err != nil { if typed, ok := err.(*clschema.SchemaObjectsDeleteBadRequest); ok { fmt.Println(typed.Payload.Error[0].Message) } } }(t) t.Run("initially creating the class", func(t *testing.T) { c := &models.Class{ Class: className, Vectorizer: "none", Properties: []*models.Property{ { Name: "name", DataType: schema.DataTypeText.PropString(), Tokenization: models.PropertyTokenizationWhitespace, }, }, VectorIndexConfig: map[string]interface{}{ "distance": "cosine", }, } params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(c) _, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) assert.Nil(t, err) }) t.Run("Trying to change the distance measurement", func(t *testing.T) { params := clschema.NewSchemaObjectsGetParams(). WithClassName(className) res, err := helper.Client(t).Schema.SchemaObjectsGet(params, nil) require.Nil(t, err) class := res.Payload class.VectorIndexConfig.(map[string]interface{})["distance"] = "l2-squared" updateParams := clschema.NewSchemaObjectsUpdateParams(). WithClassName(className). WithObjectClass(class) _, err = helper.Client(t).Schema.SchemaObjectsUpdate(updateParams, nil) assert.NotNil(t, err) }) } // TODO: https://github.com/weaviate/weaviate/issues/973 // // This test prevents a regression on the fix for this bug: // // https://github.com/weaviate/weaviate/issues/831 // func TestDeleteSingleProperties(t *testing.T) { // t.Parallel() // randomObjectClassName := "RedShip" // // Ensure that this name is not in the schema yet. // t.Log("Asserting that this class does not exist yet") // assert.NotContains(t, GetThingClassNames(t), randomThingClassName) // tc := &models.Class{ // Class: randomThingClassName, // Properties: []*models.Property{ // &models.Property{ // DataType: schema.DataTypeText.PropString(), // Tokenization: models.PropertyTokenizationWhitespace, // Name: "name", // }, // &models.Property{ // DataType: schema.DataTypeText.PropString(), // Tokenization: models.PropertyTokenizationWhitespace, // Name: "description", // }, // }, // } // t.Log("Creating class") // params := clschema.NewSchemaThingsCreateParams().WithThingClass(tc) // resp, err := helper.Client(t).Schema.SchemaThingsCreate(params, nil) // helper.AssertRequestOk(t, resp, err, nil) // t.Log("Asserting that this class is now created") // assert.Contains(t, GetThingClassNames(t), randomThingClassName) // t.Log("adding an instance of this particular class that uses both properties") // instanceParams := things.NewThingsCreateParams().WithBody( // &models.Thing{ // Class: randomThingClassName, // Schema: map[string]interface{}{ // "name": "my name", // "description": "my description", // }, // }) // instanceRes, err := helper.Client(t).Things.ThingsCreate(instanceParams, nil) // assert.Nil(t, err, "adding a class instance should not error") // t.Log("delete a single property of the class") // deleteParams := clschema.NewSchemaThingsPropertiesDeleteParams(). // WithClassName(randomThingClassName). // WithPropertyName("description") // _, err = helper.Client(t).Schema.SchemaThingsPropertiesDelete(deleteParams, nil) // assert.Nil(t, err, "deleting the property should not error") // t.Log("retrieve the class and make sure the property is gone") // thing := assertGetThingEventually(t, instanceRes.Payload.ID) // expectedSchema := map[string]interface{}{ // "name": "my name", // } // assert.Equal(t, expectedSchema, thing.Schema) // t.Log("verifying that we can still retrieve the thing through graphQL") // result := gql.AssertGraphQL(t, helper.RootAuth, "{ Get { Things { RedShip { name } } } }") // ships := result.Get("Get", "Things", "RedShip").AsSlice() // expectedShip := map[string]interface{}{ // "name": "my name", // } // assert.Contains(t, ships, expectedShip) // t.Log("verifying other GQL/REST queries still work") // gql.AssertGraphQL(t, helper.RootAuth, "{ Meta { Things { RedShip { name { count } } } } }") // gql.AssertGraphQL(t, helper.RootAuth, `{ Aggregate { Things { RedShip(groupBy: ["name"]) { name { count } } } } }`) // _, err = helper.Client(t).Things.ThingsList(things.NewThingsListParams(), nil) // assert.Nil(t, err, "listing things should not error") // t.Log("verifying we could re-add the property with the same name") // readdParams := clschema.NewSchemaThingsPropertiesAddParams(). // WithClassName(randomThingClassName). // WithBody(&models.Property{ // Name: "description", // DataType: schema.DataTypeText.PropString(), // Tokenization: models.PropertyTokenizationWhitespace, // }) // _, err = helper.Client(t).Schema.SchemaThingsPropertiesAdd(readdParams, nil) // assert.Nil(t, err, "adding the previously deleted property again should not error") // // Now clean up this class. // t.Log("Remove the class") // delParams := clschema.NewSchemaThingsDeleteParams().WithClassName(randomThingClassName) // delResp, err := helper.Client(t).Schema.SchemaThingsDelete(delParams, nil) // helper.AssertRequestOk(t, delResp, err, nil) // // And verify that the class does not exist anymore. // assert.NotContains(t, GetThingClassNames(t), randomThingClassName) // }