github.com/weaviate/weaviate@v1.24.6/test/acceptance/objects/crefs_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 import ( 15 "fmt" 16 "testing" 17 18 "github.com/google/uuid" 19 20 "github.com/stretchr/testify/assert" 21 "github.com/stretchr/testify/require" 22 23 "github.com/weaviate/weaviate/client/batch" 24 25 "github.com/go-openapi/strfmt" 26 "github.com/weaviate/weaviate/client/objects" 27 clschema "github.com/weaviate/weaviate/client/schema" 28 "github.com/weaviate/weaviate/entities/models" 29 "github.com/weaviate/weaviate/entities/schema" 30 "github.com/weaviate/weaviate/test/helper" 31 testhelper "github.com/weaviate/weaviate/test/helper" 32 ) 33 34 const ( 35 beaconStart = "weaviate://localhost/" 36 pathStart = "/v1/objects/" 37 ) 38 39 func TestRefsWithTenantWithoutToClass(t *testing.T) { 40 refToClassName := "ReferenceTo" 41 refFromClassName := "ReferenceFrom" 42 43 toParam := clschema.NewSchemaObjectsCreateParams().WithObjectClass( 44 &models.Class{Class: refToClassName, MultiTenancyConfig: &models.MultiTenancyConfig{Enabled: true}}, 45 ) 46 respTo, err := helper.Client(t).Schema.SchemaObjectsCreate(toParam, nil) 47 helper.AssertRequestOk(t, respTo, err, nil) 48 49 fromParam := clschema.NewSchemaObjectsCreateParams().WithObjectClass( 50 &models.Class{ 51 Class: refFromClassName, 52 MultiTenancyConfig: &models.MultiTenancyConfig{Enabled: true}, 53 Properties: []*models.Property{ 54 { 55 DataType: []string{refToClassName}, 56 Name: "ref", 57 }, 58 }, 59 }, 60 ) 61 respFrom, err := helper.Client(t).Schema.SchemaObjectsCreate(fromParam, nil) 62 helper.AssertRequestOk(t, respFrom, err, nil) 63 64 defer deleteObjectClass(t, refToClassName) 65 defer deleteObjectClass(t, refFromClassName) 66 67 tenant := "tenant" 68 tenants := make([]*models.Tenant, 1) 69 for i := range tenants { 70 tenants[i] = &models.Tenant{Name: tenant} 71 } 72 helper.CreateTenants(t, refToClassName, tenants) 73 helper.CreateTenants(t, refFromClassName, tenants) 74 75 refToId := strfmt.UUID(uuid.New().String()) 76 assertCreateObjectWithID(t, refToClassName, tenant, refToId, map[string]interface{}{}) 77 78 refFromId1 := strfmt.UUID(uuid.New().String()) 79 assertCreateObjectWithID(t, refFromClassName, tenant, refFromId1, map[string]interface{}{}) 80 81 // add reference between objects without to class name 82 postRefParams := objects.NewObjectsClassReferencesCreateParams(). 83 WithID(refFromId1). 84 WithPropertyName("ref").WithClassName(refFromClassName). 85 WithBody(&models.SingleRef{ 86 Beacon: strfmt.URI(fmt.Sprintf(beaconStart+"%s", refToId.String())), 87 }).WithTenant(&tenant) 88 postRefResponse, err := helper.Client(t).Objects.ObjectsClassReferencesCreate(postRefParams, nil) 89 helper.AssertRequestOk(t, postRefResponse, err, nil) 90 91 // add reference from batch 92 refFromId2 := strfmt.UUID(uuid.New().String()) 93 assertCreateObjectWithID(t, refFromClassName, tenant, refFromId2, map[string]interface{}{}) 94 95 // add refs without toClass 96 batchRefs := []*models.BatchReference{ 97 {From: strfmt.URI(beaconStart + "ReferenceFrom/" + refFromId2 + "/ref"), To: strfmt.URI(beaconStart + refToId), Tenant: tenant}, 98 } 99 postRefBatchParams := batch.NewBatchReferencesCreateParams().WithBody(batchRefs) 100 postRefBatchResponse, err := helper.Client(t).Batch.BatchReferencesCreate(postRefBatchParams, nil) 101 helper.AssertRequestOk(t, postRefBatchResponse, err, nil) 102 require.Nil(t, postRefBatchResponse.Payload[0].Result.Errors) 103 } 104 105 func TestRefsWithoutToClass(t *testing.T) { 106 params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(&models.Class{Class: "ReferenceTo"}) 107 resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) 108 helper.AssertRequestOk(t, resp, err, nil) 109 refToClassName := "ReferenceTo" 110 refFromClassName := "ReferenceFrom" 111 otherClassMT := "Other" 112 113 paramsMT := clschema.NewSchemaObjectsCreateParams().WithObjectClass( 114 &models.Class{ 115 Class: otherClassMT, 116 MultiTenancyConfig: &models.MultiTenancyConfig{Enabled: true}, 117 Properties: []*models.Property{ 118 { 119 DataType: []string{refToClassName}, 120 Name: "ref", 121 }, 122 }, 123 }, 124 ) 125 respMT, err := helper.Client(t).Schema.SchemaObjectsCreate(paramsMT, nil) 126 helper.AssertRequestOk(t, respMT, err, nil) 127 128 tenant := "tenant" 129 tenants := make([]*models.Tenant, 1) 130 for i := range tenants { 131 tenants[i] = &models.Tenant{Name: tenant} 132 } 133 helper.CreateTenants(t, otherClassMT, tenants) 134 135 refFromClass := &models.Class{ 136 Class: refFromClassName, 137 Properties: []*models.Property{ 138 { 139 DataType: []string{refToClassName}, 140 Name: "ref", 141 }, 142 }, 143 } 144 params2 := clschema.NewSchemaObjectsCreateParams().WithObjectClass(refFromClass) 145 resp2, err := helper.Client(t).Schema.SchemaObjectsCreate(params2, nil) 146 helper.AssertRequestOk(t, resp2, err, nil) 147 148 defer deleteObjectClass(t, refToClassName) 149 defer deleteObjectClass(t, refFromClassName) 150 defer deleteObjectClass(t, otherClassMT) 151 152 refToId := assertCreateObject(t, refToClassName, map[string]interface{}{}) 153 assertGetObjectWithClass(t, refToId, refToClassName) 154 assertCreateObjectWithID(t, otherClassMT, tenant, refToId, map[string]interface{}{}) 155 refFromId := assertCreateObject(t, refFromClassName, map[string]interface{}{}) 156 assertGetObjectWithClass(t, refFromId, refFromClassName) 157 158 postRefParams := objects.NewObjectsClassReferencesCreateParams(). 159 WithID(refFromId). 160 WithPropertyName("ref").WithClassName(refFromClass.Class). 161 WithBody(&models.SingleRef{ 162 Beacon: strfmt.URI(fmt.Sprintf(beaconStart+"%s", refToId.String())), 163 }) 164 postRefResponse, err := helper.Client(t).Objects.ObjectsClassReferencesCreate(postRefParams, nil) 165 helper.AssertRequestOk(t, postRefResponse, err, nil) 166 167 // validate that ref was create for the correct class 168 objWithRef := func() interface{} { 169 obj := assertGetObjectWithClass(t, refFromId, refFromClassName) 170 return obj.Properties 171 } 172 testhelper.AssertEventuallyEqual(t, map[string]interface{}{ 173 "ref": []interface{}{ 174 map[string]interface{}{ 175 "beacon": fmt.Sprintf(beaconStart+"%s/%s", refToClassName, refToId.String()), 176 "href": fmt.Sprintf(pathStart+"%s/%s", refToClassName, refToId.String()), 177 }, 178 }, 179 }, objWithRef) 180 181 // update prop with multiple references 182 updateRefParams := objects.NewObjectsClassReferencesPutParams(). 183 WithID(refFromId). 184 WithPropertyName("ref").WithClassName(refFromClass.Class). 185 WithBody(models.MultipleRef{ 186 {Beacon: strfmt.URI(fmt.Sprintf(beaconStart+"%s", refToId.String()))}, 187 {Beacon: strfmt.URI(fmt.Sprintf(beaconStart+"%s/%s", refToClassName, refToId.String()))}, 188 }) 189 updateRefResponse, err := helper.Client(t).Objects.ObjectsClassReferencesPut(updateRefParams, nil) 190 helper.AssertRequestOk(t, updateRefResponse, err, nil) 191 192 objWithTwoRef := func() interface{} { 193 obj := assertGetObjectWithClass(t, refFromId, refFromClassName) 194 return obj.Properties 195 } 196 testhelper.AssertEventuallyEqual(t, map[string]interface{}{ 197 "ref": []interface{}{ 198 map[string]interface{}{ 199 "beacon": fmt.Sprintf(beaconStart+"%s/%s", refToClassName, refToId.String()), 200 "href": fmt.Sprintf(pathStart+"%s/%s", refToClassName, refToId.String()), 201 }, 202 map[string]interface{}{ 203 "beacon": fmt.Sprintf(beaconStart+"%s/%s", refToClassName, refToId.String()), 204 "href": fmt.Sprintf(pathStart+"%s/%s", refToClassName, refToId.String()), 205 }, 206 }, 207 }, objWithTwoRef) 208 209 // delete reference without class 210 deleteRefParams := objects.NewObjectsClassReferencesDeleteParams(). 211 WithID(refFromId). 212 WithPropertyName("ref").WithClassName(refFromClass.Class). 213 WithBody(&models.SingleRef{ 214 Beacon: strfmt.URI(fmt.Sprintf(beaconStart+"%s", refToId.String())), 215 }) 216 deleteRefResponse, err := helper.Client(t).Objects.ObjectsClassReferencesDelete(deleteRefParams, nil) 217 helper.AssertRequestOk(t, deleteRefResponse, err, nil) 218 objWithoutRef := func() interface{} { 219 obj := assertGetObjectWithClass(t, refFromId, refFromClassName) 220 return obj.Properties 221 } 222 testhelper.AssertEventuallyEqual(t, map[string]interface{}{ 223 "ref": []interface{}{}, 224 }, objWithoutRef) 225 } 226 227 func TestRefsMultiTarget(t *testing.T) { 228 refToClassName := "ReferenceTo" 229 refFromClassName := "ReferenceFrom" 230 defer deleteObjectClass(t, refToClassName) 231 defer deleteObjectClass(t, refFromClassName) 232 233 params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(&models.Class{Class: refToClassName}) 234 resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) 235 helper.AssertRequestOk(t, resp, err, nil) 236 237 refFromClass := &models.Class{ 238 Class: refFromClassName, 239 Properties: []*models.Property{ 240 { 241 DataType: []string{refToClassName, refFromClassName}, 242 Name: "ref", 243 }, 244 }, 245 } 246 params2 := clschema.NewSchemaObjectsCreateParams().WithObjectClass(refFromClass) 247 resp2, err := helper.Client(t).Schema.SchemaObjectsCreate(params2, nil) 248 helper.AssertRequestOk(t, resp2, err, nil) 249 250 refToId := assertCreateObject(t, refToClassName, map[string]interface{}{}) 251 assertGetObjectEventually(t, refToId) 252 refFromId := assertCreateObject(t, refFromClassName, map[string]interface{}{}) 253 assertGetObjectEventually(t, refFromId) 254 255 cases := []struct { 256 classRef string 257 id string 258 }{ 259 {classRef: "", id: refToId.String()}, 260 {classRef: refToClassName + "/", id: refToId.String()}, 261 {classRef: refFromClassName + "/", id: refFromId.String()}, 262 } 263 for _, tt := range cases { 264 postRefParams := objects.NewObjectsClassReferencesCreateParams(). 265 WithID(refFromId). 266 WithPropertyName("ref").WithClassName(refFromClass.Class). 267 WithBody(&models.SingleRef{ 268 Beacon: strfmt.URI(fmt.Sprintf(beaconStart+"%s%s", tt.classRef, tt.id)), 269 }) 270 postRefResponse, err := helper.Client(t).Objects.ObjectsClassReferencesCreate(postRefParams, nil) 271 helper.AssertRequestOk(t, postRefResponse, err, nil) 272 273 // validate that ref was create for the correct class 274 objWithRef := func() interface{} { 275 obj := assertGetObjectWithClass(t, refFromId, refFromClassName) 276 return obj.Properties 277 } 278 testhelper.AssertEventuallyEqual(t, map[string]interface{}{ 279 "ref": []interface{}{ 280 map[string]interface{}{ 281 "beacon": fmt.Sprintf(beaconStart+"%s%s", tt.classRef, tt.id), 282 "href": fmt.Sprintf(pathStart+"%s%s", tt.classRef, tt.id), 283 }, 284 }, 285 }, objWithRef) 286 287 // delete refs 288 updateRefParams := objects.NewObjectsClassReferencesPutParams(). 289 WithID(refFromId). 290 WithPropertyName("ref").WithClassName(refFromClass.Class). 291 WithBody(models.MultipleRef{}) 292 updateRefResponse, err := helper.Client(t).Objects.ObjectsClassReferencesPut(updateRefParams, nil) 293 helper.AssertRequestOk(t, updateRefResponse, err, nil) 294 } 295 } 296 297 func TestBatchRefsMultiTarget(t *testing.T) { 298 refToClassName := "ReferenceTo" 299 refFromClassName := "ReferenceFrom" 300 defer deleteObjectClass(t, refToClassName) 301 defer deleteObjectClass(t, refFromClassName) 302 303 params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(&models.Class{Class: refToClassName}) 304 resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) 305 helper.AssertRequestOk(t, resp, err, nil) 306 307 refFromClass := &models.Class{ 308 Class: refFromClassName, 309 Properties: []*models.Property{ 310 { 311 DataType: []string{refToClassName, refFromClassName}, 312 Name: "ref", 313 }, 314 }, 315 } 316 params2 := clschema.NewSchemaObjectsCreateParams().WithObjectClass(refFromClass) 317 resp2, err := helper.Client(t).Schema.SchemaObjectsCreate(params2, nil) 318 helper.AssertRequestOk(t, resp2, err, nil) 319 320 uuidsTo := make([]strfmt.UUID, 10) 321 uuidsFrom := make([]strfmt.UUID, 10) 322 for i := 0; i < 10; i++ { 323 uuidsTo[i] = assertCreateObject(t, refToClassName, map[string]interface{}{}) 324 assertGetObjectEventually(t, uuidsTo[i]) 325 uuidsFrom[i] = assertCreateObject(t, refFromClassName, map[string]interface{}{}) 326 assertGetObjectEventually(t, uuidsFrom[i]) 327 } 328 329 // add refs without toClass 330 var batchRefs []*models.BatchReference 331 for i := range uuidsFrom[:2] { 332 from := beaconStart + "ReferenceFrom/" + uuidsFrom[i] + "/ref" 333 to := beaconStart + uuidsTo[i] 334 batchRefs = append(batchRefs, &models.BatchReference{From: strfmt.URI(from), To: strfmt.URI(to)}) 335 } 336 337 // add refs with toClass target 1 338 for i := range uuidsFrom[2:5] { 339 j := i + 2 340 from := beaconStart + "ReferenceFrom/" + uuidsFrom[j] + "/ref" 341 to := beaconStart + "ReferenceTo/" + uuidsTo[j] 342 batchRefs = append(batchRefs, &models.BatchReference{From: strfmt.URI(from), To: strfmt.URI(to)}) 343 } 344 345 // add refs with toClass target 2 346 for i := range uuidsFrom[5:] { 347 j := i + 5 348 from := beaconStart + "ReferenceFrom/" + uuidsFrom[j] + "/ref" 349 to := beaconStart + "ReferenceFrom/" + uuidsTo[j] 350 batchRefs = append(batchRefs, &models.BatchReference{From: strfmt.URI(from), To: strfmt.URI(to)}) 351 } 352 353 postRefParams := batch.NewBatchReferencesCreateParams().WithBody(batchRefs) 354 postRefResponse, err := helper.Client(t).Batch.BatchReferencesCreate(postRefParams, nil) 355 helper.AssertRequestOk(t, postRefResponse, err, nil) 356 357 // no autodetect for multi-target 358 for i := range uuidsFrom[:2] { 359 objWithRef := func() interface{} { 360 obj := assertGetObjectWithClass(t, uuidsFrom[i], refFromClassName) 361 return obj.Properties 362 } 363 testhelper.AssertEventuallyEqual(t, map[string]interface{}{ 364 "ref": []interface{}{ 365 map[string]interface{}{ 366 "beacon": fmt.Sprintf(beaconStart+"%s", uuidsTo[i].String()), 367 "href": fmt.Sprintf(pathStart+"%s", uuidsTo[i].String()), 368 }, 369 }, 370 }, objWithRef) 371 } 372 373 // refs for target 1 374 for i := range uuidsFrom[2:5] { 375 j := i + 2 376 objWithRef := func() interface{} { 377 obj := assertGetObjectWithClass(t, uuidsFrom[j], refFromClassName) 378 return obj.Properties 379 } 380 testhelper.AssertEventuallyEqual(t, map[string]interface{}{ 381 "ref": []interface{}{ 382 map[string]interface{}{ 383 "beacon": fmt.Sprintf(beaconStart+"%s/%s", refToClassName, uuidsTo[j].String()), 384 "href": fmt.Sprintf(pathStart+"%s/%s", refToClassName, uuidsTo[j].String()), 385 }, 386 }, 387 }, objWithRef) 388 } 389 390 // refs for target 2 391 for i := range uuidsFrom[5:] { 392 j := i + 5 393 objWithRef := func() interface{} { 394 obj := assertGetObjectWithClass(t, uuidsFrom[j], refFromClassName) 395 return obj.Properties 396 } 397 testhelper.AssertEventuallyEqual(t, map[string]interface{}{ 398 "ref": []interface{}{ 399 map[string]interface{}{ 400 "beacon": fmt.Sprintf(beaconStart+"%s/%s", refFromClassName, uuidsTo[j].String()), 401 "href": fmt.Sprintf(pathStart+"%s/%s", refFromClassName, uuidsTo[j].String()), 402 }, 403 }, 404 }, objWithRef) 405 } 406 } 407 408 func TestBatchRefsWithoutFromAndToClass(t *testing.T) { 409 refToClassName := "ReferenceTo" 410 refFromClassName := "ReferenceFrom" 411 412 params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(&models.Class{Class: refToClassName}) 413 resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) 414 helper.AssertRequestOk(t, resp, err, nil) 415 416 refFromClass := &models.Class{ 417 Class: refFromClassName, 418 Properties: []*models.Property{ 419 { 420 DataType: []string{refToClassName}, 421 Name: "ref", 422 }, 423 }, 424 } 425 params2 := clschema.NewSchemaObjectsCreateParams().WithObjectClass(refFromClass) 426 resp2, err := helper.Client(t).Schema.SchemaObjectsCreate(params2, nil) 427 helper.AssertRequestOk(t, resp2, err, nil) 428 429 defer deleteObjectClass(t, refToClassName) 430 defer deleteObjectClass(t, refFromClassName) 431 432 uuidsTo := make([]strfmt.UUID, 10) 433 uuidsFrom := make([]strfmt.UUID, 10) 434 for i := 0; i < 10; i++ { 435 uuidsTo[i] = assertCreateObject(t, refToClassName, map[string]interface{}{}) 436 assertGetObjectWithClass(t, uuidsTo[i], refToClassName) 437 438 uuidsFrom[i] = assertCreateObject(t, refFromClassName, map[string]interface{}{}) 439 assertGetObjectWithClass(t, uuidsFrom[i], refFromClassName) 440 } 441 442 // cannot do from urls without class 443 var batchRefs []*models.BatchReference 444 for i := range uuidsFrom { 445 from := beaconStart + uuidsFrom[i] + "/ref" 446 to := beaconStart + uuidsTo[i] 447 batchRefs = append(batchRefs, &models.BatchReference{From: strfmt.URI(from), To: strfmt.URI(to)}) 448 } 449 450 postRefParams := batch.NewBatchReferencesCreateParams().WithBody(batchRefs) 451 resp3, err := helper.Client(t).Batch.BatchReferencesCreate(postRefParams, nil) 452 require.Nil(t, err) 453 require.NotNil(t, resp3) 454 for i := range resp3.Payload { 455 require.NotNil(t, resp3.Payload[i].Result.Errors) 456 } 457 } 458 459 func TestBatchRefWithErrors(t *testing.T) { 460 refToClassName := "ReferenceTo" 461 refFromClassName := "ReferenceFrom" 462 463 params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(&models.Class{Class: refToClassName}) 464 resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) 465 helper.AssertRequestOk(t, resp, err, nil) 466 467 refFromClass := &models.Class{ 468 Class: refFromClassName, 469 Properties: []*models.Property{ 470 { 471 DataType: []string{refToClassName}, 472 Name: "ref", 473 }, 474 }, 475 } 476 params2 := clschema.NewSchemaObjectsCreateParams().WithObjectClass(refFromClass) 477 resp2, err := helper.Client(t).Schema.SchemaObjectsCreate(params2, nil) 478 helper.AssertRequestOk(t, resp2, err, nil) 479 480 defer deleteObjectClass(t, refToClassName) 481 defer deleteObjectClass(t, refFromClassName) 482 483 uuidsTo := make([]strfmt.UUID, 2) 484 uuidsFrom := make([]strfmt.UUID, 2) 485 for i := 0; i < 2; i++ { 486 uuidsTo[i] = assertCreateObject(t, refToClassName, map[string]interface{}{}) 487 assertGetObjectWithClass(t, uuidsTo[i], refToClassName) 488 489 uuidsFrom[i] = assertCreateObject(t, refFromClassName, map[string]interface{}{}) 490 assertGetObjectWithClass(t, uuidsFrom[i], refFromClassName) 491 } 492 493 var batchRefs []*models.BatchReference 494 for i := range uuidsFrom { 495 from := beaconStart + "ReferenceFrom/" + uuidsFrom[i] + "/ref" 496 to := beaconStart + uuidsTo[i] 497 batchRefs = append(batchRefs, &models.BatchReference{From: strfmt.URI(from), To: strfmt.URI(to)}) 498 } 499 500 // append one entry with a non-existent class 501 batchRefs = append(batchRefs, &models.BatchReference{From: strfmt.URI(beaconStart + "DoesNotExist/" + uuidsFrom[0] + "/ref"), To: strfmt.URI(beaconStart + uuidsTo[0])}) 502 503 // append one entry with a non-existent property for existing class 504 batchRefs = append(batchRefs, &models.BatchReference{From: strfmt.URI(beaconStart + "ReferenceFrom/" + uuidsFrom[0] + "/doesNotExist"), To: strfmt.URI(beaconStart + uuidsTo[0])}) 505 506 postRefParams := batch.NewBatchReferencesCreateParams().WithBody(batchRefs) 507 postRefResponse, err := helper.Client(t).Batch.BatchReferencesCreate(postRefParams, nil) 508 helper.AssertRequestOk(t, postRefResponse, err, nil) 509 510 require.NotNil(t, postRefResponse.Payload[2].Result.Errors) 511 require.Contains(t, postRefResponse.Payload[2].Result.Errors.Error[0].Message, "class DoesNotExist does not exist") 512 513 require.NotNil(t, postRefResponse.Payload[3].Result.Errors) 514 require.Contains(t, postRefResponse.Payload[3].Result.Errors.Error[0].Message, "property doesNotExist does not exist for class ReferenceFrom") 515 } 516 517 func TestBatchRefsWithoutToClass(t *testing.T) { 518 refToClassName := "ReferenceTo" 519 refFromClassName := "ReferenceFrom" 520 otherClassMT := "Other" 521 522 // other class has multi-tenancy enabled to make sure that problems trigger an error 523 paramsMT := clschema.NewSchemaObjectsCreateParams().WithObjectClass( 524 &models.Class{Class: otherClassMT, MultiTenancyConfig: &models.MultiTenancyConfig{Enabled: true}}, 525 ) 526 respMT, err := helper.Client(t).Schema.SchemaObjectsCreate(paramsMT, nil) 527 helper.AssertRequestOk(t, respMT, err, nil) 528 529 tenant := "tenant" 530 tenants := make([]*models.Tenant, 1) 531 for i := range tenants { 532 tenants[i] = &models.Tenant{Name: tenant} 533 } 534 helper.CreateTenants(t, otherClassMT, tenants) 535 536 params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(&models.Class{Class: refToClassName}) 537 resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) 538 helper.AssertRequestOk(t, resp, err, nil) 539 540 refFromClass := &models.Class{ 541 Class: refFromClassName, 542 Properties: []*models.Property{ 543 { 544 DataType: []string{refToClassName}, 545 Name: "ref", 546 }, 547 }, 548 } 549 params2 := clschema.NewSchemaObjectsCreateParams().WithObjectClass(refFromClass) 550 resp2, err := helper.Client(t).Schema.SchemaObjectsCreate(params2, nil) 551 helper.AssertRequestOk(t, resp2, err, nil) 552 553 defer deleteObjectClass(t, refToClassName) 554 defer deleteObjectClass(t, refFromClassName) 555 defer deleteObjectClass(t, otherClassMT) 556 557 uuidsTo := make([]strfmt.UUID, 10) 558 uuidsFrom := make([]strfmt.UUID, 10) 559 for i := 0; i < 10; i++ { 560 uuidsTo[i] = assertCreateObject(t, refToClassName, map[string]interface{}{}) 561 assertGetObjectWithClass(t, uuidsTo[i], refToClassName) 562 563 // create object with same id in MT class 564 assertCreateObjectWithID(t, otherClassMT, tenant, uuidsTo[i], map[string]interface{}{}) 565 566 uuidsFrom[i] = assertCreateObject(t, refFromClassName, map[string]interface{}{}) 567 assertGetObjectWithClass(t, uuidsFrom[i], refFromClassName) 568 } 569 570 var batchRefs []*models.BatchReference 571 for i := range uuidsFrom { 572 from := beaconStart + "ReferenceFrom/" + uuidsFrom[i] + "/ref" 573 to := beaconStart + uuidsTo[i] 574 batchRefs = append(batchRefs, &models.BatchReference{From: strfmt.URI(from), To: strfmt.URI(to)}) 575 } 576 577 postRefParams := batch.NewBatchReferencesCreateParams().WithBody(batchRefs) 578 postRefResponse, err := helper.Client(t).Batch.BatchReferencesCreate(postRefParams, nil) 579 helper.AssertRequestOk(t, postRefResponse, err, nil) 580 581 for i := range uuidsFrom { 582 // validate that ref was create for the correct class 583 objWithRef := func() interface{} { 584 obj := assertGetObjectWithClass(t, uuidsFrom[i], refFromClassName) 585 return obj.Properties 586 } 587 testhelper.AssertEventuallyEqual(t, map[string]interface{}{ 588 "ref": []interface{}{ 589 map[string]interface{}{ 590 "beacon": fmt.Sprintf(beaconStart+"%s/%s", refToClassName, uuidsTo[i].String()), 591 "href": fmt.Sprintf(pathStart+"%s/%s", refToClassName, uuidsTo[i].String()), 592 }, 593 }, 594 }, objWithRef) 595 } 596 } 597 598 func TestObjectBatchToClassDetection(t *testing.T) { 599 // uses same code path as normal object add 600 refToClassName := "ReferenceTo" 601 refFromClassName := "ReferenceFrom" 602 defer deleteObjectClass(t, refToClassName) 603 defer deleteObjectClass(t, refFromClassName) 604 605 params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(&models.Class{Class: refToClassName}) 606 resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) 607 helper.AssertRequestOk(t, resp, err, nil) 608 609 refFromClass := &models.Class{ 610 Class: refFromClassName, 611 Properties: []*models.Property{ 612 { 613 DataType: []string{refToClassName}, 614 Name: "ref", 615 }, 616 }, 617 } 618 params2 := clschema.NewSchemaObjectsCreateParams().WithObjectClass(refFromClass) 619 resp2, err := helper.Client(t).Schema.SchemaObjectsCreate(params2, nil) 620 helper.AssertRequestOk(t, resp2, err, nil) 621 622 refs := make([]interface{}, 10) 623 uuidsTo := make([]strfmt.UUID, 10) 624 625 for i := 0; i < 10; i++ { 626 uuidTo := assertCreateObject(t, refToClassName, map[string]interface{}{}) 627 uuidsTo[i] = uuidTo 628 assertGetObjectEventually(t, uuidTo) 629 refs[i] = map[string]interface{}{ 630 "beacon": beaconStart + uuidTo, 631 } 632 } 633 634 fromBatch := make([]*models.Object, 10) 635 for i := 0; i < 10; i++ { 636 fromBatch[i] = &models.Object{ 637 Class: refFromClassName, 638 ID: strfmt.UUID(uuid.New().String()), 639 Properties: map[string]interface{}{ 640 "ref": refs[i : i+1], 641 }, 642 } 643 } 644 paramsBatch := batch.NewBatchObjectsCreateParams().WithBody( 645 batch.BatchObjectsCreateBody{ 646 Objects: fromBatch, 647 }, 648 ) 649 res, err := helper.Client(t).Batch.BatchObjectsCreate(paramsBatch, nil) 650 require.Nil(t, err) 651 for _, elem := range res.Payload { 652 assert.Nil(t, elem.Result.Errors) 653 } 654 655 for i := range fromBatch { 656 // validate that ref was create for the correct class 657 objWithRef := func() interface{} { 658 obj := assertGetObjectWithClass(t, fromBatch[i].ID, refFromClassName) 659 return obj.Properties 660 } 661 testhelper.AssertEventuallyEqual(t, map[string]interface{}{ 662 "ref": []interface{}{ 663 map[string]interface{}{ 664 "beacon": fmt.Sprintf(beaconStart+"%s/%s", refToClassName, uuidsTo[i].String()), 665 "href": fmt.Sprintf(pathStart+"%s/%s", refToClassName, uuidsTo[i].String()), 666 }, 667 }, 668 }, objWithRef) 669 } 670 } 671 672 func TestObjectCrefWithoutToClass(t *testing.T) { 673 refToClassName := "ReferenceTo" 674 refFromClassName := "ReferenceFrom" 675 otherClassMT := "Other" 676 677 // other class has multi-tenancy enabled to make sure that problems trigger an error 678 paramsMT := clschema.NewSchemaObjectsCreateParams().WithObjectClass( 679 &models.Class{Class: otherClassMT, MultiTenancyConfig: &models.MultiTenancyConfig{Enabled: true}}, 680 ) 681 respMT, err := helper.Client(t).Schema.SchemaObjectsCreate(paramsMT, nil) 682 helper.AssertRequestOk(t, respMT, err, nil) 683 684 tenant := "tenant" 685 tenants := make([]*models.Tenant, 1) 686 for i := range tenants { 687 tenants[i] = &models.Tenant{Name: tenant} 688 } 689 helper.CreateTenants(t, otherClassMT, tenants) 690 691 params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(&models.Class{Class: refToClassName}) 692 resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) 693 helper.AssertRequestOk(t, resp, err, nil) 694 695 refFromClass := &models.Class{ 696 Class: refFromClassName, 697 Properties: []*models.Property{ 698 { 699 DataType: []string{refToClassName}, 700 Name: "ref", 701 }, 702 }, 703 } 704 params2 := clschema.NewSchemaObjectsCreateParams().WithObjectClass(refFromClass) 705 resp2, err := helper.Client(t).Schema.SchemaObjectsCreate(params2, nil) 706 helper.AssertRequestOk(t, resp2, err, nil) 707 708 defer deleteObjectClass(t, refToClassName) 709 defer deleteObjectClass(t, refFromClassName) 710 defer deleteObjectClass(t, otherClassMT) 711 712 refs := make([]interface{}, 10) 713 uuids := make([]strfmt.UUID, 10) 714 for i := 0; i < 10; i++ { 715 uuidTo := assertCreateObject(t, refToClassName, map[string]interface{}{}) 716 assertGetObjectWithClass(t, uuidTo, refToClassName) 717 718 // create object with same id in MT class 719 assertCreateObjectWithID(t, otherClassMT, tenant, uuidTo, map[string]interface{}{}) 720 721 refs[i] = map[string]interface{}{ 722 "beacon": beaconStart + uuidTo, 723 } 724 uuids[i] = uuidTo 725 } 726 727 uuidFrom := assertCreateObject(t, refFromClassName, map[string]interface{}{"ref": refs}) 728 assertGetObjectWithClass(t, uuidFrom, refFromClassName) 729 730 objWithRef := assertGetObjectWithClass(t, uuidFrom, refFromClassName) 731 assert.NotNil(t, objWithRef.Properties) 732 refsReturned := objWithRef.Properties.(map[string]interface{})["ref"].([]interface{}) 733 for i := range refsReturned { 734 require.Equal(t, refsReturned[i].(map[string]interface{})["beacon"], string(beaconStart+"ReferenceTo/"+uuids[i])) 735 } 736 } 737 738 // This test suite is meant to prevent a regression on 739 // https://github.com/weaviate/weaviate/issues/868, hence it tries to 740 // reproduce the steps outlined in there as closely as possible 741 func Test_CREFWithCardinalityMany_UsingPatch(t *testing.T) { 742 defer func() { 743 // clean up so we can run this test multiple times in a row 744 delCityParams := clschema.NewSchemaObjectsDeleteParams().WithClassName("ReferenceTestCity") 745 dresp, err := helper.Client(t).Schema.SchemaObjectsDelete(delCityParams, nil) 746 t.Logf("clean up - delete city \n%v\n %v", dresp, err) 747 748 delPlaceParams := clschema.NewSchemaObjectsDeleteParams().WithClassName("ReferenceTestPlace") 749 dresp, err = helper.Client(t).Schema.SchemaObjectsDelete(delPlaceParams, nil) 750 t.Logf("clean up - delete place \n%v\n %v", dresp, err) 751 }() 752 753 t.Log("1. create ReferenceTestPlace class") 754 placeClass := &models.Class{ 755 Class: "ReferenceTestPlace", 756 Properties: []*models.Property{ 757 { 758 DataType: schema.DataTypeText.PropString(), 759 Tokenization: models.PropertyTokenizationWhitespace, 760 Name: "name", 761 }, 762 }, 763 } 764 params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(placeClass) 765 resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) 766 helper.AssertRequestOk(t, resp, err, nil) 767 768 t.Log("2. create ReferenceTestCity class with HasPlaces (many) cross-ref") 769 cityClass := &models.Class{ 770 Class: "ReferenceTestCity", 771 Properties: []*models.Property{ 772 { 773 DataType: schema.DataTypeText.PropString(), 774 Tokenization: models.PropertyTokenizationWhitespace, 775 Name: "name", 776 }, 777 { 778 DataType: []string{"ReferenceTestPlace"}, 779 Name: "HasPlaces", 780 }, 781 }, 782 } 783 params = clschema.NewSchemaObjectsCreateParams().WithObjectClass(cityClass) 784 resp, err = helper.Client(t).Schema.SchemaObjectsCreate(params, nil) 785 helper.AssertRequestOk(t, resp, err, nil) 786 787 t.Log("3. add two places and save their IDs") 788 place1ID := assertCreateObject(t, "ReferenceTestPlace", map[string]interface{}{ 789 "name": "Place 1", 790 }) 791 place2ID := assertCreateObject(t, "ReferenceTestPlace", map[string]interface{}{ 792 "name": "Place 2", 793 }) 794 assertGetObjectEventually(t, place1ID) 795 assertGetObjectEventually(t, place2ID) 796 797 t.Log("4. add one city") 798 cityID := assertCreateObject(t, "ReferenceTestCity", map[string]interface{}{ 799 "name": "My City", 800 }) 801 assertGetObjectEventually(t, cityID) 802 803 t.Log("5. patch city to point to the first place") 804 patchParams := objects.NewObjectsPatchParams(). 805 WithID(cityID). 806 WithBody(&models.Object{ 807 Class: "ReferenceTestCity", 808 Properties: map[string]interface{}{ 809 "hasPlaces": []interface{}{ 810 map[string]interface{}{ 811 "beacon": fmt.Sprintf("weaviate://localhost/%s", place1ID.String()), 812 }, 813 }, 814 }, 815 }) 816 patchResp, err := helper.Client(t).Objects.ObjectsPatch(patchParams, nil) 817 helper.AssertRequestOk(t, patchResp, err, nil) 818 819 t.Log("6. verify first cross ref was added") 820 821 actualThunk := func() interface{} { 822 cityAfterFirstPatch := assertGetObject(t, cityID) 823 return cityAfterFirstPatch.Properties 824 } 825 826 testhelper.AssertEventuallyEqual(t, map[string]interface{}{ 827 "name": "My City", 828 "hasPlaces": []interface{}{ 829 map[string]interface{}{ 830 "beacon": fmt.Sprintf("weaviate://localhost/%s/%s", placeClass.Class, place1ID.String()), 831 "href": fmt.Sprintf("/v1/objects/%s/%s", placeClass.Class, place1ID.String()), 832 }, 833 }, 834 }, actualThunk) 835 836 t.Log("7. patch city to point to the second place") 837 patchParams = objects.NewObjectsPatchParams(). 838 WithID(cityID). 839 WithBody(&models.Object{ 840 Class: "ReferenceTestCity", 841 Properties: map[string]interface{}{ 842 "hasPlaces": []interface{}{ 843 map[string]interface{}{ 844 "beacon": fmt.Sprintf("weaviate://localhost/%s", place2ID.String()), 845 }, 846 }, 847 }, 848 }) 849 patchResp, err = helper.Client(t).Objects.ObjectsPatch(patchParams, nil) 850 helper.AssertRequestOk(t, patchResp, err, nil) 851 852 actualThunk = func() interface{} { 853 city := assertGetObject(t, cityID) 854 return city.Properties.(map[string]interface{})["hasPlaces"].([]interface{}) 855 } 856 857 t.Log("9. verify both cross refs are present") 858 expectedRefs := []interface{}{ 859 map[string]interface{}{ 860 "beacon": fmt.Sprintf("weaviate://localhost/%s/%s", placeClass.Class, place1ID.String()), 861 "href": fmt.Sprintf("/v1/objects/%s/%s", placeClass.Class, place1ID.String()), 862 }, 863 map[string]interface{}{ 864 "beacon": fmt.Sprintf("weaviate://localhost/%s/%s", placeClass.Class, place2ID.String()), 865 "href": fmt.Sprintf("/v1/objects/%s/%s", placeClass.Class, place2ID.String()), 866 }, 867 } 868 869 testhelper.AssertEventuallyEqual(t, expectedRefs, actualThunk) 870 } 871 872 // This test suite is meant to prevent a regression on 873 // https://github.com/weaviate/weaviate/issues/868, hence it tries to 874 // reproduce the steps outlined in there as closely as possible 875 func Test_CREFWithCardinalityMany_UsingPostReference(t *testing.T) { 876 defer func() { 877 // clean up so we can run this test multiple times in a row 878 delCityParams := clschema.NewSchemaObjectsDeleteParams().WithClassName("ReferenceTestCity") 879 dresp, err := helper.Client(t).Schema.SchemaObjectsDelete(delCityParams, nil) 880 t.Logf("clean up - delete city \n%v\n %v", dresp, err) 881 882 delPlaceParams := clschema.NewSchemaObjectsDeleteParams().WithClassName("ReferenceTestPlace") 883 dresp, err = helper.Client(t).Schema.SchemaObjectsDelete(delPlaceParams, nil) 884 t.Logf("clean up - delete place \n%v\n %v", dresp, err) 885 }() 886 887 t.Log("1. create ReferenceTestPlace class") 888 placeClass := &models.Class{ 889 Class: "ReferenceTestPlace", 890 Properties: []*models.Property{ 891 { 892 DataType: schema.DataTypeText.PropString(), 893 Tokenization: models.PropertyTokenizationWhitespace, 894 Name: "name", 895 }, 896 }, 897 } 898 params := clschema.NewSchemaObjectsCreateParams().WithObjectClass(placeClass) 899 resp, err := helper.Client(t).Schema.SchemaObjectsCreate(params, nil) 900 helper.AssertRequestOk(t, resp, err, nil) 901 902 t.Log("2. create ReferenceTestCity class with HasPlaces (many) cross-ref") 903 cityClass := &models.Class{ 904 Class: "ReferenceTestCity", 905 Properties: []*models.Property{ 906 { 907 DataType: schema.DataTypeText.PropString(), 908 Tokenization: models.PropertyTokenizationWhitespace, 909 Name: "name", 910 }, 911 { 912 DataType: []string{"ReferenceTestPlace"}, 913 Name: "HasPlaces", 914 }, 915 }, 916 } 917 params = clschema.NewSchemaObjectsCreateParams().WithObjectClass(cityClass) 918 resp, err = helper.Client(t).Schema.SchemaObjectsCreate(params, nil) 919 helper.AssertRequestOk(t, resp, err, nil) 920 921 t.Log("3. add two places and save their IDs") 922 place1ID := assertCreateObject(t, "ReferenceTestPlace", map[string]interface{}{ 923 "name": "Place 1", 924 }) 925 place2ID := assertCreateObject(t, "ReferenceTestPlace", map[string]interface{}{ 926 "name": "Place 2", 927 }) 928 assertGetObjectEventually(t, place1ID) 929 assertGetObjectEventually(t, place2ID) 930 t.Logf("Place 1 ID: %s", place1ID) 931 t.Logf("Place 2 ID: %s", place2ID) 932 933 t.Log("4. add one city") 934 cityID := assertCreateObject(t, "ReferenceTestCity", map[string]interface{}{ 935 "name": "My City", 936 }) 937 assertGetObjectEventually(t, cityID) 938 939 t.Log("5. POST /references/ for place 1") 940 postRefParams := objects.NewObjectsReferencesCreateParams(). 941 WithID(cityID). 942 WithPropertyName("hasPlaces"). 943 WithBody(&models.SingleRef{ 944 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", place1ID.String())), 945 }) 946 postRefResponse, err := helper.Client(t).Objects.ObjectsReferencesCreate(postRefParams, nil) 947 helper.AssertRequestOk(t, postRefResponse, err, nil) 948 949 actualThunk := func() interface{} { 950 city := assertGetObject(t, cityID) 951 return city.Properties 952 } 953 t.Log("7. verify first cross ref was added") 954 testhelper.AssertEventuallyEqual(t, map[string]interface{}{ 955 "name": "My City", 956 "hasPlaces": []interface{}{ 957 map[string]interface{}{ 958 "beacon": fmt.Sprintf("weaviate://localhost/%s/%s", placeClass.Class, place1ID.String()), 959 "href": fmt.Sprintf("/v1/objects/%s/%s", placeClass.Class, place1ID.String()), 960 }, 961 }, 962 }, actualThunk) 963 964 t.Log("8. POST /references/ for place 2") 965 postRefParams = objects.NewObjectsReferencesCreateParams(). 966 WithID(cityID). 967 WithPropertyName("hasPlaces"). 968 WithBody(&models.SingleRef{ 969 Beacon: strfmt.URI(fmt.Sprintf("weaviate://localhost/%s", place2ID.String())), 970 }) 971 postRefResponse, err = helper.Client(t).Objects.ObjectsReferencesCreate(postRefParams, nil) 972 helper.AssertRequestOk(t, postRefResponse, err, nil) 973 974 t.Log("9. verify both cross refs are present") 975 actualThunk = func() interface{} { 976 city := assertGetObject(t, cityID) 977 return city.Properties.(map[string]interface{})["hasPlaces"].([]interface{}) 978 } 979 980 expectedRefs := []interface{}{ 981 map[string]interface{}{ 982 "beacon": fmt.Sprintf("weaviate://localhost/%s/%s", placeClass.Class, place1ID.String()), 983 "href": fmt.Sprintf("/v1/objects/%s/%s", placeClass.Class, place1ID.String()), 984 }, 985 map[string]interface{}{ 986 "beacon": fmt.Sprintf("weaviate://localhost/%s/%s", placeClass.Class, place2ID.String()), 987 "href": fmt.Sprintf("/v1/objects/%s/%s", placeClass.Class, place2ID.String()), 988 }, 989 } 990 991 testhelper.AssertEventuallyEqual(t, expectedRefs, actualThunk) 992 }