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  }