package connector_test import ( "context" "encoding/hex" "errors" "net/url" "path" "regexp" "strings" "time" appproviderv1beta1 "github.com/cs3org/go-cs3apis/cs3/app/provider/v1beta1" auth "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" providerv1beta1 "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/golang-jwt/jwt/v5" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/owncloud/ocis/v2/ocis-pkg/shared" collabmocks "github.com/owncloud/ocis/v2/services/collaboration/mocks" "github.com/owncloud/ocis/v2/services/collaboration/pkg/config" "github.com/owncloud/ocis/v2/services/collaboration/pkg/connector" "github.com/owncloud/ocis/v2/services/collaboration/pkg/connector/fileinfo" "github.com/owncloud/ocis/v2/services/collaboration/pkg/middleware" "github.com/owncloud/ocis/v2/services/graph/mocks" ctxpkg "github.com/owncloud/reva/v2/pkg/ctx" "github.com/owncloud/reva/v2/pkg/rgrpc/status" "github.com/owncloud/reva/v2/pkg/utils" cs3mocks "github.com/owncloud/reva/v2/tests/cs3mocks/mocks" "github.com/stretchr/testify/mock" "google.golang.org/grpc" ) var _ = Describe("FileConnector", func() { var ( fc *connector.FileConnector ccs *collabmocks.ContentConnectorService gatewayClient *cs3mocks.GatewayAPIClient gatewaySelector *mocks.Selectable[gateway.GatewayAPIClient] cfg *config.Config wopiCtx middleware.WopiContext ) BeforeEach(func() { cfg = &config.Config{ Commons: &shared.Commons{ OcisURL: "https://ocis.example.prv", }, App: config.App{ Name: "test", Product: "Microsoft", }, Wopi: config.Wopi{ WopiSrc: "https://ocis.server.prv", Secret: "topsecret", }, TokenManager: &config.TokenManager{JWTSecret: "topsecret"}, } ccs = &collabmocks.ContentConnectorService{} gatewayClient = cs3mocks.NewGatewayAPIClient(GinkgoT()) gatewaySelector = mocks.NewSelectable[gateway.GatewayAPIClient](GinkgoT()) gatewaySelector.On("Next").Return(gatewayClient, nil) fc = connector.NewFileConnector(gatewaySelector, cfg, nil) // Generate a valid token for testing now := time.Now() claims := jwt.MapClaims{ "aud": "web", "exp": now.Add(24 * time.Hour).Unix(), "iat": now.Unix(), "iss": "https://ocis.jp.solidgear.prv", "jti": "fBWi7AXhQPUhah2CWEPTQKpCfgpFlAiL", "lg.i": map[string]interface{}{ "dn": "bro", "id": "ownCloudUUID=faf11647-7451-4b9a-bffe-3b5ddcc5972b", "un": "brotato", }, "lg.p": "identifier-ldap", "lg.t": "1", "scp": "openid profile email", "sub": "cAvuzX8gXLZdiXx-@1NWTJtCPDqUJ44nt46FtD9p5L7tjePFdZMVJ9E30Nx2-dui7HKCLxAiaCTtbX511JcdHw", } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) accessToken, _ := token.SignedString([]byte(cfg.Wopi.Secret)) wopiCtx = middleware.WopiContext{ AccessToken: accessToken, FileReference: &providerv1beta1.Reference{ ResourceId: &providerv1beta1.ResourceId{ StorageId: "abc", OpaqueId: "12345", SpaceId: "zzz", }, Path: ".", }, ViewMode: appproviderv1beta1.ViewMode_VIEW_MODE_READ_WRITE, } }) Describe("GetLock", func() { It("No valid context", func() { gatewaySelector.EXPECT().Next().Unset() ctx := context.Background() response, err := fc.GetLock(ctx) Expect(err).To(HaveOccurred()) Expect(response).To(BeNil()) }) It("Get lock failed", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) targetErr := errors.New("Something went wrong") gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewInternal(ctx, "Something failed"), }, targetErr) response, err := fc.GetLock(ctx) Expect(err).To(Equal(targetErr)) Expect(response).To(BeNil()) }) It("Get lock failed status not ok", func() { // assume failure happens because the target file doesn't exists ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewNotFound(ctx, "File is missing"), }, nil) response, err := fc.GetLock(ctx) Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(404)) Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("")) }) It("Get lock success", func() { // assume failure happens because the target file doesn't exists ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewOK(ctx), Lock: &providerv1beta1.Lock{ LockId: "zzz999", Type: providerv1beta1.LockType_LOCK_TYPE_WRITE, }, }, nil) response, err := fc.GetLock(ctx) Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999")) }) }) Describe("Lock", func() { Describe("Lock", func() { It("No valid context", func() { gatewaySelector.EXPECT().Next().Unset() ctx := context.Background() response, err := fc.Lock(ctx, "newLock", "") Expect(err).To(HaveOccurred()) Expect(response).To(BeNil()) }) It("Empty lockId", func() { gatewaySelector.EXPECT().Next().Unset() ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) response, err := fc.Lock(ctx, "", "") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(400)) Expect(response.Headers).To(BeNil()) }) It("Read-only view mode returns 200 without locking", func() { gatewaySelector.EXPECT().Next().Unset() readOnlyCtx := wopiCtx readOnlyCtx.ViewMode = appproviderv1beta1.ViewMode_VIEW_MODE_READ_ONLY ctx := middleware.WopiContextToCtx(context.Background(), readOnlyCtx) response, err := fc.Lock(ctx, "abcdef123", "") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) }) It("View-only mode returns 200 without locking", func() { gatewaySelector.EXPECT().Next().Unset() viewOnlyCtx := wopiCtx viewOnlyCtx.ViewMode = appproviderv1beta1.ViewMode_VIEW_MODE_VIEW_ONLY ctx := middleware.WopiContextToCtx(context.Background(), viewOnlyCtx) response, err := fc.Lock(ctx, "abcdef123", "") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) }) It("Set lock failed", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) targetErr := errors.New("Something went wrong") gatewayClient.On("SetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.SetLockResponse{ Status: status.NewInternal(ctx, "Something failed"), }, targetErr) response, err := fc.Lock(ctx, "abcdef123", "") Expect(err).To(HaveOccurred()) Expect(err).To(Equal(targetErr)) Expect(response).To(BeNil()) }) It("Set lock success", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("SetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.SetLockResponse{ Status: status.NewOK(ctx), }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return( &providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Mtime: &typesv1beta1.Timestamp{ Seconds: 12345, Nanos: 6789, }, }, }, nil, ) response, err := fc.Lock(ctx, "abcdef123", "") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) Expect(response.Headers).To(HaveLen(1)) Expect(response.Headers[connector.HeaderWopiVersion]).To(Equal("v123456789")) }) It("Set lock mismatches error getting lock", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("SetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.SetLockResponse{ Status: status.NewFailedPrecondition(ctx, nil, "lock mismatch"), }, nil) targetErr := errors.New("Something went wrong getting the lock") gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewInternal(ctx, "lock mismatch"), }, targetErr) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.Lock(ctx, "abcdef123", "") Expect(err).To(HaveOccurred()) Expect(err).To(Equal(targetErr)) Expect(response).To(BeNil()) }) It("Set lock mismatches", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("SetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.SetLockResponse{ Status: status.NewFailedPrecondition(ctx, nil, "lock mismatch"), }, nil) gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewOK(ctx), Lock: &providerv1beta1.Lock{ LockId: "zzz999", Type: providerv1beta1.LockType_LOCK_TYPE_WRITE, }, }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.Lock(ctx, "abcdef123", "") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(409)) Expect(response.Headers).To(HaveLen(2)) Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999")) Expect(response.Headers[connector.HeaderWopiLockFailureReason]).To(Equal("Conflicting LockID")) }) It("Set lock mismatches but get lock matches", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("SetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.SetLockResponse{ Status: status.NewFailedPrecondition(ctx, nil, "lock mismatch"), }, nil) gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewOK(ctx), Lock: &providerv1beta1.Lock{ LockId: "abcdef123", Type: providerv1beta1.LockType_LOCK_TYPE_WRITE, }, }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Mtime: &typesv1beta1.Timestamp{Seconds: uint64(12345), Nanos: uint32(6789)}, }, }, nil) response, err := fc.Lock(ctx, "abcdef123", "") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) Expect(response.Headers).To(HaveLen(1)) Expect(response.Headers[connector.HeaderWopiVersion]).To(Equal("v123456789")) }) It("Set lock mismatches but get lock doesn't return lockId", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("SetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.SetLockResponse{ Status: status.NewFailedPrecondition(ctx, nil, "lock mismatch"), }, nil) gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewOK(ctx), }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.Lock(ctx, "abcdef123", "") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(500)) Expect(response.Headers).To(BeNil()) }) It("File not found", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("SetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.SetLockResponse{ Status: status.NewNotFound(ctx, "file not found"), }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.Lock(ctx, "abcdef123", "") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(404)) Expect(response.Headers).To(BeNil()) }) It("Default error handling (insufficient storage)", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("SetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.SetLockResponse{ Status: status.NewInsufficientStorage(ctx, nil, "file too big"), }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.Lock(ctx, "abcdef123", "") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(500)) Expect(response.Headers).To(BeNil()) }) }) Describe("Unlock and relock", func() { It("No valid context", func() { gatewaySelector.EXPECT().Next().Unset() ctx := context.Background() response, err := fc.Lock(ctx, "newLock", "oldLock") Expect(err).To(HaveOccurred()) Expect(response).To(BeNil()) }) It("Empty lockId", func() { gatewaySelector.EXPECT().Next().Unset() ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) response, err := fc.Lock(ctx, "", "oldLock") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(400)) Expect(response.Headers).To(BeNil()) }) It("Refresh lock failed", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) targetErr := errors.New("Something went wrong") gatewayClient.On("RefreshLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.RefreshLockResponse{ Status: status.NewInternal(ctx, "Something failed"), }, targetErr) response, err := fc.Lock(ctx, "abcdef123", "oldLock") Expect(err).To(HaveOccurred()) Expect(err).To(Equal(targetErr)) Expect(response).To(BeNil()) }) It("Refresh lock success", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("RefreshLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.RefreshLockResponse{ Status: status.NewOK(ctx), }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Mtime: &typesv1beta1.Timestamp{Seconds: uint64(12345), Nanos: uint32(6789)}, }, }, nil) response, err := fc.Lock(ctx, "abcdef123", "oldLock") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) Expect(response.Headers).To(HaveLen(1)) Expect(response.Headers[connector.HeaderWopiVersion]).To(Equal("v123456789")) }) It("Refresh lock mismatches error getting lock", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("RefreshLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.RefreshLockResponse{ Status: status.NewConflict(ctx, nil, "lock mismatch"), }, nil) targetErr := errors.New("Something went wrong getting the lock") gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewInternal(ctx, "lock mismatch"), }, targetErr) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.Lock(ctx, "abcdef123", "112233") Expect(err).To(HaveOccurred()) Expect(err).To(Equal(targetErr)) Expect(response).To(BeNil()) }) It("Refresh lock mismatches", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("RefreshLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.RefreshLockResponse{ Status: status.NewConflict(ctx, nil, "lock mismatch"), }, nil) gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewOK(ctx), Lock: &providerv1beta1.Lock{ LockId: "zzz999", Type: providerv1beta1.LockType_LOCK_TYPE_WRITE, }, }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.Lock(ctx, "abcdef123", "112233") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(409)) Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999")) }) It("Refresh lock mismatches but get lock matches", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("RefreshLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.RefreshLockResponse{ Status: status.NewConflict(ctx, nil, "lock mismatch"), }, nil) gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewOK(ctx), Lock: &providerv1beta1.Lock{ LockId: "abcdef123", Type: providerv1beta1.LockType_LOCK_TYPE_WRITE, }, }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Mtime: &typesv1beta1.Timestamp{Seconds: uint64(12345), Nanos: uint32(6789)}, }, }, nil) response, err := fc.Lock(ctx, "abcdef123", "112233") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) Expect(response.Headers).To(HaveLen(1)) Expect(response.Headers[connector.HeaderWopiVersion]).To(Equal("v123456789")) }) It("Refresh lock mismatches but get lock doesn't return lockId", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("RefreshLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.RefreshLockResponse{ Status: status.NewConflict(ctx, nil, "lock mismatch"), }, nil) gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewOK(ctx), }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.Lock(ctx, "abcdef123", "112233") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(500)) Expect(response.Headers).To(BeNil()) }) It("File not found", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("RefreshLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.RefreshLockResponse{ Status: status.NewNotFound(ctx, "file not found"), }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.Lock(ctx, "abcdef123", "112233") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(404)) Expect(response.Headers).To(BeNil()) }) It("Default error handling (insufficient storage)", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("RefreshLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.RefreshLockResponse{ Status: status.NewInsufficientStorage(ctx, nil, "file too big"), }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.Lock(ctx, "abcdef123", "112233") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(500)) Expect(response.Headers).To(BeNil()) }) }) }) Describe("RefreshLock", func() { It("No valid context", func() { gatewaySelector.EXPECT().Next().Unset() ctx := context.Background() response, err := fc.RefreshLock(ctx, "") Expect(err).To(HaveOccurred()) Expect(response).To(BeNil()) }) It("Empty lockId", func() { gatewaySelector.EXPECT().Next().Unset() ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) response, err := fc.RefreshLock(ctx, "") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(400)) Expect(response.Headers).To(BeNil()) }) It("Refresh lock fails", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) targetErr := errors.New("Something went wrong") gatewayClient.On("RefreshLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.RefreshLockResponse{ Status: status.NewConflict(ctx, nil, "lock mismatch"), }, targetErr) response, err := fc.RefreshLock(ctx, "abcdef123") Expect(err).To(HaveOccurred()) Expect(err).To(Equal(targetErr)) Expect(response).To(BeNil()) }) It("Refresh lock success", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("RefreshLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.RefreshLockResponse{ Status: status.NewOK(ctx), }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Mtime: &typesv1beta1.Timestamp{Seconds: uint64(12345), Nanos: uint32(6789)}, }, }, nil) response, err := fc.RefreshLock(ctx, "abcdef123") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) Expect(response.Headers).To(HaveLen(1)) Expect(response.Headers[connector.HeaderWopiVersion]).To(Equal("v123456789")) }) It("Refresh lock file not found", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("RefreshLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.RefreshLockResponse{ Status: status.NewNotFound(ctx, "file not found"), }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.RefreshLock(ctx, "abcdef123") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(404)) Expect(response.Headers).To(BeNil()) }) It("Refresh lock mismatch and get lock fails", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("RefreshLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.RefreshLockResponse{ Status: status.NewConflict(ctx, nil, "lock mismatch"), }, nil) targetErr := errors.New("Something went wrong") gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewConflict(ctx, nil, "lock mismatch"), }, targetErr) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.RefreshLock(ctx, "abcdef123") Expect(err).To(HaveOccurred()) Expect(err).To(Equal(targetErr)) Expect(response).To(BeNil()) }) It("Refresh lock mismatch and get lock status not ok", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("RefreshLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.RefreshLockResponse{ Status: status.NewConflict(ctx, nil, "lock mismatch"), }, nil) gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewInternal(ctx, "lock mismatch"), }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.RefreshLock(ctx, "abcdef123") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(500)) Expect(response.Headers).To(BeNil()) }) It("Refresh lock mismatch and no lock", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("RefreshLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.RefreshLockResponse{ Status: status.NewConflict(ctx, nil, "lock mismatch"), }, nil) gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewOK(ctx), }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.RefreshLock(ctx, "abcdef123") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(409)) Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("")) }) It("Refresh lock mismatch", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("RefreshLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.RefreshLockResponse{ Status: status.NewConflict(ctx, nil, "lock mismatch"), }, nil) gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewOK(ctx), Lock: &providerv1beta1.Lock{ LockId: "zzz999", Type: providerv1beta1.LockType_LOCK_TYPE_WRITE, }, }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.RefreshLock(ctx, "abcdef123") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(409)) Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999")) }) It("Default error handling (insufficient storage)", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("RefreshLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.RefreshLockResponse{ Status: status.NewInsufficientStorage(ctx, nil, "file too big"), }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.RefreshLock(ctx, "abcdef123") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(500)) Expect(response.Headers).To(BeNil()) }) }) Describe("Unlock", func() { It("No valid context", func() { gatewaySelector.EXPECT().Next().Unset() ctx := context.Background() response, err := fc.UnLock(ctx, "") Expect(err).To(HaveOccurred()) Expect(response).To(BeNil()) }) It("Empty lockId", func() { gatewaySelector.EXPECT().Next().Unset() ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) response, err := fc.UnLock(ctx, "") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(400)) Expect(response.Headers).To(BeNil()) }) It("Unlock fails", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) targetErr := errors.New("Something went wrong") gatewayClient.On("Unlock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.UnlockResponse{ Status: status.NewInternal(ctx, "something failed"), }, targetErr) response, err := fc.UnLock(ctx, "abcdef123") Expect(err).To(HaveOccurred()) Expect(err).To(Equal(targetErr)) Expect(response).To(BeNil()) }) It("Unlock success", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Unlock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.UnlockResponse{ Status: status.NewOK(ctx), }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Mtime: &typesv1beta1.Timestamp{Seconds: uint64(12345), Nanos: uint32(6789)}, }, }, nil) response, err := fc.UnLock(ctx, "abcdef123") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) Expect(response.Headers).To(HaveLen(1)) Expect(response.Headers[connector.HeaderWopiVersion]).To(Equal("v123456789")) }) It("Unlock file isn't locked", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Unlock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.UnlockResponse{ Status: status.NewConflict(ctx, nil, "lock mismatch"), }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Mtime: &typesv1beta1.Timestamp{Seconds: uint64(12345), Nanos: uint32(6789)}, }, }, nil) response, err := fc.UnLock(ctx, "abcdef123") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(409)) Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("")) }) It("Unlock mismatch get lock fails", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Unlock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.UnlockResponse{ Status: status.NewLocked(ctx, "lock mismatch"), }, nil) targetErr := errors.New("Something went wrong") gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewInternal(ctx, "something failed"), }, targetErr) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.UnLock(ctx, "abcdef123") Expect(err).To(HaveOccurred()) Expect(err).To(Equal(targetErr)) Expect(response).To(BeNil()) }) It("Unlock mismatch get lock status not ok", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Unlock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.UnlockResponse{ Status: status.NewLocked(ctx, "lock mismatch"), }, nil) gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewInternal(ctx, "something failed"), }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.UnLock(ctx, "abcdef123") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(500)) Expect(response.Headers).To(BeNil()) }) It("Unlock mismatch get lock doesn't return lock", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Unlock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.UnlockResponse{ Status: status.NewLocked(ctx, "lock mismatch"), }, nil) gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewOK(ctx), }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.UnLock(ctx, "abcdef123") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(409)) Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("")) }) It("Unlock mismatch", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Unlock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.UnlockResponse{ Status: status.NewLocked(ctx, "lock mismatch"), }, nil) gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewOK(ctx), Lock: &providerv1beta1.Lock{ LockId: "zzz999", Type: providerv1beta1.LockType_LOCK_TYPE_WRITE, }, }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.UnLock(ctx, "abcdef123") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(409)) Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999")) }) It("Default error handling (insufficient storage)", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Unlock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.UnlockResponse{ Status: status.NewInsufficientStorage(ctx, nil, "file too big"), }, nil) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything). Return(&providerv1beta1.StatResponse{Status: status.NewOK(ctx)}, nil) response, err := fc.UnLock(ctx, "abcdef123") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(500)) Expect(response.Headers).To(BeNil()) }) }) Describe("PutRelativeFileSuggested", func() { It("No valid context", func() { gatewaySelector.EXPECT().Next().Unset() ctx := context.Background() stream := strings.NewReader("This is the content of a file") response, err := fc.PutRelativeFileSuggested(ctx, ccs, stream, int64(stream.Len()), "newFile.txt") Expect(err).To(HaveOccurred()) Expect(response).To(BeNil()) }) It("Stat fails", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) targetErr := errors.New("Something went wrong") gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewInternal(ctx, "something failed"), }, targetErr) stream := strings.NewReader("This is the content of a file") response, err := fc.PutRelativeFileSuggested(ctx, ccs, stream, int64(stream.Len()), "newFile.txt") Expect(err).To(HaveOccurred()) Expect(err).To(Equal(targetErr)) Expect(response).To(BeNil()) }) It("Stat fails status not ok", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewInternal(ctx, "something failed"), }, nil) stream := strings.NewReader("This is the content of a file") response, err := fc.PutRelativeFileSuggested(ctx, ccs, stream, int64(stream.Len()), "newFile.txt") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(500)) Expect(response.Headers).To(BeNil()) Expect(response.Body).To(BeNil()) }) It("PutRelativeFileSuggested success", func() { // requested filename is "newDocument.docx" so we'll write that ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) stream := strings.NewReader("This is the content of a file") stat1ParamMatcher := mock.MatchedBy(func(statReq *providerv1beta1.StatRequest) bool { return statReq.Ref.ResourceId == wopiCtx.FileReference.ResourceId }) gatewayClient.On("Stat", mock.Anything, stat1ParamMatcher).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Path: "/personal/path/to/file.docx", ParentId: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "opaqueid", SpaceId: "spaceid", }, }, }, nil) ccs.On("PutFile", mock.Anything, stream, int64(stream.Len()), "").Times(1).Return(connector.NewResponse(200), nil) stat2ParamMatcher := mock.MatchedBy(func(statReq *providerv1beta1.StatRequest) bool { if statReq.Ref.ResourceId.StorageId == "storageid" && statReq.Ref.ResourceId.OpaqueId == "opaqueid" && statReq.Ref.ResourceId.SpaceId == "spaceid" && statReq.Ref.Path == "./newDocument.docx" { return true } return false }) gatewayClient.On("Stat", mock.Anything, stat2ParamMatcher).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Path: "/personal/path/to/newDocument.docx", Id: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "opaqueid_newDoc", SpaceId: "spaceid", }, }, }, nil) response, err := fc.PutRelativeFileSuggested(ctx, ccs, stream, int64(stream.Len()), "newDocument.docx") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) Expect(response.Headers).To(BeNil()) rBody := response.Body.(map[string]interface{}) Expect(rBody["Name"]).To(Equal("newDocument.docx")) Expect(rBody["Url"]).To(HavePrefix("https://ocis.server.prv/wopi/files/")) // skip checking the actual reference Expect(rBody["HostEditUrl"]).To(Equal("https://ocis.example.prv/external-test/personal/path/to/newDocument.docx?fileId=storageid%24spaceid%21opaqueid_newDoc&view_mode=write")) Expect(rBody["HostViewUrl"]).To(Equal("https://ocis.example.prv/external-test/personal/path/to/newDocument.docx?fileId=storageid%24spaceid%21opaqueid_newDoc&view_mode=view")) }) It("PutRelativeFileSuggested success only extension", func() { // requested file is ".pdf" so we'll change the "file.docx" to "file.pdf" ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) stream := strings.NewReader("This is the content of a file") stat1ParamMatcher := mock.MatchedBy(func(statReq *providerv1beta1.StatRequest) bool { return statReq.Ref.ResourceId == wopiCtx.FileReference.ResourceId }) gatewayClient.On("Stat", mock.Anything, stat1ParamMatcher).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Path: "/personal/path/to/file.docx", ParentId: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "opaqueid", SpaceId: "spaceid", }, }, }, nil) ccs.On("PutFile", mock.Anything, stream, int64(stream.Len()), "").Times(1).Return(connector.NewResponse(200), nil) stat2ParamMatcher := mock.MatchedBy(func(statReq *providerv1beta1.StatRequest) bool { if statReq.Ref.ResourceId.StorageId == "storageid" && statReq.Ref.ResourceId.OpaqueId == "opaqueid" && statReq.Ref.ResourceId.SpaceId == "spaceid" && statReq.Ref.Path == "./file.pdf" { return true } return false }) gatewayClient.On("Stat", mock.Anything, stat2ParamMatcher).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Path: "/personal/path/to/file.pdf", Id: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "opaqueid_newDoc", SpaceId: "spaceid", }, }, }, nil) response, err := fc.PutRelativeFileSuggested(ctx, ccs, stream, int64(stream.Len()), ".pdf") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) Expect(response.Headers).To(BeNil()) rBody := response.Body.(map[string]interface{}) Expect(rBody["Name"]).To(Equal("file.pdf")) Expect(rBody["Url"]).To(HavePrefix("https://ocis.server.prv/wopi/files/")) // skip checking the actual reference Expect(rBody["HostEditUrl"]).To(Equal("https://ocis.example.prv/external-test/personal/path/to/file.pdf?fileId=storageid%24spaceid%21opaqueid_newDoc&view_mode=write")) Expect(rBody["HostViewUrl"]).To(Equal("https://ocis.example.prv/external-test/personal/path/to/file.pdf?fileId=storageid%24spaceid%21opaqueid_newDoc&view_mode=view")) }) It("PutRelativeFileSuggested success conflict", func() { // requested file is ".pdf", but "file.pdf" exists as target file (we get a conflict) // so we change the "file.docx" to " file.pdf" file, where the is a // sequence of base64 chars containing alphanumeric chars plus "-" and "_" (the char // sequence is based on time, so we can't be too specific) ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) stream := strings.NewReader("This is the content of a file") stat1ParamMatcher := mock.MatchedBy(func(statReq *providerv1beta1.StatRequest) bool { return statReq.Ref.ResourceId == wopiCtx.FileReference.ResourceId }) gatewayClient.On("Stat", mock.Anything, stat1ParamMatcher).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Path: "/personal/path/to/file.docx", ParentId: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "opaqueid", SpaceId: "spaceid", }, }, }, nil) // first call will fail with conflict, second call succeeds. // we're only interested on whether the file is locked or not, the actual lockID is irrelevant ccs.On("PutFile", mock.Anything, stream, int64(stream.Len()), "").Times(1).Return(connector.NewResponse(409), nil).Once() ccs.On("PutFile", mock.Anything, stream, int64(stream.Len()), "").Times(1).Return(connector.NewResponse(200), nil).Once() newFilePath := new(string) stat2ParamMatcher := mock.MatchedBy(func(statReq *providerv1beta1.StatRequest) bool { r := regexp.MustCompile(`^\./[a-zA-Z0-9_-]+ file\.pdf$`) if statReq.Ref.ResourceId.StorageId == "storageid" && statReq.Ref.ResourceId.OpaqueId == "opaqueid" && statReq.Ref.ResourceId.SpaceId == "spaceid" && r.MatchString(statReq.Ref.Path) { *newFilePath = statReq.Ref.Path return true } return false }) gatewayClient.EXPECT().Stat(mock.Anything, stat2ParamMatcher). RunAndReturn(func(ctx context.Context, req *providerv1beta1.StatRequest, opts ...grpc.CallOption) (*providerv1beta1.StatResponse, error) { return &providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Path: path.Join("/personal/path/to", path.Base(req.GetRef().GetPath())), Id: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "opaqueid_newDoc", SpaceId: "spaceid", }, Lock: &providerv1beta1.Lock{ LockId: "zzz999", Type: providerv1beta1.LockType_LOCK_TYPE_WRITE, }, }, }, nil }) response, err := fc.PutRelativeFileSuggested(ctx, ccs, stream, int64(stream.Len()), ".pdf") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) Expect(response.Headers).To(BeNil()) rBody := response.Body.(map[string]interface{}) Expect(rBody["Name"]).To(MatchRegexp(`[a-zA-Z0-9_-] file\.pdf`)) Expect(rBody["Url"]).To(HavePrefix("https://ocis.server.prv/wopi/files/")) // skip checking the actual reference Expect(rBody["HostEditUrl"]).To(Equal("https://ocis.example.prv/external-test/personal/path/to/" + url.PathEscape(path.Base(*newFilePath)) + "?fileId=storageid%24spaceid%21opaqueid_newDoc&view_mode=write")) Expect(rBody["HostViewUrl"]).To(Equal("https://ocis.example.prv/external-test/personal/path/to/" + url.PathEscape(path.Base(*newFilePath)) + "?fileId=storageid%24spaceid%21opaqueid_newDoc&view_mode=view")) }) It("PutRelativeFileSuggested put file fails", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) stream := strings.NewReader("This is the content of a file") stat1ParamMatcher := mock.MatchedBy(func(statReq *providerv1beta1.StatRequest) bool { return statReq.Ref.ResourceId == wopiCtx.FileReference.ResourceId }) gatewayClient.On("Stat", mock.Anything, stat1ParamMatcher).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Path: "/personal/path/to/file.docx", ParentId: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "opaqueid", SpaceId: "spaceid", }, }, }, nil) ccs.On("PutFile", mock.Anything, stream, int64(stream.Len()), "").Times(1).Return(connector.NewResponse(500), nil) response, err := fc.PutRelativeFileSuggested(ctx, ccs, stream, int64(stream.Len()), ".pdf") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(500)) Expect(response.Headers).To(BeNil()) Expect(response.Body).To(BeNil()) }) }) Describe("PutRelativeFileRelative", func() { It("No valid context", func() { gatewaySelector.EXPECT().Next().Unset() ctx := context.Background() stream := strings.NewReader("This is the content of a file") response, err := fc.PutRelativeFileRelative(ctx, ccs, stream, int64(stream.Len()), "newFile.txt") Expect(err).To(HaveOccurred()) Expect(response).To(BeNil()) }) It("Stat fails", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) targetErr := errors.New("Something went wrong") gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewInternal(ctx, "something failed"), }, targetErr) stream := strings.NewReader("This is the content of a file") response, err := fc.PutRelativeFileRelative(ctx, ccs, stream, int64(stream.Len()), "newFile.txt") Expect(err).To(HaveOccurred()) Expect(err).To(Equal(targetErr)) Expect(response).To(BeNil()) }) It("Stat fails status not ok", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewInternal(ctx, "something failed"), }, nil) stream := strings.NewReader("This is the content of a file") response, err := fc.PutRelativeFileRelative(ctx, ccs, stream, int64(stream.Len()), "newFile.txt") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(500)) Expect(response.Headers).To(BeNil()) Expect(response.Body).To(BeNil()) }) It("PutRelativeFileRelative success", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) stream := strings.NewReader("This is the content of a file") stat1ParamMatcher := mock.MatchedBy(func(statReq *providerv1beta1.StatRequest) bool { return statReq.Ref.ResourceId == wopiCtx.FileReference.ResourceId }) gatewayClient.On("Stat", mock.Anything, stat1ParamMatcher).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Path: "/personal/path/to/file.docx", ParentId: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "opaqueid", SpaceId: "spaceid", }, }, }, nil) ccs.On("PutFile", mock.Anything, stream, int64(stream.Len()), "").Times(1).Return(connector.NewResponse(200), nil) stat2ParamMatcher := mock.MatchedBy(func(statReq *providerv1beta1.StatRequest) bool { if statReq.Ref.ResourceId.StorageId == "storageid" && statReq.Ref.ResourceId.OpaqueId == "opaqueid" && statReq.Ref.ResourceId.SpaceId == "spaceid" && statReq.Ref.Path == "./newDocument.docx" { return true } return false }) gatewayClient.On("Stat", mock.Anything, stat2ParamMatcher).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Path: "/personal/path/to/newDocument.docx", Id: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "opaqueid_newDoc", SpaceId: "spaceid", }, }, }, nil) response, err := fc.PutRelativeFileRelative(ctx, ccs, stream, int64(stream.Len()), "newDocument.docx") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) Expect(response.Headers).To(BeNil()) rBody := response.Body.(map[string]interface{}) Expect(rBody["Name"]).To(Equal("newDocument.docx")) Expect(rBody["Url"]).To(HavePrefix("https://ocis.server.prv/wopi/files/")) // skip checking the actual reference Expect(rBody["HostEditUrl"]).To(Equal("https://ocis.example.prv/external-test/personal/path/to/newDocument.docx?fileId=storageid%24spaceid%21opaqueid_newDoc&view_mode=write")) Expect(rBody["HostViewUrl"]).To(Equal("https://ocis.example.prv/external-test/personal/path/to/newDocument.docx?fileId=storageid%24spaceid%21opaqueid_newDoc&view_mode=view")) }) It("PutRelativeFileRelative conflict", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) stream := strings.NewReader("This is the content of a file") stat1ParamMatcher := mock.MatchedBy(func(statReq *providerv1beta1.StatRequest) bool { return statReq.Ref.ResourceId == wopiCtx.FileReference.ResourceId }) gatewayClient.On("Stat", mock.Anything, stat1ParamMatcher).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Path: "/personal/path/to/file.docx", ParentId: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "opaqueid", SpaceId: "spaceid", }, }, }, nil) ccs.On("PutFile", mock.Anything, stream, int64(stream.Len()), "").Times(1).Return(connector.NewResponseWithLock(409, "zzz999"), nil) stat2ParamMatcher := mock.MatchedBy(func(statReq *providerv1beta1.StatRequest) bool { if statReq.Ref.ResourceId.StorageId == "storageid" && statReq.Ref.ResourceId.OpaqueId == "opaqueid" && statReq.Ref.ResourceId.SpaceId == "spaceid" && statReq.Ref.Path == "./convFile.pdf" { return true } return false }) gatewayClient.On("Stat", mock.Anything, stat2ParamMatcher).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Path: "/personal/path/to/convFile.pdf", Id: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "opaqueid_newDoc", SpaceId: "spaceid", }, Lock: &providerv1beta1.Lock{ LockId: "zzz999", Type: providerv1beta1.LockType_LOCK_TYPE_WRITE, }, }, }, nil) response, err := fc.PutRelativeFileRelative(ctx, ccs, stream, int64(stream.Len()), "convFile.pdf") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(409)) Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999")) Expect(response.Headers[connector.HeaderWopiValidRT]).To(MatchRegexp(`[a-zA-Z0-9_-] convFile\.pdf`)) rBody := response.Body.(map[string]interface{}) Expect(rBody["Name"]).To(Equal("convFile.pdf")) Expect(rBody["Url"]).To(HavePrefix("https://ocis.server.prv/wopi/files/")) // skip checking the actual reference Expect(rBody["HostEditUrl"]).To(Equal("https://ocis.example.prv/external-test/personal/path/to/convFile.pdf?fileId=storageid%24spaceid%21opaqueid_newDoc&view_mode=write")) Expect(rBody["HostViewUrl"]).To(Equal("https://ocis.example.prv/external-test/personal/path/to/convFile.pdf?fileId=storageid%24spaceid%21opaqueid_newDoc&view_mode=view")) }) It("PutRelativeFileRelative put file fails", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) stream := strings.NewReader("This is the content of a file") stat1ParamMatcher := mock.MatchedBy(func(statReq *providerv1beta1.StatRequest) bool { return statReq.Ref.ResourceId == wopiCtx.FileReference.ResourceId }) gatewayClient.On("Stat", mock.Anything, stat1ParamMatcher).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Path: "/personal/path/to/file.docx", ParentId: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "opaqueid", SpaceId: "spaceid", }, }, }, nil) ccs.On("PutFile", mock.Anything, stream, int64(stream.Len()), "").Times(1).Return(nil, connector.NewConnectorError(500, "Something happened")) response, err := fc.PutRelativeFileRelative(ctx, ccs, stream, int64(stream.Len()), "convFile.pdf") targetErr := connector.NewConnectorError(500, "Something happened") Expect(err).To(Equal(targetErr)) Expect(response).To(BeNil()) }) }) Describe("DeleteFile", func() { It("No valid context", func() { gatewaySelector.EXPECT().Next().Unset() ctx := context.Background() response, err := fc.DeleteFile(ctx, "lock") Expect(err).To(HaveOccurred()) Expect(response).To(BeNil()) }) It("Delete fails", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) targetErr := errors.New("Something went wrong") gatewayClient.On("Delete", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.DeleteResponse{ Status: status.NewInternal(ctx, "something failed"), }, targetErr) gatewayClient.EXPECT().Stat(mock.Anything, mock.Anything).Unset() response, err := fc.DeleteFile(ctx, "newlock") Expect(err).To(HaveOccurred()) Expect(err).To(Equal(targetErr)) Expect(response).To(BeNil()) }) It("Delete fails status not ok, get lock fails", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Delete", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.DeleteResponse{ Status: status.NewInternal(ctx, "something failed"), }, nil) targetErr := errors.New("Something went wrong") gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewInternal(ctx, "something failed"), }, targetErr) response, err := fc.DeleteFile(ctx, "newlock") Expect(err).To(HaveOccurred()) Expect(err).To(Equal(targetErr)) Expect(response).To(BeNil()) }) It("Delete fails file missing", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Delete", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.DeleteResponse{ Status: status.NewNotFound(ctx, "something failed"), }, nil) response, err := fc.DeleteFile(ctx, "newlock") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(404)) Expect(response.Headers).To(BeNil()) }) It("Delete fails status not ok, get lock not ok", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Delete", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.DeleteResponse{ Status: status.NewInternal(ctx, "something failed"), }, nil) gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewInternal(ctx, "something failed"), }, nil) response, err := fc.DeleteFile(ctx, "newlock") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(500)) Expect(response.Headers).To(BeNil()) }) It("Delete fails, file locked", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Delete", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.DeleteResponse{ Status: status.NewInternal(ctx, "something failed"), }, nil) gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewOK(ctx), Lock: &providerv1beta1.Lock{ LockId: "zzz999", Type: providerv1beta1.LockType_LOCK_TYPE_WRITE, }, }, nil) response, err := fc.DeleteFile(ctx, "newlock") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(409)) Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999")) }) It("Delete fails, file not locked", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Delete", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.DeleteResponse{ Status: status.NewInternal(ctx, "something failed"), }, nil) gatewayClient.On("GetLock", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.GetLockResponse{ Status: status.NewOK(ctx), }, nil) response, err := fc.DeleteFile(ctx, "newlock") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(500)) Expect(response.Headers).To(BeNil()) }) It("Delete success", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Delete", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.DeleteResponse{ Status: status.NewOK(ctx), }, nil) response, err := fc.DeleteFile(ctx, "newlock") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) Expect(response.Headers).To(BeNil()) }) }) Describe("RenameFile", func() { It("No valid context", func() { gatewaySelector.EXPECT().Next().Unset() ctx := context.Background() response, err := fc.RenameFile(ctx, "lockid", "newFile.doc") Expect(err).To(HaveOccurred()) Expect(response).To(BeNil()) }) It("Stat fails", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) targetErr := errors.New("Something went wrong") gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewInternal(ctx, "something failed"), }, targetErr) response, err := fc.RenameFile(ctx, "lockid", "newFile.doc") Expect(err).To(HaveOccurred()) Expect(err).To(Equal(targetErr)) Expect(response).To(BeNil()) }) It("Stat fails status not ok", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewInternal(ctx, "something failed"), }, nil) response, err := fc.RenameFile(ctx, "lockid", "newFile.doc") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(500)) Expect(response.Headers).To(BeNil()) Expect(response.Body).To(BeNil()) }) It("Rename failed", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Lock: &providerv1beta1.Lock{ LockId: "zzz999", Type: providerv1beta1.LockType_LOCK_TYPE_WRITE, }, }, }, nil) targetErr := errors.New("Something went wrong") gatewayClient.On("Move", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.MoveResponse{ Status: status.NewInternal(ctx, "something failed"), }, targetErr) response, err := fc.RenameFile(ctx, "lockid", "newFile.doc") Expect(err).To(HaveOccurred()) Expect(err).To(Equal(targetErr)) Expect(response).To(BeNil()) }) It("Rename failed status not ok", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Lock: &providerv1beta1.Lock{ LockId: "zzz999", Type: providerv1beta1.LockType_LOCK_TYPE_WRITE, }, }, }, nil) gatewayClient.On("Move", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.MoveResponse{ Status: status.NewInternal(ctx, "something failed"), }, nil) response, err := fc.RenameFile(ctx, "lockid", "newFile.doc") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(500)) Expect(response.Headers).To(BeNil()) Expect(response.Body).To(BeNil()) }) It("Rename conflict", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Lock: &providerv1beta1.Lock{ LockId: "zzz999", Type: providerv1beta1.LockType_LOCK_TYPE_WRITE, }, }, }, nil) gatewayClient.On("Move", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.MoveResponse{ Status: status.NewLocked(ctx, "lock mismatch"), }, nil) response, err := fc.RenameFile(ctx, "lockid", "newFile.doc") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(409)) Expect(response.Headers[connector.HeaderWopiLock]).To(Equal("zzz999")) Expect(response.Body).To(BeNil()) }) It("Rename already exists", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Lock: &providerv1beta1.Lock{ LockId: "zzz999", Type: providerv1beta1.LockType_LOCK_TYPE_WRITE, }, }, }, nil) moveParamMatcher := mock.MatchedBy(func(moveReq *providerv1beta1.MoveRequest) bool { if moveReq.Destination.Path == "./newFile.doc" && moveReq.LockId == "zzz999" { return true } return false }) gatewayClient.On("Move", mock.Anything, moveParamMatcher).Times(1).Return(&providerv1beta1.MoveResponse{ Status: status.NewAlreadyExists(ctx, nil, "target already exists"), }, nil).Once() move2ParamMatcher := mock.MatchedBy(func(moveReq *providerv1beta1.MoveRequest) bool { r := regexp.MustCompile(`^\./[a-zA-Z0-9_-]+ newFile\.doc$`) if r.MatchString(moveReq.Destination.Path) && moveReq.LockId == "zzz999" { return true } return false }) gatewayClient.On("Move", mock.Anything, move2ParamMatcher).Times(1).Return(&providerv1beta1.MoveResponse{ Status: status.NewOK(ctx), }, nil).Once() response, err := fc.RenameFile(ctx, "zzz999", "newFile.doc") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) Expect(response.Headers).To(BeNil()) rBody := response.Body.(map[string]interface{}) Expect(rBody["Name"]).To(MatchRegexp(`^[a-zA-Z0-9_-]+ newFile$`)) }) It("Success", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Lock: &providerv1beta1.Lock{ LockId: "zzz999", Type: providerv1beta1.LockType_LOCK_TYPE_WRITE, }, }, }, nil) moveParamMatcher := mock.MatchedBy(func(moveReq *providerv1beta1.MoveRequest) bool { if moveReq.Destination.Path == "./newFile.doc" && moveReq.LockId == "zzz999" { return true } return false }) gatewayClient.On("Move", mock.Anything, moveParamMatcher).Times(1).Return(&providerv1beta1.MoveResponse{ Status: status.NewOK(ctx), }, nil).Once() response, err := fc.RenameFile(ctx, "zzz999", "newFile.doc") Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) Expect(response.Headers).To(BeNil()) rBody := response.Body.(map[string]interface{}) Expect(rBody["Name"]).To(Equal("newFile")) }) }) Describe("CheckFileInfo", func() { It("No valid context", func() { gatewaySelector.EXPECT().Next().Unset() ctx := context.Background() response, err := fc.CheckFileInfo(ctx) Expect(err).To(HaveOccurred()) Expect(response).To(BeNil()) }) It("Stat fails", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) targetErr := errors.New("Something went wrong") gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewInternal(ctx, "something failed"), }, targetErr) response, err := fc.CheckFileInfo(ctx) Expect(err).To(HaveOccurred()) Expect(err).To(Equal(targetErr)) Expect(response).To(BeNil()) }) It("Stat fails status not ok", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewInternal(ctx, "something failed"), }, nil) response, err := fc.CheckFileInfo(ctx) Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(500)) Expect(response.Body).To(BeNil()) }) It("Stat fails status not found", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewNotFound(ctx, "something not found"), }, nil) response, err := fc.CheckFileInfo(ctx) Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(404)) Expect(response.Body).To(BeNil()) }) It("Stat success", func() { ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) u := &userv1beta1.User{ Id: &userv1beta1.UserId{ Idp: "customIdp", OpaqueId: "admin", }, DisplayName: "Pet Shaft", } ctx = ctxpkg.ContextSetUser(ctx, u) gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Owner: &userv1beta1.UserId{ Idp: "customIdp", OpaqueId: "aabbcc", Type: userv1beta1.UserType_USER_TYPE_PRIMARY, }, Size: uint64(998877), Mtime: &typesv1beta1.Timestamp{ Seconds: uint64(16273849), Nanos: uint32(123456789), }, Path: "/path/to/test.txt", Id: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "opaqueid", SpaceId: "spaceid", }, ParentId: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "parentopaqueid", SpaceId: "spaceid", }, }, }, nil) expectedFileInfo := &fileinfo.Microsoft{ OwnerID: "61616262636340637573746f6d496470", // hex of aabbcc@customIdp Size: int64(998877), Version: "v16273849123456789", LastModifiedTime: "1970-07-08T08:30:49.1234567Z", BaseFileName: "test.txt", BreadcrumbDocName: "test.txt", BreadcrumbFolderName: "/path/to", BreadcrumbFolderURL: "https://ocis.example.prv/f/storageid$spaceid%21parentopaqueid", UserCanNotWriteRelative: false, SupportsExtendedLockLength: true, SupportsGetLock: true, SupportsLocks: true, SupportsUpdate: true, SupportsDeleteFile: true, SupportsRename: true, UserCanWrite: true, UserCanRename: true, UserID: "61646d696e40637573746f6d496470", // hex of admin@customIdp UserFriendlyName: "Pet Shaft", FileSharingURL: "https://ocis.example.prv/f/storageid$spaceid%21opaqueid?details=sharing", FileVersionURL: "https://ocis.example.prv/f/storageid$spaceid%21opaqueid?details=versions", HostEditURL: "https://ocis.example.prv/external-test/path/to/test.txt?fileId=storageid%24spaceid%21opaqueid&view_mode=write", HostViewURL: "https://ocis.example.prv/external-test/path/to/test.txt?fileId=storageid%24spaceid%21opaqueid&view_mode=view", } response, err := fc.CheckFileInfo(ctx) Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) Expect(response.Body.(*fileinfo.Microsoft)).To(Equal(expectedFileInfo)) }) It("Stat success guests", func() { // add user's opaque to include public-share-role u := &userv1beta1.User{} u.Opaque = &typesv1beta1.Opaque{ Map: map[string]*typesv1beta1.OpaqueEntry{ "public-share-role": { Decoder: "plain", Value: []byte("viewer"), }, }, } // change view mode to view only wopiCtx.ViewMode = appproviderv1beta1.ViewMode_VIEW_MODE_VIEW_ONLY ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) ctx = ctxpkg.ContextSetUser(ctx, u) gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Owner: &userv1beta1.UserId{ Idp: "customIdp", OpaqueId: "aabbcc", Type: userv1beta1.UserType_USER_TYPE_PRIMARY, }, // Size is intentionally nil for guest users Mtime: &typesv1beta1.Timestamp{ Seconds: uint64(16273849), Nanos: uint32(1234567), }, Path: "/path/to/test.txt", Id: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "opaqueid", SpaceId: "spaceid", }, ParentId: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "parentopaqueid", SpaceId: "spaceid", }, // Other properties aren't used for now. }, }, nil) // change wopi app provider cfg.App.Name = "Collabora" cfg.App.Product = "Collabora" expectedFileInfo := &fileinfo.Collabora{ OwnerID: "61616262636340637573746f6d496470", // hex of aabbcc@customIdp Size: 0, BaseFileName: "test.txt", UserCanNotWriteRelative: false, DisableExport: true, DisableCopy: true, DisablePrint: true, UserID: "guest-zzz000", UserFriendlyName: "guest zzz000", EnableOwnerTermination: true, SupportsLocks: true, SupportsRename: true, UserCanRename: false, BreadcrumbDocName: "test.txt", PostMessageOrigin: "https://ocis.example.prv", } response, err := fc.CheckFileInfo(ctx) // UserID and UserFriendlyName have random Ids generated which are impossible to guess // Check both separately Expect(response.Body.(*fileinfo.Collabora).UserID).To(HavePrefix(hex.EncodeToString([]byte("guest-")))) Expect(response.Body.(*fileinfo.Collabora).UserFriendlyName).To(HavePrefix("Guest ")) // overwrite UserID and UserFriendlyName here for easier matching response.Body.(*fileinfo.Collabora).UserID = "guest-zzz000" response.Body.(*fileinfo.Collabora).UserFriendlyName = "guest zzz000" Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) Expect(response.Body.(*fileinfo.Collabora)).To(Equal(expectedFileInfo)) }) It("Stat success guests", func() { ResourceId := &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "opaqueid", SpaceId: "spaceid", } // add user's opaque to include public-share-role u := &userv1beta1.User{} u.Opaque = &typesv1beta1.Opaque{ Map: map[string]*typesv1beta1.OpaqueEntry{ "public-share-role": { Decoder: "plain", Value: []byte("viewer"), }, }, } // Create a new "scope share" to only expose the required fields `ResourceId` and `Token` to the scope. scopeShare := &link.PublicShare{ResourceId: ResourceId, Token: "ABC123"} val, err := utils.MarshalProtoV1ToJSON(scopeShare) Expect(err).ToNot(HaveOccurred()) scopes := map[string]*auth.Scope{ "publicshare:ABC123": { Resource: &typesv1beta1.OpaqueEntry{ Decoder: "json", Value: val, }, Role: auth.Role(appproviderv1beta1.ViewMode_VIEW_MODE_VIEW_ONLY), }, } // change view mode to view only wopiCtx.ViewMode = appproviderv1beta1.ViewMode_VIEW_MODE_VIEW_ONLY ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) ctx = ctxpkg.ContextSetUser(ctx, u) ctx = ctxpkg.ContextSetScopes(ctx, scopes) gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Owner: &userv1beta1.UserId{ Idp: "customIdp", OpaqueId: "aabbcc", Type: userv1beta1.UserType_USER_TYPE_PRIMARY, }, // Size is intentionally nil for guest users Mtime: &typesv1beta1.Timestamp{ Seconds: uint64(16273849), Nanos: uint32(123456789), }, Path: "/path/to/test.txt", Id: ResourceId, ParentId: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "parentopaqueid", SpaceId: "spaceid", }, // Other properties aren't used for now. }, }, nil) // change wopi app provider cfg.App.Name = "OnlyOffice" cfg.App.Product = "OnlyOffice" expectedFileInfo := &fileinfo.OnlyOffice{ Version: "v16273849123456789", LastModifiedTime: "1970-07-08T08:30:49.1234567Z", BaseFileName: "test.txt", BreadcrumbDocName: "test.txt", BreadcrumbFolderName: "/path/to", BreadcrumbFolderURL: "https://ocis.example.prv/s/ABC123", // Match share token format DisablePrint: true, UserCanNotWriteRelative: false, SupportsLocks: true, SupportsUpdate: true, SupportsRename: true, IsAnonymousUser: true, UserCanRename: false, UserCanReview: false, UserCanWrite: false, UserID: "guest-zzz000", UserFriendlyName: "guest zzz000", FileSharingURL: "https://ocis.example.prv/f/storageid$spaceid%21opaqueid?details=sharing", FileVersionURL: "https://ocis.example.prv/f/storageid$spaceid%21opaqueid?details=versions", HostEditURL: "https://ocis.example.prv/external-onlyoffice/path/to/test.txt?fileId=storageid%24spaceid%21opaqueid&view_mode=write", PostMessageOrigin: "https://ocis.example.prv", } response, err := fc.CheckFileInfo(ctx) // UserID and UserFriendlyName have random Ids generated which are impossible to guess // Check both separately Expect(response.Body.(*fileinfo.OnlyOffice).UserID).To(HavePrefix(hex.EncodeToString([]byte("guest-")))) Expect(response.Body.(*fileinfo.OnlyOffice).UserFriendlyName).To(HavePrefix("Guest ")) // overwrite UserID and UserFriendlyName here for easier matching response.Body.(*fileinfo.OnlyOffice).UserID = "guest-zzz000" response.Body.(*fileinfo.OnlyOffice).UserFriendlyName = "guest zzz000" Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) Expect(response.Body.(*fileinfo.OnlyOffice)).To(Equal(expectedFileInfo)) }) It("Stat success authenticated user", func() { // change view mode to view only wopiCtx.ViewMode = appproviderv1beta1.ViewMode_VIEW_MODE_VIEW_ONLY ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) u := &userv1beta1.User{ Id: &userv1beta1.UserId{ Idp: "example.com", OpaqueId: "aabbcc", Type: userv1beta1.UserType_USER_TYPE_PRIMARY, }, DisplayName: "Pet Shaft", Mail: "shaft@example.com", } ctx = ctxpkg.ContextSetUser(ctx, u) gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Owner: &userv1beta1.UserId{ Idp: "customIdp", OpaqueId: "aabbcc", Type: userv1beta1.UserType_USER_TYPE_PRIMARY, }, Size: uint64(998877), Mtime: &typesv1beta1.Timestamp{ Seconds: uint64(16273849), }, Path: "/path/to/test.txt", Id: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "opaqueid", SpaceId: "spaceid", }, ParentId: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "parentopaqueid", SpaceId: "spaceid", }, }, }, nil) // change wopi app provider cfg.App.Name = "Collabora" cfg.App.Product = "Collabora" expectedFileInfo := &fileinfo.Collabora{ OwnerID: "61616262636340637573746f6d496470", // hex of aabbcc@customIdp Size: int64(998877), BaseFileName: "test.txt", UserCanNotWriteRelative: false, DisableExport: true, DisableCopy: true, DisablePrint: true, UserID: hex.EncodeToString([]byte("aabbcc@example.com")), UserFriendlyName: "Pet Shaft", EnableOwnerTermination: true, WatermarkText: "", SupportsLocks: true, SupportsRename: true, UserCanRename: false, BreadcrumbDocName: "test.txt", PostMessageOrigin: "https://ocis.example.prv", } response, err := fc.CheckFileInfo(ctx) Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) Expect(response.Body.(*fileinfo.Collabora)).To(Equal(expectedFileInfo)) }) It("Stat success secure view authenticated user", func() { // change view mode to view only wopiCtx.ViewMode = appproviderv1beta1.ViewMode_VIEW_MODE_VIEW_ONLY wopiCtx.ViewOnlyToken = "ABC123" ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) u := &userv1beta1.User{ Id: &userv1beta1.UserId{ Idp: "example.com", OpaqueId: "aabbcc", Type: userv1beta1.UserType_USER_TYPE_PRIMARY, }, DisplayName: "Pet Shaft", Mail: "shaft@example.com", } ctx = ctxpkg.ContextSetUser(ctx, u) gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Owner: &userv1beta1.UserId{ Idp: "customIdp", OpaqueId: "aabbcc", Type: userv1beta1.UserType_USER_TYPE_PRIMARY, }, Size: uint64(998877), Mtime: &typesv1beta1.Timestamp{ Seconds: uint64(16273849), }, Path: "/path/to/test.txt", Id: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "opaqueid", SpaceId: "spaceid", }, ParentId: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "parentopaqueid", SpaceId: "spaceid", }, }, }, nil) // change wopi app provider cfg.App.Name = "Collabora" cfg.App.Product = "Collabora" expectedFileInfo := &fileinfo.Collabora{ OwnerID: "61616262636340637573746f6d496470", // hex of aabbcc@customIdp Size: int64(998877), BaseFileName: "test.txt", UserCanNotWriteRelative: false, DisableExport: true, DisableCopy: true, DisablePrint: true, UserID: hex.EncodeToString([]byte("aabbcc@example.com")), UserFriendlyName: "Pet Shaft", EnableOwnerTermination: true, WatermarkText: "Pet Shaft shaft@example.com", SupportsLocks: true, SupportsRename: true, UserCanRename: false, BreadcrumbDocName: "test.txt", PostMessageOrigin: "https://ocis.example.prv", } response, err := fc.CheckFileInfo(ctx) Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) Expect(response.Body.(*fileinfo.Collabora)).To(Equal(expectedFileInfo)) }) It("Stat success with template", func() { wopiCtx.TemplateReference = &providerv1beta1.Reference{ ResourceId: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "opaqueid2", SpaceId: "spaceid", }, } ctx := middleware.WopiContextToCtx(context.Background(), wopiCtx) u := &userv1beta1.User{ Id: &userv1beta1.UserId{ Idp: "customIdp", OpaqueId: "admin", }, DisplayName: "Pet Shaft", } ctx = ctxpkg.ContextSetUser(ctx, u) gatewayClient.On("Stat", mock.Anything, mock.Anything).Times(1).Return(&providerv1beta1.StatResponse{ Status: status.NewOK(ctx), Info: &providerv1beta1.ResourceInfo{ Owner: &userv1beta1.UserId{ Idp: "customIdp", OpaqueId: "aabbcc", Type: userv1beta1.UserType_USER_TYPE_PRIMARY, }, Size: uint64(0), Mtime: &typesv1beta1.Timestamp{ Seconds: uint64(16273849), }, Path: "/path/to/test.txt", Id: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "opaqueid", SpaceId: "spaceid", }, ParentId: &providerv1beta1.ResourceId{ StorageId: "storageid", OpaqueId: "parentopaqueid", SpaceId: "spaceid", }, }, }, nil) expectedFileInfo := &fileinfo.OnlyOffice{ Version: "v162738490", LastModifiedTime: "1970-07-08T08:30:49.0000000Z", BaseFileName: "test.txt", BreadcrumbDocName: "test.txt", BreadcrumbFolderName: "/path/to", BreadcrumbFolderURL: "https://ocis.example.prv/f/storageid$spaceid%21parentopaqueid", UserCanNotWriteRelative: false, SupportsLocks: true, SupportsUpdate: true, SupportsRename: true, UserCanWrite: true, UserCanRename: true, UserID: "61646d696e40637573746f6d496470", // hex of admin@customIdp UserFriendlyName: "Pet Shaft", FileSharingURL: "https://ocis.example.prv/f/storageid$spaceid%21opaqueid?details=sharing", FileVersionURL: "https://ocis.example.prv/f/storageid$spaceid%21opaqueid?details=versions", HostEditURL: "https://ocis.example.prv/external-onlyoffice/path/to/test.txt?fileId=storageid%24spaceid%21opaqueid&view_mode=write", PostMessageOrigin: "https://ocis.example.prv", TemplateSource: "", // Remove the hardcoded token since it's dynamically generated } // change wopi app provider cfg.App.Name = "OnlyOffice" cfg.App.Product = "OnlyOffice" response, err := fc.CheckFileInfo(ctx) Expect(err).ToNot(HaveOccurred()) Expect(response.Status).To(Equal(200)) returnedFileInfo := response.Body.(*fileinfo.OnlyOffice) templateSource := returnedFileInfo.TemplateSource expectedTemplateSource := "https://ocis.server.prv/wopi/templates/a340d017568d0d579ee061a9ac02109e32fb07082d35c40aa175864303bd9107?access_token=" // take TemplateSource out of the response for easier comparison returnedFileInfo.TemplateSource = "" Expect(returnedFileInfo).To(Equal(expectedFileInfo)) // check if the template source is correct // the url is using a generated access token which always has a new ttl // so we can't compare the whole url Expect(templateSource).To(HavePrefix(expectedTemplateSource)) }) }) })