test(wopi): add tests for wopi client

pull/1626/head
HFO4 2 years ago
parent f7fdf10d70
commit 99434d7aa5

@ -0,0 +1,112 @@
package middleware
import (
"errors"
"github.com/DATA-DOG/go-sqlmock"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/mocks/wopimock"
"github.com/cloudreve/Cloudreve/v3/pkg/wopi"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"net/http/httptest"
"testing"
)
func TestWopiWriteAccess(t *testing.T) {
asserts := assert.New(t)
rec := httptest.NewRecorder()
testFunc := WopiWriteAccess()
// deny preview only session
{
c, _ := gin.CreateTestContext(rec)
c.Set(WopiSessionCtx, &wopi.SessionCache{Action: wopi.ActionPreview})
testFunc(c)
asserts.True(c.IsAborted())
}
// pass
{
c, _ := gin.CreateTestContext(rec)
c.Set(WopiSessionCtx, &wopi.SessionCache{Action: wopi.ActionEdit})
testFunc(c)
asserts.False(c.IsAborted())
}
}
func TestWopiAccessValidation(t *testing.T) {
asserts := assert.New(t)
rec := httptest.NewRecorder()
mockWopi := &wopimock.WopiClientMock{}
mockCache := cache.NewMemoStore()
testFunc := WopiAccessValidation(mockWopi, mockCache)
// malformed access token
{
c, _ := gin.CreateTestContext(rec)
c.AddParam(wopi.AccessTokenQuery, "000")
testFunc(c)
asserts.True(c.IsAborted())
}
// session key not exist
{
c, _ := gin.CreateTestContext(rec)
c.Request = httptest.NewRequest("GET", "/wopi/files/1?access_token=", nil)
query := c.Request.URL.Query()
query.Set(wopi.AccessTokenQuery, "sessionID.key")
c.Request.URL.RawQuery = query.Encode()
testFunc(c)
asserts.True(c.IsAborted())
}
// user key not exist
{
c, _ := gin.CreateTestContext(rec)
c.Request = httptest.NewRequest("GET", "/wopi/files/1?access_token=", nil)
query := c.Request.URL.Query()
query.Set(wopi.AccessTokenQuery, "sessionID.key")
c.Request.URL.RawQuery = query.Encode()
mockCache.Set(wopi.SessionCachePrefix+"sessionID", wopi.SessionCache{UserID: 1, FileID: 1}, 0)
mock.ExpectQuery("SELECT(.+)users(.+)").WillReturnError(errors.New("error"))
testFunc(c)
asserts.True(c.IsAborted())
asserts.NoError(mock.ExpectationsWereMet())
}
// file not found
{
c, _ := gin.CreateTestContext(rec)
c.Request = httptest.NewRequest("GET", "/wopi/files/1?access_token=", nil)
query := c.Request.URL.Query()
query.Set(wopi.AccessTokenQuery, "sessionID.key")
c.Request.URL.RawQuery = query.Encode()
mockCache.Set(wopi.SessionCachePrefix+"sessionID", wopi.SessionCache{UserID: 1, FileID: 1}, 0)
mock.ExpectQuery("SELECT(.+)users(.+)").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
c.Set("object_id", uint(0))
testFunc(c)
asserts.True(c.IsAborted())
asserts.NoError(mock.ExpectationsWereMet())
}
// all pass
{
c, _ := gin.CreateTestContext(rec)
c.Request = httptest.NewRequest("GET", "/wopi/files/1?access_token=", nil)
query := c.Request.URL.Query()
query.Set(wopi.AccessTokenQuery, "sessionID.key")
c.Request.URL.RawQuery = query.Encode()
mockCache.Set(wopi.SessionCachePrefix+"sessionID", wopi.SessionCache{UserID: 1, FileID: 1}, 0)
mock.ExpectQuery("SELECT(.+)users(.+)").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
c.Set("object_id", uint(1))
testFunc(c)
asserts.False(c.IsAborted())
asserts.NoError(mock.ExpectationsWereMet())
asserts.NotPanics(func() {
c.MustGet(WopiSessionCtx)
})
asserts.NotPanics(func() {
c.MustGet("user")
})
}
}

