Spaces:
Running
Running
// _ _ | |
// __ _____ __ ___ ___ __ _| |_ ___ | |
// \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ | |
// \ V V / __/ (_| |\ V /| | (_| | || __/ | |
// \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| | |
// | |
// Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. | |
// | |
// CONTACT: [email protected] | |
// | |
package rest | |
import ( | |
"context" | |
stderrors "errors" | |
"net/http/httptest" | |
"testing" | |
"github.com/go-openapi/strfmt" | |
"github.com/sirupsen/logrus" | |
"github.com/weaviate/weaviate/adapters/handlers/rest/operations/objects" | |
"github.com/weaviate/weaviate/entities/additional" | |
"github.com/weaviate/weaviate/entities/models" | |
"github.com/weaviate/weaviate/usecases/auth/authorization/errors" | |
"github.com/weaviate/weaviate/usecases/config" | |
uco "github.com/weaviate/weaviate/usecases/objects" | |
"github.com/stretchr/testify/assert" | |
"github.com/stretchr/testify/require" | |
) | |
func TestEnrichObjectsWithLinks(t *testing.T) { | |
t.Run("add object", func(t *testing.T) { | |
type test struct { | |
name string | |
object *models.Object | |
expectedResult *models.Object | |
} | |
tests := []test{ | |
{ | |
name: "without props - nothing changes", | |
object: &models.Object{Class: "Foo", Properties: nil}, | |
expectedResult: &models.Object{Class: "Foo", Properties: nil}, | |
}, | |
{ | |
name: "without ref props - nothing changes", | |
object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
}, | |
{ | |
name: "with a ref prop - no origin configured", | |
object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
"someRef": models.MultipleRef{ | |
&models.SingleRef{ | |
Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", | |
}, | |
}, | |
}}, | |
expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
"someRef": models.MultipleRef{ | |
&models.SingleRef{ | |
Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", | |
Href: "/v1/objects/85f78e29-5937-4390-a121-5379f262b4e5", | |
}, | |
}, | |
}}, | |
}, | |
} | |
for _, test := range tests { | |
t.Run(test.name, func(t *testing.T) { | |
fakeManager := &fakeManager{ | |
addObjectReturn: test.object, | |
} | |
h := &objectHandlers{manager: fakeManager, metricRequestsTotal: &fakeMetricRequestsTotal{}} | |
res := h.addObject(objects.ObjectsCreateParams{ | |
HTTPRequest: httptest.NewRequest("POST", "/v1/objects", nil), | |
Body: test.object, | |
}, nil) | |
parsed, ok := res.(*objects.ObjectsCreateOK) | |
require.True(t, ok) | |
assert.Equal(t, test.expectedResult, parsed.Payload) | |
}) | |
} | |
}) | |
// This test "with an origin configured" is not repeated for every handler, | |
// as testing this feature once was deemed sufficient | |
t.Run("add object - with an origin configured", func(t *testing.T) { | |
type test struct { | |
name string | |
object *models.Object | |
expectedResult *models.Object | |
} | |
tests := []test{ | |
{ | |
name: "without props - nothing changes", | |
object: &models.Object{Class: "Foo", Properties: nil}, | |
expectedResult: &models.Object{Class: "Foo", Properties: nil}, | |
}, | |
{ | |
name: "without ref props - nothing changes", | |
object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
}, | |
{ | |
name: "with a ref prop - no origin configured", | |
object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
"someRef": models.MultipleRef{ | |
&models.SingleRef{ | |
Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", | |
}, | |
}, | |
}}, | |
expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
"someRef": models.MultipleRef{ | |
&models.SingleRef{ | |
Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", | |
Href: "https://awesomehost.com/v1/objects/85f78e29-5937-4390-a121-5379f262b4e5", | |
}, | |
}, | |
}}, | |
}, | |
} | |
for _, test := range tests { | |
t.Run(test.name, func(t *testing.T) { | |
fakeManager := &fakeManager{ | |
addObjectReturn: test.object, | |
} | |
config := config.Config{Origin: "https://awesomehost.com"} | |
h := &objectHandlers{manager: fakeManager, config: config, metricRequestsTotal: &fakeMetricRequestsTotal{}} | |
res := h.addObject(objects.ObjectsCreateParams{ | |
HTTPRequest: httptest.NewRequest("POST", "/v1/objects", nil), | |
Body: test.object, | |
}, nil) | |
parsed, ok := res.(*objects.ObjectsCreateOK) | |
require.True(t, ok) | |
assert.Equal(t, test.expectedResult, parsed.Payload) | |
}) | |
} | |
}) | |
t.Run("get object deprecated", func(t *testing.T) { | |
type test struct { | |
name string | |
object *models.Object | |
expectedResult *models.Object | |
} | |
tests := []test{ | |
{ | |
name: "without props - nothing changes", | |
object: &models.Object{Class: "Foo", Properties: nil}, | |
expectedResult: &models.Object{Class: "Foo", Properties: nil}, | |
}, | |
{ | |
name: "without ref props - nothing changes", | |
object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
}, | |
{ | |
name: "with a ref prop - no origin configured", | |
object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
"someRef": models.MultipleRef{ | |
&models.SingleRef{ | |
Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", | |
}, | |
}, | |
}}, | |
expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
"someRef": models.MultipleRef{ | |
&models.SingleRef{ | |
Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", | |
Href: "/v1/objects/85f78e29-5937-4390-a121-5379f262b4e5", | |
}, | |
}, | |
}}, | |
}, | |
} | |
for _, test := range tests { | |
t.Run(test.name, func(t *testing.T) { | |
fakeManager := &fakeManager{ | |
getObjectReturn: test.object, | |
} | |
h := &objectHandlers{manager: fakeManager, logger: &logrus.Logger{}, metricRequestsTotal: &fakeMetricRequestsTotal{}} | |
res := h.getObjectDeprecated(objects.ObjectsGetParams{HTTPRequest: httptest.NewRequest("GET", "/v1/objects", nil)}, nil) | |
parsed, ok := res.(*objects.ObjectsClassGetOK) | |
require.True(t, ok) | |
assert.Equal(t, test.expectedResult, parsed.Payload) | |
}) | |
} | |
}) | |
t.Run("get objects", func(t *testing.T) { | |
type test struct { | |
name string | |
object []*models.Object | |
expectedResult []*models.Object | |
} | |
tests := []test{ | |
{ | |
name: "without props - nothing changes", | |
object: []*models.Object{{Class: "Foo", Properties: nil}}, | |
expectedResult: []*models.Object{{Class: "Foo", Properties: nil}}, | |
}, | |
{ | |
name: "without ref props - nothing changes", | |
object: []*models.Object{ | |
{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
{Class: "Bar", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 234, | |
}}, | |
}, | |
expectedResult: []*models.Object{ | |
{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
{Class: "Bar", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 234, | |
}}, | |
}, | |
}, | |
{ | |
name: "with a ref prop - no origin configured", | |
object: []*models.Object{ | |
{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
{Class: "Bar", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 234, | |
"someRef": models.MultipleRef{ | |
&models.SingleRef{ | |
Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", | |
}, | |
}, | |
}}, | |
}, | |
expectedResult: []*models.Object{ | |
{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
{Class: "Bar", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 234, | |
"someRef": models.MultipleRef{ | |
&models.SingleRef{ | |
Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", | |
Href: "/v1/objects/85f78e29-5937-4390-a121-5379f262b4e5", | |
}, | |
}, | |
}}, | |
}, | |
}, | |
} | |
for _, test := range tests { | |
t.Run(test.name, func(t *testing.T) { | |
fakeManager := &fakeManager{ | |
queryResult: test.object, | |
} | |
h := &objectHandlers{manager: fakeManager, metricRequestsTotal: &fakeMetricRequestsTotal{}} | |
res := h.getObjects(objects.ObjectsListParams{HTTPRequest: httptest.NewRequest("GET", "/v1/objects", nil)}, nil) | |
parsed, ok := res.(*objects.ObjectsListOK) | |
require.True(t, ok) | |
assert.Equal(t, test.expectedResult, parsed.Payload.Objects) | |
}) | |
} | |
}) | |
t.Run("update object deprecated", func(t *testing.T) { | |
type test struct { | |
name string | |
object *models.Object | |
expectedResult *models.Object | |
} | |
tests := []test{ | |
{ | |
name: "without props - nothing changes", | |
object: &models.Object{Class: "Foo", Properties: nil}, | |
expectedResult: &models.Object{Class: "Foo", Properties: nil}, | |
}, | |
{ | |
name: "without ref props - nothing changes", | |
object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
}, | |
{ | |
name: "with a ref prop - no origin configured", | |
object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
"someRef": models.MultipleRef{ | |
&models.SingleRef{ | |
Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", | |
}, | |
}, | |
}}, | |
expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
"someRef": models.MultipleRef{ | |
&models.SingleRef{ | |
Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", | |
Href: "/v1/objects/85f78e29-5937-4390-a121-5379f262b4e5", | |
}, | |
}, | |
}}, | |
}, | |
} | |
for _, test := range tests { | |
t.Run(test.name, func(t *testing.T) { | |
fakeManager := &fakeManager{ | |
updateObjectReturn: test.object, | |
} | |
h := &objectHandlers{manager: fakeManager, logger: &logrus.Logger{}, metricRequestsTotal: &fakeMetricRequestsTotal{}} | |
res := h.updateObjectDeprecated(objects.ObjectsUpdateParams{ | |
HTTPRequest: httptest.NewRequest("POST", "/v1/objects", nil), | |
Body: test.object, | |
}, nil) | |
parsed, ok := res.(*objects.ObjectsClassPutOK) | |
require.True(t, ok) | |
assert.Equal(t, test.expectedResult, parsed.Payload) | |
}) | |
} | |
}) | |
t.Run("add object", func(t *testing.T) { | |
type test struct { | |
name string | |
object *models.Object | |
expectedResult *models.Object | |
} | |
tests := []test{ | |
{ | |
name: "without props - noaction changes", | |
object: &models.Object{Class: "Foo", Properties: nil}, | |
expectedResult: &models.Object{Class: "Foo", Properties: nil}, | |
}, | |
{ | |
name: "without ref props - noaction changes", | |
object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
}, | |
{ | |
name: "with a ref prop - no origin configured", | |
object: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
"someRef": models.MultipleRef{ | |
&models.SingleRef{ | |
Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", | |
}, | |
}, | |
}}, | |
expectedResult: &models.Object{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
"someRef": models.MultipleRef{ | |
&models.SingleRef{ | |
Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", | |
Href: "/v1/objects/85f78e29-5937-4390-a121-5379f262b4e5", | |
}, | |
}, | |
}}, | |
}, | |
} | |
for _, test := range tests { | |
t.Run(test.name, func(t *testing.T) { | |
fakeManager := &fakeManager{ | |
addObjectReturn: test.object, | |
} | |
h := &objectHandlers{manager: fakeManager, metricRequestsTotal: &fakeMetricRequestsTotal{}} | |
res := h.addObject(objects.ObjectsCreateParams{ | |
HTTPRequest: httptest.NewRequest("POST", "/v1/objects", nil), | |
Body: test.object, | |
}, nil) | |
parsed, ok := res.(*objects.ObjectsCreateOK) | |
require.True(t, ok) | |
assert.Equal(t, test.expectedResult, parsed.Payload) | |
}) | |
} | |
}) | |
t.Run("get objects", func(t *testing.T) { | |
type test struct { | |
name string | |
object []*models.Object | |
expectedResult []*models.Object | |
} | |
tests := []test{ | |
{ | |
name: "without props - noaction changes", | |
object: []*models.Object{{Class: "Foo", Properties: nil}}, | |
expectedResult: []*models.Object{{Class: "Foo", Properties: nil}}, | |
}, | |
{ | |
name: "without ref props - noaction changes", | |
object: []*models.Object{ | |
{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
{Class: "Bar", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 234, | |
}}, | |
}, | |
expectedResult: []*models.Object{ | |
{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
{Class: "Bar", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 234, | |
}}, | |
}, | |
}, | |
{ | |
name: "with a ref prop - no origin configured", | |
object: []*models.Object{ | |
{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
{Class: "Bar", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 234, | |
"someRef": models.MultipleRef{ | |
&models.SingleRef{ | |
Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", | |
}, | |
}, | |
}}, | |
}, | |
expectedResult: []*models.Object{ | |
{Class: "Foo", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
{Class: "Bar", Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 234, | |
"someRef": models.MultipleRef{ | |
&models.SingleRef{ | |
Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", | |
Href: "/v1/objects/85f78e29-5937-4390-a121-5379f262b4e5", | |
}, | |
}, | |
}}, | |
}, | |
}, | |
} | |
for _, test := range tests { | |
t.Run(test.name, func(t *testing.T) { | |
fakeManager := &fakeManager{ | |
queryResult: test.object, | |
} | |
h := &objectHandlers{manager: fakeManager, metricRequestsTotal: &fakeMetricRequestsTotal{}} | |
res := h.getObjects(objects.ObjectsListParams{HTTPRequest: httptest.NewRequest("GET", "/v1/objects", nil)}, nil) | |
parsed, ok := res.(*objects.ObjectsListOK) | |
require.True(t, ok) | |
assert.Equal(t, test.expectedResult, parsed.Payload.Objects) | |
}) | |
} | |
}) | |
// New endpoints which uniquely identify objects of a class | |
t.Run("UpdateObject", func(t *testing.T) { | |
cls := "MyClass" | |
type test struct { | |
name string | |
object *models.Object | |
expectedResult *models.Object | |
err error | |
} | |
tests := []test{ | |
{ | |
name: "without props - noaction changes", | |
object: &models.Object{Class: cls, Properties: nil}, | |
expectedResult: &models.Object{Class: cls, Properties: nil}, | |
}, | |
{ | |
name: "without ref props - noaction changes", | |
object: &models.Object{Class: cls, Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
expectedResult: &models.Object{Class: cls, Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
}, | |
{ | |
name: "with a ref prop - no origin configured", | |
object: &models.Object{Class: cls, Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
"someRef": models.MultipleRef{ | |
&models.SingleRef{ | |
Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", | |
}, | |
}, | |
}}, | |
expectedResult: &models.Object{Class: cls, Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
"someRef": models.MultipleRef{ | |
&models.SingleRef{ | |
Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", | |
Href: "/v1/objects/85f78e29-5937-4390-a121-5379f262b4e5", | |
}, | |
}, | |
}}, | |
}, | |
{ | |
name: "forbidden", | |
err: errors.NewForbidden(&models.Principal{}, "get", "Myclass/123"), | |
}, | |
{ | |
name: "validation", | |
err: uco.ErrInvalidUserInput{}, | |
}, | |
{ | |
name: "not found", | |
err: uco.ErrNotFound{}, | |
}, | |
{ | |
name: "unknown error", | |
err: stderrors.New("any error"), | |
}, | |
} | |
for _, test := range tests { | |
t.Run(test.name, func(t *testing.T) { | |
fakeManager := &fakeManager{ | |
updateObjectReturn: test.object, | |
updateObjectErr: test.err, | |
} | |
h := &objectHandlers{manager: fakeManager, metricRequestsTotal: &fakeMetricRequestsTotal{}} | |
res := h.updateObject(objects.ObjectsClassPutParams{ | |
HTTPRequest: httptest.NewRequest("POST", "/v1/objects/123", nil), | |
Body: test.object, | |
ID: "123", | |
ClassName: cls, | |
}, nil) | |
parsed, ok := res.(*objects.ObjectsClassPutOK) | |
if test.err != nil { | |
require.False(t, ok) | |
return | |
} | |
require.True(t, ok) | |
assert.Equal(t, test.expectedResult, parsed.Payload) | |
}) | |
} | |
}) | |
t.Run("PatchObject", func(t *testing.T) { | |
var ( | |
fakeManager = &fakeManager{} | |
fakeMetricRequestsTotal = &fakeMetricRequestsTotal{} | |
h = &objectHandlers{ | |
manager: fakeManager, | |
logger: &logrus.Logger{}, | |
metricRequestsTotal: fakeMetricRequestsTotal, | |
} | |
req = objects.ObjectsClassPatchParams{ | |
HTTPRequest: httptest.NewRequest("PATCH", "/v1/objects/MyClass/123", nil), | |
ClassName: "MyClass", | |
ID: "123", | |
Body: &models.Object{Properties: map[string]interface{}{"name": "hello world"}}, | |
} | |
) | |
res := h.patchObject(req, nil) | |
if _, ok := res.(*objects.ObjectsClassPatchNoContent); !ok { | |
t.Errorf("unexpected result %v", res) | |
} | |
fakeManager.patchObjectReturn = &uco.Error{Code: uco.StatusBadRequest} | |
res = h.patchObject(req, nil) | |
if _, ok := res.(*objects.ObjectsClassPatchUnprocessableEntity); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassPatchUnprocessableEntity{}, res) | |
} | |
fakeManager.patchObjectReturn = &uco.Error{Code: uco.StatusNotFound} | |
res = h.patchObject(req, nil) | |
if _, ok := res.(*objects.ObjectsClassPatchNotFound); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassPatchNotFound{}, res) | |
} | |
fakeManager.patchObjectReturn = &uco.Error{Code: uco.StatusForbidden} | |
res = h.patchObject(req, nil) | |
if _, ok := res.(*objects.ObjectsClassPatchForbidden); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassPatchForbidden{}, res) | |
} | |
fakeManager.patchObjectReturn = &uco.Error{Code: uco.StatusInternalServerError} | |
res = h.patchObject(req, nil) | |
if _, ok := res.(*objects.ObjectsClassPatchInternalServerError); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassPatchInternalServerError{}, res) | |
} | |
// test deprecated function | |
fakeManager.patchObjectReturn = nil | |
res = h.patchObjectDeprecated(objects.ObjectsPatchParams{ | |
HTTPRequest: httptest.NewRequest("PATCH", "/v1/objects/123", nil), | |
ID: "123", | |
Body: &models.Object{ | |
Class: "MyClass", | |
Properties: map[string]interface{}{"name": "hello world"}, | |
}, | |
}, nil) | |
if _, ok := res.(*objects.ObjectsClassPatchNoContent); !ok { | |
t.Errorf("unexpected result %v", res) | |
} | |
}) | |
t.Run("GetObject", func(t *testing.T) { | |
cls := "MyClass" | |
type test struct { | |
name string | |
object *models.Object | |
err error | |
expectedResult *models.Object | |
} | |
tests := []test{ | |
{ | |
name: "without props - noaction changes", | |
object: &models.Object{Class: cls, Properties: nil}, | |
expectedResult: &models.Object{Class: cls, Properties: nil}, | |
}, | |
{ | |
name: "without ref props - noaction changes", | |
object: &models.Object{Class: cls, Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
expectedResult: &models.Object{Class: cls, Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
}}, | |
}, | |
{ | |
name: "with a ref prop - no origin configured", | |
object: &models.Object{Class: cls, Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
"someRef": models.MultipleRef{ | |
&models.SingleRef{ | |
Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", | |
}, | |
}, | |
}}, | |
expectedResult: &models.Object{Class: cls, Properties: map[string]interface{}{ | |
"name": "hello world", | |
"numericalField": 134, | |
"someRef": models.MultipleRef{ | |
&models.SingleRef{ | |
Beacon: "weaviate://localhost/85f78e29-5937-4390-a121-5379f262b4e5", | |
Href: "/v1/objects/85f78e29-5937-4390-a121-5379f262b4e5", | |
}, | |
}, | |
}}, | |
}, | |
{ | |
name: "error forbidden", | |
err: errors.NewForbidden(&models.Principal{}, "get", "Myclass/123"), | |
}, | |
{ | |
name: "use case err not found", | |
err: uco.ErrNotFound{}, | |
}, | |
{ | |
name: "any other error", | |
err: stderrors.New("unknown error"), | |
}, | |
} | |
for _, test := range tests { | |
t.Run(test.name, func(t *testing.T) { | |
fakeManager := &fakeManager{ | |
getObjectReturn: test.object, | |
getObjectErr: test.err, | |
} | |
h := &objectHandlers{manager: fakeManager, metricRequestsTotal: &fakeMetricRequestsTotal{}} | |
req := objects.ObjectsClassGetParams{ | |
HTTPRequest: httptest.NewRequest("GET", "/v1/objects/MyClass/123", nil), | |
ClassName: cls, | |
ID: "123", | |
} | |
res := h.getObject(req, nil) | |
parsed, ok := res.(*objects.ObjectsClassGetOK) | |
if test.err != nil { | |
require.False(t, ok) | |
return | |
} | |
require.True(t, ok) | |
assert.Equal(t, test.expectedResult, parsed.Payload) | |
}) | |
} | |
}) | |
t.Run("DeleteObject", func(t *testing.T) { | |
cls := "MyClass" | |
type test struct { | |
name string | |
err error | |
} | |
tests := []test{ | |
{ | |
name: "without props - noaction changes", | |
}, | |
{ | |
name: "error forbidden", | |
err: errors.NewForbidden(&models.Principal{}, "get", "Myclass/123"), | |
}, | |
{ | |
name: "use case err not found", | |
err: uco.ErrNotFound{}, | |
}, | |
{ | |
name: "unknown error", | |
err: stderrors.New("any error"), | |
}, | |
} | |
for _, test := range tests { | |
t.Run(test.name, func(t *testing.T) { | |
fakeManager := &fakeManager{ | |
deleteObjectReturn: test.err, | |
} | |
h := &objectHandlers{manager: fakeManager, metricRequestsTotal: &fakeMetricRequestsTotal{}} | |
req := objects.ObjectsClassDeleteParams{ | |
HTTPRequest: httptest.NewRequest("GET", "/v1/objects/MyClass/123", nil), | |
ClassName: cls, | |
ID: "123", | |
} | |
res := h.deleteObject(req, nil) | |
_, ok := res.(*objects.ObjectsClassDeleteNoContent) | |
if test.err != nil { | |
require.False(t, ok) | |
return | |
} | |
require.True(t, ok) | |
}) | |
} | |
}) | |
t.Run("HeadObject", func(t *testing.T) { | |
m := &fakeManager{ | |
headObjectReturn: true, | |
} | |
h := &objectHandlers{manager: m, logger: &logrus.Logger{}, metricRequestsTotal: &fakeMetricRequestsTotal{}} | |
req := objects.ObjectsClassHeadParams{ | |
HTTPRequest: httptest.NewRequest("HEAD", "/v1/objects/MyClass/123", nil), | |
ClassName: "MyClass", | |
ID: "123", | |
} | |
res := h.headObject(req, nil) | |
if _, ok := res.(*objects.ObjectsClassHeadNoContent); !ok { | |
t.Errorf("unexpected result %v", res) | |
} | |
m.headObjectErr = &uco.Error{Code: uco.StatusForbidden} | |
res = h.headObject(req, nil) | |
if _, ok := res.(*objects.ObjectsClassHeadForbidden); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassHeadForbidden{}, res) | |
} | |
m.headObjectErr = &uco.Error{Code: uco.StatusInternalServerError} | |
res = h.headObject(req, nil) | |
if _, ok := res.(*objects.ObjectsClassHeadInternalServerError); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassHeadInternalServerError{}, res) | |
} | |
m.headObjectErr = nil | |
m.headObjectReturn = false | |
res = h.headObject(req, nil) | |
if _, ok := res.(*objects.ObjectsClassHeadNotFound); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassHeadNotFound{}, res) | |
} | |
// same test as before but using old request | |
oldRequest := objects.ObjectsHeadParams{HTTPRequest: req.HTTPRequest} | |
res = h.headObjectDeprecated(oldRequest, nil) | |
if _, ok := res.(*objects.ObjectsClassHeadNotFound); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassHeadNotFound{}, res) | |
} | |
}) | |
t.Run("PostReference", func(t *testing.T) { | |
m := &fakeManager{} | |
h := &objectHandlers{manager: m, logger: &logrus.Logger{}, metricRequestsTotal: &fakeMetricRequestsTotal{}} | |
req := objects.ObjectsClassReferencesCreateParams{ | |
HTTPRequest: httptest.NewRequest("HEAD", "/v1/objects/MyClass/123/references/prop", nil), | |
ClassName: "MyClass", | |
ID: "123", | |
Body: new(models.SingleRef), | |
PropertyName: "prop", | |
} | |
res := h.addObjectReference(req, nil) | |
if _, ok := res.(*objects.ObjectsClassReferencesCreateOK); !ok { | |
t.Errorf("unexpected result %v", res) | |
} | |
m.addRefErr = &uco.Error{Code: uco.StatusForbidden} | |
res = h.addObjectReference(req, nil) | |
if _, ok := res.(*objects.ObjectsClassReferencesCreateForbidden); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesCreateForbidden{}, res) | |
} | |
// source object not found | |
m.addRefErr = &uco.Error{Code: uco.StatusNotFound} | |
res = h.addObjectReference(req, nil) | |
if _, ok := res.(*objects.ObjectsClassReferencesCreateNotFound); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesCreateNotFound{}, res) | |
} | |
m.addRefErr = &uco.Error{Code: uco.StatusInternalServerError} | |
res = h.addObjectReference(req, nil) | |
if _, ok := res.(*objects.ObjectsClassReferencesCreateInternalServerError); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesCreateInternalServerError{}, res) | |
} | |
m.addRefErr = &uco.Error{Code: uco.StatusBadRequest} | |
res = h.addObjectReference(req, nil) | |
if _, ok := res.(*objects.ObjectsClassReferencesCreateUnprocessableEntity); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesCreateUnprocessableEntity{}, res) | |
} | |
// same test as before but using old request | |
oldRequest := objects.ObjectsReferencesCreateParams{ | |
HTTPRequest: req.HTTPRequest, | |
Body: req.Body, | |
ID: req.ID, | |
PropertyName: req.ClassName, | |
} | |
res = h.addObjectReferenceDeprecated(oldRequest, nil) | |
if _, ok := res.(*objects.ObjectsClassReferencesCreateUnprocessableEntity); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesCreateUnprocessableEntity{}, res) | |
} | |
}) | |
t.Run("PutReferences", func(t *testing.T) { | |
m := &fakeManager{} | |
h := &objectHandlers{manager: m, logger: &logrus.Logger{}, metricRequestsTotal: &fakeMetricRequestsTotal{}} | |
req := objects.ObjectsClassReferencesPutParams{ | |
HTTPRequest: httptest.NewRequest("HEAD", "/v1/objects/MyClass/123/references/prop", nil), | |
ClassName: "MyClass", | |
ID: "123", | |
Body: models.MultipleRef{}, | |
PropertyName: "prop", | |
} | |
res := h.putObjectReferences(req, nil) | |
if _, ok := res.(*objects.ObjectsClassReferencesPutOK); !ok { | |
t.Errorf("unexpected result %v", res) | |
} | |
m.putRefErr = &uco.Error{Code: uco.StatusForbidden} | |
res = h.putObjectReferences(req, nil) | |
if _, ok := res.(*objects.ObjectsClassReferencesPutForbidden); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesPutForbidden{}, res) | |
} | |
m.putRefErr = &uco.Error{Code: uco.StatusInternalServerError} | |
res = h.putObjectReferences(req, nil) | |
if _, ok := res.(*objects.ObjectsClassReferencesPutInternalServerError); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesPutInternalServerError{}, res) | |
} | |
m.putRefErr = &uco.Error{Code: uco.StatusBadRequest} | |
res = h.putObjectReferences(req, nil) | |
if _, ok := res.(*objects.ObjectsClassReferencesPutUnprocessableEntity); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesPutUnprocessableEntity{}, res) | |
} | |
// same test as before but using old request | |
oldRequest := objects.ObjectsReferencesUpdateParams{ | |
HTTPRequest: req.HTTPRequest, | |
Body: req.Body, | |
ID: req.ID, | |
PropertyName: req.ClassName, | |
} | |
res = h.updateObjectReferencesDeprecated(oldRequest, nil) | |
if _, ok := res.(*objects.ObjectsClassReferencesPutUnprocessableEntity); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesPutUnprocessableEntity{}, res) | |
} | |
}) | |
t.Run("DeleteReference", func(t *testing.T) { | |
m := &fakeManager{} | |
h := &objectHandlers{manager: m, logger: &logrus.Logger{}, metricRequestsTotal: &fakeMetricRequestsTotal{}} | |
req := objects.ObjectsClassReferencesDeleteParams{ | |
HTTPRequest: httptest.NewRequest("HEAD", "/v1/objects/MyClass/123/references/prop", nil), | |
ClassName: "MyClass", | |
ID: "123", | |
Body: new(models.SingleRef), | |
PropertyName: "prop", | |
} | |
res := h.deleteObjectReference(req, nil) | |
if _, ok := res.(*objects.ObjectsClassReferencesDeleteNoContent); !ok { | |
t.Errorf("unexpected result %v", res) | |
} | |
m.deleteRefErr = &uco.Error{Code: uco.StatusForbidden} | |
res = h.deleteObjectReference(req, nil) | |
if _, ok := res.(*objects.ObjectsClassReferencesDeleteForbidden); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesDeleteForbidden{}, res) | |
} | |
// source object not found | |
m.deleteRefErr = &uco.Error{Code: uco.StatusNotFound} | |
res = h.deleteObjectReference(req, nil) | |
if _, ok := res.(*objects.ObjectsClassReferencesDeleteNotFound); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesDeleteNotFound{}, res) | |
} | |
m.deleteRefErr = &uco.Error{Code: uco.StatusInternalServerError} | |
res = h.deleteObjectReference(req, nil) | |
if _, ok := res.(*objects.ObjectsClassReferencesDeleteInternalServerError); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesDeleteInternalServerError{}, res) | |
} | |
m.deleteRefErr = &uco.Error{Code: uco.StatusBadRequest} | |
res = h.deleteObjectReference(req, nil) | |
if _, ok := res.(*objects.ObjectsClassReferencesDeleteUnprocessableEntity); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesDeleteUnprocessableEntity{}, res) | |
} | |
// same test as before but using old request | |
oldRequest := objects.ObjectsReferencesDeleteParams{ | |
HTTPRequest: req.HTTPRequest, | |
Body: req.Body, | |
ID: req.ID, | |
PropertyName: req.ClassName, | |
} | |
res = h.deleteObjectReferenceDeprecated(oldRequest, nil) | |
if _, ok := res.(*objects.ObjectsClassReferencesDeleteUnprocessableEntity); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsClassReferencesDeleteUnprocessableEntity{}, res) | |
} | |
}) | |
t.Run("Query", func(t *testing.T) { | |
var ( | |
cls = "MyClass" | |
m = &fakeManager{ | |
queryErr: nil, | |
queryResult: []*models.Object{{ | |
Properties: map[string]interface{}{"name": "John"}, | |
}}, | |
} | |
fakeMetricRequestsTotal = &fakeMetricRequestsTotal{} | |
h = &objectHandlers{ | |
manager: m, | |
logger: &logrus.Logger{}, | |
metricRequestsTotal: fakeMetricRequestsTotal, | |
} | |
req = objects.ObjectsListParams{ | |
HTTPRequest: httptest.NewRequest("HEAD", "/v1/objects/", nil), | |
Class: &cls, | |
} | |
) | |
res := h.query(req, nil) | |
if _, ok := res.(*objects.ObjectsListOK); !ok { | |
t.Errorf("unexpected result %v", res) | |
} | |
m.queryErr = &uco.Error{Code: uco.StatusForbidden} | |
res = h.query(req, nil) | |
if _, ok := res.(*objects.ObjectsListForbidden); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsListForbidden{}, res) | |
} | |
m.queryErr = &uco.Error{Code: uco.StatusNotFound} | |
res = h.query(req, nil) | |
if _, ok := res.(*objects.ObjectsListNotFound); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsListNotFound{}, res) | |
} | |
m.queryErr = &uco.Error{Code: uco.StatusBadRequest} | |
res = h.query(req, nil) | |
if _, ok := res.(*objects.ObjectsListUnprocessableEntity); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsListUnprocessableEntity{}, res) | |
} | |
m.queryErr = &uco.Error{Code: uco.StatusInternalServerError} | |
res = h.query(req, nil) | |
if _, ok := res.(*objects.ObjectsListInternalServerError); !ok { | |
t.Errorf("expected: %T got: %T", objects.ObjectsListInternalServerError{}, res) | |
} | |
}) | |
} | |
type fakeManager struct { | |
getObjectReturn *models.Object | |
getObjectErr error | |
addObjectReturn *models.Object | |
queryResult []*models.Object | |
queryErr *uco.Error | |
updateObjectReturn *models.Object | |
updateObjectErr error | |
deleteObjectReturn error | |
patchObjectReturn *uco.Error | |
headObjectReturn bool | |
headObjectErr *uco.Error | |
addRefErr *uco.Error | |
putRefErr *uco.Error | |
deleteRefErr *uco.Error | |
} | |
func (f *fakeManager) HeadObject(context.Context, *models.Principal, | |
string, strfmt.UUID, *additional.ReplicationProperties, string, | |
) (bool, *uco.Error) { | |
return f.headObjectReturn, f.headObjectErr | |
} | |
func (f *fakeManager) AddObject(_ context.Context, _ *models.Principal, | |
object *models.Object, _ *additional.ReplicationProperties, | |
) (*models.Object, error) { | |
return object, nil | |
} | |
func (f *fakeManager) ValidateObject(_ context.Context, _ *models.Principal, | |
_ *models.Object, _ *additional.ReplicationProperties, | |
) error { | |
panic("not implemented") // TODO: Implement | |
} | |
func (f *fakeManager) GetObject(_ context.Context, _ *models.Principal, class string, | |
_ strfmt.UUID, _ additional.Properties, _ *additional.ReplicationProperties, _ string, | |
) (*models.Object, error) { | |
return f.getObjectReturn, f.getObjectErr | |
} | |
func (f *fakeManager) GetObjectsClass(ctx context.Context, | |
principal *models.Principal, id strfmt.UUID, | |
) (*models.Class, error) { | |
class := &models.Class{ | |
Class: f.getObjectReturn.Class, | |
Vectorizer: "text2vec-contextionary", | |
} | |
return class, nil | |
} | |
func (f *fakeManager) GetObjectClassFromName(ctx context.Context, principal *models.Principal, | |
className string, | |
) (*models.Class, error) { | |
class := &models.Class{ | |
Class: f.getObjectReturn.Class, | |
Vectorizer: "text2vec-contextionary", | |
} | |
return class, nil | |
} | |
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) { | |
return f.queryResult, nil | |
} | |
func (f *fakeManager) Query(_ context.Context, | |
_ *models.Principal, _ *uco.QueryParams, | |
) ([]*models.Object, *uco.Error) { | |
return f.queryResult, f.queryErr | |
} | |
func (f *fakeManager) UpdateObject(_ context.Context, _ *models.Principal, _ string, | |
_ strfmt.UUID, updates *models.Object, _ *additional.ReplicationProperties, | |
) (*models.Object, error) { | |
return updates, f.updateObjectErr | |
} | |
func (f *fakeManager) MergeObject(_ context.Context, _ *models.Principal, | |
_ *models.Object, _ *additional.ReplicationProperties, | |
) *uco.Error { | |
return f.patchObjectReturn | |
} | |
func (f *fakeManager) DeleteObject(_ context.Context, _ *models.Principal, | |
class string, _ strfmt.UUID, _ *additional.ReplicationProperties, _ string, | |
) error { | |
return f.deleteObjectReturn | |
} | |
func (f *fakeManager) AddObjectReference(context.Context, *models.Principal, | |
*uco.AddReferenceInput, *additional.ReplicationProperties, string, | |
) *uco.Error { | |
return f.addRefErr | |
} | |
func (f *fakeManager) UpdateObjectReferences(context.Context, *models.Principal, | |
*uco.PutReferenceInput, *additional.ReplicationProperties, string, | |
) *uco.Error { | |
return f.putRefErr | |
} | |
func (f *fakeManager) DeleteObjectReference(context.Context, *models.Principal, | |
*uco.DeleteReferenceInput, *additional.ReplicationProperties, string, | |
) *uco.Error { | |
return f.deleteRefErr | |
} | |
type fakeMetricRequestsTotal struct{} | |
func (f *fakeMetricRequestsTotal) logError(className string, err error) {} | |
func (f *fakeMetricRequestsTotal) logOk(className string) {} | |
func (f *fakeMetricRequestsTotal) logUserError(className string) {} | |
func (f *fakeMetricRequestsTotal) logServerError(className string, err error) {} | |