github.com/cs3org/reva/v2@v2.27.7/internal/http/services/owncloud/ocdav/ocdav_blackbox_test.go (about)

     1  // Copyright 2021 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //	http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  package ocdav_test
    19  
    20  import (
    21  	"context"
    22  	"errors"
    23  	"fmt"
    24  	"net/http"
    25  	"net/http/httptest"
    26  	"path"
    27  	"strings"
    28  
    29  	cs3gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
    30  	gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
    31  	cs3user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    32  	cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
    33  	link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1"
    34  	cs3storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    35  	cs3types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    36  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav"
    37  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/config"
    38  	"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net"
    39  	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
    40  	"github.com/cs3org/reva/v2/pkg/rgrpc/status"
    41  	"github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool"
    42  	"github.com/cs3org/reva/v2/pkg/rhttp/global"
    43  	"github.com/cs3org/reva/v2/pkg/storagespace"
    44  	"github.com/cs3org/reva/v2/pkg/utils"
    45  	"github.com/cs3org/reva/v2/tests/cs3mocks/mocks"
    46  	"github.com/stretchr/testify/mock"
    47  	"google.golang.org/grpc"
    48  
    49  	. "github.com/onsi/ginkgo/v2"
    50  	. "github.com/onsi/gomega"
    51  )
    52  
    53  type selector struct {
    54  	client gateway.GatewayAPIClient
    55  }
    56  
    57  func (s selector) Next(opts ...pool.Option) (gateway.GatewayAPIClient, error) {
    58  	return s.client, nil
    59  }
    60  
    61  // TODO for now we have to test all of ocdav. when this testsuite is complete we can move
    62  // the handlers to dedicated packages to reduce the amount of complexity to get a test environment up
    63  var _ = Describe("ocdav", func() {
    64  	var (
    65  		handler global.Service
    66  		client  *mocks.GatewayAPIClient
    67  		ctx     context.Context
    68  
    69  		userspace *cs3storageprovider.StorageSpace
    70  		user      *cs3user.User
    71  
    72  		dataSvr *httptest.Server
    73  		rr      *httptest.ResponseRecorder
    74  		req     *http.Request
    75  		err     error
    76  
    77  		basePath string
    78  
    79  		// mockPathStat is used to by path based endpoints
    80  		mockPathStat = func(path string, s *cs3rpc.Status, info *cs3storageprovider.ResourceInfo) {
    81  			client.On("Stat", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.StatRequest) bool {
    82  				return req.Ref.Path == path
    83  			})).Return(&cs3storageprovider.StatResponse{
    84  				Status: s,
    85  				Info:   info,
    86  			}, nil)
    87  		}
    88  		mockStat = func(ref *cs3storageprovider.Reference, s *cs3rpc.Status, info *cs3storageprovider.ResourceInfo) {
    89  			client.On("Stat", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.StatRequest) bool {
    90  				return utils.ResourceIDEqual(req.Ref.ResourceId, ref.ResourceId) &&
    91  					(ref.Path == "" || req.Ref.Path == ref.Path)
    92  			})).Return(&cs3storageprovider.StatResponse{
    93  				Status: s,
    94  				Info:   info,
    95  			}, nil)
    96  		}
    97  		mockStatOK = func(ref *cs3storageprovider.Reference, info *cs3storageprovider.ResourceInfo) {
    98  			mockStat(ref, status.NewOK(ctx), info)
    99  		}
   100  		// two mock helpers to build references and resource infos in the userspace of provider-1
   101  		mockReference = func(id, path string) *cs3storageprovider.Reference {
   102  			return &cs3storageprovider.Reference{
   103  				ResourceId: &cs3storageprovider.ResourceId{
   104  					StorageId: "provider-1",
   105  					SpaceId:   "userspace",
   106  					OpaqueId:  id,
   107  				},
   108  				Path: path,
   109  			}
   110  		}
   111  		mockInfo = func(m map[string]interface{}) *cs3storageprovider.ResourceInfo {
   112  
   113  			if _, ok := m["storageid"]; !ok {
   114  				m["storageid"] = "provider-1"
   115  			}
   116  			if _, ok := m["spaceid"]; !ok {
   117  				m["spaceid"] = "userspace"
   118  			}
   119  			if _, ok := m["opaqueid"]; !ok {
   120  				m["opaqueid"] = "root"
   121  			}
   122  			if _, ok := m["type"]; !ok {
   123  				m["type"] = cs3storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER
   124  			}
   125  			if _, ok := m["size"]; !ok {
   126  				m["size"] = uint64(0)
   127  			}
   128  
   129  			return &cs3storageprovider.ResourceInfo{
   130  				Id: &cs3storageprovider.ResourceId{
   131  					StorageId: m["storageid"].(string),
   132  					SpaceId:   m["spaceid"].(string),
   133  					OpaqueId:  m["opaqueid"].(string),
   134  				},
   135  				Type: m["type"].(cs3storageprovider.ResourceType),
   136  				Size: m["size"].(uint64),
   137  			}
   138  		}
   139  		mReq *cs3storageprovider.MoveRequest
   140  	)
   141  
   142  	BeforeEach(func() {
   143  		user = &cs3user.User{Id: &cs3user.UserId{OpaqueId: "username"}, Username: "username"}
   144  
   145  		dataSvr = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   146  			w.WriteHeader(http.StatusCreated)
   147  		}))
   148  
   149  		ctx = ctxpkg.ContextSetUser(context.Background(), user)
   150  		client = &mocks.GatewayAPIClient{}
   151  
   152  		cfg := &config.Config{
   153  			FilesNamespace:  "/users/{{.Username}}",
   154  			WebdavNamespace: "/users/{{.Username}}",
   155  			NameValidation: config.NameValidation{
   156  				MaxLength:    255,
   157  				InvalidChars: []string{"\f", "\r", "\n", "\\"},
   158  			},
   159  		}
   160  		sel := selector{
   161  			client: client,
   162  		}
   163  		handler, err = ocdav.NewWith(cfg, nil, ocdav.NewCS3LS(sel), nil, sel)
   164  		Expect(err).ToNot(HaveOccurred())
   165  
   166  		userspace = &cs3storageprovider.StorageSpace{
   167  			Opaque: &cs3types.Opaque{
   168  				Map: map[string]*cs3types.OpaqueEntry{
   169  					"path": {
   170  						Decoder: "plain",
   171  						Value:   []byte("/users/username/"),
   172  					},
   173  				},
   174  			},
   175  			Id:   &cs3storageprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&cs3storageprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"})},
   176  			Root: &cs3storageprovider.ResourceId{StorageId: "provider-1", SpaceId: "userspace", OpaqueId: "root"},
   177  			Name: "username",
   178  			RootInfo: &cs3storageprovider.ResourceInfo{
   179  				Name: "username",
   180  				Path: "/users/username",
   181  			},
   182  		}
   183  
   184  		client.On("GetPublicShare", mock.Anything, mock.Anything).Return(&link.GetPublicShareResponse{
   185  			Status: status.NewNotFound(ctx, "not found")},
   186  			nil)
   187  		client.On("GetUser", mock.Anything, mock.Anything).Return(&cs3user.GetUserResponse{
   188  			Status: status.NewNotFound(ctx, "not found"),
   189  		}, nil)
   190  		client.On("GetUserByClaim", mock.Anything, mock.Anything).Return(&cs3user.GetUserByClaimResponse{
   191  			Status: status.NewNotFound(ctx, "not found"),
   192  		}, nil)
   193  
   194  		// for public access
   195  		client.On("Authenticate", mock.Anything, mock.MatchedBy(func(req *cs3gateway.AuthenticateRequest) bool {
   196  			return req.Type == "publicshares" &&
   197  				strings.HasPrefix(req.ClientId, "tokenfor") &&
   198  				strings.HasPrefix(req.ClientSecret, "signature||")
   199  		})).Return(&cs3gateway.AuthenticateResponse{
   200  			Status: status.NewOK(ctx),
   201  			User:   user,
   202  			Token:  "jwt",
   203  		}, nil)
   204  		client.On("Stat", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.StatRequest) bool {
   205  			return req.Ref.ResourceId.StorageId == utils.PublicStorageProviderID &&
   206  				req.Ref.ResourceId.SpaceId == utils.PublicStorageSpaceID &&
   207  				req.Ref.ResourceId.OpaqueId == "tokenforfile"
   208  		})).Return(&cs3storageprovider.StatResponse{
   209  			Status: status.NewOK(ctx),
   210  			Info: &cs3storageprovider.ResourceInfo{
   211  				Type: cs3storageprovider.ResourceType_RESOURCE_TYPE_FILE,
   212  			},
   213  		}, nil)
   214  		client.On("Stat", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.StatRequest) bool {
   215  			return req.Ref.ResourceId.StorageId == utils.PublicStorageProviderID &&
   216  				req.Ref.ResourceId.SpaceId == utils.PublicStorageSpaceID &&
   217  				req.Ref.ResourceId.OpaqueId == "tokenforfolder"
   218  		})).Return(&cs3storageprovider.StatResponse{
   219  			Status: status.NewOK(ctx),
   220  			Info: &cs3storageprovider.ResourceInfo{
   221  				Type: cs3storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
   222  			},
   223  		}, nil)
   224  	})
   225  	AfterEach(func() {
   226  		dataSvr.Close()
   227  	})
   228  
   229  	Describe("NewHandler", func() {
   230  		It("returns a handler", func() {
   231  			Expect(handler).ToNot(BeNil())
   232  		})
   233  	})
   234  
   235  	// TODO for every endpoint test the different WebDAV Methods
   236  
   237  	// basic metadata
   238  	// PROPFIND
   239  	// MKCOL
   240  	// DELETE
   241  
   242  	// basic data
   243  	// PUT
   244  	// GET
   245  	// HEAD
   246  
   247  	// move & copy
   248  	// MOVE
   249  	// COPY
   250  
   251  	// additional methods
   252  	// PROPPATCH
   253  	// LOCK
   254  	// UNLOCK
   255  	// REPORT
   256  	// POST (Tus)
   257  	// OPTIONS?
   258  
   259  	Context("at the very legacy /webdav endpoint", func() {
   260  
   261  		BeforeEach(func() {
   262  			// set the webdav endpoint to test
   263  			basePath = "/webdav"
   264  
   265  			// path based requests at the /webdav endpoint first look up the storage space
   266  			client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.ListStorageSpacesRequest) bool {
   267  				p := string(req.Opaque.Map["path"].Value)
   268  				return p == "/" || strings.HasPrefix(p, "/users")
   269  			})).Return(&cs3storageprovider.ListStorageSpacesResponse{
   270  				Status:        status.NewOK(ctx),
   271  				StorageSpaces: []*cs3storageprovider.StorageSpace{userspace},
   272  			}, nil)
   273  		})
   274  
   275  		Describe("PROPFIND to root", func() {
   276  
   277  			BeforeEach(func() {
   278  				// setup the request
   279  				rr = httptest.NewRecorder()
   280  				req, err = http.NewRequest("PROPFIND", basePath, strings.NewReader(""))
   281  				Expect(err).ToNot(HaveOccurred())
   282  				req = req.WithContext(ctx)
   283  
   284  			})
   285  			When("the gateway returns a file list", func() {
   286  				It("returns a multistatus with the file info", func() {
   287  
   288  					// the ocdav handler uses the space.rootinfo so we don't need to mock stat here
   289  
   290  					handler.Handler().ServeHTTP(rr, req)
   291  					Expect(rr).To(HaveHTTPStatus(http.StatusMultiStatus))
   292  					Expect(rr).To(HaveHTTPBody(Not(BeEmpty())), "Body must not be empty")
   293  					// TODO test listing more thoroughly
   294  				})
   295  
   296  			})
   297  			// TODO test when list storage space returns not found
   298  			// TODO test when list storage space dos not have a root info
   299  
   300  		})
   301  		Describe("PROPFIND to a file", func() {
   302  
   303  			BeforeEach(func() {
   304  				// set the webdav endpoint to test
   305  				basePath = "/webdav/file"
   306  
   307  				// setup the request
   308  				rr = httptest.NewRecorder()
   309  				req, err = http.NewRequest("PROPFIND", basePath, strings.NewReader(""))
   310  				Expect(err).ToNot(HaveOccurred())
   311  				req = req.WithContext(ctx)
   312  
   313  			})
   314  
   315  			When("the gateway returns the file info", func() {
   316  				It("returns a multistatus with the file properties", func() {
   317  
   318  					mockStatOK(mockReference("root", "./file"), mockInfo(map[string]interface{}{"opaqueid": "file", "type": cs3storageprovider.ResourceType_RESOURCE_TYPE_FILE, "size": uint64(123)}))
   319  
   320  					handler.Handler().ServeHTTP(rr, req)
   321  					Expect(rr).To(HaveHTTPStatus(http.StatusMultiStatus))
   322  					Expect(rr).To(HaveHTTPBody(
   323  						And(
   324  							ContainSubstring("<d:href>%s</d:href>", basePath),
   325  							ContainSubstring("<d:getcontentlength>123</d:getcontentlength>"))),
   326  						"Body must contain resource href and properties")
   327  					// TODO test properties more thoroughly
   328  				})
   329  
   330  			})
   331  
   332  			When("the gateway returns not found", func() {
   333  				It("returns a not found status", func() {
   334  
   335  					mockStat(mockReference("root", "./file"), status.NewNotFound(ctx, "not found"), nil)
   336  
   337  					handler.Handler().ServeHTTP(rr, req)
   338  					Expect(rr).To(HaveHTTPStatus(http.StatusNotFound))
   339  					Expect(rr).To(HaveHTTPBody(
   340  						And(
   341  							ContainSubstring("<s:exception>Sabre\\DAV\\Exception\\NotFound</s:exception>"),
   342  							ContainSubstring("<s:message>Resource not found</s:message>"))),
   343  						"Body must contain sabredav exception and message")
   344  				})
   345  			})
   346  		})
   347  
   348  		Describe("MKCOL", func() {
   349  
   350  			BeforeEach(func() {
   351  				// setup the request
   352  				rr = httptest.NewRecorder()
   353  				req, err = http.NewRequest("MKCOL", basePath+"/subfolder/newfolder", strings.NewReader(""))
   354  				Expect(err).ToNot(HaveOccurred())
   355  				req = req.WithContext(ctx)
   356  
   357  			})
   358  
   359  			When("the gateway returns OK", func() {
   360  				It("returns a created status", func() {
   361  
   362  					// MKCOL needs to check if the resource already exists to return the correct status
   363  					mockPathStat("/users/username/subfolder/newfolder", status.NewNotFound(ctx, "not found"), nil)
   364  
   365  					client.On("CreateContainer", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.CreateContainerRequest) bool {
   366  						return utils.ResourceEqual(req.Ref, &cs3storageprovider.Reference{
   367  							ResourceId: userspace.Root,
   368  							Path:       "./subfolder/newfolder",
   369  						})
   370  					})).Return(&cs3storageprovider.CreateContainerResponse{
   371  						Status: status.NewOK(ctx),
   372  					}, nil)
   373  
   374  					handler.Handler().ServeHTTP(rr, req)
   375  					Expect(rr).To(HaveHTTPStatus(http.StatusCreated))
   376  					Expect(rr).To(HaveHTTPBody(BeEmpty()), "Body must be empty")
   377  					// TODO expect fileid and etag header?
   378  				})
   379  
   380  			})
   381  
   382  			When("the gateway aborts the stat", func() {
   383  				// eg when an if match etag header was sent and mismatches
   384  				// TODO send lock id
   385  				It("returns a precondition failed status", func() {
   386  
   387  					// MKCOL needs to check if the resource already exists to return the correct status
   388  					// TODO check the etag is forwarded to make the request conditional
   389  					// TODO should be part of the CS3 api?
   390  					mockPathStat("/users/username/subfolder/newfolder", status.NewAborted(ctx, errors.New("etag mismatch"), "etag mismatch"), nil)
   391  
   392  					handler.Handler().ServeHTTP(rr, req)
   393  					Expect(rr).To(HaveHTTPStatus(http.StatusPreconditionFailed))
   394  
   395  					Expect(rr).To(HaveHTTPBody(
   396  						ContainSubstring("<s:exception>Sabre\\DAV\\Exception\\PreconditionFailed</s:exception>"),
   397  						// TODO what message does oc10 return? "error: aborted:" is probably not it
   398  						// ContainSubstring("<s:message>error: aborted: </s:message>"),
   399  					),
   400  						"Body must contain sabredav exception and message")
   401  
   402  				})
   403  			})
   404  
   405  			When("the resource already exists", func() {
   406  				It("returns a method not allowed status", func() {
   407  
   408  					// MKCOL needs to check if the resource already exists to return the correct status
   409  					mockPathStat("/users/username/subfolder/newfolder", status.NewOK(ctx), &cs3storageprovider.ResourceInfo{})
   410  
   411  					handler.Handler().ServeHTTP(rr, req)
   412  					Expect(rr).To(HaveHTTPStatus(http.StatusMethodNotAllowed))
   413  
   414  					Expect(rr).To(HaveHTTPBody(
   415  						And(
   416  							ContainSubstring("<s:exception>Sabre\\DAV\\Exception\\MethodNotAllowed</s:exception>"),
   417  							ContainSubstring("<s:message>The resource you tried to create already exists</s:message>"))),
   418  						"Body must contain sabredav exception and message")
   419  
   420  				})
   421  			})
   422  
   423  			When("an intermediate collection does not exists", func() {
   424  				It("returns a conflict status", func() {
   425  
   426  					// MKCOL needs to check if the resource already exists to return the correct status
   427  					mockPathStat("/users/username/subfolder/newfolder", status.NewNotFound(ctx, "not found"), nil)
   428  
   429  					client.On("CreateContainer", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.CreateContainerRequest) bool {
   430  						return utils.ResourceEqual(req.Ref, &cs3storageprovider.Reference{
   431  							ResourceId: userspace.Root,
   432  							Path:       "./subfolder/newfolder",
   433  						})
   434  					})).Return(&cs3storageprovider.CreateContainerResponse{
   435  						Status: status.NewFailedPrecondition(ctx, errors.New("parent does not exist"), "parent does not exist"),
   436  					}, nil)
   437  
   438  					handler.Handler().ServeHTTP(rr, req)
   439  					Expect(rr).To(HaveHTTPStatus(http.StatusConflict))
   440  
   441  					Expect(rr).To(HaveHTTPBody(
   442  						And(
   443  							ContainSubstring("<s:exception>Sabre\\DAV\\Exception\\Conflict</s:exception>"),
   444  							ContainSubstring("<s:message>parent does not exist</s:message>"))),
   445  						"Body must contain sabredav exception and message")
   446  
   447  				})
   448  			})
   449  		})
   450  
   451  		Describe("DELETE", func() {
   452  
   453  			BeforeEach(func() {
   454  				// setup the request
   455  				rr = httptest.NewRecorder()
   456  				req, err = http.NewRequest("DELETE", basePath+"/existingfolder", strings.NewReader(""))
   457  				Expect(err).ToNot(HaveOccurred())
   458  				req = req.WithContext(ctx)
   459  
   460  			})
   461  
   462  			When("the gateway returns OK", func() {
   463  				It("returns a no content status", func() {
   464  
   465  					client.On("Delete", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.DeleteRequest) bool {
   466  						return utils.ResourceEqual(req.Ref, &cs3storageprovider.Reference{
   467  							ResourceId: userspace.Root,
   468  							Path:       "./existingfolder",
   469  						})
   470  					})).Return(&cs3storageprovider.DeleteResponse{
   471  						Status: status.NewOK(ctx),
   472  					}, nil)
   473  
   474  					handler.Handler().ServeHTTP(rr, req)
   475  					Expect(rr).To(HaveHTTPStatus(http.StatusNoContent))
   476  					Expect(rr).To(HaveHTTPBody(BeEmpty()), "Body must be empty")
   477  					// TODO expect fileid and etag header?
   478  				})
   479  
   480  			})
   481  
   482  			When("the gateway returns not found", func() {
   483  				It("returns a method not found status", func() {
   484  
   485  					client.On("Delete", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.DeleteRequest) bool {
   486  						return utils.ResourceEqual(req.Ref, &cs3storageprovider.Reference{
   487  							ResourceId: userspace.Root,
   488  							Path:       "./existingfolder",
   489  						})
   490  					})).Return(&cs3storageprovider.DeleteResponse{
   491  						Status: status.NewNotFound(ctx, "not found"),
   492  					}, nil)
   493  
   494  					handler.Handler().ServeHTTP(rr, req)
   495  					Expect(rr).To(HaveHTTPStatus(http.StatusNotFound))
   496  					Expect(rr).To(HaveHTTPBody("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<d:error xmlns:d=\"DAV\" xmlns:s=\"http://sabredav.org/ns\"><s:exception>Sabre\\DAV\\Exception\\NotFound</s:exception><s:message>Resource not found</s:message><s:errorcode></s:errorcode></d:error>"), "Body must have a not found sabredav exception")
   497  
   498  				})
   499  			})
   500  		})
   501  
   502  		Describe("PUT", func() {
   503  
   504  			BeforeEach(func() {
   505  				// setup the request
   506  				rr = httptest.NewRecorder()
   507  				req, err = http.NewRequest("PUT", basePath+"/newfile", strings.NewReader("new content"))
   508  				Expect(err).ToNot(HaveOccurred())
   509  				req.Header.Set(net.HeaderContentLength, "11")
   510  				req = req.WithContext(ctx)
   511  
   512  			})
   513  
   514  			When("the gateway returns OK", func() {
   515  				It("returns a created status", func() {
   516  
   517  					client.On("InitiateFileUpload", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.InitiateFileUploadRequest) bool {
   518  						return utils.ResourceEqual(req.Ref, &cs3storageprovider.Reference{
   519  							ResourceId: userspace.Root,
   520  							Path:       "./newfile",
   521  						})
   522  					})).Return(&cs3gateway.InitiateFileUploadResponse{
   523  						Status: status.NewOK(ctx),
   524  						Protocols: []*cs3gateway.FileUploadProtocol{
   525  							{
   526  								Protocol:       "simple",
   527  								UploadEndpoint: dataSvr.URL,
   528  							},
   529  						},
   530  					}, nil)
   531  
   532  					handler.Handler().ServeHTTP(rr, req)
   533  					Expect(rr).To(HaveHTTPStatus(http.StatusCreated))
   534  					Expect(rr).To(HaveHTTPBody(BeEmpty()), "Body must be empty")
   535  					// TODO expect fileid and etag header?
   536  				})
   537  			})
   538  
   539  			When("the gateway returns aborted", func() {
   540  				It("returns a precondition failed status", func() {
   541  
   542  					client.On("InitiateFileUpload", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.InitiateFileUploadRequest) bool {
   543  						return utils.ResourceEqual(req.Ref, &cs3storageprovider.Reference{
   544  							ResourceId: userspace.Root,
   545  							Path:       "./newfile",
   546  						})
   547  					})).Return(&cs3gateway.InitiateFileUploadResponse{
   548  						Status: status.NewAborted(ctx, errors.New("parent does not exist"), "parent does not exist"),
   549  					}, nil)
   550  
   551  					handler.Handler().ServeHTTP(rr, req)
   552  					Expect(rr).To(HaveHTTPStatus(http.StatusPreconditionFailed))
   553  					// TODO Expect(rr).To(HaveHTTPBody(BeEmpty()), "Body must be a sabredav exception")
   554  				})
   555  			})
   556  
   557  			When("the resource already exists", func() {
   558  				It("returns a conflict status", func() {
   559  
   560  					client.On("InitiateFileUpload", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.InitiateFileUploadRequest) bool {
   561  						return utils.ResourceEqual(req.Ref, &cs3storageprovider.Reference{
   562  							ResourceId: userspace.Root,
   563  							Path:       "./newfile",
   564  						})
   565  					})).Return(&cs3gateway.InitiateFileUploadResponse{
   566  						Status: status.NewFailedPrecondition(ctx, errors.New("precondition failed"), "precondition failed"),
   567  					}, nil)
   568  
   569  					client.On("Stat", mock.Anything, mock.Anything).Return(&cs3storageprovider.StatResponse{
   570  						Status: status.NewOK(ctx),
   571  						Info: &cs3storageprovider.ResourceInfo{
   572  							Type: cs3storageprovider.ResourceType_RESOURCE_TYPE_FILE,
   573  						},
   574  					}, nil)
   575  
   576  					handler.Handler().ServeHTTP(rr, req)
   577  					Expect(rr).To(HaveHTTPStatus(http.StatusConflict))
   578  					// TODO Expect(rr).To(HaveHTTPBody(BeEmpty()), "Body must be a sabredav exception")
   579  				})
   580  			})
   581  
   582  			When("the gateway returns not found", func() {
   583  				It("returns a not found", func() {
   584  
   585  					client.On("InitiateFileUpload", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.InitiateFileUploadRequest) bool {
   586  						return utils.ResourceEqual(req.Ref, &cs3storageprovider.Reference{
   587  							ResourceId: userspace.Root,
   588  							Path:       "./newfile",
   589  						})
   590  					})).Return(&cs3gateway.InitiateFileUploadResponse{
   591  						Status: status.NewNotFound(ctx, "not found"),
   592  					}, nil)
   593  
   594  					handler.Handler().ServeHTTP(rr, req)
   595  					Expect(rr).To(HaveHTTPStatus(http.StatusNotFound))
   596  					// TODO Expect(rr).To(HaveHTTPBody(BeEmpty()), "Body must be a sabredav exception")
   597  				})
   598  			})
   599  
   600  		})
   601  
   602  		Describe("MOVE", func() {
   603  
   604  			BeforeEach(func() {
   605  				// setup the request
   606  				rr = httptest.NewRecorder()
   607  				req, err = http.NewRequest("MOVE", basePath+"/file", strings.NewReader(""))
   608  				Expect(err).ToNot(HaveOccurred())
   609  				req = req.WithContext(ctx)
   610  				req.Header.Set(net.HeaderDestination, basePath+"/newfile")
   611  				req.Header.Set("Overwrite", "T")
   612  
   613  				mReq = &cs3storageprovider.MoveRequest{
   614  					Source: &cs3storageprovider.Reference{
   615  						ResourceId: userspace.Root,
   616  						Path:       "./file",
   617  					},
   618  					Destination: &cs3storageprovider.Reference{
   619  						ResourceId: userspace.Root,
   620  						Path:       "./newfile",
   621  					},
   622  				}
   623  			})
   624  
   625  			When("the gateway returns OK when moving file", func() {
   626  				It("the source exists, the destination doesn't exists", func() {
   627  
   628  					mockPathStat(mReq.Source.Path, status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: mReq.Source.ResourceId})
   629  					client.On("Stat", mock.Anything, mock.Anything).Return(&cs3storageprovider.StatResponse{
   630  						Status: status.NewNotFound(ctx, ""),
   631  						Info:   &cs3storageprovider.ResourceInfo{},
   632  					}, nil).Once()
   633  					mockPathStat(".", status.NewOK(ctx), nil)
   634  
   635  					client.On("Move", mock.Anything, mReq).Return(&cs3storageprovider.MoveResponse{
   636  						Status: status.NewOK(ctx),
   637  					}, nil)
   638  
   639  					mockPathStat(mReq.Destination.Path, status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: &cs3storageprovider.ResourceId{}})
   640  
   641  					handler.Handler().ServeHTTP(rr, req)
   642  					Expect(rr).To(HaveHTTPStatus(http.StatusCreated))
   643  				})
   644  
   645  				It("the source and the destination exist", func() {
   646  
   647  					mockPathStat(mReq.Source.Path, status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: mReq.Source.ResourceId})
   648  					mockPathStat(mReq.Destination.Path, status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: mReq.Destination.ResourceId})
   649  
   650  					client.On("Delete", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.DeleteRequest) bool {
   651  						return utils.ResourceEqual(req.Ref, mReq.Destination)
   652  					})).Return(&cs3storageprovider.DeleteResponse{
   653  						Status: status.NewOK(ctx),
   654  					}, nil)
   655  
   656  					client.On("Move", mock.Anything, mReq).Return(&cs3storageprovider.MoveResponse{
   657  						Status: status.NewOK(ctx),
   658  					}, nil)
   659  
   660  					mockPathStat(mReq.Destination.Path, status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: &cs3storageprovider.ResourceId{}})
   661  
   662  					handler.Handler().ServeHTTP(rr, req)
   663  					Expect(rr).To(HaveHTTPStatus(http.StatusNoContent))
   664  				})
   665  			})
   666  
   667  			When("the gateway returns error when moving file", func() {
   668  				It("the source Stat error", func() {
   669  
   670  					client.On("Stat", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.StatRequest) bool {
   671  						return utils.ResourceEqual(req.Ref, mReq.Source)
   672  					})).Return(nil, fmt.Errorf("unexpected io error"))
   673  
   674  					handler.Handler().ServeHTTP(rr, req)
   675  					Expect(rr).To(HaveHTTPStatus(http.StatusInternalServerError))
   676  				})
   677  
   678  				It("moves a file. the source not found", func() {
   679  
   680  					mockPathStat(mReq.Source.Path, status.NewNotFound(ctx, ""), nil)
   681  
   682  					handler.Handler().ServeHTTP(rr, req)
   683  					Expect(rr).To(HaveHTTPStatus(http.StatusNotFound))
   684  				})
   685  
   686  				It("the destination Stat error", func() {
   687  
   688  					mockPathStat(mReq.Source.Path, status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: mReq.Source.ResourceId})
   689  
   690  					client.On("Stat", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.StatRequest) bool {
   691  						return utils.ResourceEqual(req.Ref, mReq.Destination)
   692  					})).Return(nil, fmt.Errorf("unexpected io error"))
   693  
   694  					handler.Handler().ServeHTTP(rr, req)
   695  					Expect(rr).To(HaveHTTPStatus(http.StatusInternalServerError))
   696  				})
   697  
   698  				It("error when the 'Overwrite' header is 'F'", func() {
   699  
   700  					req.Header.Set("Overwrite", "F")
   701  
   702  					mockPathStat(mReq.Source.Path, status.NewOK(ctx), nil)
   703  					mockPathStat(mReq.Destination.Path, status.NewOK(ctx), nil)
   704  
   705  					handler.Handler().ServeHTTP(rr, req)
   706  					Expect(rr).To(HaveHTTPStatus(http.StatusBadRequest))
   707  				})
   708  
   709  				It("error when deleting an existing tree", func() {
   710  
   711  					mockPathStat(mReq.Source.Path, status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: mReq.Source.ResourceId, Path: "./file"})
   712  					mockPathStat(mReq.Destination.Path, status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: mReq.Destination.ResourceId, Path: "./newfile"})
   713  
   714  					client.On("Delete", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.DeleteRequest) bool {
   715  						return utils.ResourceEqual(req.Ref, mReq.Destination)
   716  					})).Return(nil, fmt.Errorf("unexpected io error"))
   717  
   718  					handler.Handler().ServeHTTP(rr, req)
   719  					Expect(rr).To(HaveHTTPStatus(http.StatusInternalServerError))
   720  				})
   721  
   722  				It("error when destination Stat returns unexpected code", func() {
   723  
   724  					mockPathStat(mReq.Source.Path, status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: mReq.Source.ResourceId})
   725  					mockPathStat(mReq.Destination.Path, status.NewInternal(ctx, ""), nil)
   726  
   727  					handler.Handler().ServeHTTP(rr, req)
   728  					Expect(rr).To(HaveHTTPStatus(http.StatusInternalServerError))
   729  				})
   730  
   731  				It("error when Delete returns unexpected code", func() {
   732  
   733  					mockPathStat(mReq.Source.Path, status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: mReq.Source.ResourceId, Path: "./file"})
   734  					mockPathStat(mReq.Destination.Path, status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: mReq.Destination.ResourceId, Path: "./newfile"})
   735  
   736  					client.On("Delete", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.DeleteRequest) bool {
   737  						return utils.ResourceEqual(req.Ref, mReq.Destination)
   738  					})).Return(&cs3storageprovider.DeleteResponse{
   739  						Status: status.NewInvalid(ctx, ""),
   740  					}, nil)
   741  					handler.Handler().ServeHTTP(rr, req)
   742  					Expect(rr).To(HaveHTTPStatus(http.StatusBadRequest))
   743  				})
   744  
   745  				It("the destination Stat error", func() {
   746  
   747  					mockPathStat(mReq.Source.Path, status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: mReq.Source.ResourceId})
   748  					mockPathStat(mReq.Destination.Path, status.NewNotFound(ctx, ""), nil)
   749  					client.On("Stat", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.StatRequest) bool {
   750  						return utils.ResourceEqual(req.Ref, &cs3storageprovider.Reference{
   751  							ResourceId: userspace.Root,
   752  							Path:       ".",
   753  						})
   754  					})).Return(nil, fmt.Errorf("unexpected io error")).Once()
   755  
   756  					handler.Handler().ServeHTTP(rr, req)
   757  					Expect(rr).To(HaveHTTPStatus(http.StatusInternalServerError))
   758  				})
   759  
   760  				It("error when destination Stat is not found", func() {
   761  
   762  					mockPathStat(mReq.Source.Path, status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: mReq.Source.ResourceId})
   763  					mockPathStat(mReq.Destination.Path, status.NewNotFound(ctx, ""), nil)
   764  					mockPathStat(".", status.NewNotFound(ctx, ""), nil)
   765  
   766  					handler.Handler().ServeHTTP(rr, req)
   767  					Expect(rr).To(HaveHTTPStatus(http.StatusConflict))
   768  				})
   769  
   770  				It("an unexpected error when destination Stat", func() {
   771  
   772  					mockPathStat(mReq.Source.Path, status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: mReq.Source.ResourceId})
   773  					mockPathStat(mReq.Destination.Path, status.NewNotFound(ctx, ""), nil)
   774  					mockPathStat(".", status.NewInvalid(ctx, ""), nil)
   775  
   776  					handler.Handler().ServeHTTP(rr, req)
   777  					Expect(rr).To(HaveHTTPStatus(http.StatusBadRequest))
   778  				})
   779  
   780  				It("error when removing", func() {
   781  
   782  					mockPathStat(mReq.Source.Path, status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: mReq.Source.ResourceId})
   783  					mockPathStat(mReq.Destination.Path, status.NewNotFound(ctx, ""), nil)
   784  					mockPathStat(".", status.NewOK(ctx), nil)
   785  					client.On("Move", mock.Anything, mReq).Return(nil, fmt.Errorf("unexpected io error"))
   786  
   787  					handler.Handler().ServeHTTP(rr, req)
   788  					Expect(rr).To(HaveHTTPStatus(http.StatusInternalServerError))
   789  				})
   790  
   791  				It("status 'Aborted' when removing", func() {
   792  
   793  					mockPathStat(mReq.Source.Path, status.NewOK(ctx), nil)
   794  					mockPathStat(mReq.Destination.Path, status.NewNotFound(ctx, ""), nil)
   795  					mockPathStat(".", status.NewOK(ctx), nil)
   796  
   797  					client.On("Move", mock.Anything, mReq).Return(&cs3storageprovider.MoveResponse{
   798  						Status: status.NewAborted(ctx, fmt.Errorf("aborted"), ""),
   799  					}, nil)
   800  
   801  					handler.Handler().ServeHTTP(rr, req)
   802  					Expect(rr).To(HaveHTTPStatus(http.StatusBadRequest))
   803  				})
   804  
   805  				It("status 'Unimplemented' when removing", func() {
   806  
   807  					mockPathStat(mReq.Source.Path, status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: mReq.Source.ResourceId})
   808  					mockPathStat(mReq.Destination.Path, status.NewNotFound(ctx, ""), nil)
   809  					mockPathStat(".", status.NewOK(ctx), nil)
   810  
   811  					client.On("Move", mock.Anything, mReq).Return(&cs3storageprovider.MoveResponse{
   812  						Status: status.NewUnimplemented(ctx, fmt.Errorf("unimplemeted"), ""),
   813  					}, nil)
   814  
   815  					handler.Handler().ServeHTTP(rr, req)
   816  					Expect(rr).To(HaveHTTPStatus(http.StatusBadGateway))
   817  				})
   818  
   819  				It("the destination Stat error after moving", func() {
   820  
   821  					mockPathStat(mReq.Source.Path, status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: mReq.Source.ResourceId})
   822  					client.On("Stat", mock.Anything, mock.Anything).Return(&cs3storageprovider.StatResponse{
   823  						Status: status.NewNotFound(ctx, ""),
   824  						Info:   &cs3storageprovider.ResourceInfo{},
   825  					}, nil).Once()
   826  					mockPathStat(".", status.NewOK(ctx), nil)
   827  
   828  					client.On("Move", mock.Anything, mReq).Return(&cs3storageprovider.MoveResponse{
   829  						Status: status.NewOK(ctx),
   830  					}, nil)
   831  
   832  					client.On("Stat", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.StatRequest) bool {
   833  						return utils.ResourceEqual(req.Ref, &cs3storageprovider.Reference{
   834  							ResourceId: userspace.Root,
   835  							Path:       mReq.Destination.Path,
   836  						})
   837  					})).Return(nil, fmt.Errorf("unexpected io error"))
   838  
   839  					handler.Handler().ServeHTTP(rr, req)
   840  					Expect(rr).To(HaveHTTPStatus(http.StatusInternalServerError))
   841  				})
   842  
   843  				It("the destination Stat returned not OK status after moving", func() {
   844  
   845  					mockPathStat(mReq.Source.Path, status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: mReq.Source.ResourceId})
   846  					mockPathStat(mReq.Destination.Path, status.NewNotFound(ctx, ""), nil)
   847  					mockPathStat(".", status.NewOK(ctx), nil)
   848  
   849  					client.On("Move", mock.Anything, mReq).Return(&cs3storageprovider.MoveResponse{
   850  						Status: status.NewOK(ctx),
   851  					}, nil)
   852  
   853  					mockPathStat(mReq.Destination.Path, status.NewNotFound(ctx, ""), nil)
   854  
   855  					handler.Handler().ServeHTTP(rr, req)
   856  					Expect(rr).To(HaveHTTPStatus(http.StatusNotFound))
   857  				})
   858  			})
   859  		})
   860  
   861  		Describe("MOVE validation failed", func() {
   862  
   863  			BeforeEach(func() {
   864  				// setup the request
   865  				// set the webdav endpoint to test
   866  				basePath = "/webdav"
   867  				userspace.Id = &cs3storageprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&cs3storageprovider.ResourceId{StorageId: "provider-1", SpaceId: "userspace", OpaqueId: "userspace"})}
   868  				userspace.Root = &cs3storageprovider.ResourceId{StorageId: "provider-1", SpaceId: "userspace", OpaqueId: "userspace"}
   869  
   870  				// path based requests at the /webdav endpoint first look up the storage space
   871  				client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.ListStorageSpacesRequest) bool {
   872  					p := string(req.Opaque.Map["path"].Value)
   873  					return p == "/" || strings.HasPrefix(p, "/users")
   874  				})).Return(&cs3storageprovider.ListStorageSpacesResponse{
   875  					Status:        status.NewOK(ctx),
   876  					StorageSpaces: []*cs3storageprovider.StorageSpace{userspace},
   877  				}, nil)
   878  
   879  				rr = httptest.NewRecorder()
   880  				req, err = http.NewRequest("MOVE", basePath+"/file", strings.NewReader(""))
   881  				Expect(err).ToNot(HaveOccurred())
   882  				req = req.WithContext(ctx)
   883  				req.Header.Set(net.HeaderDestination, basePath+"/provider-1$userspace!userspace")
   884  				req.Header.Set("Overwrite", "T")
   885  				mReq = &cs3storageprovider.MoveRequest{
   886  					Source:      mockReference("userspace", "./file"),
   887  					Destination: mockReference("userspace", ""),
   888  				}
   889  			})
   890  
   891  			When("the gateway returns error when moving file", func() {
   892  				It("error when the source is a file and the destination is a folder", func() {
   893  					mockPathStat(mReq.Source.Path, status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: mReq.Source.ResourceId})
   894  
   895  					mockStat(mockReference("userspace", ""), status.NewOK(ctx), &cs3storageprovider.ResourceInfo{
   896  						Id: mReq.Destination.ResourceId, Path: mReq.Destination.Path,
   897  						Type:  cs3storageprovider.ResourceType_RESOURCE_TYPE_CONTAINER,
   898  						Space: userspace,
   899  					})
   900  
   901  					handler.Handler().ServeHTTP(rr, req)
   902  					Expect(rr).To(HaveHTTPStatus(http.StatusBadRequest))
   903  				})
   904  			})
   905  		})
   906  	})
   907  
   908  	Context("at the /dav/avatars endpoint", func() {
   909  
   910  		BeforeEach(func() {
   911  			basePath = "/dav/avatars"
   912  		})
   913  
   914  	})
   915  	Context("at the legacy /dav/files endpoint", func() {
   916  
   917  		BeforeEach(func() {
   918  			basePath = "/dav/files"
   919  		})
   920  
   921  	})
   922  	Context("at the /dav/meta endpoint", func() {
   923  
   924  		BeforeEach(func() {
   925  			basePath = "/dav/meta"
   926  		})
   927  
   928  	})
   929  	Context("at the /dav/trash-bin endpoint", func() {
   930  
   931  		BeforeEach(func() {
   932  			basePath = "/dav/trash-bin"
   933  		})
   934  
   935  	})
   936  	Context("at the /dav/spaces endpoint", func() {
   937  
   938  		BeforeEach(func() {
   939  			basePath = "/dav/spaces"
   940  
   941  			userspace.Id = &cs3storageprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&cs3storageprovider.ResourceId{StorageId: "provider-1", SpaceId: "userspace", OpaqueId: "userspace"})}
   942  			userspace.Root = &cs3storageprovider.ResourceId{StorageId: "provider-1", SpaceId: "userspace", OpaqueId: "userspace"}
   943  			// path based requests at the /webdav endpoint first look up the storage space
   944  			client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.ListStorageSpacesRequest) bool {
   945  				p := string(req.Opaque.Map["path"].Value)
   946  				return p == "/" || strings.HasPrefix(p, "/users")
   947  			})).Return(&cs3storageprovider.ListStorageSpacesResponse{
   948  				Status:        status.NewOK(ctx),
   949  				StorageSpaces: []*cs3storageprovider.StorageSpace{userspace},
   950  			}, nil)
   951  		})
   952  
   953  		Describe("MOVE", func() {
   954  			// The variables that used in a JustBeforeEach must be defined in the BeforeEach
   955  			var reqPath, dstPath, dstFileName string
   956  
   957  			JustBeforeEach(func() {
   958  				// setup the request
   959  				rr = httptest.NewRecorder()
   960  				req, err = http.NewRequest("MOVE", basePath+reqPath, strings.NewReader(""))
   961  				Expect(err).ToNot(HaveOccurred())
   962  				req = req.WithContext(ctx)
   963  				req.Header.Set(net.HeaderDestination, basePath+dstPath)
   964  				req.Header.Set("Overwrite", "T")
   965  
   966  				client.On("GetPath", mock.Anything, mock.Anything).Return(func(ctx context.Context, req *cs3storageprovider.GetPathRequest, _ ...grpc.CallOption) (*cs3storageprovider.GetPathResponse, error) {
   967  					switch req.ResourceId.OpaqueId {
   968  					case "dstId":
   969  						return &cs3storageprovider.GetPathResponse{
   970  							Status: status.NewOK(ctx),
   971  							Path:   "/dstFileName",
   972  						}, nil
   973  					default:
   974  						return &cs3storageprovider.GetPathResponse{
   975  							Status: status.NewOK(ctx),
   976  							Path:   "/file",
   977  						}, nil
   978  					}
   979  				})
   980  
   981  				client.On("Stat", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.StatRequest) bool {
   982  					return req.Ref.Path == mReq.Source.Path
   983  				})).Return(&cs3storageprovider.StatResponse{
   984  					Status: status.NewOK(ctx),
   985  					Info:   &cs3storageprovider.ResourceInfo{Id: mReq.Source.ResourceId},
   986  				}, nil).Once()
   987  
   988  				client.On("Stat", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.StatRequest) bool {
   989  					return req.Ref.Path == mReq.Destination.Path
   990  				})).Return(&cs3storageprovider.StatResponse{
   991  					Status: status.NewOK(ctx),
   992  					Info: &cs3storageprovider.ResourceInfo{
   993  						Id:       mReq.Source.ResourceId,
   994  						ParentId: &cs3storageprovider.ResourceId{StorageId: "provider-1", OpaqueId: "dstId", SpaceId: "userspace"},
   995  						Name:     dstFileName,
   996  					},
   997  				}, nil)
   998  
   999  				client.On("Delete", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.DeleteRequest) bool {
  1000  					return utils.ResourceEqual(req.Ref, mReq.Destination)
  1001  				})).Return(&cs3storageprovider.DeleteResponse{
  1002  					Status: status.NewOK(ctx),
  1003  				}, nil)
  1004  
  1005  			})
  1006  
  1007  			When("use the id as a destination. the gateway returns OK when moving file", func() {
  1008  				BeforeEach(func() {
  1009  					reqPath = "/provider-1$userspace/file"
  1010  					dstPath = "/provider-1$userspace!dstId"
  1011  					dstFileName = "dstFileName"
  1012  
  1013  					mReq = &cs3storageprovider.MoveRequest{
  1014  						Source:      mockReference("userspace", "./file"),
  1015  						Destination: mockReference("dstId", "."),
  1016  					}
  1017  				})
  1018  				It("the source and the destination exist", func() {
  1019  
  1020  					expReq := &cs3storageprovider.MoveRequest{
  1021  						Source: &cs3storageprovider.Reference{ResourceId: &cs3storageprovider.ResourceId{
  1022  							StorageId: "provider-1", SpaceId: "userspace"}, Path: "./file"},
  1023  						Destination: &cs3storageprovider.Reference{ResourceId: &cs3storageprovider.ResourceId{
  1024  							StorageId: "provider-1", OpaqueId: "dstId", SpaceId: "userspace"}, Path: "./dstFileName"},
  1025  					}
  1026  
  1027  					client.On("Move", mock.Anything, expReq).Return(&cs3storageprovider.MoveResponse{
  1028  						Status: status.NewOK(ctx),
  1029  					}, nil)
  1030  
  1031  					mockPathStat("./dstFileName", status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: &cs3storageprovider.ResourceId{}})
  1032  
  1033  					handler.Handler().ServeHTTP(rr, req)
  1034  					Expect(rr).To(HaveHTTPStatus(http.StatusNoContent))
  1035  				})
  1036  			})
  1037  			When("use the id as a source and destination. the gateway returns OK when moving file", func() {
  1038  				BeforeEach(func() {
  1039  					reqPath = "/provider-1$userspace!srcId"
  1040  					dstPath = "/provider-1$userspace!dstId"
  1041  					dstFileName = ""
  1042  
  1043  					mReq = &cs3storageprovider.MoveRequest{
  1044  						Source:      mockReference("srcId", "."),
  1045  						Destination: mockReference("dstId", "."),
  1046  					}
  1047  				})
  1048  				It("the source and the destination exist", func() {
  1049  
  1050  					expReq := &cs3storageprovider.MoveRequest{
  1051  						Source: &cs3storageprovider.Reference{ResourceId: &cs3storageprovider.ResourceId{
  1052  							StorageId: "provider-1", OpaqueId: "srcId", SpaceId: "userspace"}, Path: "."},
  1053  						Destination: &cs3storageprovider.Reference{ResourceId: &cs3storageprovider.ResourceId{
  1054  							StorageId: "provider-1", OpaqueId: "dstId", SpaceId: "userspace"}, Path: "."},
  1055  					}
  1056  
  1057  					client.On("Move", mock.Anything, expReq).Return(&cs3storageprovider.MoveResponse{
  1058  						Status: status.NewOK(ctx),
  1059  					}, nil)
  1060  
  1061  					mockPathStat(".", status.NewOK(ctx), &cs3storageprovider.ResourceInfo{Id: &cs3storageprovider.ResourceId{}})
  1062  
  1063  					handler.Handler().ServeHTTP(rr, req)
  1064  					Expect(rr).To(HaveHTTPStatus(http.StatusNoContent))
  1065  				})
  1066  			})
  1067  		})
  1068  
  1069  	})
  1070  	Context("at the /dav/public-files endpoint", func() {
  1071  
  1072  		BeforeEach(func() {
  1073  			basePath = "/dav/public-files"
  1074  		})
  1075  
  1076  	})
  1077  
  1078  	// TODO restructure the tests and split them up by endpoint?
  1079  	// - that should allow reusing the set up of expected requests to the gateway
  1080  
  1081  	// listing spaces is a precondition for path based requests, what if listing spaces currently is broken?
  1082  	Context("bad requests", func() {
  1083  
  1084  		It("to the /dav/spaces endpoint root return a method not allowed status ", func() {
  1085  			rr := httptest.NewRecorder()
  1086  			req, err := http.NewRequest("DELETE", "/dav/spaces", strings.NewReader(""))
  1087  			Expect(err).ToNot(HaveOccurred())
  1088  			req = req.WithContext(ctx)
  1089  
  1090  			handler.Handler().ServeHTTP(rr, req)
  1091  			Expect(rr).To(HaveHTTPStatus(http.StatusMethodNotAllowed))
  1092  		})
  1093  		It("when deleting a space at the /dav/spaces endpoint return method not allowed status", func() {
  1094  			rr := httptest.NewRecorder()
  1095  			req, err := http.NewRequest("DELETE", "/dav/spaces/trytodeleteme", strings.NewReader(""))
  1096  			Expect(err).ToNot(HaveOccurred())
  1097  			req = req.WithContext(ctx)
  1098  
  1099  			handler.Handler().ServeHTTP(rr, req)
  1100  			Expect(rr).To(HaveHTTPStatus(http.StatusMethodNotAllowed))
  1101  			Expect(rr).To(HaveHTTPBody("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<d:error xmlns:d=\"DAV\" xmlns:s=\"http://sabredav.org/ns\"><s:exception>Sabre\\DAV\\Exception\\MethodNotAllowed</s:exception><s:message>deleting spaces via dav is not allowed</s:message><s:errorcode></s:errorcode></d:error>"), "Body must have a sabredav exception")
  1102  		})
  1103  		It("with invalid if header return bad request status", func() {
  1104  			rr := httptest.NewRecorder()
  1105  			req, err := http.NewRequest("DELETE", "/dav/spaces/somespace/foo", strings.NewReader(""))
  1106  			req.Header.Set("If", "invalid")
  1107  			Expect(err).ToNot(HaveOccurred())
  1108  			req = req.WithContext(ctx)
  1109  
  1110  			handler.Handler().ServeHTTP(rr, req)
  1111  			Expect(rr).To(HaveHTTPStatus(http.StatusBadRequest))
  1112  		})
  1113  
  1114  		DescribeTable("returns 415 when no body was expected",
  1115  			func(method string, path string) {
  1116  				// as per https://www.rfc-editor.org/rfc/rfc4918#section-8.4
  1117  				rr := httptest.NewRecorder()
  1118  				req, err := http.NewRequest(method, path, strings.NewReader("should be empty"))
  1119  				Expect(err).ToNot(HaveOccurred())
  1120  				req = req.WithContext(ctx)
  1121  
  1122  				handler.Handler().ServeHTTP(rr, req)
  1123  				Expect(rr).To(HaveHTTPStatus(http.StatusUnsupportedMediaType))
  1124  				Expect(rr).To(HaveHTTPBody("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<d:error xmlns:d=\"DAV\" xmlns:s=\"http://sabredav.org/ns\"><s:exception>Sabre\\DAV\\Exception\\UnsupportedMediaType</s:exception><s:message>body must be empty</s:message><s:errorcode></s:errorcode></d:error>"), "Body must have a sabredav exception")
  1125  			},
  1126  			Entry("MOVE", "MOVE", "/webdav/source"),
  1127  			Entry("COPY", "COPY", "/webdav/source"),
  1128  			Entry("DELETE", "DELETE", "/webdav/source"),
  1129  			PEntry("MKCOL", "MKCOL", "/webdav/source"),
  1130  		)
  1131  
  1132  		DescribeTable("check naming rules",
  1133  			func(method string, path string, expectedStatus int) {
  1134  				rr := httptest.NewRecorder()
  1135  				req, err := http.NewRequest(method, "", strings.NewReader(""))
  1136  				Expect(err).ToNot(HaveOccurred())
  1137  				req.URL.Path = path // we need to overwrite the path here to send invalid chars
  1138  
  1139  				if method == "COPY" || method == "MOVE" {
  1140  					req.Header.Set(net.HeaderDestination, path+".bak")
  1141  				}
  1142  
  1143  				handler.Handler().ServeHTTP(rr, req)
  1144  				Expect(rr).To(HaveHTTPStatus(expectedStatus))
  1145  
  1146  				Expect(rr).To(HaveHTTPBody(HavePrefix("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<d:error xmlns:d=\"DAV\" xmlns:s=\"http://sabredav.org/ns\"><s:exception>Sabre\\DAV\\Exception\\BadRequest</s:exception><s:message>")), "Body must have a sabredav exception")
  1147  			},
  1148  			Entry("MKCOL no \\f", "MKCOL", "/webdav/forbidden \f char", http.StatusBadRequest),
  1149  			Entry("MKCOL no \\r", "MKCOL", "/webdav/forbidden \r char", http.StatusBadRequest),
  1150  			Entry("MKCOL no \\n", "MKCOL", "/webdav/forbidden \n char", http.StatusBadRequest),
  1151  			Entry("MKCOL no \\\\", "MKCOL", "/webdav/forbidden \\ char", http.StatusBadRequest),
  1152  
  1153  			// COPY source path
  1154  			Entry("COPY no \\f", "COPY", "/webdav/forbidden \f char", http.StatusBadRequest),
  1155  			Entry("COPY no \\r", "COPY", "/webdav/forbidden \r char", http.StatusBadRequest),
  1156  			Entry("COPY no \\n", "COPY", "/webdav/forbidden \n char", http.StatusBadRequest),
  1157  			Entry("COPY no \\\\", "COPY", "/webdav/forbidden \\ char", http.StatusBadRequest),
  1158  
  1159  			// MOVE source path
  1160  			Entry("MOVE no \\f", "MOVE", "/webdav/forbidden \f char", http.StatusBadRequest),
  1161  			Entry("MOVE no \\r", "MOVE", "/webdav/forbidden \r char", http.StatusBadRequest),
  1162  			Entry("MOVE no \\n", "MOVE", "/webdav/forbidden \n char", http.StatusBadRequest),
  1163  			Entry("MOVE no \\\\", "MOVE", "/webdav/forbidden \\ char", http.StatusBadRequest),
  1164  		)
  1165  
  1166  		DescribeTable("check naming rules",
  1167  			func(method string, path string, expectedStatus int) {
  1168  				rr := httptest.NewRecorder()
  1169  				req, err := http.NewRequest(method, "/webdav/safe path", strings.NewReader(""))
  1170  				Expect(err).ToNot(HaveOccurred())
  1171  
  1172  				req.Header.Set(net.HeaderDestination, path+".bak")
  1173  
  1174  				handler.Handler().ServeHTTP(rr, req)
  1175  				Expect(rr).To(HaveHTTPStatus(expectedStatus))
  1176  
  1177  				Expect(rr).To(HaveHTTPBody(HavePrefix("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<d:error xmlns:d=\"DAV\" xmlns:s=\"http://sabredav.org/ns\"><s:exception>Sabre\\DAV\\Exception\\BadRequest</s:exception><s:message>")), "Body must have a sabredav exception")
  1178  			},
  1179  			// COPY
  1180  			Entry("COPY no \\f", "COPY", "/webdav/forbidden \f char", http.StatusBadRequest),
  1181  			Entry("COPY no \\r", "COPY", "/webdav/forbidden \r char", http.StatusBadRequest),
  1182  			Entry("COPY no \\n", "COPY", "/webdav/forbidden \n char", http.StatusBadRequest),
  1183  			Entry("COPY no \\\\", "COPY", "/webdav/forbidden \\ char", http.StatusBadRequest),
  1184  
  1185  			// MOVE
  1186  			Entry("MOVE no \\f", "MOVE", "/webdav/forbidden \f char", http.StatusBadRequest),
  1187  			Entry("MOVE no \\r", "MOVE", "/webdav/forbidden \r char", http.StatusBadRequest),
  1188  			Entry("MOVE no \\n", "MOVE", "/webdav/forbidden \n char", http.StatusBadRequest),
  1189  			Entry("MOVE no \\\\", "MOVE", "/webdav/forbidden \\ char", http.StatusBadRequest),
  1190  		)
  1191  
  1192  	})
  1193  
  1194  	// listing spaces is a precondition for path based requests, what if listing spaces currently is broken?
  1195  	Context("When listing spaces fails with an error", func() {
  1196  
  1197  		DescribeTable("HandleDelete",
  1198  			func(endpoint string, expectedPathPrefix string, expectedStatus int) {
  1199  
  1200  				client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.ListStorageSpacesRequest) bool {
  1201  					p := string(req.Opaque.Map["path"].Value)
  1202  					return p == "/" || strings.HasPrefix(p, expectedPathPrefix)
  1203  				})).Return(nil, fmt.Errorf("unexpected io error"))
  1204  
  1205  				// the spaces endpoint omits the list storage spaces call, it directly executes the delete call
  1206  				client.On("Delete", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.DeleteRequest) bool {
  1207  					return utils.ResourceEqual(req.Ref, &cs3storageprovider.Reference{
  1208  						ResourceId: userspace.Root,
  1209  						Path:       "./foo",
  1210  					})
  1211  				})).Return(&cs3storageprovider.DeleteResponse{
  1212  					Status: status.NewOK(ctx),
  1213  				}, nil)
  1214  
  1215  				rr := httptest.NewRecorder()
  1216  				req, err := http.NewRequest("DELETE", endpoint+"/foo", strings.NewReader(""))
  1217  				Expect(err).ToNot(HaveOccurred())
  1218  				req = req.WithContext(ctx)
  1219  
  1220  				handler.Handler().ServeHTTP(rr, req)
  1221  				Expect(rr).To(HaveHTTPStatus(expectedStatus))
  1222  				if expectedStatus == http.StatusInternalServerError {
  1223  					Expect(rr).To(HaveHTTPBody("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<d:error xmlns:d=\"DAV\" xmlns:s=\"http://sabredav.org/ns\"><s:exception></s:exception><s:message>unexpected io error</s:message><s:errorcode></s:errorcode></d:error>"), "Body must have a sabredav exception")
  1224  				} else {
  1225  					Expect(rr).To(HaveHTTPBody(""), "Body must be empty")
  1226  				}
  1227  
  1228  			},
  1229  			Entry("at the /webdav endpoint", "/webdav", "/users", http.StatusInternalServerError),
  1230  			Entry("at the /dav/files endpoint", "/dav/files/username", "/users/username", http.StatusInternalServerError),
  1231  			Entry("at the /dav/spaces endpoint", "/dav/spaces/provider-1$userspace!root", "/users/username", http.StatusNoContent),
  1232  			Entry("at the /dav/public-files endpoint for a file", "/dav/public-files/tokenforfile", "", http.StatusMethodNotAllowed),
  1233  			Entry("at the /dav/public-files endpoint for a folder", "/dav/public-files/tokenforfolder", "/public/tokenforfolder", http.StatusInternalServerError),
  1234  		)
  1235  
  1236  		DescribeTable("HandleMkcol",
  1237  			func(endpoint string, expectedPathPrefix string, expectedStatPath string, expectedStatus int) {
  1238  
  1239  				client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.ListStorageSpacesRequest) bool {
  1240  					p := string(req.Opaque.Map["path"].Value)
  1241  					return p == "/" || strings.HasPrefix(p, expectedPathPrefix)
  1242  				})).Return(nil, fmt.Errorf("unexpected io error"))
  1243  
  1244  				// path based requests need to check if the resource already exists
  1245  				mockPathStat(expectedStatPath, status.NewNotFound(ctx, "not found"), nil)
  1246  
  1247  				// the spaces endpoint omits the list storage spaces call, it directly executes the create container call
  1248  				client.On("CreateContainer", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.CreateContainerRequest) bool {
  1249  					return utils.ResourceEqual(req.Ref, &cs3storageprovider.Reference{
  1250  						ResourceId: userspace.Root,
  1251  						Path:       "./foo",
  1252  					})
  1253  				})).Return(&cs3storageprovider.CreateContainerResponse{
  1254  					Status: status.NewOK(ctx),
  1255  				}, nil)
  1256  
  1257  				rr := httptest.NewRecorder()
  1258  				req, err := http.NewRequest("MKCOL", endpoint+"/foo", strings.NewReader(""))
  1259  				Expect(err).ToNot(HaveOccurred())
  1260  				req = req.WithContext(ctx)
  1261  
  1262  				handler.Handler().ServeHTTP(rr, req)
  1263  				Expect(rr).To(HaveHTTPStatus(expectedStatus))
  1264  				if expectedStatus == http.StatusInternalServerError {
  1265  					Expect(rr).To(HaveHTTPBody("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<d:error xmlns:d=\"DAV\" xmlns:s=\"http://sabredav.org/ns\"><s:exception></s:exception><s:message>unexpected io error</s:message><s:errorcode></s:errorcode></d:error>"), "Body must have a sabredav exception")
  1266  				} else {
  1267  					Expect(rr).To(HaveHTTPBody(""), "Body must be empty")
  1268  				}
  1269  
  1270  			},
  1271  			Entry("at the /webdav endpoint", "/webdav", "/users", "/users/username/foo", http.StatusInternalServerError),
  1272  			Entry("at the /dav/files endpoint", "/dav/files/username", "/users/username", "/users/username/foo", http.StatusInternalServerError),
  1273  			Entry("at the /dav/spaces endpoint", "/dav/spaces/provider-1$userspace!root", "/users/username", "/users/username/foo", http.StatusCreated),
  1274  			Entry("at the /dav/public-files endpoint for a file", "/dav/public-files/tokenforfile", "", "/public/tokenforfolder/foo", http.StatusMethodNotAllowed),
  1275  			Entry("at the /dav/public-files endpoint for a folder", "/dav/public-files/tokenforfolder", "/public/tokenforfolder", "/public/tokenforfolder/foo", http.StatusInternalServerError),
  1276  		)
  1277  	})
  1278  
  1279  	Context("When calls fail with an error", func() {
  1280  
  1281  		DescribeTable("HandleDelete",
  1282  			func(endpoint string, expectedPathPrefix string, expectedPath string, expectedStatus int) {
  1283  
  1284  				client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.ListStorageSpacesRequest) bool {
  1285  					p := string(req.Opaque.Map["path"].Value)
  1286  					return p == "/" || strings.HasPrefix(p, expectedPathPrefix)
  1287  				})).Return(&cs3storageprovider.ListStorageSpacesResponse{
  1288  					Status:        status.NewOK(ctx),
  1289  					StorageSpaces: []*cs3storageprovider.StorageSpace{userspace},
  1290  				}, nil)
  1291  
  1292  				ref := cs3storageprovider.Reference{
  1293  					ResourceId: userspace.Root,
  1294  					Path:       expectedPath,
  1295  				}
  1296  
  1297  				client.On("Delete", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.DeleteRequest) bool {
  1298  					return utils.ResourceEqual(req.Ref, &ref)
  1299  				})).Return(nil, fmt.Errorf("unexpected io error"))
  1300  
  1301  				rr := httptest.NewRecorder()
  1302  				req, err := http.NewRequest("DELETE", endpoint+"/foo", strings.NewReader(""))
  1303  				Expect(err).ToNot(HaveOccurred())
  1304  				req = req.WithContext(ctx)
  1305  
  1306  				handler.Handler().ServeHTTP(rr, req)
  1307  				Expect(rr).To(HaveHTTPStatus(expectedStatus))
  1308  				if expectedStatus == http.StatusInternalServerError {
  1309  					Expect(rr).To(HaveHTTPBody("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<d:error xmlns:d=\"DAV\" xmlns:s=\"http://sabredav.org/ns\"><s:exception></s:exception><s:message>unexpected io error</s:message><s:errorcode></s:errorcode></d:error>"), "Body must have a sabredav exception")
  1310  				} else {
  1311  					Expect(rr).To(HaveHTTPBody(""), "Body must be empty")
  1312  				}
  1313  
  1314  			},
  1315  			Entry("at the /webdav endpoint", "/webdav", "/users", "./foo", http.StatusInternalServerError),
  1316  			Entry("at the /dav/files endpoint", "/dav/files/username", "/users/username", "./foo", http.StatusInternalServerError),
  1317  			Entry("at the /dav/spaces endpoint", "/dav/spaces/provider-1$userspace!root", "/users/username", "./foo", http.StatusInternalServerError),
  1318  			Entry("at the /dav/public-files endpoint for a file", "/dav/public-files/tokenforfile", "", "", http.StatusMethodNotAllowed),
  1319  			Entry("at the /dav/public-files endpoint for a folder", "/dav/public-files/tokenforfolder", "/public/tokenforfolder", ".", http.StatusInternalServerError),
  1320  		)
  1321  
  1322  		DescribeTable("HandleMkcol",
  1323  			func(endpoint string, expectedPathPrefix string, expectedStatPath string, expectedCreatePath string, expectedStatus int) {
  1324  
  1325  				client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.ListStorageSpacesRequest) bool {
  1326  					p := string(req.Opaque.Map["path"].Value)
  1327  					return p == "/" || strings.HasPrefix(p, expectedPathPrefix)
  1328  				})).Return(&cs3storageprovider.ListStorageSpacesResponse{
  1329  					Status:        status.NewOK(ctx),
  1330  					StorageSpaces: []*cs3storageprovider.StorageSpace{userspace}, // FIXME we may need to return the /public storage provider id and mock it
  1331  				}, nil)
  1332  
  1333  				// path based requests need to check if the resource already exists
  1334  				mockPathStat(expectedStatPath, status.NewNotFound(ctx, "not found"), nil)
  1335  
  1336  				ref := cs3storageprovider.Reference{
  1337  					ResourceId: userspace.Root,
  1338  					Path:       expectedCreatePath,
  1339  				}
  1340  
  1341  				client.On("CreateContainer", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.CreateContainerRequest) bool {
  1342  					return utils.ResourceEqual(req.Ref, &ref)
  1343  				})).Return(nil, fmt.Errorf("unexpected io error"))
  1344  
  1345  				rr := httptest.NewRecorder()
  1346  				req, err := http.NewRequest("MKCOL", endpoint+"/foo", strings.NewReader(""))
  1347  				Expect(err).ToNot(HaveOccurred())
  1348  				req = req.WithContext(ctx)
  1349  
  1350  				handler.Handler().ServeHTTP(rr, req)
  1351  				Expect(rr).To(HaveHTTPStatus(expectedStatus))
  1352  				if expectedStatus == http.StatusInternalServerError {
  1353  					Expect(rr).To(HaveHTTPBody("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<d:error xmlns:d=\"DAV\" xmlns:s=\"http://sabredav.org/ns\"><s:exception></s:exception><s:message>unexpected io error</s:message><s:errorcode></s:errorcode></d:error>"), "Body must have a sabredav exception")
  1354  				} else {
  1355  					Expect(rr).To(HaveHTTPBody(""), "Body must be empty")
  1356  				}
  1357  
  1358  			},
  1359  			Entry("at the /webdav endpoint", "/webdav", "/users", "/users/username/foo", "./foo", http.StatusInternalServerError),
  1360  			Entry("at the /dav/files endpoint", "/dav/files/username", "/users/username", "/users/username/foo", "./foo", http.StatusInternalServerError),
  1361  			Entry("at the /dav/spaces endpoint", "/dav/spaces/provider-1$userspace!root", "/users/username", "/users/username/foo", "./foo", http.StatusInternalServerError),
  1362  			Entry("at the /dav/public-files endpoint for a file", "/dav/public-files/tokenforfile", "", "/public/tokenforfolder/foo", "./foo", http.StatusMethodNotAllowed),
  1363  			Entry("at the /dav/public-files endpoint for a folder", "/dav/public-files/tokenforfolder", "/public/tokenforfolder", "/public/tokenforfolder/foo", ".", http.StatusInternalServerError),
  1364  		)
  1365  
  1366  	})
  1367  
  1368  	Context("When calls return ok", func() {
  1369  
  1370  		DescribeTable("HandleDelete",
  1371  			func(endpoint string, expectedPathPrefix string, expectedPath string, expectedStatus int) {
  1372  
  1373  				client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.ListStorageSpacesRequest) bool {
  1374  					p := string(req.Opaque.Map["path"].Value)
  1375  					return p == "/" || strings.HasPrefix(p, expectedPathPrefix)
  1376  				})).Return(&cs3storageprovider.ListStorageSpacesResponse{
  1377  					Status:        status.NewOK(ctx),
  1378  					StorageSpaces: []*cs3storageprovider.StorageSpace{userspace},
  1379  				}, nil)
  1380  
  1381  				ref := cs3storageprovider.Reference{
  1382  					ResourceId: userspace.Root,
  1383  					Path:       expectedPath,
  1384  				}
  1385  
  1386  				client.On("Delete", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.DeleteRequest) bool {
  1387  					return utils.ResourceEqual(req.Ref, &ref)
  1388  				})).Return(&cs3storageprovider.DeleteResponse{
  1389  					Status: status.NewOK(ctx),
  1390  				}, nil)
  1391  
  1392  				rr := httptest.NewRecorder()
  1393  				req, err := http.NewRequest("DELETE", endpoint+"/foo", strings.NewReader(""))
  1394  				Expect(err).ToNot(HaveOccurred())
  1395  				req = req.WithContext(ctx)
  1396  
  1397  				handler.Handler().ServeHTTP(rr, req)
  1398  				Expect(rr).To(HaveHTTPStatus(expectedStatus))
  1399  				Expect(rr).To(HaveHTTPBody(""), "Body must be empty")
  1400  
  1401  			},
  1402  			Entry("at the /webdav endpoint", "/webdav", "/users", "./foo", http.StatusNoContent),
  1403  			Entry("at the /dav/files endpoint", "/dav/files/username", "/users/username", "./foo", http.StatusNoContent),
  1404  			Entry("at the /dav/spaces endpoint", "/dav/spaces/provider-1$userspace!root", "/users/username", "./foo", http.StatusNoContent),
  1405  			Entry("at the /dav/public-files endpoint for a file", "/dav/public-files/tokenforfile", "", "", http.StatusMethodNotAllowed),
  1406  			Entry("at the /dav/public-files endpoint for a folder", "/dav/public-files/tokenforfolder", "/public/tokenforfolder", ".", http.StatusNoContent),
  1407  		)
  1408  
  1409  		DescribeTable("HandleMkcol",
  1410  			func(endpoint string, expectedPathPrefix string, expectedStatPath string, expectedCreatePath string, expectedStatus int) {
  1411  
  1412  				client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.ListStorageSpacesRequest) bool {
  1413  					p := string(req.Opaque.Map["path"].Value)
  1414  					return p == "/" || strings.HasPrefix(p, expectedPathPrefix)
  1415  				})).Return(&cs3storageprovider.ListStorageSpacesResponse{
  1416  					Status:        status.NewOK(ctx),
  1417  					StorageSpaces: []*cs3storageprovider.StorageSpace{userspace}, // FIXME we may need to return the /public storage provider id and mock it
  1418  				}, nil)
  1419  
  1420  				// path based requests need to check if the resource already exists
  1421  				mockPathStat(expectedStatPath, status.NewNotFound(ctx, "not found"), nil)
  1422  
  1423  				ref := cs3storageprovider.Reference{
  1424  					ResourceId: userspace.Root,
  1425  					Path:       expectedCreatePath,
  1426  				}
  1427  
  1428  				client.On("CreateContainer", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.CreateContainerRequest) bool {
  1429  					return utils.ResourceEqual(req.Ref, &ref)
  1430  				})).Return(&cs3storageprovider.CreateContainerResponse{
  1431  					Status: status.NewOK(ctx),
  1432  				}, nil)
  1433  
  1434  				rr := httptest.NewRecorder()
  1435  				req, err := http.NewRequest("MKCOL", endpoint+"/foo", strings.NewReader(""))
  1436  				Expect(err).ToNot(HaveOccurred())
  1437  				req = req.WithContext(ctx)
  1438  
  1439  				handler.Handler().ServeHTTP(rr, req)
  1440  				Expect(rr).To(HaveHTTPStatus(expectedStatus))
  1441  				Expect(rr).To(HaveHTTPBody(""), "Body must be empty")
  1442  
  1443  			},
  1444  			Entry("at the /webdav endpoint", "/webdav", "/users", "/users/username/foo", "./foo", http.StatusCreated),
  1445  			Entry("at the /dav/files endpoint", "/dav/files/username", "/users/username", "/users/username/foo", "./foo", http.StatusCreated),
  1446  			Entry("at the /dav/spaces endpoint", "/dav/spaces/provider-1$userspace!root", "/users/username", "/users/username/foo", "./foo", http.StatusCreated),
  1447  			Entry("at the /dav/public-files endpoint for a file", "/dav/public-files/tokenforfile", "", "/public/tokenforfolder/foo", "./foo", http.StatusMethodNotAllowed),
  1448  			Entry("at the /dav/public-files endpoint for a folder", "/dav/public-files/tokenforfolder", "/public/tokenforfolder", "/public/tokenforfolder/foo", ".", http.StatusCreated),
  1449  		)
  1450  
  1451  	})
  1452  
  1453  	Context("When the resource is not found", func() {
  1454  
  1455  		DescribeTable("HandleDelete",
  1456  			func(endpoint string, expectedPathPrefix string, expectedPath string, expectedStatus int) {
  1457  
  1458  				client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.ListStorageSpacesRequest) bool {
  1459  					p := string(req.Opaque.Map["path"].Value)
  1460  					return p == "/" || strings.HasPrefix(p, expectedPathPrefix)
  1461  				})).Return(&cs3storageprovider.ListStorageSpacesResponse{
  1462  					Status:        status.NewOK(ctx),
  1463  					StorageSpaces: []*cs3storageprovider.StorageSpace{userspace},
  1464  				}, nil)
  1465  
  1466  				ref := cs3storageprovider.Reference{
  1467  					ResourceId: userspace.Root,
  1468  					Path:       expectedPath,
  1469  				}
  1470  
  1471  				client.On("Delete", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.DeleteRequest) bool {
  1472  					return utils.ResourceEqual(req.Ref, &ref)
  1473  				})).Return(&cs3storageprovider.DeleteResponse{
  1474  					Status: status.NewNotFound(ctx, "not found"),
  1475  				}, nil)
  1476  
  1477  				rr := httptest.NewRecorder()
  1478  				req, err := http.NewRequest("DELETE", endpoint+"/foo", strings.NewReader(""))
  1479  				Expect(err).ToNot(HaveOccurred())
  1480  				req = req.WithContext(ctx)
  1481  
  1482  				handler.Handler().ServeHTTP(rr, req)
  1483  				Expect(rr).To(HaveHTTPStatus(expectedStatus))
  1484  				if expectedStatus == http.StatusNotFound {
  1485  					Expect(rr).To(HaveHTTPBody("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<d:error xmlns:d=\"DAV\" xmlns:s=\"http://sabredav.org/ns\"><s:exception>Sabre\\DAV\\Exception\\NotFound</s:exception><s:message>Resource not found</s:message><s:errorcode></s:errorcode></d:error>"), "Body must have a not found sabredav exception")
  1486  				} else {
  1487  					Expect(rr).To(HaveHTTPBody(""), "Body must be empty")
  1488  				}
  1489  			},
  1490  			Entry("at the /webdav endpoint", "/webdav", "/users", "./foo", http.StatusNotFound),
  1491  			Entry("at the /dav/files endpoint", "/dav/files/username", "/users/username", "./foo", http.StatusNotFound),
  1492  			Entry("at the /dav/spaces endpoint", "/dav/spaces/provider-1$userspace!root", "/users/username", "./foo", http.StatusNotFound),
  1493  			Entry("at the /dav/public-files endpoint for a file", "/dav/public-files/tokenforfile", "", "", http.StatusMethodNotAllowed),
  1494  			Entry("at the /dav/public-files endpoint for a folder", "/dav/public-files/tokenforfolder", "/public/tokenforfolder", ".", http.StatusNotFound),
  1495  		)
  1496  
  1497  		DescribeTable("HandleMkcol",
  1498  			func(endpoint string, expectedPathPrefix string, expectedStatPath string, expectedPath string, expectedStatus int) {
  1499  
  1500  				client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.ListStorageSpacesRequest) bool {
  1501  					p := string(req.Opaque.Map["path"].Value)
  1502  					return p == "/" || strings.HasPrefix(p, expectedPathPrefix)
  1503  				})).Return(&cs3storageprovider.ListStorageSpacesResponse{
  1504  					Status:        status.NewOK(ctx),
  1505  					StorageSpaces: []*cs3storageprovider.StorageSpace{userspace},
  1506  				}, nil)
  1507  
  1508  				// path based requests need to check if the resource already exists
  1509  				mockPathStat(expectedStatPath, status.NewNotFound(ctx, "not found"), nil)
  1510  
  1511  				ref := cs3storageprovider.Reference{
  1512  					ResourceId: userspace.Root,
  1513  					Path:       expectedPath,
  1514  				}
  1515  
  1516  				client.On("CreateContainer", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.CreateContainerRequest) bool {
  1517  					return utils.ResourceEqual(req.Ref, &ref)
  1518  				})).Return(&cs3storageprovider.CreateContainerResponse{
  1519  					Status: status.NewNotFound(ctx, "not found"),
  1520  				}, nil)
  1521  
  1522  				rr := httptest.NewRecorder()
  1523  				req, err := http.NewRequest("MKCOL", endpoint+"/foo", strings.NewReader(""))
  1524  				Expect(err).ToNot(HaveOccurred())
  1525  				req = req.WithContext(ctx)
  1526  
  1527  				handler.Handler().ServeHTTP(rr, req)
  1528  				Expect(rr).To(HaveHTTPStatus(expectedStatus))
  1529  				if expectedStatus == http.StatusNotFound {
  1530  					Expect(rr).To(HaveHTTPBody("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<d:error xmlns:d=\"DAV\" xmlns:s=\"http://sabredav.org/ns\"><s:exception>Sabre\\DAV\\Exception\\NotFound</s:exception><s:message>Resource not found</s:message><s:errorcode></s:errorcode></d:error>"), "Body must have a not found sabredav exception")
  1531  				} else {
  1532  					Expect(rr).To(HaveHTTPBody(""), "Body must be empty")
  1533  				}
  1534  			},
  1535  			Entry("at the /webdav endpoint", "/webdav", "/users", "/users/username/foo", "./foo", http.StatusNotFound),
  1536  			Entry("at the /dav/files endpoint", "/dav/files/username", "/users/username", "/users/username/foo", "./foo", http.StatusNotFound),
  1537  			Entry("at the /dav/spaces endpoint", "/dav/spaces/provider-1$userspace!root", "/users/username", "/users/username/foo", "./foo", http.StatusNotFound),
  1538  			Entry("at the /dav/public-files endpoint for a file", "/dav/public-files/tokenforfile", "", "/public/tokenforfolder/foo", "", http.StatusMethodNotAllowed),
  1539  			Entry("at the /dav/public-files endpoint for a folder", "/dav/public-files/tokenforfolder", "/public/tokenforfolder", "/public/tokenforfolder/foo", ".", http.StatusNotFound),
  1540  		)
  1541  
  1542  	})
  1543  
  1544  	Context("When the operation is forbidden", func() {
  1545  
  1546  		DescribeTable("HandleDelete",
  1547  			func(endpoint string, expectedPathPrefix string, expectedPath string, locked, userHasAccess bool, expectedStatus int) {
  1548  
  1549  				client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.ListStorageSpacesRequest) bool {
  1550  					p := string(req.Opaque.Map["path"].Value)
  1551  					return p == "/" || strings.HasPrefix(p, expectedPathPrefix)
  1552  				})).Return(&cs3storageprovider.ListStorageSpacesResponse{
  1553  					Status:        status.NewOK(ctx),
  1554  					StorageSpaces: []*cs3storageprovider.StorageSpace{userspace},
  1555  				}, nil)
  1556  
  1557  				ref := cs3storageprovider.Reference{
  1558  					ResourceId: userspace.Root,
  1559  					Path:       expectedPath,
  1560  				}
  1561  
  1562  				if locked {
  1563  					client.On("Delete", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.DeleteRequest) bool {
  1564  						return utils.ResourceEqual(req.Ref, &ref)
  1565  					})).Return(&cs3storageprovider.DeleteResponse{
  1566  						Opaque: &cs3types.Opaque{Map: map[string]*cs3types.OpaqueEntry{
  1567  							"lockid": {Decoder: "plain", Value: []byte("somelockid")},
  1568  						}},
  1569  						Status: status.NewPermissionDenied(ctx, fmt.Errorf("permission denied error"), "permission denied message"),
  1570  					}, nil)
  1571  				} else {
  1572  					client.On("Delete", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.DeleteRequest) bool {
  1573  						return utils.ResourceEqual(req.Ref, &ref)
  1574  					})).Return(&cs3storageprovider.DeleteResponse{
  1575  						Status: status.NewPermissionDenied(ctx, fmt.Errorf("permission denied error"), "permission denied message"),
  1576  					}, nil)
  1577  				}
  1578  
  1579  				if userHasAccess {
  1580  					mockStatOK(&ref, mockInfo(map[string]interface{}{}))
  1581  				} else {
  1582  					mockStat(&ref, status.NewPermissionDenied(ctx, fmt.Errorf("permission denied error"), "permission denied message"), nil)
  1583  				}
  1584  
  1585  				rr := httptest.NewRecorder()
  1586  				req, err := http.NewRequest("DELETE", endpoint+"/foo", strings.NewReader(""))
  1587  				Expect(err).ToNot(HaveOccurred())
  1588  				req = req.WithContext(ctx)
  1589  
  1590  				handler.Handler().ServeHTTP(rr, req)
  1591  				Expect(rr).To(HaveHTTPStatus(expectedStatus))
  1592  				if expectedStatus == http.StatusMethodNotAllowed {
  1593  					Expect(rr).To(HaveHTTPBody(""), "Body must be empty")
  1594  				} else {
  1595  					if userHasAccess {
  1596  						if locked {
  1597  							Expect(rr).To(HaveHTTPBody("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<d:error xmlns:d=\"DAV\" xmlns:s=\"http://sabredav.org/ns\"><s:exception>Sabre\\DAV\\Exception\\Locked</s:exception><s:message></s:message><s:errorcode></s:errorcode></d:error>"), "Body must have a locked sabredav exception")
  1598  							Expect(rr).To(HaveHTTPHeaderWithValue("Lock-Token", "<somelockid>"))
  1599  						} else {
  1600  							Expect(rr).To(HaveHTTPBody("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<d:error xmlns:d=\"DAV\" xmlns:s=\"http://sabredav.org/ns\"><s:exception>Sabre\\DAV\\Exception\\Forbidden</s:exception><s:message></s:message><s:errorcode></s:errorcode></d:error>"), "Body must have a forbidden sabredav exception")
  1601  						}
  1602  					} else {
  1603  						Expect(rr).To(HaveHTTPBody("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<d:error xmlns:d=\"DAV\" xmlns:s=\"http://sabredav.org/ns\"><s:exception>Sabre\\DAV\\Exception\\NotFound</s:exception><s:message>Resource not found</s:message><s:errorcode></s:errorcode></d:error>"), "Body must have a not found sabredav exception")
  1604  					}
  1605  				}
  1606  			},
  1607  
  1608  			// without lock
  1609  
  1610  			// when user has access he should see forbidden status
  1611  			Entry("at the /webdav endpoint", "/webdav", "/users", "./foo", false, true, http.StatusForbidden),
  1612  			Entry("at the /dav/files endpoint", "/dav/files/username", "/users/username", "./foo", false, true, http.StatusForbidden),
  1613  			Entry("at the /dav/spaces endpoint", "/dav/spaces/provider-1$userspace!root", "/users/username", "./foo", false, true, http.StatusForbidden),
  1614  			Entry("at the /dav/public-files endpoint for a file", "/dav/public-files/tokenforfile", "", "", false, true, http.StatusMethodNotAllowed),
  1615  			Entry("at the /dav/public-files endpoint for a folder", "/dav/public-files/tokenforfolder", "/public/tokenforfolder", ".", false, true, http.StatusForbidden),
  1616  			// when user does not have access he should get not found status
  1617  			Entry("at the /webdav endpoint", "/webdav", "/users", "./foo", false, false, http.StatusNotFound),
  1618  			Entry("at the /dav/files endpoint", "/dav/files/username", "/users/username", "./foo", false, false, http.StatusNotFound),
  1619  			Entry("at the /dav/spaces endpoint", "/dav/spaces/provider-1$userspace!root", "/users/username", "./foo", false, false, http.StatusNotFound),
  1620  			Entry("at the /dav/public-files endpoint for a file", "/dav/public-files/tokenforfile", "", "", false, false, http.StatusMethodNotAllowed),
  1621  			Entry("at the /dav/public-files endpoint for a folder", "/dav/public-files/tokenforfolder", "/public/tokenforfolder", ".", false, false, http.StatusNotFound),
  1622  
  1623  			// With lock
  1624  
  1625  			// when user has access he should see locked status
  1626  			Entry("at the /webdav endpoint", "/webdav", "/users", "./foo", true, true, http.StatusLocked),
  1627  			Entry("at the /dav/files endpoint", "/dav/files/username", "/users/username", "./foo", true, true, http.StatusLocked),
  1628  			Entry("at the /dav/spaces endpoint", "/dav/spaces/provider-1$userspace!root", "/users/username", "./foo", true, true, http.StatusLocked),
  1629  			Entry("at the /dav/public-files endpoint for a file", "/dav/public-files/tokenforfile", "", "", true, true, http.StatusMethodNotAllowed),
  1630  			Entry("at the /dav/public-files endpoint for a folder", "/dav/public-files/tokenforfolder", "/public/tokenforfolder", ".", true, true, http.StatusLocked),
  1631  			// when user does not have access he should get not found status
  1632  			Entry("at the /webdav endpoint", "/webdav", "/users", "./foo", true, false, http.StatusNotFound),
  1633  			Entry("at the /dav/files endpoint", "/dav/files/username", "/users/username", "./foo", true, false, http.StatusNotFound),
  1634  			Entry("at the /dav/spaces endpoint", "/dav/spaces/provider-1$userspace!root", "/users/username", "./foo", true, false, http.StatusNotFound),
  1635  			Entry("at the /dav/public-files endpoint for a file", "/dav/public-files/tokenforfile", "", "", true, false, http.StatusMethodNotAllowed),
  1636  			Entry("at the /dav/public-files endpoint for a folder", "/dav/public-files/tokenforfolder", "/public/tokenforfolder", ".", true, false, http.StatusNotFound),
  1637  		)
  1638  
  1639  		DescribeTable("HandleMkcol",
  1640  			func(endpoint string, expectedPathPrefix string, expectedStatPath string, expectedPath string, locked, userHasAccess bool, expectedStatus int) {
  1641  
  1642  				client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.ListStorageSpacesRequest) bool {
  1643  					p := string(req.Opaque.Map["path"].Value)
  1644  					return p == "/" || strings.HasPrefix(p, expectedPathPrefix)
  1645  				})).Return(&cs3storageprovider.ListStorageSpacesResponse{
  1646  					Status:        status.NewOK(ctx),
  1647  					StorageSpaces: []*cs3storageprovider.StorageSpace{userspace},
  1648  				}, nil)
  1649  
  1650  				// path based requests need to check if the resource already exists
  1651  				mockPathStat(expectedStatPath, status.NewNotFound(ctx, "not found"), nil)
  1652  
  1653  				ref := cs3storageprovider.Reference{
  1654  					ResourceId: userspace.Root,
  1655  					Path:       expectedPath,
  1656  				}
  1657  
  1658  				if locked {
  1659  					client.On("CreateContainer", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.CreateContainerRequest) bool {
  1660  						return utils.ResourceEqual(req.Ref, &ref)
  1661  					})).Return(&cs3storageprovider.CreateContainerResponse{
  1662  						Opaque: &cs3types.Opaque{Map: map[string]*cs3types.OpaqueEntry{
  1663  							"lockid": {Decoder: "plain", Value: []byte("somelockid")},
  1664  						}},
  1665  						Status: status.NewPermissionDenied(ctx, fmt.Errorf("permission denied error"), "permission denied message"),
  1666  					}, nil)
  1667  				} else {
  1668  					client.On("CreateContainer", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.CreateContainerRequest) bool {
  1669  						return utils.ResourceEqual(req.Ref, &ref)
  1670  					})).Return(&cs3storageprovider.CreateContainerResponse{
  1671  						Status: status.NewPermissionDenied(ctx, fmt.Errorf("permission denied error"), "permission denied message"),
  1672  					}, nil)
  1673  				}
  1674  
  1675  				parentRef := cs3storageprovider.Reference{
  1676  					ResourceId: userspace.Root,
  1677  					Path:       utils.MakeRelativePath(path.Dir(expectedPath)),
  1678  				}
  1679  
  1680  				if userHasAccess {
  1681  					mockStatOK(&parentRef, mockInfo(map[string]interface{}{}))
  1682  				} else {
  1683  					mockStat(&parentRef, status.NewPermissionDenied(ctx, fmt.Errorf("permission denied error"), "permission denied message"), nil)
  1684  				}
  1685  
  1686  				rr := httptest.NewRecorder()
  1687  				req, err := http.NewRequest("MKCOL", endpoint+"/foo", strings.NewReader(""))
  1688  				Expect(err).ToNot(HaveOccurred())
  1689  				req = req.WithContext(ctx)
  1690  
  1691  				handler.Handler().ServeHTTP(rr, req)
  1692  				Expect(rr).To(HaveHTTPStatus(expectedStatus))
  1693  				if expectedStatus == http.StatusMethodNotAllowed {
  1694  					Expect(rr).To(HaveHTTPBody(""), "Body must be empty")
  1695  				} else {
  1696  					if userHasAccess {
  1697  						if locked {
  1698  							Expect(rr).To(HaveHTTPBody("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<d:error xmlns:d=\"DAV\" xmlns:s=\"http://sabredav.org/ns\"><s:exception>Sabre\\DAV\\Exception\\Locked</s:exception><s:message></s:message><s:errorcode></s:errorcode></d:error>"), "Body must have a locked sabredav exception")
  1699  							Expect(rr).To(HaveHTTPHeaderWithValue("Lock-Token", "<somelockid>"))
  1700  						} else {
  1701  							Expect(rr).To(HaveHTTPBody("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<d:error xmlns:d=\"DAV\" xmlns:s=\"http://sabredav.org/ns\"><s:exception>Sabre\\DAV\\Exception\\Forbidden</s:exception><s:message></s:message><s:errorcode></s:errorcode></d:error>"), "Body must have a forbidden sabredav exception")
  1702  						}
  1703  					} else {
  1704  						Expect(rr).To(HaveHTTPBody("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<d:error xmlns:d=\"DAV\" xmlns:s=\"http://sabredav.org/ns\"><s:exception>Sabre\\DAV\\Exception\\NotFound</s:exception><s:message>Resource not found</s:message><s:errorcode></s:errorcode></d:error>"), "Body must have a not found sabredav exception")
  1705  					}
  1706  				}
  1707  			},
  1708  
  1709  			// without lock
  1710  
  1711  			// when user has access he should see forbidden status
  1712  			Entry("at the /webdav endpoint", "/webdav", "/users", "/users/username/foo", "./foo", false, true, http.StatusForbidden),
  1713  			Entry("at the /dav/files endpoint", "/dav/files/username", "/users/username", "/users/username/foo", "./foo", false, true, http.StatusForbidden),
  1714  			Entry("at the /dav/spaces endpoint", "/dav/spaces/provider-1$userspace!root", "/users/username", "/users/username/foo", "./foo", false, true, http.StatusForbidden),
  1715  			Entry("at the /dav/public-files endpoint for a file", "/dav/public-files/tokenforfile", "", "/public/tokenforfolder/foo", "", false, true, http.StatusMethodNotAllowed),
  1716  			Entry("at the /dav/public-files endpoint for a folder", "/dav/public-files/tokenforfolder", "/public/tokenforfolder", "/public/tokenforfolder/foo", ".", false, true, http.StatusForbidden),
  1717  			// when user does not have access he should get not found status
  1718  			Entry("at the /webdav endpoint", "/webdav", "/users", "/users/username/foo", "./foo", false, false, http.StatusNotFound),
  1719  			Entry("at the /dav/files endpoint", "/dav/files/username", "/users/username", "/users/username/foo", "./foo", false, false, http.StatusNotFound),
  1720  			Entry("at the /dav/spaces endpoint", "/dav/spaces/provider-1$userspace!root", "/users/username", "/users/username/foo", "./foo", false, false, http.StatusNotFound),
  1721  			Entry("at the /dav/public-files endpoint for a file", "/dav/public-files/tokenforfile", "", "/public/tokenforfolder/foo", "", false, false, http.StatusMethodNotAllowed),
  1722  			Entry("at the /dav/public-files endpoint for a folder", "/dav/public-files/tokenforfolder", "/public/tokenforfolder", "/public/tokenforfolder/foo", ".", false, false, http.StatusNotFound),
  1723  
  1724  			// With lock
  1725  
  1726  			// when user has access he should see locked status
  1727  			// FIXME currently the ocdav mkcol handler is not forwarding a lockid ... but decomposedfs at least cannot create locks for unmapped resources, yet
  1728  			PEntry("at the /webdav endpoint", "/webdav", "/users", "/users/username/foo", "./foo", true, true, http.StatusLocked),
  1729  			PEntry("at the /dav/files endpoint", "/dav/files/username", "/users/username", "/users/username/foo", "./foo", true, true, http.StatusLocked),
  1730  			PEntry("at the /dav/spaces endpoint", "/dav/spaces/provider-1$userspace!root", "/users/username", "/users/username/foo", "./foo", true, true, http.StatusLocked),
  1731  			Entry("at the /dav/public-files endpoint for a file", "/dav/public-files/tokenforfile", "", "/public/tokenforfolder/foo", "", true, true, http.StatusMethodNotAllowed),
  1732  			PEntry("at the /dav/public-files endpoint for a folder", "/dav/public-files/tokenforfolder", "/public/tokenforfolder", "/public/tokenforfolder/foo", ".", true, true, http.StatusLocked),
  1733  			// when user does not have access he should get not found status
  1734  			Entry("at the /webdav endpoint", "/webdav", "/users", "/users/username/foo", "./foo", true, false, http.StatusNotFound),
  1735  			Entry("at the /dav/files endpoint", "/dav/files/username", "/users/username", "/users/username/foo", "./foo", true, false, http.StatusNotFound),
  1736  			Entry("at the /dav/spaces endpoint", "/dav/spaces/provider-1$userspace!root", "/users/username", "/users/username/foo", "./foo", true, false, http.StatusNotFound),
  1737  			Entry("at the /dav/public-files endpoint for a file", "/dav/public-files/tokenforfile", "", "/public/tokenforfolder/foo", "", true, false, http.StatusMethodNotAllowed),
  1738  			Entry("at the /dav/public-files endpoint for a folder", "/dav/public-files/tokenforfolder", "/public/tokenforfolder", "/public/tokenforfolder/foo", ".", true, false, http.StatusNotFound),
  1739  		)
  1740  
  1741  	})
  1742  	// listing spaces is a precondition for path based requests, what if listing spaces currently is broken?
  1743  	Context("locks are forwarded", func() {
  1744  
  1745  		DescribeTable("HandleDelete",
  1746  			func(endpoint string, expectedPathPrefix string, expectedPath string, expectedStatus int) {
  1747  
  1748  				client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.ListStorageSpacesRequest) bool {
  1749  					p := string(req.Opaque.Map["path"].Value)
  1750  					return p == "/" || strings.HasPrefix(p, expectedPathPrefix)
  1751  				})).Return(&cs3storageprovider.ListStorageSpacesResponse{
  1752  					Status:        status.NewOK(ctx),
  1753  					StorageSpaces: []*cs3storageprovider.StorageSpace{userspace},
  1754  				}, nil)
  1755  
  1756  				ref := cs3storageprovider.Reference{
  1757  					ResourceId: userspace.Root,
  1758  					Path:       expectedPath,
  1759  				}
  1760  
  1761  				client.On("Delete", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.DeleteRequest) bool {
  1762  					Expect(utils.ReadPlainFromOpaque(req.Opaque, "lockid")).To(Equal("urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2"))
  1763  					return utils.ResourceEqual(req.Ref, &ref)
  1764  				})).Return(&cs3storageprovider.DeleteResponse{
  1765  					Status: status.NewOK(ctx),
  1766  				}, nil)
  1767  
  1768  				rr := httptest.NewRecorder()
  1769  				req, err := http.NewRequest("DELETE", endpoint+"/foo", strings.NewReader(""))
  1770  				req.Header.Set("If", "(<urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2>)")
  1771  				Expect(err).ToNot(HaveOccurred())
  1772  				req = req.WithContext(ctx)
  1773  
  1774  				handler.Handler().ServeHTTP(rr, req)
  1775  				Expect(rr).To(HaveHTTPStatus(expectedStatus))
  1776  			},
  1777  			Entry("at the /webdav endpoint", "/webdav", "/users", "./foo", http.StatusNoContent),
  1778  			Entry("at the /dav/files endpoint", "/dav/files/username", "/users/username", "./foo", http.StatusNoContent),
  1779  			Entry("at the /dav/spaces endpoint", "/dav/spaces/provider-1$userspace!root", "/users/username", "./foo", http.StatusNoContent),
  1780  			Entry("at the /dav/public-files endpoint for a file", "/dav/public-files/tokenforfile", "", "", http.StatusMethodNotAllowed),
  1781  			Entry("at the /dav/public-files endpoint for a folder", "/dav/public-files/tokenforfolder", "/public/tokenforfolder", ".", http.StatusNoContent),
  1782  		)
  1783  
  1784  		// FIXME currently the ocdav mkcol handler is not forwarding a lockid ... but decomposedfs at least cannot create locks for unmapped resources, yet
  1785  		PDescribeTable("HandleMkcol",
  1786  			func(endpoint string, expectedPathPrefix string, expectedStatPath string, expectedPath string, expectedStatus int) {
  1787  
  1788  				client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.ListStorageSpacesRequest) bool {
  1789  					p := string(req.Opaque.Map["path"].Value)
  1790  					return p == "/" || strings.HasPrefix(p, expectedPathPrefix)
  1791  				})).Return(&cs3storageprovider.ListStorageSpacesResponse{
  1792  					Status:        status.NewOK(ctx),
  1793  					StorageSpaces: []*cs3storageprovider.StorageSpace{userspace},
  1794  				}, nil)
  1795  
  1796  				// path based requests need to check if the resource already exists
  1797  				mockPathStat(expectedStatPath, status.NewNotFound(ctx, "not found"), nil)
  1798  
  1799  				ref := cs3storageprovider.Reference{
  1800  					ResourceId: userspace.Root,
  1801  					Path:       expectedPath,
  1802  				}
  1803  
  1804  				client.On("CreateContainer", mock.Anything, mock.MatchedBy(func(req *cs3storageprovider.CreateContainerRequest) bool {
  1805  					Expect(utils.ReadPlainFromOpaque(req.Opaque, "lockid")).To(Equal("urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2"))
  1806  					return utils.ResourceEqual(req.Ref, &ref)
  1807  				})).Return(&cs3storageprovider.CreateContainerResponse{
  1808  					Status: status.NewOK(ctx),
  1809  				}, nil)
  1810  
  1811  				rr := httptest.NewRecorder()
  1812  				req, err := http.NewRequest("MKCOL", endpoint+"/foo", strings.NewReader(""))
  1813  				req.Header.Set("If", "(<urn:uuid:181d4fae-7d8c-11d0-a765-00a0c91e6bf2>)")
  1814  				Expect(err).ToNot(HaveOccurred())
  1815  				req = req.WithContext(ctx)
  1816  
  1817  				handler.Handler().ServeHTTP(rr, req)
  1818  				Expect(rr).To(HaveHTTPStatus(expectedStatus))
  1819  			},
  1820  			Entry("at the /webdav endpoint", "/webdav", "/users", "/users/username/foo", "./foo", http.StatusNoContent),
  1821  			Entry("at the /dav/files endpoint", "/dav/files/username", "/users/username", "/users/username/foo", "./foo", http.StatusNoContent),
  1822  			Entry("at the /dav/spaces endpoint", "/dav/spaces/provider-1$userspace!root", "/users/username", "/users/username/foo", "./foo", http.StatusNoContent),
  1823  			Entry("at the /dav/public-files endpoint for a file", "/dav/public-files/tokenforfile", "", "/public/tokenforfolder/foo", "", http.StatusMethodNotAllowed),
  1824  			Entry("at the /dav/public-files endpoint for a folder", "/dav/public-files/tokenforfolder", "/public/tokenforfolder", "/public/tokenforfolder/foo", ".", http.StatusNoContent),
  1825  		)
  1826  
  1827  	})
  1828  
  1829  })