mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-09-09 12:19:08 -04:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9322750a65 | ||
|
|
8ab026cb65 | ||
|
|
46717d6227 | ||
|
|
8aeab6bca7 |
No files matched your search
@@ -41,6 +41,172 @@ 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
|
||||
}
|
||||
|
||||
// ResolvePath provides a mock function for the type DrivesDriveItemProvider
|
||||
func (_mock *DrivesDriveItemProvider) ResolvePath(ctx context.Context, parentID *providerv1beta1.ResourceId, relPath string, createMissing bool) (*providerv1beta1.ResourceId, error) {
|
||||
ret := _mock.Called(ctx, parentID, relPath, createMissing)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ResolvePath")
|
||||
}
|
||||
|
||||
var r0 *providerv1beta1.ResourceId
|
||||
var r1 error
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, string, bool) (*providerv1beta1.ResourceId, error)); ok {
|
||||
return returnFunc(ctx, parentID, relPath, createMissing)
|
||||
}
|
||||
if returnFunc, ok := ret.Get(0).(func(context.Context, *providerv1beta1.ResourceId, string, bool) *providerv1beta1.ResourceId); ok {
|
||||
r0 = returnFunc(ctx, parentID, relPath, createMissing)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*providerv1beta1.ResourceId)
|
||||
}
|
||||
}
|
||||
if returnFunc, ok := ret.Get(1).(func(context.Context, *providerv1beta1.ResourceId, string, bool) error); ok {
|
||||
r1 = returnFunc(ctx, parentID, relPath, createMissing)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// DrivesDriveItemProvider_ResolvePath_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ResolvePath'
|
||||
type DrivesDriveItemProvider_ResolvePath_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ResolvePath is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - parentID *providerv1beta1.ResourceId
|
||||
// - relPath string
|
||||
// - createMissing bool
|
||||
func (_e *DrivesDriveItemProvider_Expecter) ResolvePath(ctx interface{}, parentID interface{}, relPath interface{}, createMissing interface{}) *DrivesDriveItemProvider_ResolvePath_Call {
|
||||
return &DrivesDriveItemProvider_ResolvePath_Call{Call: _e.mock.On("ResolvePath", ctx, parentID, relPath, createMissing)}
|
||||
}
|
||||
|
||||
func (_c *DrivesDriveItemProvider_ResolvePath_Call) Run(run func(ctx context.Context, parentID *providerv1beta1.ResourceId, relPath string, createMissing bool)) *DrivesDriveItemProvider_ResolvePath_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)
|
||||
}
|
||||
run(
|
||||
arg0,
|
||||
arg1,
|
||||
arg2,
|
||||
arg3,
|
||||
)
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *DrivesDriveItemProvider_ResolvePath_Call) Return(resourceId *providerv1beta1.ResourceId, err error) *DrivesDriveItemProvider_ResolvePath_Call {
|
||||
_c.Call.Return(resourceId, err)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *DrivesDriveItemProvider_ResolvePath_Call) RunAndReturn(run func(ctx context.Context, parentID *providerv1beta1.ResourceId, relPath string, createMissing bool) (*providerv1beta1.ResourceId, error)) *DrivesDriveItemProvider_ResolvePath_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)
|
||||
|
||||
@@ -36,6 +36,17 @@ type contextKey string
|
||||
// tracing/logging consumers.
|
||||
const OriginalPathContextKey contextKey = "graph.original_path"
|
||||
|
||||
// ParentPathContextKey holds the unresolved parent path of a POST .../children
|
||||
// colon request; the handler resolves (or creates) it after validation.
|
||||
const ParentPathContextKey contextKey = "graph.parent_path"
|
||||
|
||||
// ParentPath returns the parent path stored by ResolveGraphPath, or "" for all
|
||||
// other requests.
|
||||
func ParentPath(ctx context.Context) string {
|
||||
path, _ := ctx.Value(ParentPathContextKey).(string)
|
||||
return path
|
||||
}
|
||||
|
||||
// Sentinels distinguishing the resolution outcomes that map to specific HTTP
|
||||
// statuses. Anything else surfaces as 500.
|
||||
//
|
||||
@@ -74,6 +85,10 @@ var (
|
||||
// failures (gateway selection, RPC transport, unexpected status) surface
|
||||
// as 5xx so outages aren't masked.
|
||||
//
|
||||
// POST .../children colon requests are never resolved here: the rewrite targets
|
||||
// the anchor item and the parent path reaches the handler via
|
||||
// ParentPathContextKey (see createChild).
|
||||
//
|
||||
// Requests whose RoutePath contains no colon fast-path through untouched.
|
||||
func ResolveGraphPath(gws pool.Selectable[gateway.GatewayAPIClient], logger log.Logger) func(http.Handler) http.Handler {
|
||||
l := logger.With().Str("middleware", "graphPathLookup").Logger()
|
||||
@@ -91,7 +106,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, parentPath, err := rewriteColonPath(r.Context(), gws, l, driveID, rctx.RoutePath, r.Method)
|
||||
switch {
|
||||
case errors.Is(err, errPathNotFound):
|
||||
l.Debug().Str("original", original).Msg("colon-path resolution: not found")
|
||||
@@ -126,6 +141,9 @@ func ResolveGraphPath(gws pool.Selectable[gateway.GatewayAPIClient], logger log.
|
||||
// tracing/logging. r.URL.Path itself stays untouched; only chi's
|
||||
// internal RoutePath is rewritten.
|
||||
r = r.WithContext(context.WithValue(r.Context(), OriginalPathContextKey, original))
|
||||
if parentPath != "" {
|
||||
r = r.WithContext(context.WithValue(r.Context(), ParentPathContextKey, parentPath))
|
||||
}
|
||||
rctx.RoutePath = rewritten
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
@@ -145,7 +163,7 @@ type colonMatch struct {
|
||||
suffix string // suffix with leading slash (e.g. "/children"); may be empty
|
||||
}
|
||||
|
||||
// rewriteColonPath returns:
|
||||
// rewriteColonPath returns (rewritten, parentPath, err):
|
||||
// - "" + nil - no colon-syntax pattern matched (passthrough)
|
||||
// - rewritten + nil - matched and resolved to a canonical RoutePath
|
||||
// - "" + errPathNotFound - path doesn't exist or user lacks permission (404)
|
||||
@@ -153,6 +171,10 @@ type colonMatch struct {
|
||||
// - "" + errUnauthenticated - gateway said caller isn't authenticated (401)
|
||||
// - "" + other error - operational / internal failure (5xx)
|
||||
//
|
||||
// parentPath is non-empty for POST .../children requests: their colon path is
|
||||
// never resolved here, the rewrite targets the anchor item and the handler
|
||||
// resolves (or creates) the parent path (see ParentPathContextKey).
|
||||
//
|
||||
// driveIDParam is the {driveID} route param (raw chi.URLParam value); routePath
|
||||
// is chi.RouteContext().RoutePath (the part below /drives/{driveID}).
|
||||
func rewriteColonPath(
|
||||
@@ -161,10 +183,11 @@ func rewriteColonPath(
|
||||
logger zerolog.Logger,
|
||||
driveIDParam string,
|
||||
routePath string,
|
||||
) (string, error) {
|
||||
method string,
|
||||
) (string, string, error) {
|
||||
match, ok := parseColonPath(routePath)
|
||||
if !ok {
|
||||
return "", nil
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
// RoutePath follows chi's RawPath, i.e. the percent-encoded wire form
|
||||
@@ -174,7 +197,7 @@ func rewriteColonPath(
|
||||
driveID, err := url.PathUnescape(driveIDParam)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Str("driveID", driveIDParam).Msg("undecodable drive id in colon path")
|
||||
return "", errInvalidRequest
|
||||
return "", "", errInvalidRequest
|
||||
}
|
||||
|
||||
anchorIDStr := driveID
|
||||
@@ -182,7 +205,7 @@ func rewriteColonPath(
|
||||
anchorIDStr, err = url.PathUnescape(match.itemAnchorID)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Str("itemID", match.itemAnchorID).Msg("undecodable item id in colon path")
|
||||
return "", errInvalidRequest
|
||||
return "", "", errInvalidRequest
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +213,7 @@ func rewriteColonPath(
|
||||
if err != nil {
|
||||
// Unparseable input is malformed by the client, not "not found".
|
||||
logger.Debug().Err(err).Str("anchor", anchorIDStr).Msg("invalid anchor id in colon path")
|
||||
return "", errInvalidRequest
|
||||
return "", "", errInvalidRequest
|
||||
}
|
||||
|
||||
// Item-anchored form: the itemID comes from the path, driveID from the
|
||||
@@ -201,28 +224,38 @@ func rewriteColonPath(
|
||||
drive, err := storagespace.ParseID(driveID)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Str("driveID", driveID).Msg("invalid drive id in colon path")
|
||||
return "", errInvalidRequest
|
||||
return "", "", errInvalidRequest
|
||||
}
|
||||
if drive.GetStorageId() != anchor.GetStorageId() || drive.GetSpaceId() != anchor.GetSpaceId() {
|
||||
logger.Debug().
|
||||
Str("driveID", driveID).
|
||||
Str("itemID", anchorIDStr).
|
||||
Msg("drive id does not match item id storage/space")
|
||||
return "", errInvalidRequest
|
||||
return "", "", errInvalidRequest
|
||||
}
|
||||
}
|
||||
|
||||
relPath, err := url.PathUnescape(match.relPath)
|
||||
if err != nil {
|
||||
logger.Debug().Err(err).Str("relPath", match.relPath).Msg("undecodable path in colon path")
|
||||
return "", errInvalidRequest
|
||||
return "", "", errInvalidRequest
|
||||
}
|
||||
|
||||
if method == http.MethodPost && match.suffix == "/children" {
|
||||
// the colon path addresses the parent of the item to create: rewrite
|
||||
// to the anchor item, the handler resolves the parent path
|
||||
if anchor.GetOpaqueId() == "" {
|
||||
// the space root item id is storage$space!space
|
||||
anchor.OpaqueId = anchor.GetSpaceId()
|
||||
}
|
||||
return buildCanonicalRoutePath(storagespace.FormatResourceID(&anchor), match.suffix), relPath, nil
|
||||
}
|
||||
|
||||
itemID, err := resolvePath(ctx, gws, &anchor, relPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
return buildCanonicalRoutePath(itemID, match.suffix), nil
|
||||
return buildCanonicalRoutePath(itemID, match.suffix), "", nil
|
||||
}
|
||||
|
||||
// parseColonPath splits a colon-syntax RoutePath (the part below
|
||||
|
||||
@@ -61,11 +61,12 @@ func statResponse(code cs3rpc.Code, withInfo bool) *storageprovider.StatResponse
|
||||
// the middleware rewrote chi's route path correctly and the request reached the
|
||||
// intended /items/{itemID}... handler with the resolved id bound as a param.
|
||||
type leafCapture struct {
|
||||
hit string // which leaf was reached ("" = none)
|
||||
urlPath string // r.URL.Path as seen by the handler (must stay the original)
|
||||
driveID string // chi.URLParam(driveID)
|
||||
itemID string // resolved item id, decoded via PathUnescape
|
||||
original any // OriginalPathContextKey value
|
||||
hit string // which leaf was reached ("" = none)
|
||||
urlPath string // r.URL.Path as seen by the handler (must stay the original)
|
||||
driveID string // chi.URLParam(driveID)
|
||||
itemID string // resolved item id, decoded via PathUnescape
|
||||
original any // OriginalPathContextKey value
|
||||
parentPath string // ParentPath(ctx) as seen by the handler
|
||||
}
|
||||
|
||||
// newGraphTestRouter wires ResolveGraphPath into a chi router that mirrors the
|
||||
@@ -94,6 +95,7 @@ func newGraphTestRouter(t *testing.T, gw *cs3mocks.GatewayAPIClient) (http.Handl
|
||||
// mirror that here so we assert on the recovered id.
|
||||
cap.itemID, _ = url.PathUnescape(raw)
|
||||
cap.original = r.Context().Value(middleware.OriginalPathContextKey)
|
||||
cap.parentPath = middleware.ParentPath(r.Context())
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}
|
||||
@@ -115,6 +117,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 +472,44 @@ 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_DeferredChildrenPost pins that POST .../children colon
|
||||
// requests are never resolved in the middleware: no CS3 calls, the rewrite
|
||||
// targets the anchor item and the handler receives the parent path via
|
||||
// ParentPathContextKey.
|
||||
func TestResolveGraphPath_DeferredChildrenPost(t *testing.T) {
|
||||
// the root-anchored form rewrites to the space root item id
|
||||
rootItemID := testDriveID + "!f503f6fe-2656-4b0f-8289-fb3184962dfd"
|
||||
|
||||
t.Run("root-anchored rewrites to the root anchor and passes the path unresolved", func(t *testing.T) {
|
||||
gw := cs3mocks.NewGatewayAPIClient(t) // no CS3 calls expected
|
||||
|
||||
router, cap := newGraphTestRouter(t, gw)
|
||||
rr := httptest.NewRecorder()
|
||||
router.ServeHTTP(rr, httptest.NewRequest(
|
||||
http.MethodPost, "/graph/v1beta1/drives/"+testDriveID+"/root:/a/b:/children", nil,
|
||||
))
|
||||
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
assert.Equal(t, "createChild", cap.hit)
|
||||
assert.Equal(t, rootItemID, cap.itemID)
|
||||
assert.Equal(t, "/a/b", cap.parentPath)
|
||||
})
|
||||
|
||||
t.Run("item-anchored rewrites to the item anchor and passes the path unresolved", func(t *testing.T) {
|
||||
gw := cs3mocks.NewGatewayAPIClient(t) // no CS3 calls expected
|
||||
|
||||
router, cap := newGraphTestRouter(t, gw)
|
||||
rr := httptest.NewRecorder()
|
||||
router.ServeHTTP(rr, httptest.NewRequest(
|
||||
http.MethodPost,
|
||||
"/graph/v1beta1/drives/"+testDriveID+"/items/"+testItemID+":/a/b:/children",
|
||||
nil,
|
||||
))
|
||||
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
assert.Equal(t, "createChild", cap.hit)
|
||||
assert.Equal(t, testItemID, cap.itemID)
|
||||
assert.Equal(t, "/a/b", cap.parentPath)
|
||||
})
|
||||
}
|
||||
@@ -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"
|
||||
@@ -20,6 +22,7 @@ import (
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
|
||||
graphm "github.com/opencloud-eu/opencloud/services/graph/pkg/middleware"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -72,6 +75,24 @@ 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")
|
||||
|
||||
// ErrExactlyOneChildFacet is returned when not exactly one of the child create facets is set
|
||||
ErrExactlyOneChildFacet = errorcode.New(errorcode.InvalidRequest, "exactly one of folder or file must be set")
|
||||
|
||||
// ErrInvalidConflictBehavior is returned when the conflictBehavior query parameter is invalid
|
||||
ErrInvalidConflictBehavior = errorcode.New(errorcode.InvalidRequest, "invalid @libre.graph.conflictBehavior")
|
||||
|
||||
// ErrInvalidMissingParentsBehavior is returned when the missingParentsBehavior query parameter is invalid
|
||||
ErrInvalidMissingParentsBehavior = errorcode.New(errorcode.InvalidRequest, "invalid @libre.graph.missingParentsBehavior")
|
||||
|
||||
// ErrPathNotFound is returned when a parent path cannot be resolved
|
||||
ErrPathNotFound = errorcode.New(errorcode.ItemNotFound, "path not found")
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -80,6 +101,12 @@ 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)
|
||||
|
||||
// ResolvePath resolves relPath below parentID to its id, creating missing folders when createMissing is set
|
||||
ResolvePath(ctx context.Context, parentID *storageprovider.ResourceId, relPath string, createMissing bool) (*storageprovider.ResourceId, error)
|
||||
|
||||
// MountShare mounts a share
|
||||
MountShare(ctx context.Context, resourceID *storageprovider.ResourceId, name string) ([]*collaboration.ReceivedShare, error)
|
||||
|
||||
@@ -330,20 +357,139 @@ func (s DrivesDriveItemService) MountShare(ctx context.Context, resourceID *stor
|
||||
return updatedShares, nil
|
||||
}
|
||||
|
||||
// CreateChild creates a folder or empty file below parent; with replace, an existing same-named child 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()
|
||||
if replace && hasErrorCode(err, 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
|
||||
}
|
||||
|
||||
// ResolvePath resolves relPath below parentID to its id; with createMissing the
|
||||
// missing folders along the path are created first. It backs colon-path POST
|
||||
// .../children requests and @libre.graph.missingParentsBehavior.
|
||||
func (s DrivesDriveItemService) ResolvePath(ctx context.Context, parentID *storageprovider.ResourceId, relPath string, createMissing bool) (*storageprovider.ResourceId, error) {
|
||||
segments := strings.Split(strings.Trim(relPath, "/"), "/")
|
||||
for _, segment := range segments {
|
||||
if segment == "" || segment == "." || segment == ".." {
|
||||
return nil, ErrInvalidItemName
|
||||
}
|
||||
}
|
||||
|
||||
gatewayClient, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stat := func() (*storageprovider.ResourceId, error) {
|
||||
res, err := gatewayClient.Stat(ctx, &storageprovider.StatRequest{
|
||||
Ref: &storageprovider.Reference{
|
||||
ResourceId: parentID,
|
||||
Path: utils.MakeRelativePath(relPath),
|
||||
},
|
||||
})
|
||||
if err := errorcode.FromCS3Status(res.GetStatus(), err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.GetInfo().GetId(), nil
|
||||
}
|
||||
|
||||
// common case: the full path exists already
|
||||
id, err := stat()
|
||||
switch {
|
||||
case err == nil:
|
||||
return id, nil
|
||||
case !hasErrorCode(err, errorcode.ItemNotFound) && !hasErrorCode(err, errorcode.AccessDenied):
|
||||
return nil, err
|
||||
case !createMissing:
|
||||
// denied collapses to not-found, matching the middleware's colon
|
||||
// lookups (no existence disclosure)
|
||||
return nil, ErrPathNotFound
|
||||
}
|
||||
|
||||
var walked string
|
||||
for _, segment := range segments {
|
||||
walked += "/" + segment
|
||||
res, err := gatewayClient.CreateContainer(ctx, &storageprovider.CreateContainerRequest{
|
||||
Ref: &storageprovider.Reference{
|
||||
ResourceId: parentID,
|
||||
Path: utils.MakeRelativePath(walked),
|
||||
},
|
||||
})
|
||||
// existing folders along the path (or a lost creation race) are fine, they are reused
|
||||
if err := errorcode.FromCS3Status(res.GetStatus(), err); err != nil && !hasErrorCode(err, errorcode.NameAlreadyExists) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return stat()
|
||||
}
|
||||
|
||||
// hasErrorCode reports whether err is an errorcode.Error carrying the given code
|
||||
func hasErrorCode(err error, code errorcode.ErrorCode) bool {
|
||||
var lgErr errorcode.Error
|
||||
return errors.As(err, &lgErr) && lgErr.GetCode() == code
|
||||
}
|
||||
|
||||
// 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 +635,8 @@ func (api DrivesDriveItemApi) UpdateDriveItem(w http.ResponseWriter, r *http.Req
|
||||
render.JSON(w, r, driveItems[0])
|
||||
}
|
||||
|
||||
// CreateDriveItem creates a drive item
|
||||
// CreateDriveItem creates a driveItem at the drive root: a folder, empty file or 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 +644,136 @@ 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
|
||||
}
|
||||
|
||||
if !exactlyOne(requestDriveItem.Folder != nil, requestDriveItem.File != nil, requestDriveItem.RemoteItem != nil) {
|
||||
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.RemoteItem != nil || !exactlyOne(requestDriveItem.Folder != nil, requestDriveItem.File != nil) {
|
||||
api.logger.Debug().Msg(ErrExactlyOneChildFacet.Error())
|
||||
ErrExactlyOneChildFacet.Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
api.createChild(w, r, itemID, requestDriveItem)
|
||||
}
|
||||
|
||||
// exactlyOne reports whether exactly one of the given flags is set
|
||||
func exactlyOne(flags ...bool) bool {
|
||||
set := 0
|
||||
for _, flag := range flags {
|
||||
if flag {
|
||||
set++
|
||||
}
|
||||
}
|
||||
return set == 1
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// colon-path POST children requests arrive unresolved: parentID is the anchor
|
||||
// item, the parent path below it is resolved (or created) here, after validation
|
||||
if parentPath := graphm.ParentPath(r.Context()); parentPath != "" {
|
||||
var createMissing bool
|
||||
switch r.URL.Query().Get("@libre.graph.missingParentsBehavior") {
|
||||
case "", "fail":
|
||||
case "create":
|
||||
createMissing = true
|
||||
default:
|
||||
api.logger.Debug().Msg(ErrInvalidMissingParentsBehavior.Error())
|
||||
ErrInvalidMissingParentsBehavior.Render(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
parentID, err = api.drivesDriveItemService.ResolvePath(r.Context(), parentID, parentPath, createMissing)
|
||||
if err != nil {
|
||||
api.logger.Debug().Err(err).Msg("resolving the parent path failed")
|
||||
errorcode.RenderError(w, r, err)
|
||||
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.StatusCreated)
|
||||
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,8 +7,10 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
collaborationv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/go-chi/chi/v5"
|
||||
@@ -27,6 +29,7 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/mocks"
|
||||
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
|
||||
graphm "github.com/opencloud-eu/opencloud/services/graph/pkg/middleware"
|
||||
svc "github.com/opencloud-eu/opencloud/services/graph/pkg/service/v0"
|
||||
)
|
||||
|
||||
@@ -42,7 +45,7 @@ var _ = Describe("DrivesDriveItemService", func() {
|
||||
gatewayClient = cs3mocks.NewGatewayAPIClient(GinkgoT())
|
||||
|
||||
gatewaySelector = mocks.NewSelectable[gateway.GatewayAPIClient](GinkgoT())
|
||||
gatewaySelector.EXPECT().Next().Return(gatewayClient, nil)
|
||||
gatewaySelector.EXPECT().Next().Return(gatewayClient, nil).Maybe()
|
||||
|
||||
service, err := svc.NewDrivesDriveItemService(logger, gatewaySelector)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
@@ -365,7 +368,6 @@ var _ = Describe("DrivesDriveItemService", func() {
|
||||
|
||||
var _ = Describe("MountShare", func() {
|
||||
It("fails if name is interpreted as absolute path", func() {
|
||||
_, _ = gatewaySelector.Next() // make mockery call count happy
|
||||
_, err := drivesDriveItemService.MountShare(context.Background(), nil, "/some")
|
||||
Expect(err).To(MatchError(svc.ErrAbsoluteNamePath))
|
||||
})
|
||||
@@ -478,6 +480,216 @@ 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() {
|
||||
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("ResolvePath", func() {
|
||||
parentID := &storageprovider.ResourceId{StorageId: "1", SpaceId: "2", OpaqueId: "2"}
|
||||
pathID := &storageprovider.ResourceId{StorageId: "1", SpaceId: "2", OpaqueId: "3"}
|
||||
|
||||
statResponse := func(code *cs3rpc.Status) *storageprovider.StatResponse {
|
||||
res := &storageprovider.StatResponse{Status: code}
|
||||
if code.GetCode() == cs3rpc.Code_CODE_OK {
|
||||
res.Info = &storageprovider.ResourceInfo{Id: pathID}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
It("rejects invalid path segments", func() {
|
||||
for _, relPath := range []string{"", ".", "..", "a/../b", "a//b"} {
|
||||
_, err := drivesDriveItemService.ResolvePath(context.Background(), parentID, relPath, false)
|
||||
Expect(err).To(MatchError(svc.ErrInvalidItemName))
|
||||
}
|
||||
})
|
||||
|
||||
It("returns the id without creating anything when the path exists", func() {
|
||||
gatewayClient.
|
||||
EXPECT().
|
||||
Stat(mock.Anything, mock.Anything, mock.Anything).
|
||||
RunAndReturn(func(ctx context.Context, request *storageprovider.StatRequest, _ ...grpc.CallOption) (*storageprovider.StatResponse, error) {
|
||||
Expect(request.GetRef().GetPath()).To(Equal("./a/b"))
|
||||
return statResponse(status.NewOK(ctx)), nil
|
||||
}).
|
||||
Once()
|
||||
|
||||
id, err := drivesDriveItemService.ResolvePath(context.Background(), parentID, "/a/b", true)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(id.GetOpaqueId()).To(Equal("3"))
|
||||
})
|
||||
|
||||
It("returns ErrPathNotFound for a missing path without createMissing", func() {
|
||||
gatewayClient.
|
||||
EXPECT().
|
||||
Stat(mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(statResponse(status.NewNotFound(context.Background(), "missing")), nil).
|
||||
Once()
|
||||
|
||||
_, err := drivesDriveItemService.ResolvePath(context.Background(), parentID, "/a/b", false)
|
||||
Expect(err).To(MatchError(svc.ErrPathNotFound))
|
||||
})
|
||||
|
||||
It("returns ErrPathNotFound for a denied path without createMissing", func() {
|
||||
gatewayClient.
|
||||
EXPECT().
|
||||
Stat(mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(statResponse(status.NewPermissionDenied(context.Background(), nil, "denied")), nil).
|
||||
Once()
|
||||
|
||||
_, err := drivesDriveItemService.ResolvePath(context.Background(), parentID, "/a/b", false)
|
||||
Expect(err).To(MatchError(svc.ErrPathNotFound))
|
||||
})
|
||||
|
||||
It("creates the folders along a missing path, reusing existing ones", func() {
|
||||
gatewayClient.
|
||||
EXPECT().
|
||||
Stat(mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(statResponse(status.NewNotFound(context.Background(), "missing")), nil).
|
||||
Once()
|
||||
gatewayClient.
|
||||
EXPECT().
|
||||
CreateContainer(mock.Anything, mock.Anything, mock.Anything).
|
||||
RunAndReturn(func(ctx context.Context, request *storageprovider.CreateContainerRequest, _ ...grpc.CallOption) (*storageprovider.CreateContainerResponse, error) {
|
||||
switch request.GetRef().GetPath() {
|
||||
case "./a":
|
||||
// exists already, reused
|
||||
return &storageprovider.CreateContainerResponse{Status: status.NewAlreadyExists(ctx, nil, "exists")}, nil
|
||||
case "./a/b":
|
||||
return &storageprovider.CreateContainerResponse{Status: status.NewOK(ctx)}, nil
|
||||
default:
|
||||
Fail("unexpected CreateContainer path " + request.GetRef().GetPath())
|
||||
return nil, nil
|
||||
}
|
||||
}).
|
||||
Times(2)
|
||||
gatewayClient.
|
||||
EXPECT().
|
||||
Stat(mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(statResponse(status.NewOK(context.Background())), nil).
|
||||
Once()
|
||||
|
||||
id, err := drivesDriveItemService.ResolvePath(context.Background(), parentID, "/a/b", true)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(id.GetOpaqueId()).To(Equal("3"))
|
||||
})
|
||||
|
||||
It("fails when folder creation is denied", func() {
|
||||
gatewayClient.
|
||||
EXPECT().
|
||||
Stat(mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(statResponse(status.NewNotFound(context.Background(), "missing")), nil).
|
||||
Once()
|
||||
gatewayClient.
|
||||
EXPECT().
|
||||
CreateContainer(mock.Anything, mock.Anything, mock.Anything).
|
||||
Return(&storageprovider.CreateContainerResponse{Status: status.NewPermissionDenied(context.Background(), nil, "denied")}, nil).
|
||||
Once()
|
||||
|
||||
_, err := drivesDriveItemService.ResolvePath(context.Background(), parentID, "/a", true)
|
||||
var lgErr errorcode.Error
|
||||
Expect(errors.As(err, &lgErr)).To(BeTrue())
|
||||
Expect(lgErr.GetCode()).To(Equal(errorcode.AccessDenied))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("DrivesDriveItemApi", func() {
|
||||
@@ -494,7 +706,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 +1251,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 +1443,271 @@ 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.StatusCreated))
|
||||
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.StatusCreated))
|
||||
})
|
||||
})
|
||||
|
||||
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.ErrExactlyOneChildFacet.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.StatusCreated))
|
||||
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"))
|
||||
})
|
||||
|
||||
It("resolves the parent path from the context first", func() {
|
||||
rCTX.URLParams.Add("driveID", "1$2")
|
||||
rCTX.URLParams.Add("itemID", "1$2!2")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
driveItemJson, err := json.Marshal(libregraph.DriveItem{
|
||||
Name: conversions.ToPointer("New Folder"),
|
||||
Folder: &libregraph.Folder{},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
parentID := &storageprovider.ResourceId{StorageId: "1", SpaceId: "2", OpaqueId: "3"}
|
||||
drivesDriveItemProvider.
|
||||
EXPECT().
|
||||
ResolvePath(mock.Anything, mock.MatchedBy(func(id *storageprovider.ResourceId) bool {
|
||||
return id.GetOpaqueId() == "2"
|
||||
}), "/a/b", true).
|
||||
Return(parentID, nil).
|
||||
Once()
|
||||
drivesDriveItemProvider.
|
||||
EXPECT().
|
||||
CreateChild(mock.Anything, parentID, "New Folder", true, false).
|
||||
Return(&storageprovider.ResourceInfo{
|
||||
Id: &storageprovider.ResourceId{StorageId: "1", SpaceId: "2", OpaqueId: "4"},
|
||||
Path: "./New Folder",
|
||||
Type: storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
|
||||
}, nil).
|
||||
Once()
|
||||
|
||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, rCTX)
|
||||
ctx = context.WithValue(ctx, graphm.ParentPathContextKey, "/a/b")
|
||||
r := httptest.NewRequest(http.MethodPost, "/?%40libre.graph.missingParentsBehavior=create", bytes.NewBuffer(driveItemJson)).WithContext(ctx)
|
||||
|
||||
drivesDriveItemApi.CreateChildDriveItem(w, r)
|
||||
Expect(w.Code).To(Equal(http.StatusCreated))
|
||||
})
|
||||
|
||||
It("fails on an invalid missingParentsBehavior without calling the provider", func() {
|
||||
rCTX.URLParams.Add("driveID", "1$2")
|
||||
rCTX.URLParams.Add("itemID", "1$2!2")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
driveItemJson, err := json.Marshal(libregraph.DriveItem{
|
||||
Name: conversions.ToPointer("New Folder"),
|
||||
Folder: &libregraph.Folder{},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, rCTX)
|
||||
ctx = context.WithValue(ctx, graphm.ParentPathContextKey, "/a/b")
|
||||
r := httptest.NewRequest(http.MethodPost, "/?%40libre.graph.missingParentsBehavior=maybe", bytes.NewBuffer(driveItemJson)).WithContext(ctx)
|
||||
|
||||
// no ResolvePath / CreateChild expectations: the provider must not be called
|
||||
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.ErrInvalidMissingParentsBehavior.Error()))
|
||||
})
|
||||
|
||||
It("does not resolve parent folders when the request is invalid", func() {
|
||||
rCTX.URLParams.Add("driveID", "1$2")
|
||||
rCTX.URLParams.Add("itemID", "1$2!2")
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
driveItemJson, err := json.Marshal(libregraph.DriveItem{
|
||||
Name: conversions.ToPointer("New Folder"),
|
||||
Folder: &libregraph.Folder{},
|
||||
})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
ctx := context.WithValue(context.Background(), chi.RouteCtxKey, rCTX)
|
||||
ctx = context.WithValue(ctx, graphm.ParentPathContextKey, "/a/b")
|
||||
r := httptest.NewRequest(http.MethodPost, "/?%40libre.graph.conflictBehavior=rename", bytes.NewBuffer(driveItemJson)).WithContext(ctx)
|
||||
|
||||
// no ResolvePath / CreateChild expectations: the provider must not be called
|
||||
drivesDriveItemApi.CreateChildDriveItem(w, r)
|
||||
Expect(w.Code).To(Equal(http.StatusBadRequest))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -160,7 +160,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
|
||||
}
|
||||
@@ -271,6 +271,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