@ -0,0 +1,29 @@
package cachemock
import "github.com/stretchr/testify/mock"
type CacheClientMock struct {
mock.Mock
}
func (c CacheClientMock) Set(key string, value interface{}, ttl int) error {
return c.Called(key, value, ttl).Error(0)
}
func (c CacheClientMock) Get(key string) (interface{}, bool) {
args := c.Called(key)
return args.Get(0), args.Bool(1)
}
func (c CacheClientMock) Gets(keys []string, prefix string) (map[string]interface{}, []string) {
args := c.Called(keys, prefix)
return args.Get(0).(map[string]interface{}), args.Get(1).([]string)
}
func (c CacheClientMock) Sets(values map[string]interface{}, prefix string) error {
return c.Called(values).Error(0)
}
func (c CacheClientMock) Delete(keys []string, prefix string) error {
return c.Called(keys, prefix).Error(0)
}

@ -0,0 +1,21 @@
package wopimock
import (
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/wopi"
"github.com/stretchr/testify/mock"
)
type WopiClientMock struct {
mock.Mock
}
func (w *WopiClientMock) NewSession(user *model.User, file *model.File, action wopi.ActonType) (*wopi.Session, error) {
args := w.Called(user, file, action)
return args.Get(0).(*wopi.Session), args.Error(1)
}
func (w *WopiClientMock) AvailableExts() []string {
args := w.Called()
return args.Get(0).([]string)
}

