Spaces:
Running
Running
// _ _ | |
// __ _____ __ ___ ___ __ _| |_ ___ | |
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ | |
// \ V V / __/ (_| |\ V /| | (_| | || __/ | |
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| | |
// | |
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. | |
// | |
// CONTACT: [email protected] | |
// | |
package test | |
import ( | |
"fmt" | |
"testing" | |
"github.com/google/uuid" | |
"github.com/stretchr/testify/assert" | |
"github.com/stretchr/testify/require" | |
"github.com/weaviate/weaviate/client/batch" | |
"github.com/go-openapi/strfmt" | |
"github.com/weaviate/weaviate/client/objects" | |
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" | |
testhelper "github.com/weaviate/weaviate/test/helper" | |
) | |
const ( | |
beaconStart = "weaviate://localhost/" | |
pathStart = "/v1/objects/" | |
) | |
func TestRefsWithoutToClass(t *testing.T) { | |
params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(&models.Class{Class: "ReferenceTo"}) | |
resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) | |
helper.AssertRequestOk(t, resp, err, nil) | |
refToClassName := "ReferenceTo" | |
refFromClassName := "ReferenceFrom" | |
otherClassMT := "Other" | |
// other class has multi-tenancy enabled to make sure that problems trigger an error | |
paramsMT := clschema.NewSchemaObjectsCreateParams().WithObjectClass( | |
&models.Class{Class: otherClassMT, MultiTenancyConfig: &models.MultiTenancyConfig{Enabled: true}}, | |
) | |
respMT, err := helper.Client(t).Schema.SchemaObjectsCreate(paramsMT, nil) | |
helper.AssertRequestOk(t, respMT, err, nil) | |
tenant := "tenant" | |
tenants := make([]*models.Tenant, 1) | |
for i := range tenants { | |
tenants[i] = &models.Tenant{Name: tenant} | |
} | |
helper.CreateTenants(t, otherClassMT, tenants) | |
refFromClass := &models.Class{ | |
Class: refFromClassName, | |
Properties: []*models.Property{ | |
{ | |
DataType: []string{refToClassName}, | |
Name: "ref", | |
}, | |
}, | |
} | |
params2 := clschema.NewSchemaObjectsCreateParams().WithObjectClass(refFromClass) | |
resp2, err := helper.Client(t).Schema.SchemaObjectsCreate(params2, nil) | |
helper.AssertRequestOk(t, resp2, err, nil) | |
defer deleteObjectClass(t, refToClassName) | |
defer deleteObjectClass(t, refFromClassName) | |
defer deleteObjectClass(t, otherClassMT) | |
refToId := assertCreateObject(t, refToClassName, map[string]interface{}{}) | |
assertGetObjectWithClass(t, refToId, refToClassName) | |
assertCreateObjectWithID(t, otherClassMT, tenant, refToId, map[string]interface{}{}) | |
refFromId := assertCreateObject(t, refFromClassName, map[string]interface{}{}) | |
assertGetObjectWithClass(t, refFromId, refFromClassName) | |
postRefParams := objects.NewObjectsClassReferencesCreateParams(). | |
WithID(refFromId). | |
WithPropertyName("ref").WithClassName(refFromClass.Class). | |
WithBody(&models.SingleRef{ | |
Beacon: strfmt.URI(fmt.Sprintf(beaconStart+"%s", refToId.String())), | |
}) | |
postRefResponse, err := helper.Client(t).Objects.ObjectsClassReferencesCreate(postRefParams, nil) | |
helper.AssertRequestOk(t, postRefResponse, err, nil) | |
// validate that ref was create for the correct class | |
objWithRef := func() interface{} { | |
obj := assertGetObjectWithClass(t, refFromId, refFromClassName) | |
return obj.Properties | |
} | |
testhelper.AssertEventuallyEqual(t, map[string]interface{}{ | |
"ref": []interface{}{ | |
map[string]interface{}{ | |
"beacon": fmt.Sprintf(beaconStart+"%s/%s", refToClassName, refToId.String()), | |
"href": fmt.Sprintf(pathStart+"%s/%s", refToClassName, refToId.String()), | |
}, | |
}, | |
}, objWithRef) | |
// update prop with multiple references | |
updateRefParams := objects.NewObjectsClassReferencesPutParams(). | |
WithID(refFromId). | |
WithPropertyName("ref").WithClassName(refFromClass.Class). | |
WithBody(models.MultipleRef{ | |
{Beacon: strfmt.URI(fmt.Sprintf(beaconStart+"%s", refToId.String()))}, | |
{Beacon: strfmt.URI(fmt.Sprintf(beaconStart+"%s/%s", refToClassName, refToId.String()))}, | |
}) | |
updateRefResponse, err := helper.Client(t).Objects.ObjectsClassReferencesPut(updateRefParams, nil) | |
helper.AssertRequestOk(t, updateRefResponse, err, nil) | |
objWithTwoRef := func() interface{} { | |
obj := assertGetObjectWithClass(t, refFromId, refFromClassName) | |
return obj.Properties | |
} | |
testhelper.AssertEventuallyEqual(t, map[string]interface{}{ | |
"ref": []interface{}{ | |
map[string]interface{}{ | |
"beacon": fmt.Sprintf(beaconStart+"%s/%s", refToClassName, refToId.String()), | |
"href": fmt.Sprintf(pathStart+"%s/%s", refToClassName, refToId.String()), | |
}, | |
map[string]interface{}{ | |
"beacon": fmt.Sprintf(beaconStart+"%s/%s", refToClassName, refToId.String()), | |
"href": fmt.Sprintf(pathStart+"%s/%s", refToClassName, refToId.String()), | |
}, | |
}, | |
}, objWithTwoRef) | |
// delete reference without class | |
deleteRefParams := objects.NewObjectsClassReferencesDeleteParams(). | |
WithID(refFromId). | |
WithPropertyName("ref").WithClassName(refFromClass.Class). | |
WithBody(&models.SingleRef{ | |
Beacon: strfmt.URI(fmt.Sprintf(beaconStart+"%s", refToId.String())), | |
}) | |
deleteRefResponse, err := helper.Client(t).Objects.ObjectsClassReferencesDelete(deleteRefParams, nil) | |
helper.AssertRequestOk(t, deleteRefResponse, err, nil) | |
objWithoutRef := func() interface{} { | |
obj := assertGetObjectWithClass(t, refFromId, refFromClassName) | |
return obj.Properties | |
} | |
testhelper.AssertEventuallyEqual(t, map[string]interface{}{ | |
"ref": []interface{}{}, | |
}, objWithoutRef) | |
} | |
func TestRefsMultiTarget(t *testing.T) { | |
refToClassName := "ReferenceTo" | |
refFromClassName := "ReferenceFrom" | |
defer deleteObjectClass(t, refToClassName) | |
defer deleteObjectClass(t, refFromClassName) | |
params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(&models.Class{Class: refToClassName}) | |
resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) | |
helper.AssertRequestOk(t, resp, err, nil) | |
refFromClass := &models.Class{ | |
Class: refFromClassName, | |
Properties: []*models.Property{ | |
{ | |
DataType: []string{refToClassName, refFromClassName}, | |
Name: "ref", | |
}, | |
}, | |
} | |
params2 := clschema.NewSchemaObjectsCreateParams().WithObjectClass(refFromClass) | |
resp2, err := helper.Client(t).Schema.SchemaObjectsCreate(params2, nil) | |
helper.AssertRequestOk(t, resp2, err, nil) | |
refToId := assertCreateObject(t, refToClassName, map[string]interface{}{}) | |
assertGetObjectEventually(t, refToId) | |
refFromId := assertCreateObject(t, refFromClassName, map[string]interface{}{}) | |
assertGetObjectEventually(t, refFromId) | |
cases := []struct { | |
classRef string | |
id string | |
}{ | |
{classRef: "", id: refToId.String()}, | |
{classRef: refToClassName + "/", id: refToId.String()}, | |
{classRef: refFromClassName + "/", id: refFromId.String()}, | |
} | |
for _, tt := range cases { | |
postRefParams := objects.NewObjectsClassReferencesCreateParams(). | |
WithID(refFromId). | |
WithPropertyName("ref").WithClassName(refFromClass.Class). | |
WithBody(&models.SingleRef{ | |
Beacon: strfmt.URI(fmt.Sprintf(beaconStart+"%s%s", tt.classRef, tt.id)), | |
}) | |
postRefResponse, err := helper.Client(t).Objects.ObjectsClassReferencesCreate(postRefParams, nil) | |
helper.AssertRequestOk(t, postRefResponse, err, nil) | |
// validate that ref was create for the correct class | |
objWithRef := func() interface{} { | |
obj := assertGetObjectWithClass(t, refFromId, refFromClassName) | |
return obj.Properties | |
} | |
testhelper.AssertEventuallyEqual(t, map[string]interface{}{ | |
"ref": []interface{}{ | |
map[string]interface{}{ | |
"beacon": fmt.Sprintf(beaconStart+"%s%s", tt.classRef, tt.id), | |
"href": fmt.Sprintf(pathStart+"%s%s", tt.classRef, tt.id), | |
}, | |
}, | |
}, objWithRef) | |
// delete refs | |
updateRefParams := objects.NewObjectsClassReferencesPutParams(). | |
WithID(refFromId). | |
WithPropertyName("ref").WithClassName(refFromClass.Class). | |
WithBody(models.MultipleRef{}) | |
updateRefResponse, err := helper.Client(t).Objects.ObjectsClassReferencesPut(updateRefParams, nil) | |
helper.AssertRequestOk(t, updateRefResponse, err, nil) | |
} | |
} | |
func TestBatchRefsMultiTarget(t *testing.T) { | |
refToClassName := "ReferenceTo" | |
refFromClassName := "ReferenceFrom" | |
defer deleteObjectClass(t, refToClassName) | |
defer deleteObjectClass(t, refFromClassName) | |
params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(&models.Class{Class: refToClassName}) | |
resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) | |
helper.AssertRequestOk(t, resp, err, nil) | |
refFromClass := &models.Class{ | |
Class: refFromClassName, | |
Properties: []*models.Property{ | |
{ | |
DataType: []string{refToClassName, refFromClassName}, | |
Name: "ref", | |
}, | |
}, | |
} | |
params2 := clschema.NewSchemaObjectsCreateParams().WithObjectClass(refFromClass) | |
resp2, err := helper.Client(t).Schema.SchemaObjectsCreate(params2, nil) | |
helper.AssertRequestOk(t, resp2, err, nil) | |
uuidsTo := make([]strfmt.UUID, 10) | |
uuidsFrom := make([]strfmt.UUID, 10) | |
for i := 0; i < 10; i++ { | |
uuidsTo[i] = assertCreateObject(t, refToClassName, map[string]interface{}{}) | |
assertGetObjectEventually(t, uuidsTo[i]) | |
uuidsFrom[i] = assertCreateObject(t, refFromClassName, map[string]interface{}{}) | |
assertGetObjectEventually(t, uuidsFrom[i]) | |
} | |
// add refs without toClass | |
var batchRefs []*models.BatchReference | |
for i := range uuidsFrom[:2] { | |
from := beaconStart + "ReferenceFrom/" + uuidsFrom[i] + "/ref" | |
to := beaconStart + uuidsTo[i] | |
batchRefs = append(batchRefs, &models.BatchReference{From: strfmt.URI(from), To: strfmt.URI(to)}) | |
} | |
// add refs with toClass target 1 | |
for i := range uuidsFrom[2:5] { | |
j := i + 2 | |
from := beaconStart + "ReferenceFrom/" + uuidsFrom[j] + "/ref" | |
to := beaconStart + "ReferenceTo/" + uuidsTo[j] | |
batchRefs = append(batchRefs, &models.BatchReference{From: strfmt.URI(from), To: strfmt.URI(to)}) | |
} | |
// add refs with toClass target 2 | |
for i := range uuidsFrom[5:] { | |
j := i + 5 | |
from := beaconStart + "ReferenceFrom/" + uuidsFrom[j] + "/ref" | |
to := beaconStart + "ReferenceFrom/" + uuidsTo[j] | |
batchRefs = append(batchRefs, &models.BatchReference{From: strfmt.URI(from), To: strfmt.URI(to)}) | |
} | |
postRefParams := batch.NewBatchReferencesCreateParams().WithBody(batchRefs) | |
postRefResponse, err := helper.Client(t).Batch.BatchReferencesCreate(postRefParams, nil) | |
helper.AssertRequestOk(t, postRefResponse, err, nil) | |
// no autodetect for multi-target | |
for i := range uuidsFrom[:2] { | |
objWithRef := func() interface{} { | |
obj := assertGetObjectWithClass(t, uuidsFrom[i], refFromClassName) | |
return obj.Properties | |
} | |
testhelper.AssertEventuallyEqual(t, map[string]interface{}{ | |
"ref": []interface{}{ | |
map[string]interface{}{ | |
"beacon": fmt.Sprintf(beaconStart+"%s", uuidsTo[i].String()), | |
"href": fmt.Sprintf(pathStart+"%s", uuidsTo[i].String()), | |
}, | |
}, | |
}, objWithRef) | |
} | |
// refs for target 1 | |
for i := range uuidsFrom[2:5] { | |
j := i + 2 | |
objWithRef := func() interface{} { | |
obj := assertGetObjectWithClass(t, uuidsFrom[j], refFromClassName) | |
return obj.Properties | |
} | |
testhelper.AssertEventuallyEqual(t, map[string]interface{}{ | |
"ref": []interface{}{ | |
map[string]interface{}{ | |
"beacon": fmt.Sprintf(beaconStart+"%s/%s", refToClassName, uuidsTo[j].String()), | |
"href": fmt.Sprintf(pathStart+"%s/%s", refToClassName, uuidsTo[j].String()), | |
}, | |
}, | |
}, objWithRef) | |
} | |
// refs for target 2 | |
for i := range uuidsFrom[5:] { | |
j := i + 5 | |
objWithRef := func() interface{} { | |
obj := assertGetObjectWithClass(t, uuidsFrom[j], refFromClassName) | |
return obj.Properties | |
} | |
testhelper.AssertEventuallyEqual(t, map[string]interface{}{ | |
"ref": []interface{}{ | |
map[string]interface{}{ | |
"beacon": fmt.Sprintf(beaconStart+"%s/%s", refFromClassName, uuidsTo[j].String()), | |
"href": fmt.Sprintf(pathStart+"%s/%s", refFromClassName, uuidsTo[j].String()), | |
}, | |
}, | |
}, objWithRef) | |
} | |
} | |
func TestBatchRefsWithoutFromAndToClass(t *testing.T) { | |
refToClassName := "ReferenceTo" | |
refFromClassName := "ReferenceFrom" | |
params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(&models.Class{Class: refToClassName}) | |
resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) | |
helper.AssertRequestOk(t, resp, err, nil) | |
refFromClass := &models.Class{ | |
Class: refFromClassName, | |
Properties: []*models.Property{ | |
{ | |
DataType: []string{refToClassName}, | |
Name: "ref", | |
}, | |
}, | |
} | |
params2 := clschema.NewSchemaObjectsCreateParams().WithObjectClass(refFromClass) | |
resp2, err := helper.Client(t).Schema.SchemaObjectsCreate(params2, nil) | |
helper.AssertRequestOk(t, resp2, err, nil) | |
defer deleteObjectClass(t, refToClassName) | |
defer deleteObjectClass(t, refFromClassName) | |
uuidsTo := make([]strfmt.UUID, 10) | |
uuidsFrom := make([]strfmt.UUID, 10) | |
for i := 0; i < 10; i++ { | |
uuidsTo[i] = assertCreateObject(t, refToClassName, map[string]interface{}{}) | |
assertGetObjectWithClass(t, uuidsTo[i], refToClassName) | |
uuidsFrom[i] = assertCreateObject(t, refFromClassName, map[string]interface{}{}) | |
assertGetObjectWithClass(t, uuidsFrom[i], refFromClassName) | |
} | |
// cannot do from urls without class | |
var batchRefs []*models.BatchReference | |
for i := range uuidsFrom { | |
from := beaconStart + uuidsFrom[i] + "/ref" | |
to := beaconStart + uuidsTo[i] | |
batchRefs = append(batchRefs, &models.BatchReference{From: strfmt.URI(from), To: strfmt.URI(to)}) | |
} | |
postRefParams := batch.NewBatchReferencesCreateParams().WithBody(batchRefs) | |
resp3, err := helper.Client(t).Batch.BatchReferencesCreate(postRefParams, nil) | |
require.Nil(t, err) | |
require.NotNil(t, resp3) | |
for i := range resp3.Payload { | |
require.NotNil(t, resp3.Payload[i].Result.Errors) | |
} | |
} | |
func TestBatchRefWithErrors(t *testing.T) { | |
refToClassName := "ReferenceTo" | |
refFromClassName := "ReferenceFrom" | |
params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(&models.Class{Class: refToClassName}) | |
resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) | |
helper.AssertRequestOk(t, resp, err, nil) | |
refFromClass := &models.Class{ | |
Class: refFromClassName, | |
Properties: []*models.Property{ | |
{ | |
DataType: []string{refToClassName}, | |
Name: "ref", | |
}, | |
}, | |
} | |
params2 := clschema.NewSchemaObjectsCreateParams().WithObjectClass(refFromClass) | |
resp2, err := helper.Client(t).Schema.SchemaObjectsCreate(params2, nil) | |
helper.AssertRequestOk(t, resp2, err, nil) | |
defer deleteObjectClass(t, refToClassName) | |
defer deleteObjectClass(t, refFromClassName) | |
uuidsTo := make([]strfmt.UUID, 2) | |
uuidsFrom := make([]strfmt.UUID, 2) | |
for i := 0; i < 2; i++ { | |
uuidsTo[i] = assertCreateObject(t, refToClassName, map[string]interface{}{}) | |
assertGetObjectWithClass(t, uuidsTo[i], refToClassName) | |
uuidsFrom[i] = assertCreateObject(t, refFromClassName, map[string]interface{}{}) | |
assertGetObjectWithClass(t, uuidsFrom[i], refFromClassName) | |
} | |
var batchRefs []*models.BatchReference | |
for i := range uuidsFrom { | |
from := beaconStart + "ReferenceFrom/" + uuidsFrom[i] + "/ref" | |
to := beaconStart + uuidsTo[i] | |
batchRefs = append(batchRefs, &models.BatchReference{From: strfmt.URI(from), To: strfmt.URI(to)}) | |
} | |
// append one entry with a non-existent class | |
batchRefs = append(batchRefs, &models.BatchReference{From: strfmt.URI(beaconStart + "DoesNotExist/" + uuidsFrom[0] + "/ref"), To: strfmt.URI(beaconStart + uuidsTo[0])}) | |
// append one entry with a non-existent property for existing class | |
batchRefs = append(batchRefs, &models.BatchReference{From: strfmt.URI(beaconStart + "ReferenceFrom/" + uuidsFrom[0] + "/doesNotExist"), To: strfmt.URI(beaconStart + uuidsTo[0])}) | |
postRefParams := batch.NewBatchReferencesCreateParams().WithBody(batchRefs) | |
postRefResponse, err := helper.Client(t).Batch.BatchReferencesCreate(postRefParams, nil) | |
helper.AssertRequestOk(t, postRefResponse, err, nil) | |
require.NotNil(t, postRefResponse.Payload[2].Result.Errors) | |
require.Contains(t, postRefResponse.Payload[2].Result.Errors.Error[0].Message, "class DoesNotExist does not exist") | |
require.NotNil(t, postRefResponse.Payload[3].Result.Errors) | |
require.Contains(t, postRefResponse.Payload[3].Result.Errors.Error[0].Message, "property doesNotExist does not exist for class ReferenceFrom") | |
} | |
func TestBatchRefsWithoutToClass(t *testing.T) { | |
refToClassName := "ReferenceTo" | |
refFromClassName := "ReferenceFrom" | |
otherClassMT := "Other" | |
// other class has multi-tenancy enabled to make sure that problems trigger an error | |
paramsMT := clschema.NewSchemaObjectsCreateParams().WithObjectClass( | |
&models.Class{Class: otherClassMT, MultiTenancyConfig: &models.MultiTenancyConfig{Enabled: true}}, | |
) | |
respMT, err := helper.Client(t).Schema.SchemaObjectsCreate(paramsMT, nil) | |
helper.AssertRequestOk(t, respMT, err, nil) | |
tenant := "tenant" | |
tenants := make([]*models.Tenant, 1) | |
for i := range tenants { | |
tenants[i] = &models.Tenant{Name: tenant} | |
} | |
helper.CreateTenants(t, otherClassMT, tenants) | |
params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(&models.Class{Class: refToClassName}) | |
resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) | |
helper.AssertRequestOk(t, resp, err, nil) | |
refFromClass := &models.Class{ | |
Class: refFromClassName, | |
Properties: []*models.Property{ | |
{ | |
DataType: []string{refToClassName}, | |
Name: "ref", | |
}, | |
}, | |
} | |
params2 := clschema.NewSchemaObjectsCreateParams().WithObjectClass(refFromClass) | |
resp2, err := helper.Client(t).Schema.SchemaObjectsCreate(params2, nil) | |
helper.AssertRequestOk(t, resp2, err, nil) | |
defer deleteObjectClass(t, refToClassName) | |
defer deleteObjectClass(t, refFromClassName) | |
defer deleteObjectClass(t, otherClassMT) | |
uuidsTo := make([]strfmt.UUID, 10) | |
uuidsFrom := make([]strfmt.UUID, 10) | |
for i := 0; i < 10; i++ { | |
uuidsTo[i] = assertCreateObject(t, refToClassName, map[string]interface{}{}) | |
assertGetObjectWithClass(t, uuidsTo[i], refToClassName) | |
// create object with same id in MT class | |
assertCreateObjectWithID(t, otherClassMT, tenant, uuidsTo[i], map[string]interface{}{}) | |
uuidsFrom[i] = assertCreateObject(t, refFromClassName, map[string]interface{}{}) | |
assertGetObjectWithClass(t, uuidsFrom[i], refFromClassName) | |
} | |
var batchRefs []*models.BatchReference | |
for i := range uuidsFrom { | |
from := beaconStart + "ReferenceFrom/" + uuidsFrom[i] + "/ref" | |
to := beaconStart + uuidsTo[i] | |
batchRefs = append(batchRefs, &models.BatchReference{From: strfmt.URI(from), To: strfmt.URI(to)}) | |
} | |
postRefParams := batch.NewBatchReferencesCreateParams().WithBody(batchRefs) | |
postRefResponse, err := helper.Client(t).Batch.BatchReferencesCreate(postRefParams, nil) | |
helper.AssertRequestOk(t, postRefResponse, err, nil) | |
for i := range uuidsFrom { | |
// validate that ref was create for the correct class | |
objWithRef := func() interface{} { | |
obj := assertGetObjectWithClass(t, uuidsFrom[i], refFromClassName) | |
return obj.Properties | |
} | |
testhelper.AssertEventuallyEqual(t, map[string]interface{}{ | |
"ref": []interface{}{ | |
map[string]interface{}{ | |
"beacon": fmt.Sprintf(beaconStart+"%s/%s", refToClassName, uuidsTo[i].String()), | |
"href": fmt.Sprintf(pathStart+"%s/%s", refToClassName, uuidsTo[i].String()), | |
}, | |
}, | |
}, objWithRef) | |
} | |
} | |
func TestObjectBatchToClassDetection(t *testing.T) { | |
// uses same code path as normal object add | |
refToClassName := "ReferenceTo" | |
refFromClassName := "ReferenceFrom" | |
defer deleteObjectClass(t, refToClassName) | |
defer deleteObjectClass(t, refFromClassName) | |
params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(&models.Class{Class: refToClassName}) | |
resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) | |
helper.AssertRequestOk(t, resp, err, nil) | |
refFromClass := &models.Class{ | |
Class: refFromClassName, | |
Properties: []*models.Property{ | |
{ | |
DataType: []string{refToClassName}, | |
Name: "ref", | |
}, | |
}, | |
} | |
params2 := clschema.NewSchemaObjectsCreateParams().WithObjectClass(refFromClass) | |
resp2, err := helper.Client(t).Schema.SchemaObjectsCreate(params2, nil) | |
helper.AssertRequestOk(t, resp2, err, nil) | |
refs := make([]interface{}, 10) | |
uuidsTo := make([]strfmt.UUID, 10) | |
for i := 0; i < 10; i++ { | |
uuidTo := assertCreateObject(t, refToClassName, map[string]interface{}{}) | |
uuidsTo[i] = uuidTo | |
assertGetObjectEventually(t, uuidTo) | |
refs[i] = map[string]interface{}{ | |
"beacon": beaconStart + uuidTo, | |
} | |
} | |
fromBatch := make([]*models.Object, 10) | |
for i := 0; i < 10; i++ { | |
fromBatch[i] = &models.Object{ | |
Class: refFromClassName, | |
ID: strfmt.UUID(uuid.New().String()), | |
Properties: map[string]interface{}{ | |
"ref": refs[i : i+1], | |
}, | |
} | |
} | |
paramsBatch := batch.NewBatchObjectsCreateParams().WithBody( | |
batch.BatchObjectsCreateBody{ | |
Objects: fromBatch, | |
}, | |
) | |
res, err := helper.Client(t).Batch.BatchObjectsCreate(paramsBatch, nil) | |
require.Nil(t, err) | |
for _, elem := range res.Payload { | |
assert.Nil(t, elem.Result.Errors) | |
} | |
for i := range fromBatch { | |
// validate that ref was create for the correct class | |
objWithRef := func() interface{} { | |
obj := assertGetObjectWithClass(t, fromBatch[i].ID, refFromClassName) | |
return obj.Properties | |
} | |
testhelper.AssertEventuallyEqual(t, map[string]interface{}{ | |
"ref": []interface{}{ | |
map[string]interface{}{ | |
"beacon": fmt.Sprintf(beaconStart+"%s/%s", refToClassName, uuidsTo[i].String()), | |
"href": fmt.Sprintf(pathStart+"%s/%s", refToClassName, uuidsTo[i].String()), | |
}, | |
}, | |
}, objWithRef) | |
} | |
} | |
func TestObjectCrefWithoutToClass(t *testing.T) { | |
refToClassName := "ReferenceTo" | |
refFromClassName := "ReferenceFrom" | |
otherClassMT := "Other" | |
// other class has multi-tenancy enabled to make sure that problems trigger an error | |
paramsMT := clschema.NewSchemaObjectsCreateParams().WithObjectClass( | |
&models.Class{Class: otherClassMT, MultiTenancyConfig: &models.MultiTenancyConfig{Enabled: true}}, | |
) | |
respMT, err := helper.Client(t).Schema.SchemaObjectsCreate(paramsMT, nil) | |
helper.AssertRequestOk(t, respMT, err, nil) | |
tenant := "tenant" | |
tenants := make([]*models.Tenant, 1) | |
for i := range tenants { | |
tenants[i] = &models.Tenant{Name: tenant} | |
} | |
helper.CreateTenants(t, otherClassMT, tenants) | |
params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(&models.Class{Class: refToClassName}) | |
resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) | |
helper.AssertRequestOk(t, resp, err, nil) | |
refFromClass := &models.Class{ | |
Class: refFromClassName, | |
Properties: []*models.Property{ | |
{ | |
DataType: []string{refToClassName}, | |
Name: "ref", | |
}, | |
}, | |
} | |
params2 := clschema.NewSchemaObjectsCreateParams().WithObjectClass(refFromClass) | |
resp2, err := helper.Client(t).Schema.SchemaObjectsCreate(params2, nil) | |
helper.AssertRequestOk(t, resp2, err, nil) | |
defer deleteObjectClass(t, refToClassName) | |
defer deleteObjectClass(t, refFromClassName) | |
defer deleteObjectClass(t, otherClassMT) | |
refs := make([]interface{}, 10) | |
uuids := make([]strfmt.UUID, 10) | |
for i := 0; i < 10; i++ { | |
uuidTo := assertCreateObject(t, refToClassName, map[string]interface{}{}) | |
assertGetObjectWithClass(t, uuidTo, refToClassName) | |
// create object with same id in MT class | |
assertCreateObjectWithID(t, otherClassMT, tenant, uuidTo, map[string]interface{}{}) | |
refs[i] = map[string]interface{}{ | |
"beacon": beaconStart + uuidTo, | |
} | |
uuids[i] = uuidTo | |
} | |
uuidFrom := assertCreateObject(t, refFromClassName, map[string]interface{}{"ref": refs}) | |
assertGetObjectWithClass(t, uuidFrom, refFromClassName) | |
objWithRef := assertGetObjectWithClass(t, uuidFrom, refFromClassName) | |
assert.NotNil(t, objWithRef.Properties) | |
refsReturned := objWithRef.Properties.(map[string]interface{})["ref"].([]interface{}) | |
for i := range refsReturned { | |
require.Equal(t, refsReturned[i].(map[string]interface{})["beacon"], string(beaconStart+"ReferenceTo/"+uuids[i])) | |
} | |
} | |
// This test suite is meant to prevent a regression on | |
// https://github.com/weaviate/weaviate/issues/868, hence it tries to | |
// reproduce the steps outlined in there as closely as possible | |
func Test_CREFWithCardinalityMany_UsingPatch(t *testing.T) { | |
defer func() { | |
// clean up so we can run this test multiple times in a row | |
delCityParams := clschema.NewSchemaObjectsDeleteParams().WithClassName("ReferenceTestCity") | |
dresp, err := helper.Client(t).Schema.SchemaObjectsDelete(delCityParams, nil) | |
t.Logf("clean up - delete city \n%v\n %v", dresp, err) | |
delPlaceParams := clschema.NewSchemaObjectsDeleteParams().WithClassName("ReferenceTestPlace") | |
dresp, err = helper.Client(t).Schema.SchemaObjectsDelete(delPlaceParams, nil) | |
t.Logf("clean up - delete place \n%v\n %v", dresp, err) | |
}() | |
t.Log("1. create ReferenceTestPlace class") | |
placeClass := &models.Class{ | |
Class: "ReferenceTestPlace", | |
Properties: []*models.Property{ | |
{ | |
DataType: schema.DataTypeText.PropString(), | |
Tokenization: models.PropertyTokenizationWhitespace, | |
Name: "name", | |
}, | |
}, | |
} | |
params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(placeClass) | |
resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) | |
helper.AssertRequestOk(t, resp, err, nil) | |
t.Log("2. create ReferenceTestCity class with HasPlaces (many) cross-ref") | |
cityClass := &models.Class{ | |
Class: "ReferenceTestCity", | |
Properties: []*models.Property{ | |
{ | |
DataType: schema.DataTypeText.PropString(), | |
Tokenization: models.PropertyTokenizationWhitespace, | |
Name: "name", | |
}, | |
{ | |
DataType: []string{"ReferenceTestPlace"}, | |
Name: "HasPlaces", | |
}, | |
}, | |
} | |
params = clschema.NewSchemaObjectsCreateParams().WithObjectClass(cityClass) | |
resp, err = helper.Client(t).Schema.SchemaObjectsCreate(params, nil) | |
helper.AssertRequestOk(t, resp, err, nil) | |
t.Log("3. add two places and save their IDs") | |
place1ID := assertCreateObject(t, "ReferenceTestPlace", map[string]interface{}{ | |
"name": "Place 1", | |
}) | |
place2ID := assertCreateObject(t, "ReferenceTestPlace", map[string]interface{}{ | |
"name": "Place 2", | |
}) | |
assertGetObjectEventually(t, place1ID) | |
assertGetObjectEventually(t, place2ID) | |
t.Log("4. add one city") | |
cityID := assertCreateObject(t, "ReferenceTestCity", map[string]interface{}{ | |
"name": "My City", | |
}) | |
assertGetObjectEventually(t, cityID) | |
t.Log("5. patch city to point to the first place") | |
patchParams := objects.NewObjectsPatchParams(). | |
WithID(cityID). | |
WithBody(&models.Object{ | |
Class: "ReferenceTestCity", | |
Properties: map[string]interface{}{ | |
"hasPlaces": []interface{}{ | |
map[string]interface{}{ | |
"beacon": fmt.Sprintf("weaviate://localhost/%s", place1ID.String()), | |
}, | |
}, | |
}, | |
}) | |
patchResp, err := helper.Client(t).Objects.ObjectsPatch(patchParams, nil) | |
helper.AssertRequestOk(t, patchResp, err, nil) | |
t.Log("6. verify first cross ref was added") | |
actualThunk := func() interface{} { | |
cityAfterFirstPatch := assertGetObject(t, cityID) | |
return cityAfterFirstPatch.Properties | |
} | |
testhelper.AssertEventuallyEqual(t, map[string]interface{}{ | |
"name": "My City", | |
"hasPlaces": []interface{}{ | |
map[string]interface{}{ | |
"beacon": fmt.Sprintf("weaviate://localhost/%s/%s", placeClass.Class, place1ID.String()), | |
"href": fmt.Sprintf("/v1/objects/%s/%s", placeClass.Class, place1ID.String()), | |
}, | |
}, | |
}, actualThunk) | |
t.Log("7. patch city to point to the second place") | |
patchParams = objects.NewObjectsPatchParams(). | |
WithID(cityID). | |
WithBody(&models.Object{ | |
Class: "ReferenceTestCity", | |
Properties: map[string]interface{}{ | |
"hasPlaces": []interface{}{ | |
map[string]interface{}{ | |
"beacon": fmt.Sprintf("weaviate://localhost/%s", place2ID.String()), | |
}, | |
}, | |
}, | |
}) | |
patchResp, err = helper.Client(t).Objects.ObjectsPatch(patchParams, nil) | |
helper.AssertRequestOk(t, patchResp, err, nil) | |
actualThunk = func() interface{} { | |
city := assertGetObject(t, cityID) | |
return city.Properties.(map[string]interface{})["hasPlaces"].([]interface{}) | |
} | |
t.Log("9. verify both cross refs are present") | |
expectedRefs := []interface{}{ | |
map[string]interface{}{ | |
"beacon": fmt.Sprintf("weaviate://localhost/%s/%s", placeClass.Class, place1ID.String()), | |
"href": fmt.Sprintf("/v1/objects/%s/%s", placeClass.Class, place1ID.String()), | |
}, | |
map[string]interface{}{ | |
"beacon": fmt.Sprintf("weaviate://localhost/%s/%s", placeClass.Class, place2ID.String()), | |
"href": fmt.Sprintf("/v1/objects/%s/%s", placeClass.Class, place2ID.String()), | |
}, | |
} | |
testhelper.AssertEventuallyEqual(t, expectedRefs, actualThunk) | |
} | |
// This test suite is meant to prevent a regression on | |
// https://github.com/weaviate/weaviate/issues/868, hence it tries to | |
// reproduce the steps outlined in there as closely as possible | |
func Test_CREFWithCardinalityMany_UsingPostReference(t *testing.T) { | |
defer func() { | |
// clean up so we can run this test multiple times in a row | |
delCityParams := clschema.NewSchemaObjectsDeleteParams().WithClassName("ReferenceTestCity") | |
dresp, err := helper.Client(t).Schema.SchemaObjectsDelete(delCityParams, nil) | |
t.Logf("clean up - delete city \n%v\n %v", dresp, err) | |
delPlaceParams := clschema.NewSchemaObjectsDeleteParams().WithClassName("ReferenceTestPlace") | |
dresp, err = helper.Client(t).Schema.SchemaObjectsDelete(delPlaceParams, nil) | |
t.Logf("clean up - delete place \n%v\n %v", dresp, err) | |
}() | |
t.Log("1. create ReferenceTestPlace class") | |
placeClass := &models.Class{ | |
Class: "ReferenceTestPlace", | |
Properties: []*models.Property{ | |
{ | |
DataType: schema.DataTypeText.PropString(), | |
Tokenization: models.PropertyTokenizationWhitespace, | |
Name: "name", | |
}, | |
}, | |
} | |
params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(placeClass) | |
resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) | |
helper.AssertRequestOk(t, resp, err, nil) | |
t.Log("2. create ReferenceTestCity class with HasPlaces (many) cross-ref") | |
cityClass := &models.Class{ | |
Class: "ReferenceTestCity", | |
Properties: []*models.Property{ | |
{ | |
DataType: schema.DataTypeText.PropString(), | |
Tokenization: models.PropertyTokenizationWhitespace, | |
Name: "name", | |
}, | |
{ | |
DataType: []string{"ReferenceTestPlace"}, | |
Name: "HasPlaces", | |
}, | |
}, | |
} | |
params = clschema.NewSchemaObjectsCreateParams().WithObjectClass(cityClass) | |
resp, err = helper.Client(t).Schema.SchemaObjectsCreate(params, nil) | |
helper.AssertRequestOk(t, resp, err, nil) | |
t.Log("3. add two places and save their IDs") | |
place1ID := assertCreateObject(t, "ReferenceTestPlace", map[string]interface{}{ | |
"name": "Place 1", | |
}) | |
place2ID := assertCreateObject(t, "ReferenceTestPlace", map[string]interface{}{ | |
"name": "Place 2", | |
}) | |
assertGetObjectEventually(t, place1ID) | |
assertGetObjectEventually(t, place2ID) | |
t.Logf("Place 1 ID: %s", place1ID) | |
t.Logf("Place 2 ID: %s", place2ID) | |
t.Log("4. add one city") | |
cityID := assertCreateObject(t, "ReferenceTestCity", map[string]interface{}{ | |
"name": "My City", | |
}) | |
assertGetObjectEventually(t, cityID) | |
t.Log("5. POST /references/ for place 1") | |
postRefParams := objects.NewObjectsReferencesCreateParams(). | |
WithID(cityID). | |
WithPropertyName("hasPlaces"). | |
WithBody(&models.SingleRef{ | |
Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", place1ID.String())), | |
}) | |
postRefResponse, err := helper.Client(t).Objects.ObjectsReferencesCreate(postRefParams, nil) | |
helper.AssertRequestOk(t, postRefResponse, err, nil) | |
actualThunk := func() interface{} { | |
city := assertGetObject(t, cityID) | |
return city.Properties | |
} | |
t.Log("7. verify first cross ref was added") | |
testhelper.AssertEventuallyEqual(t, map[string]interface{}{ | |
"name": "My City", | |
"hasPlaces": []interface{}{ | |
map[string]interface{}{ | |
"beacon": fmt.Sprintf("weaviate://localhost/%s/%s", placeClass.Class, place1ID.String()), | |
"href": fmt.Sprintf("/v1/objects/%s/%s", placeClass.Class, place1ID.String()), | |
}, | |
}, | |
}, actualThunk) | |
t.Log("8. POST /references/ for place 2") | |
postRefParams = objects.NewObjectsReferencesCreateParams(). | |
WithID(cityID). | |
WithPropertyName("hasPlaces"). | |
WithBody(&models.SingleRef{ | |
Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", place2ID.String())), | |
}) | |
postRefResponse, err = helper.Client(t).Objects.ObjectsReferencesCreate(postRefParams, nil) | |
helper.AssertRequestOk(t, postRefResponse, err, nil) | |
t.Log("9. verify both cross refs are present") | |
actualThunk = func() interface{} { | |
city := assertGetObject(t, cityID) | |
return city.Properties.(map[string]interface{})["hasPlaces"].([]interface{}) | |
} | |
expectedRefs := []interface{}{ | |
map[string]interface{}{ | |
"beacon": fmt.Sprintf("weaviate://localhost/%s/%s", placeClass.Class, place1ID.String()), | |
"href": fmt.Sprintf("/v1/objects/%s/%s", placeClass.Class, place1ID.String()), | |
}, | |
map[string]interface{}{ | |
"beacon": fmt.Sprintf("weaviate://localhost/%s/%s", placeClass.Class, place2ID.String()), | |
"href": fmt.Sprintf("/v1/objects/%s/%s", placeClass.Class, place2ID.String()), | |
}, | |
} | |
testhelper.AssertEventuallyEqual(t, expectedRefs, actualThunk) | |
} | |