mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-07-13 00:52:01 -04:00
graph: implement driveItem creation under drives/items/children
Implements the CreateDriveItem/CreateChildDriveItem operations from libre-graph-api#42 including @libre.graph.conflictBehavior and @libre.graph.missingParentsBehavior.
This commit is contained in:
@@ -41,6 +41,92 @@ func (_m *DrivesDriveItemProvider) EXPECT() *DrivesDriveItemProvider_Expecter {
|
||||
return &DrivesDriveItemProvider_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// CreateChild provides a mock function for the type DrivesDriveItemProvider
|
||||
func (_mock *DrivesDriveItemProvider) CreateChild(ctx context.Context, parentID *providerv1beta1.ResourceId, name string, isFolder bool, replace bool) (*providerv1beta1.ResourceInfo, error) {
|
||||
ret := _mock.Called(ctx, parentID, name, isFolder, replace)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CreateChild")
|
||||
}
|
||||
|
||||
var r0 *providerv1beta1.ResourceInfo
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, string, bool, bool) (*providerv1beta1.ResourceInfo, error)); ok {
|
||||
return returnFunc(ctx, parentID, name, isFolder, replace)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, string, bool, bool) *providerv1beta1.ResourceInfo); ok {
|
||||
r0 = returnFunc(ctx, parentID, name, isFolder, replace)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*providerv1beta1.ResourceInfo)
|
||||
}
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ResourceId, string, bool, bool) error); ok {
|
||||
r1 = returnFunc(ctx, parentID, name, isFolder, replace)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// DrivesDriveItemProvider_CreateChild_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateChild'
|
||||
type DrivesDriveItemProvider_CreateChild_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CreateChild is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - parentID *providerv1beta1.ResourceId
|
||||
// - name string
|
||||
// - isFolder bool
|
||||
// - replace bool
|
||||
func (_e *DrivesDriveItemProvider_Expecter) CreateChild(ctx interface{}, parentID interface{}, name interface{}, isFolder interface{}, replace interface{}) *DrivesDriveItemProvider_CreateChild_Call {
|
||||
return &DrivesDriveItemProvider_CreateChild_Call{Call: _e.mock.On("CreateChild", ctx, parentID, name, isFolder, replace)}
|
||||
}
|
||||
|
||||
func (_c *DrivesDriveItemProvider_CreateChild_Call) Run(run func(ctx context.Context, parentID *providerv1beta1.ResourceId, name string, isFolder bool, replace bool)) *DrivesDriveItemProvider_CreateChild_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
var arg0 context.Context
|
||||
if args[0] != nil {
|
||||
arg0 = args[0].(context.Context)
|
||||
}
|
||||
var arg1 *providerv1beta1.ResourceId
|
||||
if args[1] != nil {
|
||||
arg1 = args[1].(*providerv1beta1.ResourceId)
|
||||
}
|
||||
var arg2 string
|
||||
if args[2] != nil {
|
||||
arg2 = args[2].(string)
|
||||
}
|
||||
var arg3 bool
|
||||
if args[3] != nil {
|
||||
arg3 = args[3].(bool)
|
||||
}
|
||||
var arg4 bool
|
||||
if args[4] != nil {
|
||||
arg4 = args[4].(bool)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
arg3,
|
||||
arg4,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *DrivesDriveItemProvider_CreateChild_Call) Return(resourceInfo *providerv1beta1.ResourceInfo, err error) *DrivesDriveItemProvider_CreateChild_Call {
|
||||
_c.Call.Return(resourceInfo, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *DrivesDriveItemProvider_CreateChild_Call) RunAndReturn(run func(ctx context.Context, parentID *providerv1beta1.ResourceId, name string, isFolder bool, replace bool) (*providerv1beta1.ResourceInfo, error)) *DrivesDriveItemProvider_CreateChild_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetShare provides a mock function for the type DrivesDriveItemProvider
|
||||
func (_mock *DrivesDriveItemProvider) GetShare(ctx context.Context, shareID *collaborationv1beta1.ShareId) (*collaborationv1beta1.ReceivedShare, error) {
|
||||
ret := _mock.Called(ctx, shareID)
|
||||
|
||||
@@ -49,6 +49,7 @@ var (
|
||||
errPathNotFound = errors.New("path not found")
|
||||
errInvalidRequest = errors.New("invalid request")
|
||||
errUnauthenticated = errors.New("unauthenticated")
|
||||
errAccessDenied = errors.New("access denied")
|
||||
)
|
||||
|
||||
// ResolveGraphPath returns middleware that detects MS Graph colon-syntax path
|
||||
@@ -91,7 +92,7 @@ func ResolveGraphPath(gws pool.Selectable[gateway.GatewayAPIClient], logger log.
|
||||
|
||||
driveID := chi.URLParam(r, "driveID")
|
||||
original := r.URL.Path
|
||||
rewritten, err := rewriteColonPath(r.Context(), gws, l, driveID, rctx.RoutePath)
|
||||
rewritten, err := rewriteColonPath(r.Context(), gws, l, driveID, rctx.RoutePath, r)
|
||||
switch {
|
||||
case errors.Is(err, errPathNotFound):
|
||||
l.Debug().Str("original", original).Msg("colon-path resolution: not found")
|
||||
@@ -105,6 +106,10 @@ func ResolveGraphPath(gws pool.Selectable[gateway.GatewayAPIClient], logger log.
|
||||
l.Debug().Str("original", original).Msg("colon-path resolution: unauthenticated")
|
||||
errorcode.Unauthenticated.Render(w, r, http.StatusUnauthorized, "unauthenticated")
|
||||
return
|
||||
case errors.Is(err, errAccessDenied):
|
||||
l.Debug().Str("original", original).Msg("colon-path resolution: access denied")
|
||||
errorcode.AccessDenied.Render(w, r, http.StatusForbidden, "access denied")
|
||||
return
|
||||
case err != nil:
|
||||
l.Error().Err(err).Str("original", original).Msg("colon-path resolution: internal error")
|
||||
errorcode.GeneralException.Render(
|
||||
@@ -154,19 +159,38 @@ type colonMatch struct {
|
||||
// - "" + other error - operational / internal failure (5xx)
|
||||
//
|
||||
// driveIDParam is the {driveID} route param (raw chi.URLParam value); routePath
|
||||
// is chi.RouteContext().RoutePath (the part below /drives/{driveID}).
|
||||
// is chi.RouteContext().RoutePath (the part below /drives/{driveID}). r is only
|
||||
// consulted for the method and the @libre.graph.missingParentsBehavior query
|
||||
// parameter, which is meaningful for POST .../children requests only and
|
||||
// ignored otherwise.
|
||||
func rewriteColonPath(
|
||||
ctx context.Context,
|
||||
gws pool.Selectable[gateway.GatewayAPIClient],
|
||||
logger zerolog.Logger,
|
||||
driveIDParam string,
|
||||
routePath string,
|
||||
r *http.Request,
|
||||
) (string, error) {
|
||||
match, ok := parseColonPath(routePath)
|
||||
if !ok {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// The colon path addresses the parent of the item a POST .../children
|
||||
// creates. With missingParentsBehavior=create the missing folders along
|
||||
// that path are created instead of returning 404.
|
||||
createParents := false
|
||||
if r.Method == http.MethodPost && match.suffix == "/children" {
|
||||
switch r.URL.Query().Get("@libre.graph.missingParentsBehavior") {
|
||||
case "", "fail":
|
||||
case "create":
|
||||
createParents = true
|
||||
default:
|
||||
logger.Debug().Msg("invalid @libre.graph.missingParentsBehavior in colon path")
|
||||
return "", errInvalidRequest
|
||||
}
|
||||
}
|
||||
|
||||
// RoutePath follows chi's RawPath, i.e. the percent-encoded wire form
|
||||
// (e.g. "/Documents/My%20File"). A single PathUnescape reproduces exactly
|
||||
// what net/http put in r.URL.Path; it is NOT a double-decode (a crafted
|
||||
@@ -218,7 +242,11 @@ func rewriteColonPath(
|
||||
return "", errInvalidRequest
|
||||
}
|
||||
|
||||
itemID, err := resolvePath(ctx, gws, &anchor, relPath)
|
||||
resolve := resolvePath
|
||||
if createParents {
|
||||
resolve = resolveOrCreatePath
|
||||
}
|
||||
itemID, err := resolve(ctx, gws, &anchor, relPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -320,6 +348,79 @@ func resolvePath(
|
||||
return "", fmt.Errorf("gateway selector: %w", err)
|
||||
}
|
||||
|
||||
return statPath(ctx, gw, anchor, relPath)
|
||||
}
|
||||
|
||||
// resolveOrCreatePath is resolvePath for POST .../children requests with
|
||||
// @libre.graph.missingParentsBehavior=create: it walks relPath segment by
|
||||
// segment and creates the missing folders along the way.
|
||||
func resolveOrCreatePath(
|
||||
ctx context.Context,
|
||||
gws pool.Selectable[gateway.GatewayAPIClient],
|
||||
anchor *storageprovider.ResourceId,
|
||||
relPath string,
|
||||
) (string, error) {
|
||||
gw, err := gws.Next()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("gateway selector: %w", err)
|
||||
}
|
||||
|
||||
var id, walked string
|
||||
for _, segment := range strings.Split(strings.Trim(relPath, "/"), "/") {
|
||||
walked += "/" + segment
|
||||
id, err = statPath(ctx, gw, anchor, walked)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if !errors.Is(err, errPathNotFound) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
res, err := gw.CreateContainer(ctx, &storageprovider.CreateContainerRequest{
|
||||
Ref: &storageprovider.Reference{
|
||||
ResourceId: anchor,
|
||||
Path: utils.MakeRelativePath(walked),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("CS3 CreateContainer: %w", err)
|
||||
}
|
||||
switch res.GetStatus().GetCode() {
|
||||
case cs3rpc.Code_CODE_OK:
|
||||
// fall through
|
||||
case cs3rpc.Code_CODE_ALREADY_EXISTS:
|
||||
// lost a creation race, the folder is there
|
||||
case cs3rpc.Code_CODE_NOT_FOUND:
|
||||
return "", errPathNotFound
|
||||
case cs3rpc.Code_CODE_PERMISSION_DENIED:
|
||||
return "", errAccessDenied
|
||||
case cs3rpc.Code_CODE_UNAUTHENTICATED:
|
||||
return "", errUnauthenticated
|
||||
default:
|
||||
return "", fmt.Errorf(
|
||||
"CS3 CreateContainer returned %s: %s",
|
||||
res.GetStatus().GetCode(),
|
||||
res.GetStatus().GetMessage(),
|
||||
)
|
||||
}
|
||||
|
||||
id, err = statPath(ctx, gw, anchor, walked)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// statPath stats a relative filesystem path (anchored at the given CS3
|
||||
// resource id) and returns the item's id, running with the request user's
|
||||
// permissions.
|
||||
func statPath(
|
||||
ctx context.Context,
|
||||
gw gateway.GatewayAPIClient,
|
||||
anchor *storageprovider.ResourceId,
|
||||
relPath string,
|
||||
) (string, error) {
|
||||
// relPath is already decoded (PathUnescape'd once by the caller), matching
|
||||
// the form a normal handler would receive from r.URL.Path.
|
||||
statRes, err := gw.Stat(ctx, &storageprovider.StatRequest{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package middleware_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
@@ -115,6 +116,7 @@ func newGraphTestRouter(t *testing.T, gw *cs3mocks.GatewayAPIClient) (http.Handl
|
||||
r.Use(middleware.ResolveGraphPath(selector, log.NopLogger()))
|
||||
r.Route("/items/{itemID}", func(r chi.Router) {
|
||||
r.Get("/", leaf("item"))
|
||||
r.Post("/children", leaf("createChild"))
|
||||
r.Post("/createLink", leaf("createLink"))
|
||||
r.Route("/permissions", func(r chi.Router) {
|
||||
r.Get("/", leaf("permissions"))
|
||||
@@ -469,3 +471,94 @@ func TestResolveGraphPath_OriginalPathContext(t *testing.T) {
|
||||
assert.Equal(t, original, cap.original, "original URL must be available via OriginalPathContextKey")
|
||||
assert.Equal(t, original, cap.urlPath, "r.URL.Path must remain the original request path")
|
||||
}
|
||||
|
||||
// TestResolveGraphPath_MissingParentsBehavior covers the
|
||||
// @libre.graph.missingParentsBehavior query parameter on POST .../children
|
||||
// colon-syntax requests.
|
||||
func TestResolveGraphPath_MissingParentsBehavior(t *testing.T) {
|
||||
childrenURL := "/graph/v1beta1/drives/" + testDriveID + "/root:/a/b:/children"
|
||||
|
||||
t.Run("create creates the missing folders along the path", func(t *testing.T) {
|
||||
gw := cs3mocks.NewGatewayAPIClient(t)
|
||||
created := false
|
||||
gw.EXPECT().Stat(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(
|
||||
func(_ context.Context, req *storageprovider.StatRequest, _ ...grpc.CallOption) (*storageprovider.StatResponse, error) {
|
||||
switch path := req.GetRef().GetPath(); path {
|
||||
case "./a":
|
||||
return statResponse(cs3rpc.Code_CODE_OK, true), nil
|
||||
case "./a/b":
|
||||
if created {
|
||||
return statResponse(cs3rpc.Code_CODE_OK, true), nil
|
||||
}
|
||||
return statResponse(cs3rpc.Code_CODE_NOT_FOUND, false), nil
|
||||
default:
|
||||
t.Errorf("unexpected stat path %q", path)
|
||||
return statResponse(cs3rpc.Code_CODE_NOT_FOUND, false), nil
|
||||
}
|
||||
})
|
||||
gw.EXPECT().CreateContainer(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(
|
||||
func(_ context.Context, req *storageprovider.CreateContainerRequest, _ ...grpc.CallOption) (*storageprovider.CreateContainerResponse, error) {
|
||||
assert.Equal(t, "./a/b", req.GetRef().GetPath())
|
||||
created = true
|
||||
return &storageprovider.CreateContainerResponse{Status: &cs3rpc.Status{Code: cs3rpc.Code_CODE_OK}}, nil
|
||||
}).Once()
|
||||
|
||||
router, cap := newGraphTestRouter(t, gw)
|
||||
rr := httptest.NewRecorder()
|
||||
router.ServeHTTP(rr, httptest.NewRequest(
|
||||
http.MethodPost, childrenURL+"?%40libre.graph.missingParentsBehavior=create", nil,
|
||||
))
|
||||
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
assert.Equal(t, "createChild", cap.hit)
|
||||
assert.Equal(t, testItemID, cap.itemID)
|
||||
})
|
||||
|
||||
t.Run("default fail returns 404 for a missing path without creating anything", func(t *testing.T) {
|
||||
gw := cs3mocks.NewGatewayAPIClient(t)
|
||||
gw.EXPECT().Stat(mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(statResponse(cs3rpc.Code_CODE_NOT_FOUND, false), nil).
|
||||
Once()
|
||||
|
||||
router, cap := newGraphTestRouter(t, gw)
|
||||
rr := httptest.NewRecorder()
|
||||
router.ServeHTTP(rr, httptest.NewRequest(http.MethodPost, childrenURL, nil))
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, rr.Code)
|
||||
assert.Equal(t, "", cap.hit)
|
||||
})
|
||||
|
||||
t.Run("invalid value returns 400", func(t *testing.T) {
|
||||
gw := cs3mocks.NewGatewayAPIClient(t)
|
||||
|
||||
router, cap := newGraphTestRouter(t, gw)
|
||||
rr := httptest.NewRecorder()
|
||||
router.ServeHTTP(rr, httptest.NewRequest(
|
||||
http.MethodPost, childrenURL+"?%40libre.graph.missingParentsBehavior=maybe", nil,
|
||||
))
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, rr.Code)
|
||||
assert.Equal(t, "", cap.hit)
|
||||
})
|
||||
|
||||
t.Run("denied folder creation returns 403", func(t *testing.T) {
|
||||
gw := cs3mocks.NewGatewayAPIClient(t)
|
||||
gw.EXPECT().Stat(mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(statResponse(cs3rpc.Code_CODE_NOT_FOUND, false), nil).
|
||||
Once()
|
||||
gw.EXPECT().CreateContainer(mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(&storageprovider.CreateContainerResponse{
|
||||
Status: &cs3rpc.Status{Code: cs3rpc.Code_CODE_PERMISSION_DENIED},
|
||||
}, nil).
|
||||
Once()
|
||||
|
||||
router, cap := newGraphTestRouter(t, gw)
|
||||
rr := httptest.NewRecorder()
|
||||
router.ServeHTTP(rr, httptest.NewRequest(
|
||||
http.MethodPost, childrenURL+"?%40libre.graph.missingParentsBehavior=create", nil,
|
||||
))
|
||||
|
||||
assert.Equal(t, http.StatusForbidden, rr.Code)
|
||||
assert.Equal(t, "", cap.hit)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
@@ -72,6 +74,15 @@ var (
|
||||
|
||||
// ErrAlreadyUnmounted is returned when all shares are already unmounted
|
||||
ErrAlreadyUnmounted = errorcode.New(errorcode.NameAlreadyExists, "shares already unmounted")
|
||||
|
||||
// ErrInvalidItemName is returned when the name for a new drive item is invalid
|
||||
ErrInvalidItemName = errorcode.New(errorcode.InvalidRequest, "invalid item name")
|
||||
|
||||
// ErrExactlyOneFacet is returned when not exactly one of the create facets is set
|
||||
ErrExactlyOneFacet = errorcode.New(errorcode.InvalidRequest, "exactly one of folder, file or remoteItem must be set")
|
||||
|
||||
// ErrInvalidConflictBehavior is returned when the conflictBehavior query parameter is invalid
|
||||
ErrInvalidConflictBehavior = errorcode.New(errorcode.InvalidRequest, "invalid @libre.graph.conflictBehavior")
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -80,6 +91,9 @@ type (
|
||||
|
||||
// DrivesDriveItemProvider is the interface that needs to be implemented by the individual space service
|
||||
DrivesDriveItemProvider interface {
|
||||
// CreateChild creates a folder or an empty file below the given parent
|
||||
CreateChild(ctx context.Context, parentID *storageprovider.ResourceId, name string, isFolder, replace bool) (*storageprovider.ResourceInfo, error)
|
||||
|
||||
// MountShare mounts a share
|
||||
MountShare(ctx context.Context, resourceID *storageprovider.ResourceId, name string) ([]*collaboration.ReceivedShare, error)
|
||||
|
||||
@@ -330,20 +344,76 @@ func (s DrivesDriveItemService) MountShare(ctx context.Context, resourceID *stor
|
||||
return updatedShares, nil
|
||||
}
|
||||
|
||||
// CreateChild creates a folder or an empty file below the given parent.
|
||||
// With replace an existing child with the same name is deleted first.
|
||||
func (s DrivesDriveItemService) CreateChild(ctx context.Context, parentID *storageprovider.ResourceId, name string, isFolder, replace bool) (*storageprovider.ResourceInfo, error) {
|
||||
if name == "" || name == "." || name == ".." || strings.ContainsRune(name, '/') {
|
||||
return nil, ErrInvalidItemName
|
||||
}
|
||||
|
||||
gatewayClient, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ref := &storageprovider.Reference{
|
||||
ResourceId: parentID,
|
||||
Path: utils.MakeRelativePath(name),
|
||||
}
|
||||
|
||||
create := func() error {
|
||||
if isFolder {
|
||||
res, err := gatewayClient.CreateContainer(ctx, &storageprovider.CreateContainerRequest{Ref: ref})
|
||||
return errorcode.FromCS3Status(res.GetStatus(), err)
|
||||
}
|
||||
res, err := gatewayClient.TouchFile(ctx, &storageprovider.TouchFileRequest{Ref: ref})
|
||||
return errorcode.FromCS3Status(res.GetStatus(), err)
|
||||
}
|
||||
|
||||
err = create()
|
||||
var lgErr errorcode.Error
|
||||
if replace && errors.As(err, &lgErr) && lgErr.GetCode() == errorcode.NameAlreadyExists {
|
||||
delRes, delErr := gatewayClient.Delete(ctx, &storageprovider.DeleteRequest{Ref: ref})
|
||||
if err := errorcode.FromCS3Status(delRes.GetStatus(), delErr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = create()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
statRes, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{Ref: ref})
|
||||
if err := errorcode.FromCS3Status(statRes.GetStatus(), err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// the path based stat above returns the path relative to ref, stat again by id to get the space relative path
|
||||
idRes, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{
|
||||
Ref: &storageprovider.Reference{ResourceId: statRes.GetInfo().GetId()},
|
||||
})
|
||||
if err := errorcode.FromCS3Status(idRes.GetStatus(), err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return idRes.GetInfo(), nil
|
||||
}
|
||||
|
||||
// DrivesDriveItemApi is the api that registers the http endpoints which expose needed operation to the graph api.
|
||||
// the business logic is delegated to the space service and further down to the cs3 client.
|
||||
type DrivesDriveItemApi struct {
|
||||
logger log.Logger
|
||||
drivesDriveItemService DrivesDriveItemProvider
|
||||
baseGraphService BaseGraphProvider
|
||||
publicBaseURL *url.URL
|
||||
}
|
||||
|
||||
// NewDrivesDriveItemApi creates a new DrivesDriveItemApi
|
||||
func NewDrivesDriveItemApi(drivesDriveItemService DrivesDriveItemProvider, baseGraphService BaseGraphProvider, logger log.Logger) (DrivesDriveItemApi, error) {
|
||||
func NewDrivesDriveItemApi(drivesDriveItemService DrivesDriveItemProvider, baseGraphService BaseGraphProvider, publicBaseURL *url.URL, logger log.Logger) (DrivesDriveItemApi, error) {
|
||||
return DrivesDriveItemApi{
|
||||
logger: log.Logger{Logger: logger.With().Str("graph api", "DrivesDriveItemApi").Logger()},
|
||||
drivesDriveItemService: drivesDriveItemService,
|
||||
baseGraphService: baseGraphService,
|
||||
publicBaseURL: publicBaseURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -489,9 +559,9 @@ func (api DrivesDriveItemApi) UpdateDriveItem(w http.ResponseWriter, r *http.Req
|
||||
render.JSON(w, r, driveItems[0])
|
||||
}
|
||||
|
||||
// CreateDriveItem creates a drive item
|
||||
// CreateDriveItem creates a new driveItem at the drive root: a folder, an
|
||||
// empty file or a mounted share (remoteItem).
|
||||
func (api DrivesDriveItemApi) CreateDriveItem(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
driveID, err := parseIDParam(r, "driveID")
|
||||
if err != nil {
|
||||
api.logger.Debug().Err(err).Msg(ErrInvalidDriveIDOrItemID.Error())
|
||||
@@ -499,18 +569,108 @@ func (api DrivesDriveItemApi) CreateDriveItem(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
if !IsShareJail(&driveID) {
|
||||
api.logger.Debug().Interface("driveID", driveID).Msg(ErrNotAShareJail.Error())
|
||||
ErrNotAShareJail.Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
requestDriveItem := libregraph.DriveItem{}
|
||||
if err := StrictJSONUnmarshal(r.Body, &requestDriveItem); err != nil {
|
||||
api.logger.Debug().Err(err).Msg(ErrInvalidRequestBody.Error())
|
||||
ErrInvalidRequestBody.Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
facets := 0
|
||||
for _, set := range []bool{requestDriveItem.Folder != nil, requestDriveItem.File != nil, requestDriveItem.RemoteItem != nil} {
|
||||
if set {
|
||||
facets++
|
||||
}
|
||||
}
|
||||
if facets != 1 {
|
||||
api.logger.Debug().Msg(ErrExactlyOneFacet.Error())
|
||||
ErrExactlyOneFacet.Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if requestDriveItem.RemoteItem != nil {
|
||||
api.mountShare(w, r, &driveID, requestDriveItem)
|
||||
return
|
||||
}
|
||||
|
||||
if IsShareJail(&driveID) {
|
||||
api.logger.Debug().Interface("driveID", &driveID).Msg("cannot create items in the share jail")
|
||||
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "cannot create items in the share jail")
|
||||
return
|
||||
}
|
||||
|
||||
root := &storageprovider.ResourceId{
|
||||
StorageId: driveID.GetStorageId(),
|
||||
SpaceId: driveID.GetSpaceId(),
|
||||
OpaqueId: driveID.GetSpaceId(),
|
||||
}
|
||||
api.createChild(w, r, root, requestDriveItem)
|
||||
}
|
||||
|
||||
// CreateChildDriveItem creates a new driveItem (folder or empty file) below an existing parent item
|
||||
func (api DrivesDriveItemApi) CreateChildDriveItem(w http.ResponseWriter, r *http.Request) {
|
||||
_, itemID, err := GetDriveAndItemIDParam(r, &api.logger)
|
||||
if err != nil {
|
||||
api.logger.Debug().Err(err).Msg(ErrInvalidDriveIDOrItemID.Error())
|
||||
ErrInvalidDriveIDOrItemID.Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
requestDriveItem := libregraph.DriveItem{}
|
||||
if err := StrictJSONUnmarshal(r.Body, &requestDriveItem); err != nil {
|
||||
api.logger.Debug().Err(err).Msg(ErrInvalidRequestBody.Error())
|
||||
ErrInvalidRequestBody.Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if (requestDriveItem.Folder != nil) == (requestDriveItem.File != nil) || requestDriveItem.RemoteItem != nil {
|
||||
api.logger.Debug().Msg(ErrExactlyOneFacet.Error())
|
||||
ErrExactlyOneFacet.Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
api.createChild(w, r, itemID, requestDriveItem)
|
||||
}
|
||||
|
||||
// createChild creates a folder or an empty file below the given parent and renders the result
|
||||
func (api DrivesDriveItemApi) createChild(w http.ResponseWriter, r *http.Request, parentID *storageprovider.ResourceId, requestDriveItem libregraph.DriveItem) {
|
||||
var replace bool
|
||||
switch r.URL.Query().Get("@libre.graph.conflictBehavior") {
|
||||
case "", "fail":
|
||||
case "replace":
|
||||
replace = true
|
||||
default:
|
||||
api.logger.Debug().Msg(ErrInvalidConflictBehavior.Error())
|
||||
ErrInvalidConflictBehavior.Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
info, err := api.drivesDriveItemService.CreateChild(r.Context(), parentID, requestDriveItem.GetName(), requestDriveItem.Folder != nil, replace)
|
||||
if err != nil {
|
||||
api.logger.Debug().Err(err).Msg("creating drive item failed")
|
||||
errorcode.RenderError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
driveItem, err := cs3ResourceToDriveItem(&api.logger, api.publicBaseURL, info)
|
||||
if err != nil {
|
||||
api.logger.Debug().Err(err).Msg(ErrDriveItemConversion.Error())
|
||||
ErrDriveItemConversion.Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(w, r, driveItem)
|
||||
}
|
||||
|
||||
// mountShare mounts a share into the share jail
|
||||
func (api DrivesDriveItemApi) mountShare(w http.ResponseWriter, r *http.Request, driveID *storageprovider.ResourceId, requestDriveItem libregraph.DriveItem) {
|
||||
ctx := r.Context()
|
||||
|
||||
if !IsShareJail(driveID) {
|
||||
api.logger.Debug().Interface("driveID", driveID).Msg(ErrNotAShareJail.Error())
|
||||
ErrNotAShareJail.Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
remoteItem := requestDriveItem.GetRemoteItem()
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
collaborationv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
@@ -478,6 +479,109 @@ var _ = Describe("DrivesDriveItemService", func() {
|
||||
Expect(shares).To(HaveLen(1))
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("CreateChild", func() {
|
||||
parentID := &storageprovider.ResourceId{StorageId: "1", SpaceId: "2", OpaqueId: "2"}
|
||||
|
||||
It("rejects invalid names", func() {
|
||||
_, _ = gatewaySelector.Next() // make mockery call count happy
|
||||
for _, name := range []string{"", ".", "..", "a/b", "/a"} {
|
||||
_, err := drivesDriveItemService.CreateChild(context.Background(), parentID, name, true, false)
|
||||
Expect(err).To(MatchError(svc.ErrInvalidItemName))
|
||||
}
|
||||
})
|
||||
|
||||
It("creates a folder", func() {
|
||||
gatewayClient.
|
||||
EXPECT().
|
||||
CreateContainer(mock.Anything, mock.Anything, mock.Anything).
|
||||
RunAndReturn(func(ctx context.Context, request *storageprovider.CreateContainerRequest, _ ...grpc.CallOption) (*storageprovider.CreateContainerResponse, error) {
|
||||
Expect(request.GetRef().GetResourceId().GetOpaqueId()).To(Equal("2"))
|
||||
Expect(request.GetRef().GetPath()).To(Equal("./New Folder"))
|
||||
return &storageprovider.CreateContainerResponse{Status: status.NewOK(ctx)}, nil
|
||||
}).
|
||||
Once()
|
||||
gatewayClient.
|
||||
EXPECT().
|
||||
Stat(mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(&storageprovider.StatResponse{
|
||||
Status: status.NewOK(context.Background()),
|
||||
Info: &storageprovider.ResourceInfo{Id: &storageprovider.ResourceId{StorageId: "1", SpaceId: "2", OpaqueId: "3"}},
|
||||
}, nil).
|
||||
Times(2)
|
||||
|
||||
info, err := drivesDriveItemService.CreateChild(context.Background(), parentID, "New Folder", true, false)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(info.GetId().GetOpaqueId()).To(Equal("3"))
|
||||
})
|
||||
|
||||
It("creates an empty file", func() {
|
||||
gatewayClient.
|
||||
EXPECT().
|
||||
TouchFile(mock.Anything, mock.Anything, mock.Anything).
|
||||
RunAndReturn(func(ctx context.Context, request *storageprovider.TouchFileRequest, _ ...grpc.CallOption) (*storageprovider.TouchFileResponse, error) {
|
||||
Expect(request.GetRef().GetPath()).To(Equal("./file.txt"))
|
||||
return &storageprovider.TouchFileResponse{Status: status.NewOK(ctx)}, nil
|
||||
}).
|
||||
Once()
|
||||
gatewayClient.
|
||||
EXPECT().
|
||||
Stat(mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(&storageprovider.StatResponse{
|
||||
Status: status.NewOK(context.Background()),
|
||||
Info: &storageprovider.ResourceInfo{Id: &storageprovider.ResourceId{StorageId: "1", SpaceId: "2", OpaqueId: "3"}},
|
||||
}, nil).
|
||||
Times(2)
|
||||
|
||||
_, err := drivesDriveItemService.CreateChild(context.Background(), parentID, "file.txt", false, false)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("fails with nameAlreadyExists without replace", func() {
|
||||
gatewayClient.
|
||||
EXPECT().
|
||||
TouchFile(mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(&storageprovider.TouchFileResponse{Status: status.NewAlreadyExists(context.Background(), nil, "exists")}, nil).
|
||||
Once()
|
||||
|
||||
_, err := drivesDriveItemService.CreateChild(context.Background(), parentID, "file.txt", false, false)
|
||||
var lgErr errorcode.Error
|
||||
Expect(errors.As(err, &lgErr)).To(BeTrue())
|
||||
Expect(lgErr.GetCode()).To(Equal(errorcode.NameAlreadyExists))
|
||||
})
|
||||
|
||||
It("deletes the existing item with replace", func() {
|
||||
gatewayClient.
|
||||
EXPECT().
|
||||
CreateContainer(mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(&storageprovider.CreateContainerResponse{Status: status.NewAlreadyExists(context.Background(), nil, "exists")}, nil).
|
||||
Once()
|
||||
gatewayClient.
|
||||
EXPECT().
|
||||
Delete(mock.Anything, mock.Anything, mock.Anything).
|
||||
RunAndReturn(func(ctx context.Context, request *storageprovider.DeleteRequest, _ ...grpc.CallOption) (*storageprovider.DeleteResponse, error) {
|
||||
Expect(request.GetRef().GetPath()).To(Equal("./New Folder"))
|
||||
return &storageprovider.DeleteResponse{Status: status.NewOK(ctx)}, nil
|
||||
}).
|
||||
Once()
|
||||
gatewayClient.
|
||||
EXPECT().
|
||||
CreateContainer(mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(&storageprovider.CreateContainerResponse{Status: status.NewOK(context.Background())}, nil).
|
||||
Once()
|
||||
gatewayClient.
|
||||
EXPECT().
|
||||
Stat(mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(&storageprovider.StatResponse{
|
||||
Status: status.NewOK(context.Background()),
|
||||
Info: &storageprovider.ResourceInfo{Id: &storageprovider.ResourceId{StorageId: "1", SpaceId: "2", OpaqueId: "3"}},
|
||||
}, nil).
|
||||
Times(2)
|
||||
|
||||
_, err := drivesDriveItemService.CreateChild(context.Background(), parentID, "New Folder", true, true)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("DrivesDriveItemApi", func() {
|
||||
@@ -494,7 +598,9 @@ var _ = Describe("DrivesDriveItemApi", func() {
|
||||
baseGraphProvider = mocks.NewBaseGraphProvider(GinkgoT())
|
||||
|
||||
drivesDriveItemProvider = mocks.NewDrivesDriveItemProvider(GinkgoT())
|
||||
api, err := svc.NewDrivesDriveItemApi(drivesDriveItemProvider, baseGraphProvider, logger)
|
||||
publicBaseURL, err := url.Parse("https://localhost:9200")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
api, err := svc.NewDrivesDriveItemApi(drivesDriveItemProvider, baseGraphProvider, publicBaseURL, logger)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
drivesDriveItemApi = api
|
||||
@@ -1037,17 +1143,67 @@ var _ = Describe("DrivesDriveItemApi", func() {
|
||||
Expect(jsonData.Get("code").String() + ": " + jsonData.Get("message").String()).To(Equal(svc.ErrInvalidDriveIDOrItemID.Error()))
|
||||
})
|
||||
|
||||
failOnNonShareJailDriveID(drivesDriveItemApi.CreateDriveItem)
|
||||
|
||||
failOninvalidDriveItemBody(drivesDriveItemApi.CreateDriveItem)
|
||||
|
||||
It("fails on non share jail driveID when mounting a share", func() {
|
||||
rCTX.URLParams.Add("driveID", "1$2")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
driveItemJson, err := json.Marshal(libregraph.DriveItem{
|
||||
RemoteItem: &libregraph.RemoteItem{
|
||||
Id: conversions.ToPointer("123"),
|
||||
},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
r := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(driveItemJson)).
|
||||
WithContext(
|
||||
context.WithValue(context.Background(), chi.RouteCtxKey, rCTX),
|
||||
)
|
||||
|
||||
drivesDriveItemApi.CreateDriveItem(w, r)
|
||||
Expect(w.Code).To(Equal(http.StatusBadRequest))
|
||||
|
||||
jsonData := gjson.Get(w.Body.String(), "error")
|
||||
Expect(jsonData.Get("code").String() + ": " + jsonData.Get("message").String()).To(Equal(svc.ErrNotAShareJail.Error()))
|
||||
})
|
||||
|
||||
It("fails if not exactly one facet is set", func() {
|
||||
rCTX.URLParams.Add("driveID", "1$2")
|
||||
|
||||
for _, driveItem := range []libregraph.DriveItem{
|
||||
{},
|
||||
{Folder: &libregraph.Folder{}, File: &libregraph.OpenGraphFile{}},
|
||||
{Folder: &libregraph.Folder{}, RemoteItem: &libregraph.RemoteItem{}},
|
||||
} {
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
driveItemJson, err := json.Marshal(driveItem)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
r := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(driveItemJson)).
|
||||
WithContext(
|
||||
context.WithValue(context.Background(), chi.RouteCtxKey, rCTX),
|
||||
)
|
||||
|
||||
drivesDriveItemApi.CreateDriveItem(w, r)
|
||||
Expect(w.Code).To(Equal(http.StatusBadRequest))
|
||||
|
||||
jsonData := gjson.Get(w.Body.String(), "error")
|
||||
Expect(jsonData.Get("code").String() + ": " + jsonData.Get("message").String()).To(Equal(svc.ErrExactlyOneFacet.Error()))
|
||||
}
|
||||
})
|
||||
|
||||
It("fails on invalid request body id", func() {
|
||||
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!1")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
driveItemJson, err := json.Marshal(libregraph.DriveItem{})
|
||||
driveItemJson, err := json.Marshal(libregraph.DriveItem{
|
||||
RemoteItem: &libregraph.RemoteItem{},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
r := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(driveItemJson)).
|
||||
@@ -1179,5 +1335,188 @@ var _ = Describe("DrivesDriveItemApi", func() {
|
||||
drivesDriveItemApi.CreateDriveItem(w, r)
|
||||
Expect(w.Code).To(Equal(http.StatusCreated))
|
||||
})
|
||||
|
||||
It("fails on an invalid conflictBehavior", func() {
|
||||
rCTX.URLParams.Add("driveID", "1$2")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
driveItemJson, err := json.Marshal(libregraph.DriveItem{
|
||||
Name: conversions.ToPointer("New Folder"),
|
||||
Folder: &libregraph.Folder{},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
r := httptest.NewRequest(http.MethodPost, "/?%40libre.graph.conflictBehavior=rename", bytes.NewBuffer(driveItemJson)).
|
||||
WithContext(
|
||||
context.WithValue(context.Background(), chi.RouteCtxKey, rCTX),
|
||||
)
|
||||
|
||||
drivesDriveItemApi.CreateDriveItem(w, r)
|
||||
Expect(w.Code).To(Equal(http.StatusBadRequest))
|
||||
|
||||
jsonData := gjson.Get(w.Body.String(), "error")
|
||||
Expect(jsonData.Get("code").String() + ": " + jsonData.Get("message").String()).To(Equal(svc.ErrInvalidConflictBehavior.Error()))
|
||||
})
|
||||
|
||||
It("successfully creates a folder at the drive root", func() {
|
||||
rCTX.URLParams.Add("driveID", "1$2")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
driveItemJson, err := json.Marshal(libregraph.DriveItem{
|
||||
Name: conversions.ToPointer("New Folder"),
|
||||
Folder: &libregraph.Folder{},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
drivesDriveItemProvider.
|
||||
EXPECT().
|
||||
CreateChild(mock.Anything, mock.MatchedBy(func(id *storageprovider.ResourceId) bool {
|
||||
return id.GetStorageId() == "1" && id.GetSpaceId() == "2" && id.GetOpaqueId() == "2"
|
||||
}), "New Folder", true, false).
|
||||
Return(&storageprovider.ResourceInfo{
|
||||
Id: &storageprovider.ResourceId{StorageId: "1", SpaceId: "2", OpaqueId: "3"},
|
||||
Path: "./New Folder",
|
||||
Type: storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
|
||||
}, nil).
|
||||
Once()
|
||||
|
||||
r := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(driveItemJson)).
|
||||
WithContext(
|
||||
context.WithValue(context.Background(), chi.RouteCtxKey, rCTX),
|
||||
)
|
||||
|
||||
drivesDriveItemApi.CreateDriveItem(w, r)
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
Expect(gjson.Get(w.Body.String(), "name").String()).To(Equal("New Folder"))
|
||||
Expect(gjson.Get(w.Body.String(), "folder").Exists()).To(BeTrue())
|
||||
})
|
||||
|
||||
It("passes replace on conflictBehavior=replace", func() {
|
||||
rCTX.URLParams.Add("driveID", "1$2")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
driveItemJson, err := json.Marshal(libregraph.DriveItem{
|
||||
Name: conversions.ToPointer("file.txt"),
|
||||
File: &libregraph.OpenGraphFile{},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
drivesDriveItemProvider.
|
||||
EXPECT().
|
||||
CreateChild(mock.Anything, mock.Anything, "file.txt", false, true).
|
||||
Return(&storageprovider.ResourceInfo{
|
||||
Id: &storageprovider.ResourceId{StorageId: "1", SpaceId: "2", OpaqueId: "3"},
|
||||
Path: "./file.txt",
|
||||
Type: storageprovider.ResourceType_RESOURCE_TYPE_FILE,
|
||||
}, nil).
|
||||
Once()
|
||||
|
||||
r := httptest.NewRequest(http.MethodPost, "/?%40libre.graph.conflictBehavior=replace", bytes.NewBuffer(driveItemJson)).
|
||||
WithContext(
|
||||
context.WithValue(context.Background(), chi.RouteCtxKey, rCTX),
|
||||
)
|
||||
|
||||
drivesDriveItemApi.CreateDriveItem(w, r)
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("CreateChildDriveItem", func() {
|
||||
failOnInvalidDriveIDOrItemID(drivesDriveItemApi.CreateChildDriveItem)
|
||||
|
||||
failOninvalidDriveItemBody(drivesDriveItemApi.CreateChildDriveItem)
|
||||
|
||||
It("fails if not exactly one of folder and file is set", func() {
|
||||
rCTX.URLParams.Add("driveID", "1$2")
|
||||
rCTX.URLParams.Add("itemID", "1$2!3")
|
||||
|
||||
for _, driveItem := range []libregraph.DriveItem{
|
||||
{},
|
||||
{Folder: &libregraph.Folder{}, File: &libregraph.OpenGraphFile{}},
|
||||
{RemoteItem: &libregraph.RemoteItem{}},
|
||||
} {
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
driveItemJson, err := json.Marshal(driveItem)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
r := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(driveItemJson)).
|
||||
WithContext(
|
||||
context.WithValue(context.Background(), chi.RouteCtxKey, rCTX),
|
||||
)
|
||||
|
||||
drivesDriveItemApi.CreateChildDriveItem(w, r)
|
||||
Expect(w.Code).To(Equal(http.StatusBadRequest))
|
||||
|
||||
jsonData := gjson.Get(w.Body.String(), "error")
|
||||
Expect(jsonData.Get("code").String() + ": " + jsonData.Get("message").String()).To(Equal(svc.ErrExactlyOneFacet.Error()))
|
||||
}
|
||||
})
|
||||
|
||||
It("renders the service error", func() {
|
||||
rCTX.URLParams.Add("driveID", "1$2")
|
||||
rCTX.URLParams.Add("itemID", "1$2!3")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
driveItemJson, err := json.Marshal(libregraph.DriveItem{
|
||||
Name: conversions.ToPointer("New Folder"),
|
||||
Folder: &libregraph.Folder{},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
drivesDriveItemProvider.
|
||||
EXPECT().
|
||||
CreateChild(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(nil, errorcode.New(errorcode.NameAlreadyExists, "already exists")).
|
||||
Once()
|
||||
|
||||
r := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(driveItemJson)).
|
||||
WithContext(
|
||||
context.WithValue(context.Background(), chi.RouteCtxKey, rCTX),
|
||||
)
|
||||
|
||||
drivesDriveItemApi.CreateChildDriveItem(w, r)
|
||||
Expect(w.Code).To(Equal(http.StatusConflict))
|
||||
})
|
||||
|
||||
It("successfully creates a file below the parent", func() {
|
||||
rCTX.URLParams.Add("driveID", "1$2")
|
||||
rCTX.URLParams.Add("itemID", "1$2!3")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
driveItemJson, err := json.Marshal(libregraph.DriveItem{
|
||||
Name: conversions.ToPointer("file.txt"),
|
||||
File: &libregraph.OpenGraphFile{},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
drivesDriveItemProvider.
|
||||
EXPECT().
|
||||
CreateChild(mock.Anything, mock.MatchedBy(func(id *storageprovider.ResourceId) bool {
|
||||
return id.GetStorageId() == "1" && id.GetSpaceId() == "2" && id.GetOpaqueId() == "3"
|
||||
}), "file.txt", false, false).
|
||||
Return(&storageprovider.ResourceInfo{
|
||||
Id: &storageprovider.ResourceId{StorageId: "1", SpaceId: "2", OpaqueId: "4"},
|
||||
Path: "./file.txt",
|
||||
Type: storageprovider.ResourceType_RESOURCE_TYPE_FILE,
|
||||
MimeType: "text/plain",
|
||||
}, nil).
|
||||
Once()
|
||||
|
||||
r := httptest.NewRequest(http.MethodPost, "/", bytes.NewBuffer(driveItemJson)).
|
||||
WithContext(
|
||||
context.WithValue(context.Background(), chi.RouteCtxKey, rCTX),
|
||||
)
|
||||
|
||||
drivesDriveItemApi.CreateChildDriveItem(w, r)
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
Expect(gjson.Get(w.Body.String(), "name").String()).To(Equal("file.txt"))
|
||||
Expect(gjson.Get(w.Body.String(), "file.mimeType").String()).To(Equal("text/plain"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -174,7 +174,7 @@ func NewService(opts ...Option) (Graph, error) { //nolint:maintidx
|
||||
return Graph{}, err
|
||||
}
|
||||
|
||||
drivesDriveItemApi, err := NewDrivesDriveItemApi(drivesDriveItemService, baseGraphService, options.Logger)
|
||||
drivesDriveItemApi, err := NewDrivesDriveItemApi(drivesDriveItemService, baseGraphService, publicBaseURL, options.Logger)
|
||||
if err != nil {
|
||||
return Graph{}, err
|
||||
}
|
||||
@@ -288,6 +288,7 @@ func NewService(opts ...Option) (Graph, error) { //nolint:maintidx
|
||||
r.Get("/", drivesDriveItemApi.GetDriveItem)
|
||||
r.Patch("/", drivesDriveItemApi.UpdateDriveItem)
|
||||
r.Delete("/", drivesDriveItemApi.DeleteDriveItem)
|
||||
r.Post("/children", drivesDriveItemApi.CreateChildDriveItem)
|
||||
r.Post("/invite", driveItemPermissionsApi.Invite)
|
||||
r.Post("/createLink", driveItemPermissionsApi.CreateLink)
|
||||
r.Route("/permissions", func(r chi.Router) {
|
||||
|
||||
Reference in New Issue
Block a user