test: include interfaces, add mocks and add unit tests in adapter

This commit is contained in:
Juan Pablo Villafáñez
2024-04-02 14:42:50 +02:00
parent 710a0b4561
commit 9b4233e690
9 changed files with 1153 additions and 7 deletions

View File

@@ -0,0 +1,12 @@
with-expecter: true
filename: "{{.InterfaceName | snakecase }}.go"
mockname: "{{.InterfaceName}}"
outpkg: "mocks"
packages:
github.com/owncloud/ocis/v2/services/collaboration/pkg/connector:
config:
dir: "mocks"
interfaces:
ConnectorService:
ContentConnectorService:
FileConnectorService:

View File

@@ -0,0 +1,129 @@
// Code generated by mockery v2.40.2. DO NOT EDIT.
package mocks
import (
connector "github.com/owncloud/ocis/v2/services/collaboration/pkg/connector"
mock "github.com/stretchr/testify/mock"
)
// ConnectorService is an autogenerated mock type for the ConnectorService type
type ConnectorService struct {
mock.Mock
}
type ConnectorService_Expecter struct {
mock *mock.Mock
}
func (_m *ConnectorService) EXPECT() *ConnectorService_Expecter {
return &ConnectorService_Expecter{mock: &_m.Mock}
}
// GetContentConnector provides a mock function with given fields:
func (_m *ConnectorService) GetContentConnector() connector.ContentConnectorService {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for GetContentConnector")
}
var r0 connector.ContentConnectorService
if rf, ok := ret.Get(0).(func() connector.ContentConnectorService); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(connector.ContentConnectorService)
}
}
return r0
}
// ConnectorService_GetContentConnector_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetContentConnector'
type ConnectorService_GetContentConnector_Call struct {
*mock.Call
}
// GetContentConnector is a helper method to define mock.On call
func (_e *ConnectorService_Expecter) GetContentConnector() *ConnectorService_GetContentConnector_Call {
return &ConnectorService_GetContentConnector_Call{Call: _e.mock.On("GetContentConnector")}
}
func (_c *ConnectorService_GetContentConnector_Call) Run(run func()) *ConnectorService_GetContentConnector_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *ConnectorService_GetContentConnector_Call) Return(_a0 connector.ContentConnectorService) *ConnectorService_GetContentConnector_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *ConnectorService_GetContentConnector_Call) RunAndReturn(run func() connector.ContentConnectorService) *ConnectorService_GetContentConnector_Call {
_c.Call.Return(run)
return _c
}
// GetFileConnector provides a mock function with given fields:
func (_m *ConnectorService) GetFileConnector() connector.FileConnectorService {
ret := _m.Called()
if len(ret) == 0 {
panic("no return value specified for GetFileConnector")
}
var r0 connector.FileConnectorService
if rf, ok := ret.Get(0).(func() connector.FileConnectorService); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(connector.FileConnectorService)
}
}
return r0
}
// ConnectorService_GetFileConnector_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFileConnector'
type ConnectorService_GetFileConnector_Call struct {
*mock.Call
}
// GetFileConnector is a helper method to define mock.On call
func (_e *ConnectorService_Expecter) GetFileConnector() *ConnectorService_GetFileConnector_Call {
return &ConnectorService_GetFileConnector_Call{Call: _e.mock.On("GetFileConnector")}
}
func (_c *ConnectorService_GetFileConnector_Call) Run(run func()) *ConnectorService_GetFileConnector_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *ConnectorService_GetFileConnector_Call) Return(_a0 connector.FileConnectorService) *ConnectorService_GetFileConnector_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *ConnectorService_GetFileConnector_Call) RunAndReturn(run func() connector.FileConnectorService) *ConnectorService_GetFileConnector_Call {
_c.Call.Return(run)
return _c
}
// NewConnectorService creates a new instance of ConnectorService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewConnectorService(t interface {
mock.TestingT
Cleanup(func())
}) *ConnectorService {
mock := &ConnectorService{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -0,0 +1,143 @@
// Code generated by mockery v2.40.2. DO NOT EDIT.
package mocks
import (
context "context"
io "io"
mock "github.com/stretchr/testify/mock"
)
// ContentConnectorService is an autogenerated mock type for the ContentConnectorService type
type ContentConnectorService struct {
mock.Mock
}
type ContentConnectorService_Expecter struct {
mock *mock.Mock
}
func (_m *ContentConnectorService) EXPECT() *ContentConnectorService_Expecter {
return &ContentConnectorService_Expecter{mock: &_m.Mock}
}
// GetFile provides a mock function with given fields: ctx, writer
func (_m *ContentConnectorService) GetFile(ctx context.Context, writer io.Writer) error {
ret := _m.Called(ctx, writer)
if len(ret) == 0 {
panic("no return value specified for GetFile")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, io.Writer) error); ok {
r0 = rf(ctx, writer)
} else {
r0 = ret.Error(0)
}
return r0
}
// ContentConnectorService_GetFile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFile'
type ContentConnectorService_GetFile_Call struct {
*mock.Call
}
// GetFile is a helper method to define mock.On call
// - ctx context.Context
// - writer io.Writer
func (_e *ContentConnectorService_Expecter) GetFile(ctx interface{}, writer interface{}) *ContentConnectorService_GetFile_Call {
return &ContentConnectorService_GetFile_Call{Call: _e.mock.On("GetFile", ctx, writer)}
}
func (_c *ContentConnectorService_GetFile_Call) Run(run func(ctx context.Context, writer io.Writer)) *ContentConnectorService_GetFile_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(io.Writer))
})
return _c
}
func (_c *ContentConnectorService_GetFile_Call) Return(_a0 error) *ContentConnectorService_GetFile_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *ContentConnectorService_GetFile_Call) RunAndReturn(run func(context.Context, io.Writer) error) *ContentConnectorService_GetFile_Call {
_c.Call.Return(run)
return _c
}
// PutFile provides a mock function with given fields: ctx, stream, streamLength, lockID
func (_m *ContentConnectorService) PutFile(ctx context.Context, stream io.Reader, streamLength int64, lockID string) (string, error) {
ret := _m.Called(ctx, stream, streamLength, lockID)
if len(ret) == 0 {
panic("no return value specified for PutFile")
}
var r0 string
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, io.Reader, int64, string) (string, error)); ok {
return rf(ctx, stream, streamLength, lockID)
}
if rf, ok := ret.Get(0).(func(context.Context, io.Reader, int64, string) string); ok {
r0 = rf(ctx, stream, streamLength, lockID)
} else {
r0 = ret.Get(0).(string)
}
if rf, ok := ret.Get(1).(func(context.Context, io.Reader, int64, string) error); ok {
r1 = rf(ctx, stream, streamLength, lockID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ContentConnectorService_PutFile_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PutFile'
type ContentConnectorService_PutFile_Call struct {
*mock.Call
}
// PutFile is a helper method to define mock.On call
// - ctx context.Context
// - stream io.Reader
// - streamLength int64
// - lockID string
func (_e *ContentConnectorService_Expecter) PutFile(ctx interface{}, stream interface{}, streamLength interface{}, lockID interface{}) *ContentConnectorService_PutFile_Call {
return &ContentConnectorService_PutFile_Call{Call: _e.mock.On("PutFile", ctx, stream, streamLength, lockID)}
}
func (_c *ContentConnectorService_PutFile_Call) Run(run func(ctx context.Context, stream io.Reader, streamLength int64, lockID string)) *ContentConnectorService_PutFile_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(io.Reader), args[2].(int64), args[3].(string))
})
return _c
}
func (_c *ContentConnectorService_PutFile_Call) Return(_a0 string, _a1 error) *ContentConnectorService_PutFile_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *ContentConnectorService_PutFile_Call) RunAndReturn(run func(context.Context, io.Reader, int64, string) (string, error)) *ContentConnectorService_PutFile_Call {
_c.Call.Return(run)
return _c
}
// NewContentConnectorService creates a new instance of ContentConnectorService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewContentConnectorService(t interface {
mock.TestingT
Cleanup(func())
}) *ContentConnectorService {
mock := &ContentConnectorService{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -0,0 +1,322 @@
// Code generated by mockery v2.40.2. DO NOT EDIT.
package mocks
import (
context "context"
connector "github.com/owncloud/ocis/v2/services/collaboration/pkg/connector"
mock "github.com/stretchr/testify/mock"
)
// FileConnectorService is an autogenerated mock type for the FileConnectorService type
type FileConnectorService struct {
mock.Mock
}
type FileConnectorService_Expecter struct {
mock *mock.Mock
}
func (_m *FileConnectorService) EXPECT() *FileConnectorService_Expecter {
return &FileConnectorService_Expecter{mock: &_m.Mock}
}
// CheckFileInfo provides a mock function with given fields: ctx
func (_m *FileConnectorService) CheckFileInfo(ctx context.Context) (connector.FileInfo, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for CheckFileInfo")
}
var r0 connector.FileInfo
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) (connector.FileInfo, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) connector.FileInfo); ok {
r0 = rf(ctx)
} else {
r0 = ret.Get(0).(connector.FileInfo)
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FileConnectorService_CheckFileInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CheckFileInfo'
type FileConnectorService_CheckFileInfo_Call struct {
*mock.Call
}
// CheckFileInfo is a helper method to define mock.On call
// - ctx context.Context
func (_e *FileConnectorService_Expecter) CheckFileInfo(ctx interface{}) *FileConnectorService_CheckFileInfo_Call {
return &FileConnectorService_CheckFileInfo_Call{Call: _e.mock.On("CheckFileInfo", ctx)}
}
func (_c *FileConnectorService_CheckFileInfo_Call) Run(run func(ctx context.Context)) *FileConnectorService_CheckFileInfo_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *FileConnectorService_CheckFileInfo_Call) Return(_a0 connector.FileInfo, _a1 error) *FileConnectorService_CheckFileInfo_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *FileConnectorService_CheckFileInfo_Call) RunAndReturn(run func(context.Context) (connector.FileInfo, error)) *FileConnectorService_CheckFileInfo_Call {
_c.Call.Return(run)
return _c
}
// GetLock provides a mock function with given fields: ctx
func (_m *FileConnectorService) GetLock(ctx context.Context) (string, error) {
ret := _m.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for GetLock")
}
var r0 string
var r1 error
if rf, ok := ret.Get(0).(func(context.Context) (string, error)); ok {
return rf(ctx)
}
if rf, ok := ret.Get(0).(func(context.Context) string); ok {
r0 = rf(ctx)
} else {
r0 = ret.Get(0).(string)
}
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = rf(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FileConnectorService_GetLock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLock'
type FileConnectorService_GetLock_Call struct {
*mock.Call
}
// GetLock is a helper method to define mock.On call
// - ctx context.Context
func (_e *FileConnectorService_Expecter) GetLock(ctx interface{}) *FileConnectorService_GetLock_Call {
return &FileConnectorService_GetLock_Call{Call: _e.mock.On("GetLock", ctx)}
}
func (_c *FileConnectorService_GetLock_Call) Run(run func(ctx context.Context)) *FileConnectorService_GetLock_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *FileConnectorService_GetLock_Call) Return(_a0 string, _a1 error) *FileConnectorService_GetLock_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *FileConnectorService_GetLock_Call) RunAndReturn(run func(context.Context) (string, error)) *FileConnectorService_GetLock_Call {
_c.Call.Return(run)
return _c
}
// Lock provides a mock function with given fields: ctx, lockID, oldLockID
func (_m *FileConnectorService) Lock(ctx context.Context, lockID string, oldLockID string) (string, error) {
ret := _m.Called(ctx, lockID, oldLockID)
if len(ret) == 0 {
panic("no return value specified for Lock")
}
var r0 string
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string, string) (string, error)); ok {
return rf(ctx, lockID, oldLockID)
}
if rf, ok := ret.Get(0).(func(context.Context, string, string) string); ok {
r0 = rf(ctx, lockID, oldLockID)
} else {
r0 = ret.Get(0).(string)
}
if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
r1 = rf(ctx, lockID, oldLockID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FileConnectorService_Lock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Lock'
type FileConnectorService_Lock_Call struct {
*mock.Call
}
// Lock is a helper method to define mock.On call
// - ctx context.Context
// - lockID string
// - oldLockID string
func (_e *FileConnectorService_Expecter) Lock(ctx interface{}, lockID interface{}, oldLockID interface{}) *FileConnectorService_Lock_Call {
return &FileConnectorService_Lock_Call{Call: _e.mock.On("Lock", ctx, lockID, oldLockID)}
}
func (_c *FileConnectorService_Lock_Call) Run(run func(ctx context.Context, lockID string, oldLockID string)) *FileConnectorService_Lock_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string), args[2].(string))
})
return _c
}
func (_c *FileConnectorService_Lock_Call) Return(_a0 string, _a1 error) *FileConnectorService_Lock_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *FileConnectorService_Lock_Call) RunAndReturn(run func(context.Context, string, string) (string, error)) *FileConnectorService_Lock_Call {
_c.Call.Return(run)
return _c
}
// RefreshLock provides a mock function with given fields: ctx, lockID
func (_m *FileConnectorService) RefreshLock(ctx context.Context, lockID string) (string, error) {
ret := _m.Called(ctx, lockID)
if len(ret) == 0 {
panic("no return value specified for RefreshLock")
}
var r0 string
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (string, error)); ok {
return rf(ctx, lockID)
}
if rf, ok := ret.Get(0).(func(context.Context, string) string); ok {
r0 = rf(ctx, lockID)
} else {
r0 = ret.Get(0).(string)
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, lockID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FileConnectorService_RefreshLock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RefreshLock'
type FileConnectorService_RefreshLock_Call struct {
*mock.Call
}
// RefreshLock is a helper method to define mock.On call
// - ctx context.Context
// - lockID string
func (_e *FileConnectorService_Expecter) RefreshLock(ctx interface{}, lockID interface{}) *FileConnectorService_RefreshLock_Call {
return &FileConnectorService_RefreshLock_Call{Call: _e.mock.On("RefreshLock", ctx, lockID)}
}
func (_c *FileConnectorService_RefreshLock_Call) Run(run func(ctx context.Context, lockID string)) *FileConnectorService_RefreshLock_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string))
})
return _c
}
func (_c *FileConnectorService_RefreshLock_Call) Return(_a0 string, _a1 error) *FileConnectorService_RefreshLock_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *FileConnectorService_RefreshLock_Call) RunAndReturn(run func(context.Context, string) (string, error)) *FileConnectorService_RefreshLock_Call {
_c.Call.Return(run)
return _c
}
// UnLock provides a mock function with given fields: ctx, lockID
func (_m *FileConnectorService) UnLock(ctx context.Context, lockID string) (string, error) {
ret := _m.Called(ctx, lockID)
if len(ret) == 0 {
panic("no return value specified for UnLock")
}
var r0 string
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) (string, error)); ok {
return rf(ctx, lockID)
}
if rf, ok := ret.Get(0).(func(context.Context, string) string); ok {
r0 = rf(ctx, lockID)
} else {
r0 = ret.Get(0).(string)
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, lockID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FileConnectorService_UnLock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UnLock'
type FileConnectorService_UnLock_Call struct {
*mock.Call
}
// UnLock is a helper method to define mock.On call
// - ctx context.Context
// - lockID string
func (_e *FileConnectorService_Expecter) UnLock(ctx interface{}, lockID interface{}) *FileConnectorService_UnLock_Call {
return &FileConnectorService_UnLock_Call{Call: _e.mock.On("UnLock", ctx, lockID)}
}
func (_c *FileConnectorService_UnLock_Call) Run(run func(ctx context.Context, lockID string)) *FileConnectorService_UnLock_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(string))
})
return _c
}
func (_c *FileConnectorService_UnLock_Call) Return(_a0 string, _a1 error) *FileConnectorService_UnLock_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *FileConnectorService_UnLock_Call) RunAndReturn(run func(context.Context, string) (string, error)) *FileConnectorService_UnLock_Call {
_c.Call.Return(run)
return _c
}
// NewFileConnectorService creates a new instance of FileConnectorService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewFileConnectorService(t interface {
mock.TestingT
Cleanup(func())
}) *FileConnectorService {
mock := &FileConnectorService{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -20,6 +20,15 @@ func NewConnectorError(code int, msg string) *ConnectorError {
}
}
// ConnectorService is the interface to implement the WOPI operations. They're
// divided into multiple endpoints.
// The IFileConnector will implement the "File" endpoint
// The IContentConnector will implement the "File content" endpoint
type ConnectorService interface {
GetFileConnector() FileConnectorService
GetContentConnector() ContentConnectorService
}
// Connector will implement the WOPI operations.
// For convenience, the connector splits the operations based on the
// WOPI endpoints, so you'll need to get the specific connector first.
@@ -30,21 +39,21 @@ func NewConnectorError(code int, msg string) *ConnectorError {
//
// Other endpoints aren't available for now.
type Connector struct {
fileConnector *FileConnector
contentConnector *ContentConnector
fileConnector FileConnectorService
contentConnector ContentConnectorService
}
func NewConnector(fc *FileConnector, cc *ContentConnector) *Connector {
func NewConnector(fc FileConnectorService, cc ContentConnectorService) *Connector {
return &Connector{
fileConnector: fc,
contentConnector: cc,
}
}
func (c *Connector) GetFileConnector() *FileConnector {
func (c *Connector) GetFileConnector() FileConnectorService {
return c.fileConnector
}
func (c *Connector) GetContentConnector() *ContentConnector {
func (c *Connector) GetContentConnector() ContentConnectorService {
return c.contentConnector
}

View File

@@ -18,6 +18,21 @@ import (
"github.com/rs/zerolog"
)
// ContentConnectorService is the interface to implement the "File contents"
// endpoint. Basically upload and download contents.
// All operations need a context containing a WOPI context and, optionally,
// a zerolog logger.
// Target file is within the WOPI context
type ContentConnectorService interface {
// GetFile downloads the file and write its contents in the provider writer
GetFile(ctx context.Context, writer io.Writer) error
// PutFile uploads the stream up to the stream length. The file should be
// locked beforehand, so the lockID needs to be provided.
// The current lockID will be returned ONLY if a conflict happens (the file is
// locked with a different lockID)
PutFile(ctx context.Context, stream io.Reader, streamLength int64, lockID string) (string, error)
}
// ContentConnector implements the "File contents" endpoint.
// Basically, the ContentConnector handles downloads (GetFile) and
// uploads (PutFile)

View File

@@ -25,6 +25,32 @@ const (
lockDuration time.Duration = 30 * time.Minute
)
// FileConnectorService is the interface to implement the "Files"
// endpoint. Basically lock operations on the file plus the CheckFileInfo.
// All operations need a context containing a WOPI context and, optionally,
// a zerolog logger.
// Target file is within the WOPI context
type FileConnectorService interface {
// GetLock will return the lockID present in the target file.
GetLock(ctx context.Context) (string, error)
// Lock will lock the target file with the provided lockID. If the oldLockID
// is provided (not empty), the method will perform an unlockAndRelock
// operation (unlock the file with the oldLockID and immediately relock
// the file with the new lockID).
// The current lockID will be returned if a conflict happens
Lock(ctx context.Context, lockID, oldLockID string) (string, error)
// RefreshLock will extend the lock time 30 minutes. The current lockID
// needs to be provided.
// The current lockID will be returned if a conflict happens
RefreshLock(ctx context.Context, lockID string) (string, error)
// Unlock will unlock the target file. The current lockID needs to be
// provided.
// The current lockID will be returned if a conflict happens
UnLock(ctx context.Context, lockID string) (string, error)
// CheckFileInfo will return the file information of the target file
CheckFileInfo(ctx context.Context) (FileInfo, error)
}
type FileConnector struct {
gwc gatewayv1beta1.GatewayAPIClient
cfg *config.Config

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"net/http"
"strconv"
gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
"github.com/owncloud/ocis/v2/services/collaboration/pkg/config"
@@ -24,9 +25,11 @@ const (
// All operations are expected to follow the definitions found in
// https://learn.microsoft.com/en-us/microsoft-365/cloud-storage-partner-program/rest/endpoints
type HttpAdapter struct {
con *Connector
con ConnectorService
}
// NewHttpAdapter will create a new HTTP adapter. A new connector using the
// provided gateway API client and configuration will be used in the adapter
func NewHttpAdapter(gwc gatewayv1beta1.GatewayAPIClient, cfg *config.Config) *HttpAdapter {
return &HttpAdapter{
con: NewConnector(
@@ -36,6 +39,14 @@ func NewHttpAdapter(gwc gatewayv1beta1.GatewayAPIClient, cfg *config.Config) *Ht
}
}
// NewHttpAdapterWithConnector will create a new HTTP adapter that will use
// the provided connector service
func NewHttpAdapterWithConnector(con ConnectorService) *HttpAdapter {
return &HttpAdapter{
con: con,
}
}
func (h *HttpAdapter) GetLock(w http.ResponseWriter, r *http.Request) {
fileCon := h.con.GetFileConnector()
@@ -119,6 +130,9 @@ func (h *HttpAdapter) UnLock(w http.ResponseWriter, r *http.Request) {
func (h *HttpAdapter) CheckFileInfo(w http.ResponseWriter, r *http.Request) {
fileCon := h.con.GetFileConnector()
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Length", "0")
fileInfo, err := fileCon.CheckFileInfo(r.Context())
if err != nil {
var conError *ConnectorError
@@ -138,7 +152,7 @@ func (h *HttpAdapter) CheckFileInfo(w http.ResponseWriter, r *http.Request) {
return
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Length", strconv.Itoa(len(jsonFileInfo)))
w.WriteHeader(http.StatusOK)
bytes, err := w.Write(jsonFileInfo)

View File

@@ -0,0 +1,476 @@
package connector_test
import (
"encoding/json"
"errors"
"io"
"net/http/httptest"
"strings"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/owncloud/ocis/v2/services/collaboration/mocks"
"github.com/owncloud/ocis/v2/services/collaboration/pkg/connector"
"github.com/stretchr/testify/mock"
)
var _ = Describe("HttpAdapter", func() {
var (
fc *mocks.FileConnectorService
cc *mocks.ContentConnectorService
con *mocks.ConnectorService
httpAdapter *connector.HttpAdapter
)
BeforeEach(func() {
fc = &mocks.FileConnectorService{}
cc = &mocks.ContentConnectorService{}
con = &mocks.ConnectorService{}
con.On("GetContentConnector").Return(cc)
con.On("GetFileConnector").Return(fc)
httpAdapter = connector.NewHttpAdapterWithConnector(con)
})
Describe("GetLock", func() {
It("General error", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "POST_LOCK")
w := httptest.NewRecorder()
fc.On("GetLock", mock.Anything).Times(1).Return("", errors.New("Something happened"))
httpAdapter.GetLock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(500))
})
It("File not found", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "POST_LOCK")
w := httptest.NewRecorder()
fc.On("GetLock", mock.Anything).Times(1).Return("", connector.NewConnectorError(404, "Couldn't get the file"))
httpAdapter.GetLock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(404))
})
It("LockId", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "POST_LOCK")
w := httptest.NewRecorder()
fc.On("GetLock", mock.Anything).Times(1).Return("zzz111", nil)
httpAdapter.GetLock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(200))
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("zzz111"))
})
It("Empty LockId", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "POST_LOCK")
w := httptest.NewRecorder()
fc.On("GetLock", mock.Anything).Times(1).Return("", nil)
httpAdapter.GetLock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(200))
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal(""))
})
})
Describe("Lock", func() {
Describe("Just lock", func() {
It("General error", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "LOCK")
req.Header.Set(connector.HeaderWopiLock, "abc123")
w := httptest.NewRecorder()
fc.On("Lock", mock.Anything, "abc123", "").Times(1).Return("", errors.New("Something happened"))
httpAdapter.Lock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(500))
})
It("No LockId provided", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "LOCK")
req.Header.Set(connector.HeaderWopiLock, "")
w := httptest.NewRecorder()
fc.On("Lock", mock.Anything, "", "").Times(1).Return("", connector.NewConnectorError(400, "No lockId"))
httpAdapter.Lock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(400))
})
It("Conflict", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "LOCK")
req.Header.Set(connector.HeaderWopiLock, "abc123")
w := httptest.NewRecorder()
fc.On("Lock", mock.Anything, "abc123", "").Times(1).Return("zzz111", connector.NewConnectorError(409, "Lock conflict"))
httpAdapter.Lock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(409))
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("zzz111"))
})
It("Success", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "LOCK")
req.Header.Set(connector.HeaderWopiLock, "abc123")
w := httptest.NewRecorder()
fc.On("Lock", mock.Anything, "abc123", "").Times(1).Return("", nil)
httpAdapter.Lock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(200))
})
})
Describe("Unlock and relock", func() {
It("General error", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "LOCK")
req.Header.Set(connector.HeaderWopiLock, "abc123")
req.Header.Set(connector.HeaderWopiOldLock, "qwerty")
w := httptest.NewRecorder()
fc.On("Lock", mock.Anything, "abc123", "qwerty").Times(1).Return("", errors.New("Something happened"))
httpAdapter.Lock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(500))
})
It("No LockId provided", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "LOCK")
req.Header.Set(connector.HeaderWopiLock, "")
req.Header.Set(connector.HeaderWopiOldLock, "")
w := httptest.NewRecorder()
fc.On("Lock", mock.Anything, "", "").Times(1).Return("", connector.NewConnectorError(400, "No lockId"))
httpAdapter.Lock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(400))
})
It("Conflict", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "LOCK")
req.Header.Set(connector.HeaderWopiLock, "abc123")
req.Header.Set(connector.HeaderWopiOldLock, "qwerty")
w := httptest.NewRecorder()
fc.On("Lock", mock.Anything, "abc123", "qwerty").Times(1).Return("zzz111", connector.NewConnectorError(409, "Lock conflict"))
httpAdapter.Lock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(409))
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("zzz111"))
})
It("Success", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "LOCK")
req.Header.Set(connector.HeaderWopiLock, "abc123")
req.Header.Set(connector.HeaderWopiOldLock, "qwerty")
w := httptest.NewRecorder()
fc.On("Lock", mock.Anything, "abc123", "qwerty").Times(1).Return("", nil)
httpAdapter.Lock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(200))
})
})
})
Describe("RefreshLock", func() {
It("General error", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "REFRESH_LOCK")
req.Header.Set(connector.HeaderWopiLock, "abc123")
w := httptest.NewRecorder()
fc.On("RefreshLock", mock.Anything, "abc123").Times(1).Return("", errors.New("Something happened"))
httpAdapter.RefreshLock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(500))
})
It("No LockId provided", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "REFRESH_LOCK")
req.Header.Set(connector.HeaderWopiLock, "")
w := httptest.NewRecorder()
fc.On("RefreshLock", mock.Anything, "").Times(1).Return("", connector.NewConnectorError(400, "No lockId"))
httpAdapter.RefreshLock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(400))
})
It("Conflict", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "REFRESH_LOCK")
req.Header.Set(connector.HeaderWopiLock, "abc123")
w := httptest.NewRecorder()
fc.On("RefreshLock", mock.Anything, "abc123").Times(1).Return("zzz111", connector.NewConnectorError(409, "Lock conflict"))
httpAdapter.RefreshLock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(409))
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("zzz111"))
})
It("Success", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "REFRESH_LOCK")
req.Header.Set(connector.HeaderWopiLock, "abc123")
w := httptest.NewRecorder()
fc.On("RefreshLock", mock.Anything, "abc123").Times(1).Return("", nil)
httpAdapter.RefreshLock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(200))
})
})
Describe("Unlock", func() {
It("General error", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "UNLOCK")
req.Header.Set(connector.HeaderWopiLock, "abc123")
w := httptest.NewRecorder()
fc.On("UnLock", mock.Anything, "abc123").Times(1).Return("", errors.New("Something happened"))
httpAdapter.UnLock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(500))
})
It("No LockId provided", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "UNLOCK")
req.Header.Set(connector.HeaderWopiLock, "")
w := httptest.NewRecorder()
fc.On("UnLock", mock.Anything, "").Times(1).Return("", connector.NewConnectorError(400, "No lockId"))
httpAdapter.UnLock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(400))
})
It("Conflict", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "UNLOCK")
req.Header.Set(connector.HeaderWopiLock, "abc123")
w := httptest.NewRecorder()
fc.On("UnLock", mock.Anything, "abc123").Times(1).Return("zzz111", connector.NewConnectorError(409, "Lock conflict"))
httpAdapter.UnLock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(409))
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("zzz111"))
})
It("Success", func() {
req := httptest.NewRequest("POST", "/wopi/files/abcdef", nil)
req.Header.Set("X-WOPI-Override", "UNLOCK")
req.Header.Set(connector.HeaderWopiLock, "abc123")
w := httptest.NewRecorder()
fc.On("UnLock", mock.Anything, "abc123").Times(1).Return("", nil)
httpAdapter.UnLock(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(200))
})
})
Describe("CheckFileInfo", func() {
It("General error", func() {
req := httptest.NewRequest("GET", "/wopi/files/abcdef", nil)
w := httptest.NewRecorder()
fc.On("CheckFileInfo", mock.Anything).Times(1).Return(connector.FileInfo{}, errors.New("Something happened"))
httpAdapter.CheckFileInfo(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(500))
})
It("Not found", func() {
// 404 isn't thrown at the moment. Test is here to prove it's possible to
// throw any error code
req := httptest.NewRequest("GET", "/wopi/files/abcdef", nil)
w := httptest.NewRecorder()
fc.On("CheckFileInfo", mock.Anything).Times(1).Return(connector.FileInfo{}, connector.NewConnectorError(404, "Not found"))
httpAdapter.CheckFileInfo(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(404))
})
It("Success", func() {
req := httptest.NewRequest("GET", "/wopi/files/abcdef", nil)
w := httptest.NewRecorder()
// might need more info, but should be enough for the test
fileinfo := connector.FileInfo{
Size: 123456789,
BreadcrumbDocName: "testy.docx",
}
fc.On("CheckFileInfo", mock.Anything).Times(1).Return(fileinfo, nil)
httpAdapter.CheckFileInfo(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(200))
jsonInfo, _ := io.ReadAll(resp.Body)
var responseInfo connector.FileInfo
json.Unmarshal(jsonInfo, &responseInfo)
Expect(responseInfo).To(Equal(fileinfo))
})
})
Describe("GetFile", func() {
It("General error", func() {
req := httptest.NewRequest("GET", "/wopi/files/abcdef/contents", nil)
w := httptest.NewRecorder()
cc.On("GetFile", mock.Anything, mock.Anything).Times(1).Return(errors.New("Something happened"))
httpAdapter.GetFile(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(500))
})
It("Not found", func() {
// 404 isn't thrown at the moment. Test is here to prove it's possible to
// throw any error code
req := httptest.NewRequest("GET", "/wopi/files/abcdef/contents", nil)
w := httptest.NewRecorder()
cc.On("GetFile", mock.Anything, mock.Anything).Times(1).Return(connector.NewConnectorError(404, "Not found"))
httpAdapter.GetFile(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(404))
})
It("Success", func() {
req := httptest.NewRequest("GET", "/wopi/files/abcdef/contents", nil)
w := httptest.NewRecorder()
expectedContent := []byte("This is a fake content for a test file")
cc.On("GetFile", mock.Anything, mock.Anything).Times(1).Run(func(args mock.Arguments) {
w := args.Get(1).(io.Writer)
w.Write(expectedContent)
}).Return(nil)
httpAdapter.GetFile(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(200))
content, _ := io.ReadAll(resp.Body)
Expect(content).To(Equal(expectedContent))
})
})
Describe("PutFile", func() {
It("General error", func() {
contentBody := "this is the new fake content"
req := httptest.NewRequest("GET", "/wopi/files/abcdef/contents", strings.NewReader(contentBody))
req.Header.Set(connector.HeaderWopiLock, "abc123")
w := httptest.NewRecorder()
cc.On("PutFile", mock.Anything, mock.Anything, int64(len(contentBody)), "abc123").Times(1).Return("", errors.New("Something happened"))
httpAdapter.PutFile(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(500))
})
It("Conflict", func() {
contentBody := "this is the new fake content"
req := httptest.NewRequest("GET", "/wopi/files/abcdef/contents", strings.NewReader(contentBody))
req.Header.Set(connector.HeaderWopiLock, "abc123")
w := httptest.NewRecorder()
cc.On("PutFile", mock.Anything, mock.Anything, int64(len(contentBody)), "abc123").Times(1).Return("zzz111", connector.NewConnectorError(409, "Lock conflict"))
httpAdapter.PutFile(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(409))
Expect(resp.Header.Get(connector.HeaderWopiLock)).To(Equal("zzz111"))
})
It("Success", func() {
contentBody := "this is the new fake content"
req := httptest.NewRequest("GET", "/wopi/files/abcdef/contents", strings.NewReader(contentBody))
req.Header.Set(connector.HeaderWopiLock, "abc123")
w := httptest.NewRecorder()
cc.On("PutFile", mock.Anything, mock.Anything, int64(len(contentBody)), "abc123").Times(1).Return("", nil)
httpAdapter.PutFile(w, req)
resp := w.Result()
Expect(resp.StatusCode).To(Equal(200))
})
})
})