github.com/weaviate/weaviate@v1.24.6/test/acceptance/objects/objects_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 // Acceptance tests for objects. 15 16 import ( 17 "encoding/json" 18 "errors" 19 "fmt" 20 "testing" 21 22 "github.com/go-openapi/strfmt" 23 24 "github.com/stretchr/testify/assert" 25 26 "github.com/weaviate/weaviate/client/objects" 27 "github.com/weaviate/weaviate/entities/models" 28 "github.com/weaviate/weaviate/test/helper" 29 testhelper "github.com/weaviate/weaviate/test/helper" 30 ) 31 32 // run from setup_test.go 33 func creatingObjects(t *testing.T) { 34 const fakeObjectId strfmt.UUID = "11111111-1111-1111-1111-111111111111" 35 36 t.Run("create object with user specified id", func(t *testing.T) { 37 var ( 38 id = strfmt.UUID("d47ea61b-0ed7-4e5f-9c05-6d2c0786660f") 39 className = "TestObject" 40 // Set all object values to compare 41 objectTestString = "Test string" 42 ) 43 // clean up to make sure we can run this test multiple times in a row 44 defer func() { 45 params := objects.NewObjectsDeleteParams().WithID(id) 46 helper.Client(t).Objects.ObjectsDelete(params, nil) 47 { 48 params := objects.NewObjectsClassGetParams() 49 params.WithClassName(className).WithID(id) 50 _, err := helper.Client(t).Objects.ObjectsClassGet(params, nil) 51 if err == nil { 52 t.Errorf("Object %v cannot exist after deletion", id) 53 } 54 werr := new(objects.ObjectsClassGetNotFound) 55 if ok := errors.As(err, &werr); !ok { 56 t.Errorf("get deleted object err got: %v want: %v", err, werr) 57 } 58 } 59 }() 60 61 params := objects.NewObjectsCreateParams().WithBody( 62 &models.Object{ 63 ID: id, 64 Class: className, 65 Properties: map[string]interface{}{ 66 "testString": objectTestString, 67 }, 68 }) 69 70 resp, err := helper.Client(t).Objects.ObjectsCreate(params, nil) 71 72 // Ensure that the response is OK 73 helper.AssertRequestOk(t, resp, err, func() { 74 object := resp.Payload 75 assert.Regexp(t, strfmt.UUIDPattern, object.ID) 76 77 schema, ok := object.Properties.(map[string]interface{}) 78 if !ok { 79 t.Fatal("The returned schema is not an JSON object") 80 } 81 82 // Check whether the returned information is the same as the data added 83 assert.Equal(t, objectTestString, schema["testString"]) 84 }) 85 86 // wait for the object to be created 87 testhelper.AssertEventuallyEqual(t, id, func() interface{} { 88 params := objects.NewObjectsClassGetParams() 89 params.WithClassName(className).WithID(id) 90 object, err := helper.Client(t).Objects.ObjectsClassGet(params, nil) 91 if err != nil { 92 return nil 93 } 94 95 return object.Payload.ID 96 }) 97 // deprecated: is here because of backward compatibility reasons 98 testhelper.AssertEventuallyEqual(t, id, func() interface{} { 99 params := objects.NewObjectsGetParams().WithID(id) 100 object, err := helper.Client(t).Objects.ObjectsGet(params, nil) 101 if err != nil { 102 return nil 103 } 104 105 return object.Payload.ID 106 }) 107 108 // Try to create the same object again and make sure it fails 109 params = objects.NewObjectsCreateParams().WithBody( 110 &models.Object{ 111 ID: id, 112 Class: "TestObject", 113 Properties: map[string]interface{}{ 114 "testString": objectTestString, 115 }, 116 }) 117 118 resp, err = helper.Client(t).Objects.ObjectsCreate(params, nil) 119 helper.AssertRequestFail(t, resp, err, func() { 120 errResponse, ok := err.(*objects.ObjectsCreateUnprocessableEntity) 121 if !ok { 122 t.Fatalf("Did not get not found response, but %#v", err) 123 } 124 125 assert.Equal(t, fmt.Sprintf("id '%s' already exists", id), errResponse.Payload.Error[0].Message) 126 }) 127 }) 128 129 // Check if we can create a Object, and that it's properties are stored correctly. 130 t.Run("creating a object", func(t *testing.T) { 131 t.Parallel() 132 // Set all object values to compare 133 objectTestString := "Test string" 134 objectTestInt := 1 135 objectTestBoolean := true 136 objectTestNumber := 1.337 137 objectTestDate := "2017-10-06T08:15:30+01:00" 138 objectTestPhoneNumber := map[string]interface{}{ 139 "input": "0171 11122233", 140 "defaultCountry": "DE", 141 } 142 143 params := objects.NewObjectsCreateParams().WithBody( 144 &models.Object{ 145 Class: "TestObject", 146 Properties: map[string]interface{}{ 147 "testString": objectTestString, 148 "testWholeNumber": objectTestInt, 149 "testTrueFalse": objectTestBoolean, 150 "testNumber": objectTestNumber, 151 "testDateTime": objectTestDate, 152 "testPhoneNumber": objectTestPhoneNumber, 153 }, 154 }) 155 156 resp, err := helper.Client(t).Objects.ObjectsCreate(params, nil) 157 158 // Ensure that the response is OK 159 helper.AssertRequestOk(t, resp, err, func() { 160 object := resp.Payload 161 assert.Regexp(t, strfmt.UUIDPattern, object.ID) 162 163 schema, ok := object.Properties.(map[string]interface{}) 164 if !ok { 165 t.Fatal("The returned schema is not an JSON object") 166 } 167 168 testWholeNumber, _ := schema["testWholeNumber"].(json.Number).Int64() 169 testNumber, _ := schema["testNumber"].(json.Number).Float64() 170 171 expectedParsedPhoneNumber := map[string]interface{}{ 172 "input": "0171 11122233", 173 "defaultCountry": "DE", 174 "countryCode": json.Number("49"), 175 "internationalFormatted": "+49 171 11122233", 176 "national": json.Number("17111122233"), 177 "nationalFormatted": "0171 11122233", 178 "valid": true, 179 } 180 181 // Check whether the returned information is the same as the data added 182 assert.Equal(t, objectTestString, schema["testString"]) 183 assert.Equal(t, objectTestInt, int(testWholeNumber)) 184 assert.Equal(t, objectTestBoolean, schema["testTrueFalse"]) 185 assert.Equal(t, objectTestNumber, testNumber) 186 assert.Equal(t, objectTestDate, schema["testDateTime"]) 187 assert.Equal(t, expectedParsedPhoneNumber, schema["testPhoneNumber"]) 188 }) 189 }) 190 191 // Examples of how a Object can be invalid. 192 invalidObjectTestCases := []struct { 193 // What is wrong in this example 194 mistake string 195 196 // the example object, with a mistake. 197 // this is a function, so that we can use utility functions like 198 // helper.GetWeaviateURL(), which might not be initialized yet 199 // during the static construction of the examples. 200 object func() *models.Object 201 202 // Enable the option to perform some extra assertions on the error response 203 errorCheck func(t *testing.T, err *models.ErrorResponse) 204 }{ 205 { 206 mistake: "missing the class", 207 object: func() *models.Object { 208 return &models.Object{ 209 Properties: map[string]interface{}{ 210 "testString": "test", 211 }, 212 } 213 }, 214 errorCheck: func(t *testing.T, err *models.ErrorResponse) { 215 assert.Equal(t, "invalid object: the given class is empty", err.Error[0].Message) 216 }, 217 }, 218 // AUTO_SCHEMA creates classes automatically 219 // { 220 // mistake: "non existing class", 221 // object: func() *models.Object { 222 // return &models.Object{ 223 // Class: "NonExistingClass", 224 // Properties: map[string]interface{}{ 225 // "testString": "test", 226 // }, 227 // } 228 // }, 229 // errorCheck: func(t *testing.T, err *models.ErrorResponse) { 230 // assert.Equal(t, fmt.Sprintf("invalid object: class '%s' not present in schema", "NonExistingClass"), err.Error[0].Message) 231 // }, 232 // }, 233 // AUTO_SCHEMA creates missing properties automatically 234 // { 235 // mistake: "non existing property", 236 // object: func() *models.Object { 237 // return &models.Object{ 238 // Class: "TestObject", 239 // Properties: map[string]interface{}{ 240 // "nonExistingProperty": "test", 241 // }, 242 // } 243 // }, 244 // errorCheck: func(t *testing.T, err *models.ErrorResponse) { 245 // assert.Equal(t, fmt.Sprintf("invalid object: "+schema.ErrorNoSuchProperty, "nonExistingProperty", "TestObject"), err.Error[0].Message) 246 // }, 247 // }, 248 { 249 /* TODO gh-616: don't count nr of elements in validation. Just validate keys, and _also_ generate an error on superfluous keys. 250 E.g. 251 var cref *string 252 var type_ *string 253 var locationUrl *string 254 255 for key, val := range(propertyValue) { 256 switch key { 257 case "beacon": cref = val 258 case "type": type_ = val 259 case "locationUrl": locationUrl = val 260 default: 261 return fmt.Errof("Unexpected key %s", key) 262 } 263 } 264 if cref == nil { return fmt.Errorf("beacon missing") } 265 if type_ == nil { return fmt.Errorf("type missing") } 266 if locationUrl == nil { return fmt.Errorf("locationUrl missing") } 267 268 // now everything has a valid state. 269 */ 270 mistake: "invalid cref, property missing locationUrl", 271 object: func() *models.Object { 272 return &models.Object{ 273 Class: "TestObject", 274 Properties: map[string]interface{}{ 275 "testReference": map[string]interface{}{ 276 "beacon": fakeObjectId, 277 "x": nil, 278 "type": "Object", 279 }, 280 }, 281 } 282 }, 283 errorCheck: func(t *testing.T, err *models.ErrorResponse) { 284 assert.NotNil(t, err) 285 }, 286 }, 287 { 288 mistake: "invalid property; assign int to string", 289 object: func() *models.Object { 290 return &models.Object{ 291 Class: "TestObject", 292 Properties: map[string]interface{}{ 293 "testString": 2, 294 }, 295 } 296 }, 297 errorCheck: func(t *testing.T, err *models.ErrorResponse) { 298 assert.Contains(t, 299 "invalid object: invalid text property 'testString' on class 'TestObject': not a string, but json.Number", 300 err.Error[0].Message) 301 }, 302 }, 303 } 304 305 // Check that none of the examples of invalid objects can be created. 306 t.Run("cannot create invalid objects", func(t *testing.T) { 307 // invalidObjectTestCases defined below this test. 308 for _, example_ := range invalidObjectTestCases { 309 t.Run(example_.mistake, func(t *testing.T) { 310 example := example_ // Needed; example is updated to point to a new test case. 311 t.Parallel() 312 313 params := objects.NewObjectsCreateParams().WithBody(example.object()) 314 resp, err := helper.Client(t).Objects.ObjectsCreate(params, nil) 315 helper.AssertRequestFail(t, resp, err, func() { 316 errResponse, ok := err.(*objects.ObjectsCreateUnprocessableEntity) 317 if !ok { 318 t.Fatalf("Did not get not found response, but %#v", err) 319 } 320 example.errorCheck(t, errResponse.Payload) 321 }) 322 }) 323 } 324 }) 325 }