diff --git a/services/graph/.mockery.yaml b/services/graph/.mockery.yaml index 9ccccd5423..d3c9a20aa3 100644 --- a/services/graph/.mockery.yaml +++ b/services/graph/.mockery.yaml @@ -7,10 +7,11 @@ packages: config: dir: "mocks" interfaces: - HTTPClient: - Permissions: - Publisher: - RoleService: + DrivesDriveItemProvider: + HTTPClient: + Permissions: + Publisher: + RoleService: github.com/owncloud/ocis/v2/services/graph/pkg/identity: config: dir: "pkg/identity/mocks" diff --git a/services/graph/mocks/drives_drive_item_provider.go b/services/graph/mocks/drives_drive_item_provider.go new file mode 100644 index 0000000000..e3aa0cc137 --- /dev/null +++ b/services/graph/mocks/drives_drive_item_provider.go @@ -0,0 +1,144 @@ +// Code generated by mockery v2.40.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + libregraph "github.com/owncloud/libre-graph-api-go" + mock "github.com/stretchr/testify/mock" + + providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" +) + +// DrivesDriveItemProvider is an autogenerated mock type for the DrivesDriveItemProvider type +type DrivesDriveItemProvider struct { + mock.Mock +} + +type DrivesDriveItemProvider_Expecter struct { + mock *mock.Mock +} + +func (_m *DrivesDriveItemProvider) EXPECT() *DrivesDriveItemProvider_Expecter { + return &DrivesDriveItemProvider_Expecter{mock: &_m.Mock} +} + +// MountShare provides a mock function with given fields: ctx, resourceID, name +func (_m *DrivesDriveItemProvider) MountShare(ctx context.Context, resourceID providerv1beta1.ResourceId, name string) (libregraph.DriveItem, error) { + ret := _m.Called(ctx, resourceID, name) + + if len(ret) == 0 { + panic("no return value specified for MountShare") + } + + var r0 libregraph.DriveItem + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, providerv1beta1.ResourceId, string) (libregraph.DriveItem, error)); ok { + return rf(ctx, resourceID, name) + } + if rf, ok := ret.Get(0).(func(context.Context, providerv1beta1.ResourceId, string) libregraph.DriveItem); ok { + r0 = rf(ctx, resourceID, name) + } else { + r0 = ret.Get(0).(libregraph.DriveItem) + } + + if rf, ok := ret.Get(1).(func(context.Context, providerv1beta1.ResourceId, string) error); ok { + r1 = rf(ctx, resourceID, name) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DrivesDriveItemProvider_MountShare_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MountShare' +type DrivesDriveItemProvider_MountShare_Call struct { + *mock.Call +} + +// MountShare is a helper method to define mock.On call +// - ctx context.Context +// - resourceID providerv1beta1.ResourceId +// - name string +func (_e *DrivesDriveItemProvider_Expecter) MountShare(ctx interface{}, resourceID interface{}, name interface{}) *DrivesDriveItemProvider_MountShare_Call { + return &DrivesDriveItemProvider_MountShare_Call{Call: _e.mock.On("MountShare", ctx, resourceID, name)} +} + +func (_c *DrivesDriveItemProvider_MountShare_Call) Run(run func(ctx context.Context, resourceID providerv1beta1.ResourceId, name string)) *DrivesDriveItemProvider_MountShare_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(providerv1beta1.ResourceId), args[2].(string)) + }) + return _c +} + +func (_c *DrivesDriveItemProvider_MountShare_Call) Return(_a0 libregraph.DriveItem, _a1 error) *DrivesDriveItemProvider_MountShare_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DrivesDriveItemProvider_MountShare_Call) RunAndReturn(run func(context.Context, providerv1beta1.ResourceId, string) (libregraph.DriveItem, error)) *DrivesDriveItemProvider_MountShare_Call { + _c.Call.Return(run) + return _c +} + +// UnmountShare provides a mock function with given fields: ctx, resourceID +func (_m *DrivesDriveItemProvider) UnmountShare(ctx context.Context, resourceID providerv1beta1.ResourceId) error { + ret := _m.Called(ctx, resourceID) + + if len(ret) == 0 { + panic("no return value specified for UnmountShare") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, providerv1beta1.ResourceId) error); ok { + r0 = rf(ctx, resourceID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DrivesDriveItemProvider_UnmountShare_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UnmountShare' +type DrivesDriveItemProvider_UnmountShare_Call struct { + *mock.Call +} + +// UnmountShare is a helper method to define mock.On call +// - ctx context.Context +// - resourceID providerv1beta1.ResourceId +func (_e *DrivesDriveItemProvider_Expecter) UnmountShare(ctx interface{}, resourceID interface{}) *DrivesDriveItemProvider_UnmountShare_Call { + return &DrivesDriveItemProvider_UnmountShare_Call{Call: _e.mock.On("UnmountShare", ctx, resourceID)} +} + +func (_c *DrivesDriveItemProvider_UnmountShare_Call) Run(run func(ctx context.Context, resourceID providerv1beta1.ResourceId)) *DrivesDriveItemProvider_UnmountShare_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(providerv1beta1.ResourceId)) + }) + return _c +} + +func (_c *DrivesDriveItemProvider_UnmountShare_Call) Return(_a0 error) *DrivesDriveItemProvider_UnmountShare_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DrivesDriveItemProvider_UnmountShare_Call) RunAndReturn(run func(context.Context, providerv1beta1.ResourceId) error) *DrivesDriveItemProvider_UnmountShare_Call { + _c.Call.Return(run) + return _c +} + +// NewDrivesDriveItemProvider creates a new instance of DrivesDriveItemProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewDrivesDriveItemProvider(t interface { + mock.TestingT + Cleanup(func()) +}) *DrivesDriveItemProvider { + mock := &DrivesDriveItemProvider{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/services/graph/pkg/service/v0/api_drives_drive_item_test.go b/services/graph/pkg/service/v0/api_drives_drive_item_test.go new file mode 100644 index 0000000000..f9c8820e58 --- /dev/null +++ b/services/graph/pkg/service/v0/api_drives_drive_item_test.go @@ -0,0 +1,173 @@ +package svc_test + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + + storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/v2/pkg/storagespace" + "github.com/go-chi/chi/v5" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + libregraph "github.com/owncloud/libre-graph-api-go" + "github.com/stretchr/testify/mock" + "github.com/tidwall/gjson" + + "github.com/owncloud/ocis/v2/ocis-pkg/log" + "github.com/owncloud/ocis/v2/services/graph/mocks" + svc "github.com/owncloud/ocis/v2/services/graph/pkg/service/v0" +) + +var _ = Describe("DrivesDriveItemApi", func() { + + var ( + mockProvider *mocks.DrivesDriveItemProvider + httpAPI svc.DrivesDriveItemApi + rCTX *chi.Context + ) + + BeforeEach(func() { + logger := log.NewLogger() + + mockProvider = mocks.NewDrivesDriveItemProvider(GinkgoT()) + api, err := svc.NewDrivesDriveItemApi(mockProvider, logger) + Expect(err).ToNot(HaveOccurred()) + + httpAPI = api + + rCTX = chi.NewRouteContext() + rCTX.URLParams.Add("driveID", "a0ca6a90-a365-4782-871e-d44447bbc668$a0ca6a90-a365-4782-871e-d44447bbc668") + rCTX.URLParams.Add("itemID", "a0ca6a90-a365-4782-871e-d44447bbc668$a0ca6a90-a365-4782-871e-d44447bbc668!a0ca6a90-a365-4782-871e-d44447bbc668") + }) + + Describe("CreateDriveItem", func() { + It("validates the driveID and itemID url param", func() { + rCTX.URLParams.Add("driveID", "1$2") + rCTX.URLParams.Add("itemID", "3$4!5") + + responseRecorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPost, "/", nil). + WithContext( + context.WithValue(context.Background(), chi.RouteCtxKey, rCTX), + ) + + httpAPI.CreateDriveItem(responseRecorder, request) + + Expect(responseRecorder.Code).To(Equal(http.StatusUnprocessableEntity)) + + jsonData := gjson.Get(responseRecorder.Body.String(), "error") + Expect(jsonData.Get("message").String()).To(Equal("invalid driveID or itemID")) + }) + + It("checks if the idemID and driveID is in share jail", func() { + rCTX.URLParams.Add("driveID", "1$2") + rCTX.URLParams.Add("itemID", "1$2!3") + + responseRecorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPost, "/", nil). + WithContext( + context.WithValue(context.Background(), chi.RouteCtxKey, rCTX), + ) + + httpAPI.CreateDriveItem(responseRecorder, request) + + Expect(responseRecorder.Code).To(Equal(http.StatusUnprocessableEntity)) + + jsonData := gjson.Get(responseRecorder.Body.String(), "error") + Expect(jsonData.Get("message").String()).To(ContainSubstring("must be share jail")) + }) + + It("checks that the request body is valid", func() { + responseRecorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPost, "/", nil). + WithContext( + context.WithValue(context.Background(), chi.RouteCtxKey, rCTX), + ) + + httpAPI.CreateDriveItem(responseRecorder, request) + + Expect(responseRecorder.Code).To(Equal(http.StatusUnprocessableEntity)) + + jsonData := gjson.Get(responseRecorder.Body.String(), "error") + Expect(jsonData.Get("message").String()).To(Equal("invalid request body")) + + // valid drive item, but invalid remote item id + driveItem := libregraph.DriveItem{} + + driveItemJson, err := json.Marshal(driveItem) + Expect(err).ToNot(HaveOccurred()) + + responseRecorder = httptest.NewRecorder() + + request = httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(driveItemJson)). + WithContext( + context.WithValue(context.Background(), chi.RouteCtxKey, rCTX), + ) + + httpAPI.CreateDriveItem(responseRecorder, request) + + Expect(responseRecorder.Code).To(Equal(http.StatusUnprocessableEntity)) + + jsonData = gjson.Get(responseRecorder.Body.String(), "error") + Expect(jsonData.Get("message").String()).To(Equal("invalid remote item id")) + }) + + It("uses the provider implementation", func() { + driveItemName := "a name" + remoteItemID := "d66d28d8-3558-4f0f-ba2a-34a7185b806d$831997cf-a531-491b-ae72-9037739f04e9!c131a84c-7506-46b4-8e5e-60c56382da3b" + driveItem := libregraph.DriveItem{ + Name: &driveItemName, + RemoteItem: &libregraph.RemoteItem{ + Id: &remoteItemID, + }, + } + + driveItemJson, err := json.Marshal(driveItem) + Expect(err).ToNot(HaveOccurred()) + + responseRecorder := httptest.NewRecorder() + + request := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(driveItemJson)). + WithContext( + context.WithValue(context.Background(), chi.RouteCtxKey, rCTX), + ) + + onMountShare := mockProvider.On("MountShare", mock.Anything, mock.Anything, mock.Anything) + onMountShare. + Return(func(ctx context.Context, resourceID storageprovider.ResourceId, name string) (libregraph.DriveItem, error) { + return libregraph.DriveItem{}, errors.New("any") + }).Once() + + httpAPI.CreateDriveItem(responseRecorder, request) + + Expect(responseRecorder.Code).To(Equal(http.StatusFailedDependency)) + + jsonData := gjson.Get(responseRecorder.Body.String(), "error") + Expect(jsonData.Get("message").String()).To(Equal("mounting share failed")) + + // happy path + + responseRecorder = httptest.NewRecorder() + + request = httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(driveItemJson)). + WithContext( + context.WithValue(context.Background(), chi.RouteCtxKey, rCTX), + ) + + onMountShare. + Return(func(ctx context.Context, resourceID storageprovider.ResourceId, name string) (libregraph.DriveItem, error) { + Expect(storagespace.FormatResourceID(resourceID)).To(Equal(remoteItemID)) + Expect(driveItemName).To(Equal(name)) + return libregraph.DriveItem{}, nil + }).Once() + + httpAPI.CreateDriveItem(responseRecorder, request) + + Expect(responseRecorder.Code).To(Equal(http.StatusCreated)) + }) + }) +})