github.com/cs3org/reva/v2@v2.27.7/pkg/storage/fs/nextcloud/nextcloud_test.go (about)

     1  // Copyright 2018-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  
    19  package nextcloud_test
    20  
    21  import (
    22  	"context"
    23  	// "fmt"
    24  	"io"
    25  	"net/url"
    26  	"os"
    27  	"strings"
    28  
    29  	userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
    30  	provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
    31  	types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
    32  	. "github.com/onsi/ginkgo/v2"
    33  	. "github.com/onsi/gomega"
    34  	"google.golang.org/grpc/metadata"
    35  	"google.golang.org/protobuf/testing/protocmp"
    36  
    37  	"github.com/cs3org/reva/v2/pkg/auth/scope"
    38  	ctxpkg "github.com/cs3org/reva/v2/pkg/ctx"
    39  	"github.com/cs3org/reva/v2/pkg/storage"
    40  	"github.com/cs3org/reva/v2/pkg/storage/fs/nextcloud"
    41  	jwt "github.com/cs3org/reva/v2/pkg/token/manager/jwt"
    42  )
    43  
    44  func setUpNextcloudServer() (*nextcloud.StorageDriver, *[]string, func()) {
    45  	var conf *nextcloud.StorageDriverConfig
    46  
    47  	ncHost := os.Getenv("NEXTCLOUD")
    48  	if len(ncHost) == 0 {
    49  		conf = &nextcloud.StorageDriverConfig{
    50  			EndPoint: "http://mock.com/apps/sciencemesh/",
    51  			MockHTTP: true,
    52  		}
    53  		nc, _ := nextcloud.NewStorageDriver(conf)
    54  		called := make([]string, 0)
    55  		h := nextcloud.GetNextcloudServerMock(&called)
    56  		mock, teardown := nextcloud.TestingHTTPClient(h)
    57  		nc.SetHTTPClient(mock)
    58  		return nc, &called, teardown
    59  	}
    60  	conf = &nextcloud.StorageDriverConfig{
    61  		EndPoint: ncHost + "/apps/sciencemesh/",
    62  		MockHTTP: false,
    63  	}
    64  	nc, _ := nextcloud.NewStorageDriver(conf)
    65  	return nc, nil, func() {}
    66  }
    67  
    68  func checkCalled(called *[]string, expected string) {
    69  	if called == nil {
    70  		return
    71  	}
    72  	Expect(len(*called)).To(BeElementOf([]int{1, 2}))
    73  	Expect((*called)[0]).To(Equal(expected))
    74  }
    75  
    76  var _ = Describe("Nextcloud", func() {
    77  	var (
    78  		ctx     context.Context
    79  		options map[string]interface{}
    80  		tmpRoot string
    81  		user    = &userpb.User{
    82  			Id: &userpb.UserId{
    83  				Idp:      "0.0.0.0:19000",
    84  				OpaqueId: "tester",
    85  				Type:     userpb.UserType_USER_TYPE_PRIMARY,
    86  			},
    87  			Username: "tester",
    88  		}
    89  	)
    90  
    91  	BeforeEach(func() {
    92  		var err error
    93  
    94  		options = map[string]interface{}{
    95  			"endpoint":  "http://mock.com/apps/sciencemesh/",
    96  			"mock_http": true,
    97  		}
    98  
    99  		ctx = context.Background()
   100  
   101  		// Add auth token
   102  		tokenManager, err := jwt.New(map[string]interface{}{"secret": "changemeplease"})
   103  		Expect(err).ToNot(HaveOccurred())
   104  		scope, err := scope.AddOwnerScope(nil)
   105  		Expect(err).ToNot(HaveOccurred())
   106  		t, err := tokenManager.MintToken(ctx, user, scope)
   107  		Expect(err).ToNot(HaveOccurred())
   108  		ctx = ctxpkg.ContextSetToken(ctx, t)
   109  		ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, t)
   110  		ctx = ctxpkg.ContextSetUser(ctx, user)
   111  	})
   112  
   113  	AfterEach(func() {
   114  		if tmpRoot != "" {
   115  			os.RemoveAll(tmpRoot)
   116  		}
   117  	})
   118  
   119  	Describe("New", func() {
   120  		It("returns a new instance", func() {
   121  			_, err := nextcloud.New(options, nil, nil)
   122  			Expect(err).ToNot(HaveOccurred())
   123  		})
   124  	})
   125  
   126  	// 	GetHome(ctx context.Context) (string, error)
   127  	Describe("GetHome", func() {
   128  		It("calls the GetHome endpoint", func() {
   129  			nc, called, teardown := setUpNextcloudServer()
   130  			defer teardown()
   131  			home, err := nc.GetHome(ctx)
   132  			Expect(home).To(Equal("yes we are"))
   133  			Expect(err).ToNot(HaveOccurred())
   134  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/GetHome `)
   135  		})
   136  	})
   137  
   138  	// CreateHome(ctx context.Context) error
   139  	Describe("CreateHome", func() {
   140  		It("calls the CreateHome endpoint", func() {
   141  			nc, called, teardown := setUpNextcloudServer()
   142  			defer teardown()
   143  			err := nc.CreateHome(ctx)
   144  			Expect(err).ToNot(HaveOccurred())
   145  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/CreateHome `)
   146  		})
   147  	})
   148  
   149  	// CreateDir(ctx context.Context, ref *provider.Reference) error
   150  	Describe("CreateDir", func() {
   151  		It("calls the CreateDir endpoint", func() {
   152  			nc, called, teardown := setUpNextcloudServer()
   153  			defer teardown()
   154  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L550-L561
   155  			ref := &provider.Reference{
   156  				ResourceId: &provider.ResourceId{
   157  					StorageId: "storage-id",
   158  					OpaqueId:  "opaque-id",
   159  				},
   160  				Path: "/some/path",
   161  			}
   162  			err := nc.CreateDir(ctx, ref)
   163  			Expect(err).ToNot(HaveOccurred())
   164  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/CreateDir {"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"/some/path"}`)
   165  		})
   166  	})
   167  
   168  	// Delete(ctx context.Context, ref *provider.Reference) error
   169  	Describe("Delete", func() {
   170  		It("calls the Delete endpoint", func() {
   171  			nc, called, teardown := setUpNextcloudServer()
   172  			defer teardown()
   173  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L550-L561
   174  			ref := &provider.Reference{
   175  				ResourceId: &provider.ResourceId{
   176  					StorageId: "storage-id",
   177  					OpaqueId:  "opaque-id",
   178  				},
   179  				Path: "/some/path",
   180  			}
   181  			err := nc.Delete(ctx, ref)
   182  			Expect(err).ToNot(HaveOccurred())
   183  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/Delete {"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"/some/path"}`)
   184  		})
   185  	})
   186  
   187  	// Move(ctx context.Context, oldRef, newRef *provider.Reference) error
   188  	Describe("Move", func() {
   189  		It("calls the Move endpoint", func() {
   190  			nc, called, teardown := setUpNextcloudServer()
   191  			defer teardown()
   192  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L550-L561
   193  			ref1 := &provider.Reference{
   194  				ResourceId: &provider.ResourceId{
   195  					StorageId: "storage-id-1",
   196  					OpaqueId:  "opaque-id-1",
   197  				},
   198  				Path: "/some/old/path",
   199  			}
   200  			ref2 := &provider.Reference{
   201  				ResourceId: &provider.ResourceId{
   202  					StorageId: "storage-id-2",
   203  					OpaqueId:  "opaque-id-2",
   204  				},
   205  				Path: "/some/new/path",
   206  			}
   207  			err := nc.Move(ctx, ref1, ref2)
   208  			Expect(err).ToNot(HaveOccurred())
   209  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/Move {"oldRef":{"resource_id":{"storage_id":"storage-id-1","opaque_id":"opaque-id-1"},"path":"/some/old/path"},"newRef":{"resource_id":{"storage_id":"storage-id-2","opaque_id":"opaque-id-2"},"path":"/some/new/path"}}`)
   210  		})
   211  	})
   212  
   213  	// GetMD(ctx context.Context, ref *provider.Reference, mdKeys []string) (*provider.ResourceInfo, error)
   214  	Describe("GetMD", func() {
   215  		It("calls the GetMD endpoint", func() {
   216  			nc, called, teardown := setUpNextcloudServer()
   217  			defer teardown()
   218  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L550-L561
   219  			ref := &provider.Reference{
   220  				ResourceId: &provider.ResourceId{
   221  					StorageId: "storage-id",
   222  					OpaqueId:  "opaque-id",
   223  				},
   224  				Path: "/some/path",
   225  			}
   226  			mdKeys := []string{"val1", "val2", "val3"}
   227  			result, err := nc.GetMD(ctx, ref, mdKeys, nil)
   228  			Expect(err).ToNot(HaveOccurred())
   229  			Expect(result).To(BeComparableTo(provider.ResourceInfo{
   230  				Opaque: &types.Opaque{
   231  					Map: nil,
   232  				},
   233  				Type: provider.ResourceType_RESOURCE_TYPE_FILE,
   234  				Id: &provider.ResourceId{
   235  					StorageId: "",
   236  					OpaqueId:  "fileid-/some/path",
   237  				},
   238  				Checksum: &provider.ResourceChecksum{
   239  					Type: 0,
   240  					Sum:  "",
   241  				},
   242  				Etag:     "deadbeef",
   243  				MimeType: "text/plain",
   244  				Mtime: &types.Timestamp{
   245  					Seconds: 1234567890,
   246  					Nanos:   0,
   247  				},
   248  				Path: "/some/path",
   249  				PermissionSet: &provider.ResourcePermissions{
   250  					AddGrant:             false,
   251  					CreateContainer:      false,
   252  					Delete:               false,
   253  					GetPath:              false,
   254  					GetQuota:             false,
   255  					InitiateFileDownload: false,
   256  					InitiateFileUpload:   false,
   257  					ListGrants:           false,
   258  					ListContainer:        false,
   259  					ListFileVersions:     false,
   260  					ListRecycle:          false,
   261  					Move:                 false,
   262  					RemoveGrant:          false,
   263  					PurgeRecycle:         false,
   264  					RestoreFileVersion:   false,
   265  					RestoreRecycleItem:   false,
   266  					Stat:                 false,
   267  					UpdateGrant:          false,
   268  					DenyGrant:            false,
   269  				},
   270  				Size:   12345,
   271  				Owner:  nil,
   272  				Target: "",
   273  				CanonicalMetadata: &provider.CanonicalMetadata{
   274  					Target: nil,
   275  				},
   276  				ArbitraryMetadata: &provider.ArbitraryMetadata{
   277  					Metadata: map[string]string{"some": "arbi", "trary": "meta", "da": "ta"},
   278  				},
   279  			}, protocmp.Transform()))
   280  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/GetMD {"ref":{"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"/some/path"},"mdKeys":["val1","val2","val3"]}`)
   281  		})
   282  	})
   283  
   284  	// ListFolder(ctx context.Context, ref *provider.Reference, mdKeys []string) ([]*provider.ResourceInfo, error)
   285  	Describe("ListFolder", func() {
   286  		It("calls the ListFolder endpoint", func() {
   287  			nc, called, teardown := setUpNextcloudServer()
   288  			defer teardown()
   289  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L550-L561
   290  			ref := &provider.Reference{
   291  				ResourceId: &provider.ResourceId{
   292  					StorageId: "storage-id",
   293  					OpaqueId:  "opaque-id",
   294  				},
   295  				Path: "/some",
   296  			}
   297  			mdKeys := []string{"val1", "val2", "val3"}
   298  			results, err := nc.ListFolder(ctx, ref, mdKeys, []string{})
   299  			Expect(err).NotTo(HaveOccurred())
   300  			Expect(len(results)).To(Equal(1))
   301  			Expect(results[0]).To(BeComparableTo(&provider.ResourceInfo{
   302  				Opaque: &types.Opaque{
   303  					Map: nil,
   304  				},
   305  				Type: provider.ResourceType_RESOURCE_TYPE_FILE,
   306  				Id: &provider.ResourceId{
   307  					StorageId: "",
   308  					OpaqueId:  "fileid-/some/path",
   309  				},
   310  				Checksum: &provider.ResourceChecksum{
   311  					Type: 0,
   312  					Sum:  "",
   313  				},
   314  				Etag:     "deadbeef",
   315  				MimeType: "text/plain",
   316  				Mtime: &types.Timestamp{
   317  					Seconds: 1234567890,
   318  					Nanos:   0,
   319  				},
   320  				Path: "/some/path",
   321  				PermissionSet: &provider.ResourcePermissions{
   322  					AddGrant:             false,
   323  					CreateContainer:      false,
   324  					Delete:               false,
   325  					GetPath:              false,
   326  					GetQuota:             false,
   327  					InitiateFileDownload: false,
   328  					InitiateFileUpload:   false,
   329  					ListGrants:           false,
   330  					ListContainer:        false,
   331  					ListFileVersions:     false,
   332  					ListRecycle:          false,
   333  					Move:                 false,
   334  					RemoveGrant:          false,
   335  					PurgeRecycle:         false,
   336  					RestoreFileVersion:   false,
   337  					RestoreRecycleItem:   false,
   338  					Stat:                 false,
   339  					UpdateGrant:          false,
   340  					DenyGrant:            false,
   341  				},
   342  				Size:   12345,
   343  				Owner:  nil,
   344  				Target: "",
   345  				CanonicalMetadata: &provider.CanonicalMetadata{
   346  					Target: nil,
   347  				},
   348  				ArbitraryMetadata: &provider.ArbitraryMetadata{
   349  					Metadata: map[string]string{"some": "arbi", "trary": "meta", "da": "ta"},
   350  				},
   351  			}, protocmp.Transform()))
   352  			Expect(err).ToNot(HaveOccurred())
   353  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/ListFolder {"ref":{"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"/some"},"mdKeys":["val1","val2","val3"]}`)
   354  		})
   355  	})
   356  
   357  	// InitiateUpload(ctx context.Context, ref *provider.Reference, uploadLength int64, metadata map[string]string) (map[string]string, error)
   358  	Describe("InitiateUpload", func() {
   359  		It("calls the InitiateUpload endpoint", func() {
   360  			nc, called, teardown := setUpNextcloudServer()
   361  			defer teardown()
   362  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L550-L561
   363  			ref := &provider.Reference{
   364  				ResourceId: &provider.ResourceId{
   365  					StorageId: "storage-id",
   366  					OpaqueId:  "opaque-id",
   367  				},
   368  				Path: "/some/path",
   369  			}
   370  			uploadLength := int64(12345)
   371  			metadata := map[string]string{
   372  				"key1": "val1",
   373  				"key2": "val2",
   374  				"key3": "val3",
   375  			}
   376  			results, err := nc.InitiateUpload(ctx, ref, uploadLength, metadata)
   377  			Expect(err).ToNot(HaveOccurred())
   378  			Expect(results).To(Equal(map[string]string{
   379  				"not":      "sure",
   380  				"what":     "should be",
   381  				"returned": "here",
   382  			}))
   383  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/InitiateUpload {"ref":{"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"/some/path"},"uploadLength":12345,"metadata":{"key1":"val1","key2":"val2","key3":"val3"}}`)
   384  		})
   385  	})
   386  
   387  	// Upload(ctx context.Context, ref *provider.Reference, r io.ReadCloser) error
   388  	Describe("Upload", func() {
   389  		It("calls the Upload endpoint", func() {
   390  			nc, called, teardown := setUpNextcloudServer()
   391  			defer teardown()
   392  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L550-L561
   393  			ref := &provider.Reference{
   394  				ResourceId: &provider.ResourceId{
   395  					StorageId: "storage-id",
   396  					OpaqueId:  "opaque-id",
   397  				},
   398  				Path: "some/file/path.txt",
   399  			}
   400  			stringReader := strings.NewReader("shiny!")
   401  			stringReadCloser := io.NopCloser(stringReader)
   402  			_, err := nc.Upload(ctx, storage.UploadRequest{Ref: ref, Body: stringReadCloser, Length: stringReader.Size()}, nil)
   403  			Expect(err).ToNot(HaveOccurred())
   404  			Expect(len(*called)).To(Equal(2))
   405  			Expect((*called)[0]).To(Equal(`PUT /apps/sciencemesh/~tester/api/storage/Upload/some/file/path.txt shiny!`))
   406  			Expect((*called)[1]).To(Equal(`POST /apps/sciencemesh/~tester/api/storage/GetMD {"ref":{"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"},"mdKeys":[]}`))
   407  		})
   408  	})
   409  	// Download(ctx context.Context, ref *provider.Reference, openReaderfunc func(*provider.ResourceInfo) bool) (*provider.ResourceInfo, io.ReadCloser, error)
   410  	Describe("Download", func() {
   411  		It("calls the Download endpoint with GET", func() {
   412  			nc, called, teardown := setUpNextcloudServer()
   413  			defer teardown()
   414  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L550-L561
   415  			ref := &provider.Reference{
   416  				ResourceId: &provider.ResourceId{
   417  					StorageId: "storage-id",
   418  					OpaqueId:  "opaque-id",
   419  				},
   420  				Path: "some/file/path.txt",
   421  			}
   422  			_, reader, err := nc.Download(ctx, ref, func(ri *provider.ResourceInfo) bool { return true })
   423  			Expect(err).ToNot(HaveOccurred())
   424  			Expect(err).ToNot(HaveOccurred())
   425  			Expect(len(*called)).To(Equal(2))
   426  			Expect((*called)[0]).To(Equal(`POST /apps/sciencemesh/~tester/api/storage/GetMD {"ref":{"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"},"mdKeys":[]}`))
   427  			Expect((*called)[1]).To(Equal(`GET /apps/sciencemesh/~tester/api/storage/Download/some/file/path.txt `))
   428  
   429  			defer reader.Close()
   430  			body, err := io.ReadAll(reader)
   431  			Expect(err).ToNot(HaveOccurred())
   432  			Expect(string(body)).To(Equal("the contents of the file"))
   433  		})
   434  	})
   435  
   436  	// ListRevisions(ctx context.Context, ref *provider.Reference) ([]*provider.FileVersion, error)
   437  	Describe("ListRevisions", func() {
   438  		It("calls the ListRevisions endpoint", func() {
   439  			nc, called, teardown := setUpNextcloudServer()
   440  			defer teardown()
   441  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L550-L561
   442  			ref := &provider.Reference{
   443  				ResourceId: &provider.ResourceId{
   444  					StorageId: "storage-id",
   445  					OpaqueId:  "opaque-id",
   446  				},
   447  				Path: "/some/path",
   448  			}
   449  			results, err := nc.ListRevisions(ctx, ref)
   450  			Expect(err).ToNot(HaveOccurred())
   451  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L1003-L1023
   452  			Expect(len(results)).To(Equal(2))
   453  			Expect(results[0]).To(BeComparableTo(&provider.FileVersion{
   454  				Opaque: &types.Opaque{
   455  					Map: map[string]*types.OpaqueEntry{
   456  						"some": {
   457  							Value: []byte("data"),
   458  						},
   459  					},
   460  				},
   461  				Key:   "version-12",
   462  				Size:  uint64(12345),
   463  				Mtime: uint64(1234567890),
   464  				Etag:  "deadb00f",
   465  			}, protocmp.Transform()))
   466  			Expect(results[1]).To(BeComparableTo(&provider.FileVersion{
   467  				Opaque: &types.Opaque{
   468  					Map: map[string]*types.OpaqueEntry{
   469  						"different": {
   470  							Value: []byte("stuff"),
   471  						},
   472  					},
   473  				},
   474  				Key:   "asdf",
   475  				Size:  uint64(12345),
   476  				Mtime: uint64(1234567890),
   477  				Etag:  "deadbeef",
   478  			}, protocmp.Transform()))
   479  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/ListRevisions {"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"/some/path"}`)
   480  		})
   481  	})
   482  
   483  	// DownloadRevision(ctx context.Context, ref *provider.Reference, key string) (io.ReadCloser, error)
   484  	Describe("DownloadRevision", func() {
   485  		It("calls the DownloadRevision endpoint with GET", func() {
   486  			nc, called, teardown := setUpNextcloudServer()
   487  			defer teardown()
   488  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L550-L561
   489  			ref := &provider.Reference{
   490  				ResourceId: &provider.ResourceId{
   491  					StorageId: "storage-id",
   492  					OpaqueId:  "opaque-id",
   493  				},
   494  				Path: "some/file/path.txt",
   495  			}
   496  			key := "some/revision"
   497  			_, reader, err := nc.DownloadRevision(ctx, ref, key, func(ri *provider.ResourceInfo) bool { return true })
   498  			Expect(err).ToNot(HaveOccurred())
   499  			Expect(len(*called)).To(Equal(3))
   500  			Expect((*called)[0]).To(Equal(`POST /apps/sciencemesh/~tester/api/storage/GetMD {"ref":{"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"},"mdKeys":[]}`))
   501  			Expect((*called)[1]).To(Equal(`POST /apps/sciencemesh/~tester/api/storage/ListRevisions {"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"}`))
   502  			Expect((*called)[2]).To(Equal(`GET /apps/sciencemesh/~tester/api/storage/DownloadRevision/some%2Frevision/some/file/path.txt `))
   503  			defer reader.Close()
   504  			body, err := io.ReadAll(reader)
   505  			Expect(err).ToNot(HaveOccurred())
   506  			Expect(string(body)).To(Equal("the contents of that revision"))
   507  		})
   508  	})
   509  
   510  	// RestoreRevision(ctx context.Context, ref *provider.Reference, key string) error
   511  	Describe("RestoreRevision", func() {
   512  		It("calls the RestoreRevision endpoint", func() {
   513  			nc, called, teardown := setUpNextcloudServer()
   514  			defer teardown()
   515  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L550-L561
   516  			ref := &provider.Reference{
   517  				ResourceId: &provider.ResourceId{
   518  					StorageId: "storage-id",
   519  					OpaqueId:  "opaque-id",
   520  				},
   521  				Path: "some/file/path.txt",
   522  			}
   523  			key := "asdf"
   524  			err := nc.RestoreRevision(ctx, ref, key)
   525  			Expect(err).ToNot(HaveOccurred())
   526  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/RestoreRevision {"ref":{"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"},"key":"asdf"}`)
   527  		})
   528  	})
   529  
   530  	// ListRecycle(ctx context.Context, key, path string) ([]*provider.RecycleItem, error)
   531  	Describe("ListRecycle", func() {
   532  		It("calls the ListRecycle endpoint", func() {
   533  			nc, called, teardown := setUpNextcloudServer()
   534  			defer teardown()
   535  
   536  			results, err := nc.ListRecycle(ctx, nil, "asdf", "/some/file.txt")
   537  			Expect(err).ToNot(HaveOccurred())
   538  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L1085-L1110
   539  			Expect(len(results)).To(Equal(1))
   540  			Expect(results[0]).To(BeComparableTo(&provider.RecycleItem{
   541  				Opaque: &types.Opaque{},
   542  				Key:    "some-deleted-version",
   543  				Ref: &provider.Reference{
   544  					ResourceId: &provider.ResourceId{},
   545  					Path:       "/some/file.txt",
   546  				},
   547  				Size:         uint64(12345),
   548  				DeletionTime: &types.Timestamp{Seconds: uint64(1234567890)},
   549  			}, protocmp.Transform()))
   550  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/ListRecycle {"key":"asdf","path":"/some/file.txt"}`)
   551  		})
   552  	})
   553  
   554  	// RestoreRecycleItem(ctx context.Context, key, path string, restoreRef *provider.Reference) error
   555  	Describe("RestoreRecycleItem", func() {
   556  		It("calls the RestoreRecycleItem endpoint", func() {
   557  			nc, called, teardown := setUpNextcloudServer()
   558  			defer teardown()
   559  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L550-L561
   560  			restoreRef := &provider.Reference{
   561  				ResourceId: &provider.ResourceId{
   562  					StorageId: "storage-id",
   563  					OpaqueId:  "opaque-id",
   564  				},
   565  				Path: "some/file/path.txt",
   566  			}
   567  			path := "original/location/when/deleted.txt"
   568  			key := "asdf"
   569  			err := nc.RestoreRecycleItem(ctx, nil, key, path, restoreRef)
   570  			Expect(err).ToNot(HaveOccurred())
   571  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/RestoreRecycleItem {"key":"asdf","path":"original/location/when/deleted.txt","restoreRef":{"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"}}`)
   572  		})
   573  	})
   574  	// PurgeRecycleItem(ctx context.Context, key, path string) error
   575  	Describe("PurgeRecycleItem", func() {
   576  		It("calls the PurgeRecycleItem endpoint", func() {
   577  			nc, called, teardown := setUpNextcloudServer()
   578  			defer teardown()
   579  			path := "original/location/when/deleted.txt"
   580  			key := "asdf"
   581  			err := nc.PurgeRecycleItem(ctx, nil, key, path)
   582  			Expect(err).ToNot(HaveOccurred())
   583  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/PurgeRecycleItem {"key":"asdf","path":"original/location/when/deleted.txt"}`)
   584  		})
   585  	})
   586  
   587  	// EmptyRecycle(ctx context.Context) error
   588  	Describe("EmpytRecycle", func() {
   589  		It("calls the EmpytRecycle endpoint", func() {
   590  			nc, called, teardown := setUpNextcloudServer()
   591  			defer teardown()
   592  			err := nc.EmptyRecycle(ctx, nil)
   593  			Expect(err).ToNot(HaveOccurred())
   594  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/EmptyRecycle `)
   595  		})
   596  	})
   597  
   598  	// GetPathByID(ctx context.Context, id *provider.ResourceId) (string, error)
   599  	Describe("GetPathByID", func() {
   600  		It("calls the GetPathByID endpoint", func() {
   601  			nc, called, teardown := setUpNextcloudServer()
   602  			defer teardown()
   603  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L602-L618
   604  			id := &provider.ResourceId{
   605  				StorageId: "storage-id",
   606  				OpaqueId:  "opaque-id",
   607  			}
   608  			path, err := nc.GetPathByID(ctx, id)
   609  			Expect(err).ToNot(HaveOccurred())
   610  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/GetPathByID {"storage_id":"storage-id","opaque_id":"opaque-id"}`)
   611  			Expect(path).To(Equal("the/path/for/that/id.txt"))
   612  		})
   613  	})
   614  
   615  	// AddGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
   616  	Describe("AddGrant", func() {
   617  		It("calls the AddGrant endpoint", func() {
   618  			nc, called, teardown := setUpNextcloudServer()
   619  			defer teardown()
   620  			ref := &provider.Reference{
   621  				ResourceId: &provider.ResourceId{
   622  					StorageId: "storage-id",
   623  					OpaqueId:  "opaque-id",
   624  				},
   625  				Path: "some/file/path.txt",
   626  			}
   627  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L843-L855
   628  			grant := &provider.Grant{
   629  				// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L896-L915
   630  				Grantee: &provider.Grantee{
   631  					Id: &provider.Grantee_UserId{
   632  						UserId: &userpb.UserId{
   633  							Idp:      "0.0.0.0:19000",
   634  							OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c",
   635  							Type:     userpb.UserType_USER_TYPE_PRIMARY,
   636  						},
   637  					},
   638  				},
   639  				// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L659-L683
   640  				Permissions: &provider.ResourcePermissions{
   641  					AddGrant:             true,
   642  					CreateContainer:      true,
   643  					Delete:               true,
   644  					GetPath:              true,
   645  					GetQuota:             true,
   646  					InitiateFileDownload: true,
   647  					InitiateFileUpload:   true,
   648  					ListGrants:           true,
   649  					ListContainer:        true,
   650  					ListFileVersions:     true,
   651  					ListRecycle:          true,
   652  					Move:                 true,
   653  					RemoveGrant:          true,
   654  					PurgeRecycle:         true,
   655  					RestoreFileVersion:   true,
   656  					RestoreRecycleItem:   true,
   657  					Stat:                 true,
   658  					UpdateGrant:          true,
   659  					DenyGrant:            true,
   660  				},
   661  			}
   662  			err := nc.AddGrant(ctx, ref, grant)
   663  			Expect(err).ToNot(HaveOccurred())
   664  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/AddGrant {"ref":{"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"},"g":{"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}}}`)
   665  		})
   666  	})
   667  
   668  	// DenyGrant(ctx context.Context, ref *provider.Reference, g *provider.Grantee) error
   669  	Describe("DenyGrant", func() {
   670  		It("calls the DenyGrant endpoint", func() {
   671  			nc, called, teardown := setUpNextcloudServer()
   672  			defer teardown()
   673  			ref := &provider.Reference{
   674  				ResourceId: &provider.ResourceId{
   675  					StorageId: "storage-id",
   676  					OpaqueId:  "opaque-id",
   677  				},
   678  				Path: "some/file/path.txt",
   679  			}
   680  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L896-L915
   681  			grantee := &provider.Grantee{
   682  				Id: &provider.Grantee_UserId{
   683  					UserId: &userpb.UserId{
   684  						Idp:      "0.0.0.0:19000",
   685  						OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c",
   686  						Type:     userpb.UserType_USER_TYPE_PRIMARY,
   687  					},
   688  				},
   689  			}
   690  			err := nc.DenyGrant(ctx, ref, grantee)
   691  			Expect(err).ToNot(HaveOccurred())
   692  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/DenyGrant {"ref":{"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"},"g":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}}}`)
   693  		})
   694  	})
   695  
   696  	// RemoveGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
   697  	Describe("RemoveGrant", func() {
   698  		It("calls the RemoveGrant endpoint", func() {
   699  			nc, called, teardown := setUpNextcloudServer()
   700  			defer teardown()
   701  			ref := &provider.Reference{
   702  				ResourceId: &provider.ResourceId{
   703  					StorageId: "storage-id",
   704  					OpaqueId:  "opaque-id",
   705  				},
   706  				Path: "some/file/path.txt",
   707  			}
   708  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L843-L855
   709  			grant := &provider.Grant{
   710  				// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L896-L915
   711  				Grantee: &provider.Grantee{
   712  					Id: &provider.Grantee_UserId{
   713  						UserId: &userpb.UserId{
   714  							Idp:      "0.0.0.0:19000",
   715  							OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c",
   716  							Type:     userpb.UserType_USER_TYPE_PRIMARY,
   717  						},
   718  					},
   719  				},
   720  				// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L659-L683
   721  				Permissions: &provider.ResourcePermissions{
   722  					AddGrant:             true,
   723  					CreateContainer:      true,
   724  					Delete:               true,
   725  					GetPath:              true,
   726  					GetQuota:             true,
   727  					InitiateFileDownload: true,
   728  					InitiateFileUpload:   true,
   729  					ListGrants:           true,
   730  					ListContainer:        true,
   731  					ListFileVersions:     true,
   732  					ListRecycle:          true,
   733  					Move:                 true,
   734  					RemoveGrant:          true,
   735  					PurgeRecycle:         true,
   736  					RestoreFileVersion:   true,
   737  					RestoreRecycleItem:   true,
   738  					Stat:                 true,
   739  					UpdateGrant:          true,
   740  					DenyGrant:            true,
   741  				},
   742  			}
   743  			err := nc.RemoveGrant(ctx, ref, grant)
   744  			Expect(err).ToNot(HaveOccurred())
   745  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/RemoveGrant {"ref":{"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"},"g":{"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}}}`)
   746  		})
   747  	})
   748  
   749  	// UpdateGrant(ctx context.Context, ref *provider.Reference, g *provider.Grant) error
   750  	Describe("UpdateGrant", func() {
   751  		It("calls the UpdateGrant endpoint", func() {
   752  			nc, called, teardown := setUpNextcloudServer()
   753  			defer teardown()
   754  			ref := &provider.Reference{
   755  				ResourceId: &provider.ResourceId{
   756  					StorageId: "storage-id",
   757  					OpaqueId:  "opaque-id",
   758  				},
   759  				Path: "some/file/path.txt",
   760  			}
   761  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L843-L855
   762  			grant := &provider.Grant{
   763  				// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L896-L915
   764  				Grantee: &provider.Grantee{
   765  					Id: &provider.Grantee_UserId{
   766  						UserId: &userpb.UserId{
   767  							Idp:      "0.0.0.0:19000",
   768  							OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c",
   769  							Type:     userpb.UserType_USER_TYPE_PRIMARY,
   770  						},
   771  					},
   772  				},
   773  				// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L659-L683
   774  				Permissions: &provider.ResourcePermissions{
   775  					AddGrant:             true,
   776  					CreateContainer:      true,
   777  					Delete:               true,
   778  					GetPath:              true,
   779  					GetQuota:             true,
   780  					InitiateFileDownload: true,
   781  					InitiateFileUpload:   true,
   782  					ListGrants:           true,
   783  					ListContainer:        true,
   784  					ListFileVersions:     true,
   785  					ListRecycle:          true,
   786  					Move:                 true,
   787  					RemoveGrant:          true,
   788  					PurgeRecycle:         true,
   789  					RestoreFileVersion:   true,
   790  					RestoreRecycleItem:   true,
   791  					Stat:                 true,
   792  					UpdateGrant:          true,
   793  					DenyGrant:            true,
   794  				},
   795  			}
   796  			err := nc.UpdateGrant(ctx, ref, grant)
   797  			Expect(err).ToNot(HaveOccurred())
   798  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/UpdateGrant {"ref":{"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"},"g":{"grantee":{"Id":{"UserId":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},"permissions":{"add_grant":true,"create_container":true,"delete":true,"get_path":true,"get_quota":true,"initiate_file_download":true,"initiate_file_upload":true,"list_grants":true,"list_container":true,"list_file_versions":true,"list_recycle":true,"move":true,"remove_grant":true,"purge_recycle":true,"restore_file_version":true,"restore_recycle_item":true,"stat":true,"update_grant":true,"deny_grant":true}}}`)
   799  		})
   800  	})
   801  
   802  	// ListGrants(ctx context.Context, ref *provider.Reference) ([]*provider.Grant, error)
   803  	Describe("ListGrants", func() {
   804  		It("calls the ListGrants endpoint", func() {
   805  			nc, called, teardown := setUpNextcloudServer()
   806  			defer teardown()
   807  			ref := &provider.Reference{
   808  				ResourceId: &provider.ResourceId{
   809  					StorageId: "storage-id",
   810  					OpaqueId:  "opaque-id",
   811  				},
   812  				Path: "some/file/path.txt",
   813  			}
   814  			grants, err := nc.ListGrants(ctx, ref)
   815  			Expect(err).ToNot(HaveOccurred())
   816  			Expect(len(grants)).To(Equal(1))
   817  
   818  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/ListGrants {"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"}`)
   819  		})
   820  	})
   821  
   822  	// GetQuota(ctx context.Context) (uint64, uint64, error)
   823  	Describe("GetQuota", func() {
   824  		It("calls the GetQuota endpoint", func() {
   825  			nc, called, teardown := setUpNextcloudServer()
   826  			defer teardown()
   827  			maxBytes, maxFiles, remaining, err := nc.GetQuota(ctx, nil)
   828  			Expect(err).ToNot(HaveOccurred())
   829  			Expect(maxBytes).To(Equal(uint64(456)))
   830  			Expect(maxFiles).To(Equal(uint64(123)))
   831  			Expect(remaining).To(Equal(uint64(333)))
   832  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/GetQuota `)
   833  		})
   834  	})
   835  
   836  	// CreateReference(ctx context.Context, path string, targetURI *url.URL) error
   837  	Describe("CreateReference", func() {
   838  		It("calls the CreateReference endpoint", func() {
   839  			nc, called, teardown := setUpNextcloudServer()
   840  			defer teardown()
   841  			path := "some/file/path.txt"
   842  			targetURI, err := url.Parse("http://bing.com/search?q=dotnet")
   843  			Expect(err).ToNot(HaveOccurred())
   844  			err = nc.CreateReference(ctx, path, targetURI)
   845  			Expect(err).ToNot(HaveOccurred())
   846  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/CreateReference {"path":"some/file/path.txt","url":"http://bing.com/search?q=dotnet"}`)
   847  		})
   848  	})
   849  
   850  	// Shutdown(ctx context.Context) error
   851  	Describe("Shutdown", func() {
   852  		It("calls the Shutdown endpoint", func() {
   853  			nc, called, teardown := setUpNextcloudServer()
   854  			defer teardown()
   855  			err := nc.Shutdown(ctx)
   856  			Expect(err).ToNot(HaveOccurred())
   857  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/Shutdown `)
   858  		})
   859  	})
   860  
   861  	// SetArbitraryMetadata(ctx context.Context, ref *provider.Reference, md *provider.ArbitraryMetadata) error
   862  	Describe("SetArbitraryMetadata", func() {
   863  		It("calls the SetArbitraryMetadata endpoint", func() {
   864  			nc, called, teardown := setUpNextcloudServer()
   865  			defer teardown()
   866  			ref := &provider.Reference{
   867  				ResourceId: &provider.ResourceId{
   868  					StorageId: "storage-id",
   869  					OpaqueId:  "opaque-id",
   870  				},
   871  				Path: "some/file/path.txt",
   872  			}
   873  			md := &provider.ArbitraryMetadata{
   874  				Metadata: map[string]string{
   875  					"arbi": "trary",
   876  					"meta": "data",
   877  				},
   878  			}
   879  			err := nc.SetArbitraryMetadata(ctx, ref, md)
   880  			Expect(err).ToNot(HaveOccurred())
   881  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/SetArbitraryMetadata {"ref":{"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"},"md":{"metadata":{"arbi":"trary","meta":"data"}}}`)
   882  		})
   883  	})
   884  
   885  	// UnsetArbitraryMetadata(ctx context.Context, ref *provider.Reference, keys []string) error
   886  	Describe("UnsetArbitraryMetadata", func() {
   887  		It("calls the UnsetArbitraryMetadata endpoint", func() {
   888  			nc, called, teardown := setUpNextcloudServer()
   889  			defer teardown()
   890  			ref := &provider.Reference{
   891  				ResourceId: &provider.ResourceId{
   892  					StorageId: "storage-id",
   893  					OpaqueId:  "opaque-id",
   894  				},
   895  				Path: "some/file/path.txt",
   896  			}
   897  			keys := []string{"arbi"}
   898  			err := nc.UnsetArbitraryMetadata(ctx, ref, keys)
   899  			Expect(err).ToNot(HaveOccurred())
   900  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/UnsetArbitraryMetadata {"ref":{"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"},"keys":["arbi"]}`)
   901  		})
   902  	})
   903  
   904  	// ListStorageSpaces(ctx context.Context, filter []*provider.ListStorageSpacesRequest_Filter) ([]*provider.StorageSpace, error)
   905  	Describe("ListStorageSpaces", func() {
   906  		It("calls the ListStorageSpaces endpoint", func() {
   907  			nc, called, teardown := setUpNextcloudServer()
   908  			defer teardown()
   909  			filter1 := &provider.ListStorageSpacesRequest_Filter{
   910  				Type: provider.ListStorageSpacesRequest_Filter_TYPE_OWNER,
   911  				Term: &provider.ListStorageSpacesRequest_Filter_Owner{
   912  					Owner: &userpb.UserId{
   913  						Idp:      "0.0.0.0:19000",
   914  						OpaqueId: "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c",
   915  						Type:     userpb.UserType_USER_TYPE_PRIMARY,
   916  					},
   917  				},
   918  			}
   919  			filter2 := &provider.ListStorageSpacesRequest_Filter{
   920  				Type: provider.ListStorageSpacesRequest_Filter_TYPE_ID,
   921  				Term: &provider.ListStorageSpacesRequest_Filter_Id{
   922  					Id: &provider.StorageSpaceId{
   923  						OpaqueId: "opaque-id",
   924  					},
   925  				},
   926  			}
   927  			filter3 := &provider.ListStorageSpacesRequest_Filter{
   928  				Type: provider.ListStorageSpacesRequest_Filter_TYPE_SPACE_TYPE,
   929  				Term: &provider.ListStorageSpacesRequest_Filter_SpaceType{
   930  					SpaceType: string("home"),
   931  				},
   932  			}
   933  			filters := []*provider.ListStorageSpacesRequest_Filter{filter1, filter2, filter3}
   934  			spaces, err := nc.ListStorageSpaces(ctx, filters, false)
   935  			Expect(err).ToNot(HaveOccurred())
   936  			Expect(len(spaces)).To(Equal(1))
   937  			// https://github.com/cs3org/go-cs3apis/blob/970eec3/cs3/storage/provider/v1beta1/resources.pb.go#L1341-L1366
   938  			Expect(spaces[0]).To(BeComparableTo(&provider.StorageSpace{
   939  				Opaque: &types.Opaque{
   940  					Map: map[string](*types.OpaqueEntry){
   941  						"foo": &types.OpaqueEntry{Value: []byte("sama")},
   942  						"bar": &types.OpaqueEntry{Value: []byte("sama")},
   943  					},
   944  				},
   945  				Id: &provider.StorageSpaceId{OpaqueId: "some-opaque-storage-space-id"},
   946  				Owner: &userpb.User{
   947  					Id: &userpb.UserId{
   948  						Idp:      "some-idp",
   949  						OpaqueId: "some-opaque-user-id",
   950  						Type:     userpb.UserType_USER_TYPE_PRIMARY,
   951  					},
   952  				},
   953  				Root: &provider.ResourceId{
   954  					StorageId: "some-storage-ud",
   955  					OpaqueId:  "some-opaque-root-id",
   956  				},
   957  				Name: "My Storage Space",
   958  				Quota: &provider.Quota{
   959  					QuotaMaxBytes: uint64(456),
   960  					QuotaMaxFiles: uint64(123),
   961  				},
   962  				SpaceType: "home",
   963  				Mtime: &types.Timestamp{
   964  					Seconds: uint64(1234567890),
   965  				},
   966  			}, protocmp.Transform()))
   967  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/ListStorageSpaces [{"type":3,"Term":{"Owner":{"idp":"0.0.0.0:19000","opaque_id":"f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c","type":1}}},{"type":2,"Term":{"Id":{"opaque_id":"opaque-id"}}},{"type":4,"Term":{"SpaceType":"home"}}]`)
   968  		})
   969  	})
   970  
   971  	// CreateStorageSpace(ctx context.Context, req *provider.CreateStorageSpaceRequest) (*provider.CreateStorageSpaceResponse, error)
   972  	Describe("CreateStorageSpace", func() {
   973  		It("calls the CreateStorageSpace endpoint", func() {
   974  			nc, called, teardown := setUpNextcloudServer()
   975  			defer teardown()
   976  			// https://github.com/cs3org/go-cs3apis/blob/03e4a408c1f3b2882916cf3fad4c71081a20711d/cs3/storage/provider/v1beta1/provider_api.pb.go#L3176-L3192
   977  			result, err := nc.CreateStorageSpace(ctx, &provider.CreateStorageSpaceRequest{
   978  				Opaque: &types.Opaque{
   979  					Map: map[string](*types.OpaqueEntry){
   980  						"foo": &types.OpaqueEntry{Value: []byte("sama")},
   981  						"bar": &types.OpaqueEntry{Value: []byte("sama")},
   982  					},
   983  				},
   984  				Owner: &userpb.User{
   985  					Id: &userpb.UserId{
   986  						Idp:      "some-idp",
   987  						OpaqueId: "some-opaque-user-id",
   988  						Type:     userpb.UserType_USER_TYPE_PRIMARY,
   989  					},
   990  				},
   991  				Name: "My Storage Space",
   992  				Quota: &provider.Quota{
   993  					QuotaMaxBytes: uint64(456),
   994  					QuotaMaxFiles: uint64(123),
   995  				},
   996  				Type: "home",
   997  			})
   998  			Expect(err).ToNot(HaveOccurred())
   999  			Expect(result).To(BeComparableTo(&provider.CreateStorageSpaceResponse{
  1000  				Opaque: nil,
  1001  				Status: nil,
  1002  				StorageSpace: &provider.StorageSpace{
  1003  					Opaque: &types.Opaque{
  1004  						Map: map[string](*types.OpaqueEntry){
  1005  							"bar": &types.OpaqueEntry{Value: []byte("sama")},
  1006  							"foo": &types.OpaqueEntry{Value: []byte("sama")},
  1007  						},
  1008  					},
  1009  					Id: &provider.StorageSpaceId{OpaqueId: "some-opaque-storage-space-id"},
  1010  					Owner: &userpb.User{
  1011  						Id: &userpb.UserId{
  1012  							Idp:      "some-idp",
  1013  							OpaqueId: "some-opaque-user-id",
  1014  							Type:     userpb.UserType_USER_TYPE_PRIMARY,
  1015  						},
  1016  					},
  1017  					Root: &provider.ResourceId{
  1018  						StorageId: "some-storage-ud",
  1019  						OpaqueId:  "some-opaque-root-id",
  1020  					},
  1021  					Name: "My Storage Space",
  1022  					Quota: &provider.Quota{
  1023  						QuotaMaxBytes: uint64(456),
  1024  						QuotaMaxFiles: uint64(123),
  1025  					},
  1026  					SpaceType: "home",
  1027  					Mtime: &types.Timestamp{
  1028  						Seconds: uint64(1234567890),
  1029  					},
  1030  				},
  1031  			}, protocmp.Transform()))
  1032  			checkCalled(called, `POST /apps/sciencemesh/~tester/api/storage/CreateStorageSpace {"opaque":{"map":{"bar":{"value":"c2FtYQ=="},"foo":{"value":"c2FtYQ=="}}},"owner":{"id":{"idp":"some-idp","opaque_id":"some-opaque-user-id","type":1}},"type":"home","name":"My Storage Space","quota":{"quota_max_bytes":456,"quota_max_files":123}}`)
  1033  		})
  1034  	})
  1035  
  1036  })