Spaces:
Sleeping
Sleeping
| // _ _ | |
| // __ _____ __ ___ ___ __ _| |_ ___ | |
| // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ | |
| // \ V V / __/ (_| |\ V /| | (_| | || __/ | |
| // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| | |
| // | |
| // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. | |
| // | |
| // CONTACT: [email protected] | |
| // | |
| 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) | |
| // } | |