@ -3,7 +3,6 @@ package wopi
import ( import (
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/util" "github.com/cloudreve/Cloudreve/v3/pkg/util"
"net/http" "net/http"
"strings" "strings"
@ -62,7 +61,7 @@ func (c *client) refreshDiscovery() error {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
cached, exist := cache.Get(DiscoverResponseCacheKey) cached, exist := c.cache.Get(DiscoverResponseCacheKey)
if exist { if exist {
cachedDiscovery := cached.(WopiDiscovery) cachedDiscovery := cached.(WopiDiscovery)
c.discovery = &cachedDiscovery c.discovery = &cachedDiscovery
@ -70,14 +69,14 @@ func (c *client) refreshDiscovery() error {
res, err := c.http.Request("GET", c.config.discoveryEndpoint.String(), nil). res, err := c.http.Request("GET", c.config.discoveryEndpoint.String(), nil).
CheckHTTPResponse(http.StatusOK).GetResponse() CheckHTTPResponse(http.StatusOK).GetResponse()
if err != nil { if err != nil {
return fmt.Errorf("failed to request discovery endpoint: %s", err) return fmt.Errorf("failed to request discovery endpoint: %w", err)
} }
if err := xml.Unmarshal([]byte(res), &c.discovery); err != nil { if err := xml.Unmarshal([]byte(res), &c.discovery); err != nil {
return fmt.Errorf("failed to parse response discovery endpoint: %s", err) return fmt.Errorf("failed to parse response discovery endpoint: %w", err)
} }
if err := cache.Set(DiscoverResponseCacheKey, *c.discovery, DiscoverRefreshDuration); err != nil { if err := c.cache.Set(DiscoverResponseCacheKey, *c.discovery, DiscoverRefreshDuration); err != nil {
return err return err
} }
} }

File diff suppressed because one or more lines are too long

@ -165,9 +165,9 @@ func (c *client) NewSession(user *model.User, file *model.File, action ActonType
UserID: user.ID, UserID: user.ID,
Action: action, Action: action,
} }
err = cache.Set(SessionCachePrefix+sessionID.String(), *session, ttl) err = c.cache.Set(SessionCachePrefix+sessionID.String(), *session, ttl)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create document session: %s", err) return nil, fmt.Errorf("failed to create document session: %w", err)
} }
sessionRes := &Session{ sessionRes := &Session{

@ -0,0 +1,184 @@
package wopi
import (
"database/sql"
"errors"
"github.com/DATA-DOG/go-sqlmock"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/mocks/cachemock"
"github.com/cloudreve/Cloudreve/v3/pkg/mocks/requestmock"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
testMock "github.com/stretchr/testify/mock"
"net/url"
"testing"
)
var mock sqlmock.Sqlmock
// TestMain 初始化数据库Mock
func TestMain(m *testing.M) {
var db *sql.DB
var err error
db, mock, err = sqlmock.New()
if err != nil {
panic("An error was not expected when opening a stub database connection")
}
model.DB, _ = gorm.Open("mysql", db)
defer db.Close()
m.Run()
}
func TestNewSession(t *testing.T) {
a := assert.New(t)
endpoint, _ := url.Parse("http://localhost:8001/hosting/discovery")
client := &client{
cache: cache.NewMemoStore(),
config: config{
discoveryEndpoint: endpoint,
},
}
// Discovery failed
{
expectedErr := errors.New("error")
mockHttp := &requestmock.RequestMock{}
client.http = mockHttp
mockHttp.On(
"Request",
"GET",
endpoint.String(),
testMock.Anything,
testMock.Anything,
).Return(&request.Response{
Err: expectedErr,
})
res, err := client.NewSession(&model.User{}, &model.File{}, ActionPreview)
a.Nil(res)
a.ErrorIs(err, expectedErr)
mockHttp.AssertExpectations(t)
}
// not supported ext
{
client.discovery = &WopiDiscovery{}
client.actions = make(map[string]map[string]Action)
res, err := client.NewSession(&model.User{}, &model.File{}, ActionPreview)
a.Nil(res)
a.ErrorIs(err, ErrActionNotSupported)
}
// preferred action not supported
{
client.discovery = &WopiDiscovery{}
client.actions = map[string]map[string]Action{
".doc": {},
}
res, err := client.NewSession(&model.User{}, &model.File{Name: "1.doc"}, ActionPreview)
a.Nil(res)
a.ErrorIs(err, ErrActionNotSupported)
}
// src url cannot be parsed
{
client.discovery = &WopiDiscovery{}
client.actions = map[string]map[string]Action{
".doc": {
string(ActionPreviewFallback): Action{
Urlsrc: string([]byte{0x7f}),
},
},
}
res, err := client.NewSession(&model.User{}, &model.File{Name: "1.doc"}, ActionEdit)
a.Nil(res)
a.ErrorContains(err, "invalid control character in URL")
}
// all pass - default placeholder
{
client.discovery = &WopiDiscovery{}
client.actions = map[string]map[string]Action{
".doc": {
string(ActionPreviewFallback): Action{
Urlsrc: "https://doc.com/doc",
},
},
}
res, err := client.NewSession(&model.User{}, &model.File{Name: "1.doc"}, ActionEdit)
a.NotNil(res)
a.NoError(err)
resUrl := res.ActionURL.String()
a.Contains(resUrl, wopiSrcParamDefault)
}
// all pass - with placeholders
{
client.discovery = &WopiDiscovery{}
client.actions = map[string]map[string]Action{
".doc": {
string(ActionPreviewFallback): Action{
Urlsrc: "https://doc.com/doc?origin=preserved&<dc=DC_LLCC&><notsuported=DISABLE_ASYNC&><src=WOPI_SOURCE&>",
},
},
}
res, err := client.NewSession(&model.User{}, &model.File{Name: "1.doc"}, ActionEdit)
a.NotNil(res)
a.NoError(err)
resUrl := res.ActionURL.String()
a.Contains(resUrl, "origin=preserved")
a.Contains(resUrl, "dc=lng")
a.Contains(resUrl, "src=")
a.NotContains(resUrl, "notsuported")
}
// cache operation failed
{
mockCache := &cachemock.CacheClientMock{}
expectedErr := errors.New("error")
client.cache = mockCache
client.discovery = &WopiDiscovery{}
client.actions = map[string]map[string]Action{
".doc": {
string(ActionPreviewFallback): Action{
Urlsrc: "https://doc.com/doc",
},
},
}
mockCache.On("Set", testMock.Anything, testMock.Anything, testMock.Anything).Return(expectedErr)
res, err := client.NewSession(&model.User{}, &model.File{Name: "1.doc"}, ActionEdit)
a.Nil(res)
a.ErrorIs(err, expectedErr)
}
}
func TestInit(t *testing.T) {
a := assert.New(t)
// not enabled
{
a.Nil(Default)
Default = &client{}
Init()
a.Nil(Default)
}
// throw error
{
a.Nil(Default)
cache.Set("setting_wopi_enabled", "1", 0)
cache.Set("setting_wopi_endpoint", string([]byte{0x7f}), 0)
Init()
a.Nil(Default)
}
// all pass
{
a.Nil(Default)
cache.Set("setting_wopi_enabled", "1", 0)
cache.Set("setting_wopi_endpoint", "", 0)
Init()
a.NotNil(Default)
}
}
Loading…
Cancel
Save