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 })