github.com/weaviate/weaviate@v1.24.6/adapters/handlers/rest/handlers_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 rest 13 14 import ( 15 "context" 16 stderrors "errors" 17 "net/http/httptest" 18 "testing" 19 20 "github.com/go-openapi/strfmt" 21 "github.com/sirupsen/logrus" 22 "github.com/weaviate/weaviate/adapters/handlers/rest/operations/objects" 23 "github.com/weaviate/weaviate/entities/additional" 24 "github.com/weaviate/weaviate/entities/models" 25 "github.com/weaviate/weaviate/usecases/auth/authorization/errors" 26 "github.com/weaviate/weaviate/usecases/config" 27 uco "github.com/weaviate/weaviate/usecases/objects" 28 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/require" 31 ) 32 33 func TestEnrichObjectsWithLinks(t *testing.T) { 34 t.Run("add object", func(t *testing.T) { 35 type test struct { 36 name string 37 object *models.Object 38 expectedResult *models.Object 39 } 40 41 tests := []test{ 42 { 43 name: "without props - nothing changes", 44 object: &models.Object{Class: "Foo", Properties: nil}, 45 expectedResult: &models.Object{Class: "Foo", Properties: nil}, 46 }, 47 { 48 name: "without ref props - nothing changes", 49 object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 50 "name": "hello world", 51 "numericalField": 134, 52 }}, 53 expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 54 "name": "hello world", 55 "numericalField": 134, 56 }}, 57 }, 58 { 59 name: "with a ref prop - no origin configured", 60 object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 61 "name": "hello world", 62 "numericalField": 134, 63 "someRef": models.MultipleRef{ 64 &models.SingleRef{ 65 Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", 66 }, 67 }, 68 }}, 69 expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 70 "name": "hello world", 71 "numericalField": 134, 72 "someRef": models.MultipleRef{ 73 &models.SingleRef{ 74 Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", 75 Href: "/v1/objects/85f78e29-5937-4390-a121-5379f262b4e5", 76 }, 77 }, 78 }}, 79 }, 80 } 81 82 for _, test := range tests { 83 t.Run(test.name, func(t *testing.T) { 84 fakeManager := &fakeManager{ 85 addObjectReturn: test.object, 86 } 87 h := &objectHandlers{manager: fakeManager, metricRequestsTotal: &fakeMetricRequestsTotal{}} 88 res := h.addObject(objects.ObjectsCreateParams{ 89 HTTPRequest: httptest.NewRequest("POST", "/v1/objects", nil), 90 Body: test.object, 91 }, nil) 92 parsed, ok := res.(*objects.ObjectsCreateOK) 93 require.True(t, ok) 94 assert.Equal(t, test.expectedResult, parsed.Payload) 95 }) 96 } 97 }) 98 99 // This test "with an origin configured" is not repeated for every handler, 100 // as testing this feature once was deemed sufficient 101 t.Run("add object - with an origin configured", func(t *testing.T) { 102 type test struct { 103 name string 104 object *models.Object 105 expectedResult *models.Object 106 } 107 108 tests := []test{ 109 { 110 name: "without props - nothing changes", 111 object: &models.Object{Class: "Foo", Properties: nil}, 112 expectedResult: &models.Object{Class: "Foo", Properties: nil}, 113 }, 114 { 115 name: "without ref props - nothing changes", 116 object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 117 "name": "hello world", 118 "numericalField": 134, 119 }}, 120 expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 121 "name": "hello world", 122 "numericalField": 134, 123 }}, 124 }, 125 { 126 name: "with a ref prop - no origin configured", 127 object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 128 "name": "hello world", 129 "numericalField": 134, 130 "someRef": models.MultipleRef{ 131 &models.SingleRef{ 132 Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", 133 }, 134 }, 135 }}, 136 expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 137 "name": "hello world", 138 "numericalField": 134, 139 "someRef": models.MultipleRef{ 140 &models.SingleRef{ 141 Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", 142 Href: "https://awesomehost.com/v1/objects/85f78e29-5937-4390-a121-5379f262b4e5", 143 }, 144 }, 145 }}, 146 }, 147 } 148 149 for _, test := range tests { 150 t.Run(test.name, func(t *testing.T) { 151 fakeManager := &fakeManager{ 152 addObjectReturn: test.object, 153 } 154 config := config.Config{Origin: "https://awesomehost.com"} 155 h := &objectHandlers{manager: fakeManager, config: config, metricRequestsTotal: &fakeMetricRequestsTotal{}} 156 res := h.addObject(objects.ObjectsCreateParams{ 157 HTTPRequest: httptest.NewRequest("POST", "/v1/objects", nil), 158 Body: test.object, 159 }, nil) 160 parsed, ok := res.(*objects.ObjectsCreateOK) 161 require.True(t, ok) 162 assert.Equal(t, test.expectedResult, parsed.Payload) 163 }) 164 } 165 }) 166 167 t.Run("get object deprecated", func(t *testing.T) { 168 type test struct { 169 name string 170 object *models.Object 171 expectedResult *models.Object 172 } 173 174 tests := []test{ 175 { 176 name: "without props - nothing changes", 177 object: &models.Object{Class: "Foo", Properties: nil}, 178 expectedResult: &models.Object{Class: "Foo", Properties: nil}, 179 }, 180 { 181 name: "without ref props - nothing changes", 182 object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 183 "name": "hello world", 184 "numericalField": 134, 185 }}, 186 expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 187 "name": "hello world", 188 "numericalField": 134, 189 }}, 190 }, 191 { 192 name: "with a ref prop - no origin configured", 193 object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 194 "name": "hello world", 195 "numericalField": 134, 196 "someRef": models.MultipleRef{ 197 &models.SingleRef{ 198 Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", 199 }, 200 }, 201 }}, 202 expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 203 "name": "hello world", 204 "numericalField": 134, 205 "someRef": models.MultipleRef{ 206 &models.SingleRef{ 207 Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", 208 Href: "/v1/objects/85f78e29-5937-4390-a121-5379f262b4e5", 209 }, 210 }, 211 }}, 212 }, 213 } 214 215 for _, test := range tests { 216 t.Run(test.name, func(t *testing.T) { 217 fakeManager := &fakeManager{ 218 getObjectReturn: test.object, 219 } 220 h := &objectHandlers{manager: fakeManager, logger: &logrus.Logger{}, metricRequestsTotal: &fakeMetricRequestsTotal{}} 221 res := h.getObjectDeprecated(objects.ObjectsGetParams{HTTPRequest: httptest.NewRequest("GET", "/v1/objects", nil)}, nil) 222 parsed, ok := res.(*objects.ObjectsClassGetOK) 223 require.True(t, ok) 224 assert.Equal(t, test.expectedResult, parsed.Payload) 225 }) 226 } 227 }) 228 229 t.Run("get objects", func(t *testing.T) { 230 type test struct { 231 name string 232 object []*models.Object 233 expectedResult []*models.Object 234 } 235 236 tests := []test{ 237 { 238 name: "without props - nothing changes", 239 object: []*models.Object{{Class: "Foo", Properties: nil}}, 240 expectedResult: []*models.Object{{Class: "Foo", Properties: nil}}, 241 }, 242 { 243 name: "without ref props - nothing changes", 244 object: []*models.Object{ 245 {Class: "Foo", Properties: map[string]interface{}{ 246 "name": "hello world", 247 "numericalField": 134, 248 }}, 249 {Class: "Bar", Properties: map[string]interface{}{ 250 "name": "hello world", 251 "numericalField": 234, 252 }}, 253 }, 254 expectedResult: []*models.Object{ 255 {Class: "Foo", Properties: map[string]interface{}{ 256 "name": "hello world", 257 "numericalField": 134, 258 }}, 259 {Class: "Bar", Properties: map[string]interface{}{ 260 "name": "hello world", 261 "numericalField": 234, 262 }}, 263 }, 264 }, 265 { 266 name: "with a ref prop - no origin configured", 267 object: []*models.Object{ 268 {Class: "Foo", Properties: map[string]interface{}{ 269 "name": "hello world", 270 "numericalField": 134, 271 }}, 272 {Class: "Bar", Properties: map[string]interface{}{ 273 "name": "hello world", 274 "numericalField": 234, 275 "someRef": models.MultipleRef{ 276 &models.SingleRef{ 277 Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", 278 }, 279 }, 280 }}, 281 }, 282 expectedResult: []*models.Object{ 283 {Class: "Foo", Properties: map[string]interface{}{ 284 "name": "hello world", 285 "numericalField": 134, 286 }}, 287 {Class: "Bar", Properties: map[string]interface{}{ 288 "name": "hello world", 289 "numericalField": 234, 290 "someRef": models.MultipleRef{ 291 &models.SingleRef{ 292 Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", 293 Href: "/v1/objects/85f78e29-5937-4390-a121-5379f262b4e5", 294 }, 295 }, 296 }}, 297 }, 298 }, 299 } 300 301 for _, test := range tests { 302 t.Run(test.name, func(t *testing.T) { 303 fakeManager := &fakeManager{ 304 queryResult: test.object, 305 } 306 h := &objectHandlers{manager: fakeManager, metricRequestsTotal: &fakeMetricRequestsTotal{}} 307 res := h.getObjects(objects.ObjectsListParams{HTTPRequest: httptest.NewRequest("GET", "/v1/objects", nil)}, nil) 308 parsed, ok := res.(*objects.ObjectsListOK) 309 require.True(t, ok) 310 assert.Equal(t, test.expectedResult, parsed.Payload.Objects) 311 }) 312 } 313 }) 314 315 t.Run("update object deprecated", func(t *testing.T) { 316 type test struct { 317 name string 318 object *models.Object 319 expectedResult *models.Object 320 } 321 322 tests := []test{ 323 { 324 name: "without props - nothing changes", 325 object: &models.Object{Class: "Foo", Properties: nil}, 326 expectedResult: &models.Object{Class: "Foo", Properties: nil}, 327 }, 328 { 329 name: "without ref props - nothing changes", 330 object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 331 "name": "hello world", 332 "numericalField": 134, 333 }}, 334 expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 335 "name": "hello world", 336 "numericalField": 134, 337 }}, 338 }, 339 { 340 name: "with a ref prop - no origin configured", 341 object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 342 "name": "hello world", 343 "numericalField": 134, 344 "someRef": models.MultipleRef{ 345 &models.SingleRef{ 346 Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", 347 }, 348 }, 349 }}, 350 expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 351 "name": "hello world", 352 "numericalField": 134, 353 "someRef": models.MultipleRef{ 354 &models.SingleRef{ 355 Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", 356 Href: "/v1/objects/85f78e29-5937-4390-a121-5379f262b4e5", 357 }, 358 }, 359 }}, 360 }, 361 } 362 363 for _, test := range tests { 364 t.Run(test.name, func(t *testing.T) { 365 fakeManager := &fakeManager{ 366 updateObjectReturn: test.object, 367 } 368 h := &objectHandlers{manager: fakeManager, logger: &logrus.Logger{}, metricRequestsTotal: &fakeMetricRequestsTotal{}} 369 res := h.updateObjectDeprecated(objects.ObjectsUpdateParams{ 370 HTTPRequest: httptest.NewRequest("POST", "/v1/objects", nil), 371 Body: test.object, 372 }, nil) 373 parsed, ok := res.(*objects.ObjectsClassPutOK) 374 require.True(t, ok) 375 assert.Equal(t, test.expectedResult, parsed.Payload) 376 }) 377 } 378 }) 379 380 t.Run("add object", func(t *testing.T) { 381 type test struct { 382 name string 383 object *models.Object 384 expectedResult *models.Object 385 } 386 387 tests := []test{ 388 { 389 name: "without props - noaction changes", 390 object: &models.Object{Class: "Foo", Properties: nil}, 391 expectedResult: &models.Object{Class: "Foo", Properties: nil}, 392 }, 393 { 394 name: "without ref props - noaction changes", 395 object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 396 "name": "hello world", 397 "numericalField": 134, 398 }}, 399 expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 400 "name": "hello world", 401 "numericalField": 134, 402 }}, 403 }, 404 { 405 name: "with a ref prop - no origin configured", 406 object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 407 "name": "hello world", 408 "numericalField": 134, 409 "someRef": models.MultipleRef{ 410 &models.SingleRef{ 411 Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", 412 }, 413 }, 414 }}, 415 expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ 416 "name": "hello world", 417 "numericalField": 134, 418 "someRef": models.MultipleRef{ 419 &models.SingleRef{ 420 Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", 421 Href: "/v1/objects/85f78e29-5937-4390-a121-5379f262b4e5", 422 }, 423 }, 424 }}, 425 }, 426 } 427 428 for _, test := range tests { 429 t.Run(test.name, func(t *testing.T) { 430 fakeManager := &fakeManager{ 431 addObjectReturn: test.object, 432 } 433 h := &objectHandlers{manager: fakeManager, metricRequestsTotal: &fakeMetricRequestsTotal{}} 434 res := h.addObject(objects.ObjectsCreateParams{ 435 HTTPRequest: httptest.NewRequest("POST", "/v1/objects", nil), 436 Body: test.object, 437 }, nil) 438 parsed, ok := res.(*objects.ObjectsCreateOK) 439 require.True(t, ok) 440 assert.Equal(t, test.expectedResult, parsed.Payload) 441 }) 442 } 443 }) 444 445 t.Run("get objects", func(t *testing.T) { 446 type test struct { 447 name string 448 object []*models.Object 449 expectedResult []*models.Object 450 } 451 452 tests := []test{ 453 { 454 name: "without props - noaction changes", 455 object: []*models.Object{{Class: "Foo", Properties: nil}}, 456 expectedResult: []*models.Object{{Class: "Foo", Properties: nil}}, 457 }, 458 { 459 name: "without ref props - noaction changes", 460 object: []*models.Object{ 461 {Class: "Foo", Properties: map[string]interface{}{ 462 "name": "hello world", 463 "numericalField": 134, 464 }}, 465 {Class: "Bar", Properties: map[string]interface{}{ 466 "name": "hello world", 467 "numericalField": 234, 468 }}, 469 }, 470 expectedResult: []*models.Object{ 471 {Class: "Foo", Properties: map[string]interface{}{ 472 "name": "hello world", 473 "numericalField": 134, 474 }}, 475 {Class: "Bar", Properties: map[string]interface{}{ 476 "name": "hello world", 477 "numericalField": 234, 478 }}, 479 }, 480 }, 481 { 482 name: "with a ref prop - no origin configured", 483 object: []*models.Object{ 484 {Class: "Foo", Properties: map[string]interface{}{ 485 "name": "hello world", 486 "numericalField": 134, 487 }}, 488 {Class: "Bar", Properties: map[string]interface{}{ 489 "name": "hello world", 490 "numericalField": 234, 491 "someRef": models.MultipleRef{ 492 &models.SingleRef{ 493 Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", 494 }, 495 }, 496 }}, 497 }, 498 expectedResult: []*models.Object{ 499 {Class: "Foo", Properties: map[string]interface{}{ 500 "name": "hello world", 501 "numericalField": 134, 502 }}, 503 {Class: "Bar", Properties: map[string]interface{}{ 504 "name": "hello world", 505 "numericalField": 234, 506 "someRef": models.MultipleRef{ 507 &models.SingleRef{ 508 Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", 509 Href: "/v1/objects/85f78e29-5937-4390-a121-5379f262b4e5", 510 }, 511 }, 512 }}, 513 }, 514 }, 515 } 516 517 for _, test := range tests { 518 t.Run(test.name, func(t *testing.T) { 519 fakeManager := &fakeManager{ 520 queryResult: test.object, 521 } 522 h := &objectHandlers{manager: fakeManager, metricRequestsTotal: &fakeMetricRequestsTotal{}} 523 res := h.getObjects(objects.ObjectsListParams{HTTPRequest: httptest.NewRequest("GET", "/v1/objects", nil)}, nil) 524 parsed, ok := res.(*objects.ObjectsListOK) 525 require.True(t, ok) 526 assert.Equal(t, test.expectedResult, parsed.Payload.Objects) 527 }) 528 } 529 }) 530 531 // New endpoints which uniquely identify objects of a class 532 t.Run("UpdateObject", func(t *testing.T) { 533 cls := "MyClass" 534 type test struct { 535 name string 536 object *models.Object 537 expectedResult *models.Object 538 err error 539 } 540 541 tests := []test{ 542 { 543 name: "without props - noaction changes", 544 object: &models.Object{Class: cls, Properties: nil}, 545 expectedResult: &models.Object{Class: cls, Properties: nil}, 546 }, 547 { 548 name: "without ref props - noaction changes", 549 object: &models.Object{Class: cls, Properties: map[string]interface{}{ 550 "name": "hello world", 551 "numericalField": 134, 552 }}, 553 expectedResult: &models.Object{Class: cls, Properties: map[string]interface{}{ 554 "name": "hello world", 555 "numericalField": 134, 556 }}, 557 }, 558 { 559 name: "with a ref prop - no origin configured", 560 object: &models.Object{Class: cls, Properties: map[string]interface{}{ 561 "name": "hello world", 562 "numericalField": 134, 563 "someRef": models.MultipleRef{ 564 &models.SingleRef{ 565 Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", 566 }, 567 }, 568 }}, 569 expectedResult: &models.Object{Class: cls, Properties: map[string]interface{}{ 570 "name": "hello world", 571 "numericalField": 134, 572 "someRef": models.MultipleRef{ 573 &models.SingleRef{ 574 Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", 575 Href: "/v1/objects/85f78e29-5937-4390-a121-5379f262b4e5", 576 }, 577 }, 578 }}, 579 }, 580 { 581 name: "forbidden", 582 err: errors.NewForbidden(&models.Principal{}, "get", "Myclass/123"), 583 }, 584 { 585 name: "validation", 586 err: uco.ErrInvalidUserInput{}, 587 }, 588 { 589 name: "not found", 590 err: uco.ErrNotFound{}, 591 }, 592 { 593 name: "unknown error", 594 err: stderrors.New("any error"), 595 }, 596 } 597 598 for _, test := range tests { 599 t.Run(test.name, func(t *testing.T) { 600 fakeManager := &fakeManager{ 601 updateObjectReturn: test.object, 602 updateObjectErr: test.err, 603 } 604 h := &objectHandlers{manager: fakeManager, metricRequestsTotal: &fakeMetricRequestsTotal{}} 605 res := h.updateObject(objects.ObjectsClassPutParams{ 606 HTTPRequest: httptest.NewRequest("POST", "/v1/objects/123", nil), 607 Body: test.object, 608 ID: "123", 609 ClassName: cls, 610 }, nil) 611 parsed, ok := res.(*objects.ObjectsClassPutOK) 612 if test.err != nil { 613 require.False(t, ok) 614 return 615 } 616 require.True(t, ok) 617 assert.Equal(t, test.expectedResult, parsed.Payload) 618 }) 619 } 620 }) 621 622 t.Run("PatchObject", func(t *testing.T) { 623 var ( 624 fakeManager = &fakeManager{} 625 fakeMetricRequestsTotal = &fakeMetricRequestsTotal{} 626 h = &objectHandlers{ 627 manager: fakeManager, 628 logger: &logrus.Logger{}, 629 metricRequestsTotal: fakeMetricRequestsTotal, 630 } 631 req = objects.ObjectsClassPatchParams{ 632 HTTPRequest: httptest.NewRequest("PATCH", "/v1/objects/MyClass/123", nil), 633 ClassName: "MyClass", 634 ID: "123", 635 Body: &models.Object{Properties: map[string]interface{}{"name": "hello world"}}, 636 } 637 ) 638 res := h.patchObject(req, nil) 639 if _, ok := res.(*objects.ObjectsClassPatchNoContent); !ok { 640 t.Errorf("unexpected result %v", res) 641 } 642 fakeManager.patchObjectReturn = &uco.Error{Code: uco.StatusBadRequest} 643 res = h.patchObject(req, nil) 644 if _, ok := res.(*objects.ObjectsClassPatchUnprocessableEntity); !ok { 645 t.Errorf("expected: %T got: %T", objects.ObjectsClassPatchUnprocessableEntity{}, res) 646 } 647 fakeManager.patchObjectReturn = &uco.Error{Code: uco.StatusNotFound} 648 res = h.patchObject(req, nil) 649 if _, ok := res.(*objects.ObjectsClassPatchNotFound); !ok { 650 t.Errorf("expected: %T got: %T", objects.ObjectsClassPatchNotFound{}, res) 651 } 652 fakeManager.patchObjectReturn = &uco.Error{Code: uco.StatusForbidden} 653 res = h.patchObject(req, nil) 654 if _, ok := res.(*objects.ObjectsClassPatchForbidden); !ok { 655 t.Errorf("expected: %T got: %T", objects.ObjectsClassPatchForbidden{}, res) 656 } 657 fakeManager.patchObjectReturn = &uco.Error{Code: uco.StatusInternalServerError} 658 res = h.patchObject(req, nil) 659 if _, ok := res.(*objects.ObjectsClassPatchInternalServerError); !ok { 660 t.Errorf("expected: %T got: %T", objects.ObjectsClassPatchInternalServerError{}, res) 661 } 662 663 // test deprecated function 664 fakeManager.patchObjectReturn = nil 665 res = h.patchObjectDeprecated(objects.ObjectsPatchParams{ 666 HTTPRequest: httptest.NewRequest("PATCH", "/v1/objects/123", nil), 667 ID: "123", 668 Body: &models.Object{ 669 Class: "MyClass", 670 Properties: map[string]interface{}{"name": "hello world"}, 671 }, 672 }, nil) 673 if _, ok := res.(*objects.ObjectsClassPatchNoContent); !ok { 674 t.Errorf("unexpected result %v", res) 675 } 676 }) 677 678 t.Run("GetObject", func(t *testing.T) { 679 cls := "MyClass" 680 type test struct { 681 name string 682 object *models.Object 683 err error 684 expectedResult *models.Object 685 } 686 687 tests := []test{ 688 { 689 name: "without props - noaction changes", 690 object: &models.Object{Class: cls, Properties: nil}, 691 expectedResult: &models.Object{Class: cls, Properties: nil}, 692 }, 693 { 694 name: "without ref props - noaction changes", 695 object: &models.Object{Class: cls, Properties: map[string]interface{}{ 696 "name": "hello world", 697 "numericalField": 134, 698 }}, 699 expectedResult: &models.Object{Class: cls, Properties: map[string]interface{}{ 700 "name": "hello world", 701 "numericalField": 134, 702 }}, 703 }, 704 { 705 name: "with a ref prop - no origin configured", 706 object: &models.Object{Class: cls, Properties: map[string]interface{}{ 707 "name": "hello world", 708 "numericalField": 134, 709 "someRef": models.MultipleRef{ 710 &models.SingleRef{ 711 Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", 712 }, 713 }, 714 }}, 715 expectedResult: &models.Object{Class: cls, Properties: map[string]interface{}{ 716 "name": "hello world", 717 "numericalField": 134, 718 "someRef": models.MultipleRef{ 719 &models.SingleRef{ 720 Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", 721 Href: "/v1/objects/85f78e29-5937-4390-a121-5379f262b4e5", 722 }, 723 }, 724 }}, 725 }, 726 { 727 name: "error forbidden", 728 err: errors.NewForbidden(&models.Principal{}, "get", "Myclass/123"), 729 }, 730 { 731 name: "use case err not found", 732 err: uco.ErrNotFound{}, 733 }, 734 { 735 name: "any other error", 736 err: stderrors.New("unknown error"), 737 }, 738 } 739 740 for _, test := range tests { 741 t.Run(test.name, func(t *testing.T) { 742 fakeManager := &fakeManager{ 743 getObjectReturn: test.object, 744 getObjectErr: test.err, 745 } 746 h := &objectHandlers{manager: fakeManager, metricRequestsTotal: &fakeMetricRequestsTotal{}} 747 req := objects.ObjectsClassGetParams{ 748 HTTPRequest: httptest.NewRequest("GET", "/v1/objects/MyClass/123", nil), 749 ClassName: cls, 750 ID: "123", 751 } 752 res := h.getObject(req, nil) 753 parsed, ok := res.(*objects.ObjectsClassGetOK) 754 if test.err != nil { 755 require.False(t, ok) 756 return 757 } 758 require.True(t, ok) 759 assert.Equal(t, test.expectedResult, parsed.Payload) 760 }) 761 } 762 }) 763 764 t.Run("DeleteObject", func(t *testing.T) { 765 cls := "MyClass" 766 type test struct { 767 name string 768 err error 769 } 770 771 tests := []test{ 772 { 773 name: "without props - noaction changes", 774 }, 775 { 776 name: "error forbidden", 777 err: errors.NewForbidden(&models.Principal{}, "get", "Myclass/123"), 778 }, 779 { 780 name: "use case err not found", 781 err: uco.ErrNotFound{}, 782 }, 783 { 784 name: "unknown error", 785 err: stderrors.New("any error"), 786 }, 787 } 788 789 for _, test := range tests { 790 t.Run(test.name, func(t *testing.T) { 791 fakeManager := &fakeManager{ 792 deleteObjectReturn: test.err, 793 } 794 h := &objectHandlers{manager: fakeManager, metricRequestsTotal: &fakeMetricRequestsTotal{}} 795 req := objects.ObjectsClassDeleteParams{ 796 HTTPRequest: httptest.NewRequest("GET", "/v1/objects/MyClass/123", nil), 797 ClassName: cls, 798 ID: "123", 799 } 800 res := h.deleteObject(req, nil) 801 _, ok := res.(*objects.ObjectsClassDeleteNoContent) 802 if test.err != nil { 803 require.False(t, ok) 804 return 805 } 806 require.True(t, ok) 807 }) 808 } 809 }) 810 811 t.Run("HeadObject", func(t *testing.T) { 812 m := &fakeManager{ 813 headObjectReturn: true, 814 } 815 h := &objectHandlers{manager: m, logger: &logrus.Logger{}, metricRequestsTotal: &fakeMetricRequestsTotal{}} 816 req := objects.ObjectsClassHeadParams{ 817 HTTPRequest: httptest.NewRequest("HEAD", "/v1/objects/MyClass/123", nil), 818 ClassName: "MyClass", 819 ID: "123", 820 } 821 res := h.headObject(req, nil) 822 if _, ok := res.(*objects.ObjectsClassHeadNoContent); !ok { 823 t.Errorf("unexpected result %v", res) 824 } 825 826 m.headObjectErr = &uco.Error{Code: uco.StatusForbidden} 827 res = h.headObject(req, nil) 828 if _, ok := res.(*objects.ObjectsClassHeadForbidden); !ok { 829 t.Errorf("expected: %T got: %T", objects.ObjectsClassHeadForbidden{}, res) 830 } 831 m.headObjectErr = &uco.Error{Code: uco.StatusInternalServerError} 832 res = h.headObject(req, nil) 833 if _, ok := res.(*objects.ObjectsClassHeadInternalServerError); !ok { 834 t.Errorf("expected: %T got: %T", objects.ObjectsClassHeadInternalServerError{}, res) 835 } 836 m.headObjectErr = nil 837 m.headObjectReturn = false 838 res = h.headObject(req, nil) 839 if _, ok := res.(*objects.ObjectsClassHeadNotFound); !ok { 840 t.Errorf("expected: %T got: %T", objects.ObjectsClassHeadNotFound{}, res) 841 } 842 // same test as before but using old request 843 oldRequest := objects.ObjectsHeadParams{HTTPRequest: req.HTTPRequest} 844 res = h.headObjectDeprecated(oldRequest, nil) 845 if _, ok := res.(*objects.ObjectsClassHeadNotFound); !ok { 846 t.Errorf("expected: %T got: %T", objects.ObjectsClassHeadNotFound{}, res) 847 } 848 }) 849 850 t.Run("PostReference", func(t *testing.T) { 851 m := &fakeManager{} 852 h := &objectHandlers{manager: m, logger: &logrus.Logger{}, metricRequestsTotal: &fakeMetricRequestsTotal{}} 853 req := objects.ObjectsClassReferencesCreateParams{ 854 HTTPRequest: httptest.NewRequest("HEAD", "/v1/objects/MyClass/123/references/prop", nil), 855 ClassName: "MyClass", 856 ID: "123", 857 Body: new(models.SingleRef), 858 PropertyName: "prop", 859 } 860 res := h.addObjectReference(req, nil) 861 if _, ok := res.(*objects.ObjectsClassReferencesCreateOK); !ok { 862 t.Errorf("unexpected result %v", res) 863 } 864 865 m.addRefErr = &uco.Error{Code: uco.StatusForbidden} 866 res = h.addObjectReference(req, nil) 867 if _, ok := res.(*objects.ObjectsClassReferencesCreateForbidden); !ok { 868 t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesCreateForbidden{}, res) 869 } 870 // source object not found 871 m.addRefErr = &uco.Error{Code: uco.StatusNotFound} 872 res = h.addObjectReference(req, nil) 873 if _, ok := res.(*objects.ObjectsClassReferencesCreateNotFound); !ok { 874 t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesCreateNotFound{}, res) 875 } 876 877 m.addRefErr = &uco.Error{Code: uco.StatusInternalServerError} 878 res = h.addObjectReference(req, nil) 879 if _, ok := res.(*objects.ObjectsClassReferencesCreateInternalServerError); !ok { 880 t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesCreateInternalServerError{}, res) 881 } 882 m.addRefErr = &uco.Error{Code: uco.StatusBadRequest} 883 res = h.addObjectReference(req, nil) 884 if _, ok := res.(*objects.ObjectsClassReferencesCreateUnprocessableEntity); !ok { 885 t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesCreateUnprocessableEntity{}, res) 886 } 887 // same test as before but using old request 888 oldRequest := objects.ObjectsReferencesCreateParams{ 889 HTTPRequest: req.HTTPRequest, 890 Body: req.Body, 891 ID: req.ID, 892 PropertyName: req.ClassName, 893 } 894 res = h.addObjectReferenceDeprecated(oldRequest, nil) 895 if _, ok := res.(*objects.ObjectsClassReferencesCreateUnprocessableEntity); !ok { 896 t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesCreateUnprocessableEntity{}, res) 897 } 898 }) 899 900 t.Run("PutReferences", func(t *testing.T) { 901 m := &fakeManager{} 902 h := &objectHandlers{manager: m, logger: &logrus.Logger{}, metricRequestsTotal: &fakeMetricRequestsTotal{}} 903 req := objects.ObjectsClassReferencesPutParams{ 904 HTTPRequest: httptest.NewRequest("HEAD", "/v1/objects/MyClass/123/references/prop", nil), 905 ClassName: "MyClass", 906 ID: "123", 907 Body: models.MultipleRef{}, 908 PropertyName: "prop", 909 } 910 res := h.putObjectReferences(req, nil) 911 if _, ok := res.(*objects.ObjectsClassReferencesPutOK); !ok { 912 t.Errorf("unexpected result %v", res) 913 } 914 915 m.putRefErr = &uco.Error{Code: uco.StatusForbidden} 916 res = h.putObjectReferences(req, nil) 917 if _, ok := res.(*objects.ObjectsClassReferencesPutForbidden); !ok { 918 t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesPutForbidden{}, res) 919 } 920 m.putRefErr = &uco.Error{Code: uco.StatusInternalServerError} 921 res = h.putObjectReferences(req, nil) 922 if _, ok := res.(*objects.ObjectsClassReferencesPutInternalServerError); !ok { 923 t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesPutInternalServerError{}, res) 924 } 925 m.putRefErr = &uco.Error{Code: uco.StatusBadRequest} 926 res = h.putObjectReferences(req, nil) 927 if _, ok := res.(*objects.ObjectsClassReferencesPutUnprocessableEntity); !ok { 928 t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesPutUnprocessableEntity{}, res) 929 } 930 // same test as before but using old request 931 oldRequest := objects.ObjectsReferencesUpdateParams{ 932 HTTPRequest: req.HTTPRequest, 933 Body: req.Body, 934 ID: req.ID, 935 PropertyName: req.ClassName, 936 } 937 res = h.updateObjectReferencesDeprecated(oldRequest, nil) 938 if _, ok := res.(*objects.ObjectsClassReferencesPutUnprocessableEntity); !ok { 939 t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesPutUnprocessableEntity{}, res) 940 } 941 }) 942 943 t.Run("DeleteReference", func(t *testing.T) { 944 m := &fakeManager{} 945 h := &objectHandlers{manager: m, logger: &logrus.Logger{}, metricRequestsTotal: &fakeMetricRequestsTotal{}} 946 req := objects.ObjectsClassReferencesDeleteParams{ 947 HTTPRequest: httptest.NewRequest("HEAD", "/v1/objects/MyClass/123/references/prop", nil), 948 ClassName: "MyClass", 949 ID: "123", 950 Body: new(models.SingleRef), 951 PropertyName: "prop", 952 } 953 res := h.deleteObjectReference(req, nil) 954 if _, ok := res.(*objects.ObjectsClassReferencesDeleteNoContent); !ok { 955 t.Errorf("unexpected result %v", res) 956 } 957 958 m.deleteRefErr = &uco.Error{Code: uco.StatusForbidden} 959 res = h.deleteObjectReference(req, nil) 960 if _, ok := res.(*objects.ObjectsClassReferencesDeleteForbidden); !ok { 961 t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesDeleteForbidden{}, res) 962 } 963 // source object not found 964 m.deleteRefErr = &uco.Error{Code: uco.StatusNotFound} 965 res = h.deleteObjectReference(req, nil) 966 if _, ok := res.(*objects.ObjectsClassReferencesDeleteNotFound); !ok { 967 t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesDeleteNotFound{}, res) 968 } 969 970 m.deleteRefErr = &uco.Error{Code: uco.StatusInternalServerError} 971 res = h.deleteObjectReference(req, nil) 972 if _, ok := res.(*objects.ObjectsClassReferencesDeleteInternalServerError); !ok { 973 t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesDeleteInternalServerError{}, res) 974 } 975 m.deleteRefErr = &uco.Error{Code: uco.StatusBadRequest} 976 res = h.deleteObjectReference(req, nil) 977 if _, ok := res.(*objects.ObjectsClassReferencesDeleteUnprocessableEntity); !ok { 978 t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesDeleteUnprocessableEntity{}, res) 979 } 980 // same test as before but using old request 981 oldRequest := objects.ObjectsReferencesDeleteParams{ 982 HTTPRequest: req.HTTPRequest, 983 Body: req.Body, 984 ID: req.ID, 985 PropertyName: req.ClassName, 986 } 987 res = h.deleteObjectReferenceDeprecated(oldRequest, nil) 988 if _, ok := res.(*objects.ObjectsClassReferencesDeleteUnprocessableEntity); !ok { 989 t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesDeleteUnprocessableEntity{}, res) 990 } 991 }) 992 993 t.Run("Query", func(t *testing.T) { 994 var ( 995 cls = "MyClass" 996 m = &fakeManager{ 997 queryErr: nil, 998 queryResult: []*models.Object{{ 999 Properties: map[string]interface{}{"name": "John"}, 1000 }}, 1001 } 1002 fakeMetricRequestsTotal = &fakeMetricRequestsTotal{} 1003 h = &objectHandlers{ 1004 manager: m, 1005 logger: &logrus.Logger{}, 1006 metricRequestsTotal: fakeMetricRequestsTotal, 1007 } 1008 req = objects.ObjectsListParams{ 1009 HTTPRequest: httptest.NewRequest("HEAD", "/v1/objects/", nil), 1010 Class: &cls, 1011 } 1012 ) 1013 1014 res := h.query(req, nil) 1015 if _, ok := res.(*objects.ObjectsListOK); !ok { 1016 t.Errorf("unexpected result %v", res) 1017 } 1018 1019 m.queryErr = &uco.Error{Code: uco.StatusForbidden} 1020 res = h.query(req, nil) 1021 if _, ok := res.(*objects.ObjectsListForbidden); !ok { 1022 t.Errorf("expected: %T got: %T", objects.ObjectsListForbidden{}, res) 1023 } 1024 m.queryErr = &uco.Error{Code: uco.StatusNotFound} 1025 res = h.query(req, nil) 1026 if _, ok := res.(*objects.ObjectsListNotFound); !ok { 1027 t.Errorf("expected: %T got: %T", objects.ObjectsListNotFound{}, res) 1028 } 1029 m.queryErr = &uco.Error{Code: uco.StatusBadRequest} 1030 res = h.query(req, nil) 1031 if _, ok := res.(*objects.ObjectsListUnprocessableEntity); !ok { 1032 t.Errorf("expected: %T got: %T", objects.ObjectsListUnprocessableEntity{}, res) 1033 } 1034 m.queryErr = &uco.Error{Code: uco.StatusInternalServerError} 1035 res = h.query(req, nil) 1036 if _, ok := res.(*objects.ObjectsListInternalServerError); !ok { 1037 t.Errorf("expected: %T got: %T", objects.ObjectsListInternalServerError{}, res) 1038 } 1039 }) 1040 } 1041 1042 type fakeManager struct { 1043 getObjectReturn *models.Object 1044 getObjectErr error 1045 1046 addObjectReturn *models.Object 1047 queryResult []*models.Object 1048 queryErr *uco.Error 1049 updateObjectReturn *models.Object 1050 updateObjectErr error 1051 deleteObjectReturn error 1052 patchObjectReturn *uco.Error 1053 headObjectReturn bool 1054 headObjectErr *uco.Error 1055 addRefErr *uco.Error 1056 putRefErr *uco.Error 1057 deleteRefErr *uco.Error 1058 } 1059 1060 func (f *fakeManager) HeadObject(context.Context, *models.Principal, 1061 string, strfmt.UUID, *additional.ReplicationProperties, string, 1062 ) (bool, *uco.Error) { 1063 return f.headObjectReturn, f.headObjectErr 1064 } 1065 1066 func (f *fakeManager) AddObject(_ context.Context, _ *models.Principal, 1067 object *models.Object, _ *additional.ReplicationProperties, 1068 ) (*models.Object, error) { 1069 return object, nil 1070 } 1071 1072 func (f *fakeManager) ValidateObject(_ context.Context, _ *models.Principal, 1073 _ *models.Object, _ *additional.ReplicationProperties, 1074 ) error { 1075 panic("not implemented") // TODO: Implement 1076 } 1077 1078 func (f *fakeManager) GetObject(_ context.Context, _ *models.Principal, class string, 1079 _ strfmt.UUID, _ additional.Properties, _ *additional.ReplicationProperties, _ string, 1080 ) (*models.Object, error) { 1081 return f.getObjectReturn, f.getObjectErr 1082 } 1083 1084 func (f *fakeManager) GetObjectsClass(ctx context.Context, 1085 principal *models.Principal, id strfmt.UUID, 1086 ) (*models.Class, error) { 1087 class := &models.Class{ 1088 Class: f.getObjectReturn.Class, 1089 Vectorizer: "text2vec-contextionary", 1090 } 1091 return class, nil 1092 } 1093 1094 func (f *fakeManager) GetObjectClassFromName(ctx context.Context, principal *models.Principal, 1095 className string, 1096 ) (*models.Class, error) { 1097 class := &models.Class{ 1098 Class: f.getObjectReturn.Class, 1099 Vectorizer: "text2vec-contextionary", 1100 } 1101 return class, nil 1102 } 1103 1104 func (f *fakeManager) GetObjects(ctx context.Context, principal *models.Principal, offset *int64, limit *int64, sort *string, order *string, after *string, addl additional.Properties, tenant string) ([]*models.Object, error) { 1105 return f.queryResult, nil 1106 } 1107 1108 func (f *fakeManager) Query(_ context.Context, 1109 _ *models.Principal, _ *uco.QueryParams, 1110 ) ([]*models.Object, *uco.Error) { 1111 return f.queryResult, f.queryErr 1112 } 1113 1114 func (f *fakeManager) UpdateObject(_ context.Context, _ *models.Principal, _ string, 1115 _ strfmt.UUID, updates *models.Object, _ *additional.ReplicationProperties, 1116 ) (*models.Object, error) { 1117 return updates, f.updateObjectErr 1118 } 1119 1120 func (f *fakeManager) MergeObject(_ context.Context, _ *models.Principal, 1121 _ *models.Object, _ *additional.ReplicationProperties, 1122 ) *uco.Error { 1123 return f.patchObjectReturn 1124 } 1125 1126 func (f *fakeManager) DeleteObject(_ context.Context, _ *models.Principal, 1127 class string, _ strfmt.UUID, _ *additional.ReplicationProperties, _ string, 1128 ) error { 1129 return f.deleteObjectReturn 1130 } 1131 1132 func (f *fakeManager) AddObjectReference(context.Context, *models.Principal, 1133 *uco.AddReferenceInput, *additional.ReplicationProperties, string, 1134 ) *uco.Error { 1135 return f.addRefErr 1136 } 1137 1138 func (f *fakeManager) UpdateObjectReferences(context.Context, *models.Principal, 1139 *uco.PutReferenceInput, *additional.ReplicationProperties, string, 1140 ) *uco.Error { 1141 return f.putRefErr 1142 } 1143 1144 func (f *fakeManager) DeleteObjectReference(context.Context, *models.Principal, 1145 *uco.DeleteReferenceInput, *additional.ReplicationProperties, string, 1146 ) *uco.Error { 1147 return f.deleteRefErr 1148 } 1149 1150 type fakeMetricRequestsTotal struct{} 1151 1152 func (f *fakeMetricRequestsTotal) logError(className string, err error) {} 1153 func (f *fakeMetricRequestsTotal) logOk(className string) {} 1154 func (f *fakeMetricRequestsTotal) logUserError(className string) {} 1155 func (f *fakeMetricRequestsTotal) logServerError(className string, err error) {}