more work on test rig

This commit is contained in:
tsmethurst
2021-04-10 18:50:28 +02:00
parent 9a57dac5a1
commit 7ab9e78b44
18 changed files with 514 additions and 47 deletions

View File

@ -13,7 +13,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
@ -27,48 +26,43 @@ import (
)
type MediaCreateTestSuite struct {
// standard suite interfaces
suite.Suite
config *config.Config
mockOauthServer *oauth.MockServer
mockStorage *storage.MockStorage
mediaHandler media.MediaHandler
mastoConverter mastotypes.Converter
config *config.Config
db db.DB
log *logrus.Logger
storage storage.Storage
mastoConverter mastotypes.Converter
mediaHandler media.MediaHandler
oauthServer oauth.Server
// standard suite models
testTokens map[string]*oauth.Token
testClients map[string]*oauth.Client
testApplications map[string]*gtsmodel.Application
testUsers map[string]*gtsmodel.User
testAccounts map[string]*gtsmodel.Account
log *logrus.Logger
db db.DB
mediaModule *mediaModule
testAttachments map[string]*gtsmodel.MediaAttachment
// item being tested
mediaModule *mediaModule
}
/*
TEST INFRASTRUCTURE
*/
// SetupSuite sets some variables on the suite that we can use as consts (more or less) throughout
func (suite *MediaCreateTestSuite) SetupSuite() {
// some of our subsequent entities need a log so create this here
log := logrus.New()
log.SetLevel(logrus.TraceLevel)
suite.log = log
// Direct config to local postgres instance
// setup standard items
suite.config = testrig.NewTestConfig()
suite.db = testrig.NewTestDB()
suite.log = testrig.NewTestLog()
suite.storage = testrig.NewTestStorage()
suite.mastoConverter = testrig.NewTestMastoConverter(suite.db)
suite.mediaHandler = testrig.NewTestMediaHandler(suite.db, suite.storage)
suite.oauthServer = testrig.NewTestOauthServer(suite.db)
// use an actual database for this, because it's just easier than mocking one out
database, err := db.New(context.Background(), suite.config, log)
if err != nil {
suite.FailNow(err.Error())
}
suite.db = database
suite.mockOauthServer = &oauth.MockServer{}
suite.mockStorage = &storage.MockStorage{}
suite.mockStorage.On("StoreFileAt", mock.AnythingOfType("string"), mock.AnythingOfType("[]uint8")).Return(nil) // just pretend to store
suite.mediaHandler = media.New(suite.config, suite.db, suite.mockStorage, log)
suite.mastoConverter = mastotypes.New(suite.config, suite.db)
// setup module being tested
suite.mediaModule = New(suite.db, suite.mediaHandler, suite.mastoConverter, suite.config, suite.log).(*mediaModule)
}
@ -80,16 +74,18 @@ func (suite *MediaCreateTestSuite) TearDownSuite() {
func (suite *MediaCreateTestSuite) SetupTest() {
testrig.StandardDBSetup(suite.db)
testrig.StandardStorageSetup(suite.storage, "../../../testrig/media")
suite.testTokens = testrig.NewTestTokens()
suite.testClients = testrig.NewTestClients()
suite.testApplications = testrig.NewTestApplications()
suite.testUsers = testrig.NewTestUsers()
suite.testAccounts = testrig.NewTestAccounts()
suite.testAttachments = testrig.NewTestAttachments()
}
// TearDownTest drops tables to make sure there's no data in the db
func (suite *MediaCreateTestSuite) TearDownTest() {
testrig.StandardDBTeardown(suite.db)
testrig.StandardStorageTeardown(suite.storage)
}
/*
@ -98,28 +94,42 @@ func (suite *MediaCreateTestSuite) TearDownTest() {
func (suite *MediaCreateTestSuite) TestStatusCreatePOSTImageHandlerSuccessful() {
// set up the context for the request
t := suite.testTokens["local_account_1"]
oauthToken := oauth.PGTokenToOauthToken(t)
// setup
recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder)
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
buf, w, err := testrig.CreateMultipartFormData("file", "../../media/test/test-jpeg.jpg", map[string]string{
// see what's in storage *before* the request
storageKeysBeforeRequest, err := suite.storage.ListKeys()
if err != nil {
panic(err)
}
// create the request
buf, w, err := testrig.CreateMultipartFormData("file", "../../../testrig/media/test-jpeg.jpg", map[string]string{
"description": "this is a test image -- a cool background from somewhere",
"focus": "-0.5,0.5",
})
if err != nil {
panic(err)
}
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", basePath), bytes.NewReader(buf.Bytes())) // the endpoint we're hitting
ctx.Request.Header.Set("Content-Type", w.FormDataContentType())
// do the actual request
suite.mediaModule.mediaCreatePOSTHandler(ctx)
// check what's in storage *after* the request
storageKeysAfterRequest, err := suite.storage.ListKeys()
if err != nil {
panic(err)
}
// check response
suite.EqualValues(http.StatusAccepted, recorder.Code)
@ -157,6 +167,7 @@ func (suite *MediaCreateTestSuite) TestStatusCreatePOSTImageHandlerSuccessful()
assert.NotEmpty(suite.T(), attachmentReply.ID)
assert.NotEmpty(suite.T(), attachmentReply.URL)
assert.NotEmpty(suite.T(), attachmentReply.PreviewURL)
assert.Equal(suite.T(), len(storageKeysBeforeRequest) + 2, len(storageKeysAfterRequest)) // 2 images should be added to storage: the original and the thumbnail
}
func TestMediaCreateTestSuite(t *testing.T) {

View File

@ -31,6 +31,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/apimodule/account"
"github.com/superseriousbusiness/gotosocial/internal/apimodule/app"
"github.com/superseriousbusiness/gotosocial/internal/apimodule/auth"
mediaModule "github.com/superseriousbusiness/gotosocial/internal/apimodule/media"
"github.com/superseriousbusiness/gotosocial/internal/cache"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
@ -54,7 +55,7 @@ var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logr
return fmt.Errorf("error creating router: %s", err)
}
storageBackend, err := storage.NewInMem(c, log)
storageBackend, err := storage.NewLocal(c, log)
if err != nil {
return fmt.Errorf("error creating storage backend: %s", err)
}
@ -70,11 +71,13 @@ var Run action.GTSAction = func(ctx context.Context, c *config.Config, log *logr
authModule := auth.New(oauthServer, dbService, log)
accountModule := account.New(c, dbService, oauthServer, mediaHandler, mastoConverter, log)
appsModule := app.New(oauthServer, dbService, mastoConverter, log)
mm := mediaModule.New(dbService, mediaHandler, mastoConverter, c, log)
apiModules := []apimodule.ClientAPIModule{
authModule, // this one has to go first so the other modules use its middleware
accountModule,
appsModule,
mm,
}
for _, m := range apiModules {

View File

@ -104,3 +104,45 @@ func (_m *MockConverter) AppToMastoSensitive(application *gtsmodel.Application)
return r0, r1
}
// AttachmentToMasto provides a mock function with given fields: attachment
func (_m *MockConverter) AttachmentToMasto(attachment *gtsmodel.MediaAttachment) (mastotypes.Attachment, error) {
ret := _m.Called(attachment)
var r0 mastotypes.Attachment
if rf, ok := ret.Get(0).(func(*gtsmodel.MediaAttachment) mastotypes.Attachment); ok {
r0 = rf(attachment)
} else {
r0 = ret.Get(0).(mastotypes.Attachment)
}
var r1 error
if rf, ok := ret.Get(1).(func(*gtsmodel.MediaAttachment) error); ok {
r1 = rf(attachment)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MentionToMasto provides a mock function with given fields: m
func (_m *MockConverter) MentionToMasto(m *gtsmodel.Mention) (mastotypes.Mention, error) {
ret := _m.Called(m)
var r0 mastotypes.Mention
if rf, ok := ret.Get(0).(func(*gtsmodel.Mention) mastotypes.Mention); ok {
r0 = rf(m)
} else {
r0 = ret.Get(0).(mastotypes.Mention)
}
var r1 error
if rf, ok := ret.Get(1).(func(*gtsmodel.Mention) error); ok {
r1 = rf(m)
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@ -12,6 +12,29 @@ type MockMediaHandler struct {
mock.Mock
}
// ProcessAttachment provides a mock function with given fields: img, accountID
func (_m *MockMediaHandler) ProcessAttachment(img []byte, accountID string) (*gtsmodel.MediaAttachment, error) {
ret := _m.Called(img, accountID)
var r0 *gtsmodel.MediaAttachment
if rf, ok := ret.Get(0).(func([]byte, string) *gtsmodel.MediaAttachment); ok {
r0 = rf(img, accountID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*gtsmodel.MediaAttachment)
}
}
var r1 error
if rf, ok := ret.Get(1).(func([]byte, string) error); ok {
r1 = rf(img, accountID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SetHeaderOrAvatarForAccountID provides a mock function with given fields: img, accountID, headerOrAvi
func (_m *MockMediaHandler) SetHeaderOrAvatarForAccountID(img []byte, accountID string, headerOrAvi string) (*gtsmodel.MediaAttachment, error) {
ret := _m.Called(img, accountID, headerOrAvi)

View File

@ -15,22 +15,41 @@ import (
func NewInMem(c *config.Config, log *logrus.Logger) (Storage, error) {
return &inMemStorage{
stored: make(map[string][]byte),
log: log,
}, nil
}
type inMemStorage struct {
stored map[string][]byte
log *logrus.Logger
}
func (s *inMemStorage) StoreFileAt(path string, data []byte) error {
l := s.log.WithField("func", "StoreFileAt")
l.Debugf("storing at path %s", path)
s.stored[path] = data
return nil
}
func (s *inMemStorage) RetrieveFileFrom(path string) ([]byte, error) {
l := s.log.WithField("func", "RetrieveFileFrom")
l.Debugf("retrieving from path %s", path)
d, ok := s.stored[path]
if !ok {
return nil, fmt.Errorf("no data found at path %s", path)
}
return d, nil
}
func (s *inMemStorage)ListKeys() ([]string, error) {
keys := []string{}
for k := range s.stored {
keys = append(keys, k)
}
return keys, nil
}
func (s *inMemStorage) RemoveFileAt(path string) error {
delete(s.stored, path)
return nil
}

View File

@ -1,6 +1,11 @@
package storage
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config"
)
@ -8,16 +13,58 @@ import (
// NewLocal returns an implementation of the Storage interface that uses
// the local filesystem for storing and retrieving files, attachments, etc.
func NewLocal(c *config.Config, log *logrus.Logger) (Storage, error) {
return &localStorage{}, nil
return &localStorage{
config: c,
log: log,
}, nil
}
type localStorage struct {
config *config.Config
log *logrus.Logger
}
func (s *localStorage) StoreFileAt(path string, data []byte) error {
l := s.log.WithField("func", "StoreFileAt")
l.Debugf("storing at path %s", path)
components := strings.Split(path, "/")
dir := strings.Join(components[0:len(components) - 1], "/")
if err := os.MkdirAll(dir, 0777); err != nil {
return fmt.Errorf("error writing file at %s: %s", path, err)
}
if err := os.WriteFile(path, data, 0777); err != nil {
return fmt.Errorf("error writing file at %s: %s", path, err)
}
return nil
}
func (s *localStorage) RetrieveFileFrom(path string) ([]byte, error) {
return nil, nil
l := s.log.WithField("func", "RetrieveFileFrom")
l.Debugf("retrieving from path %s", path)
b, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("error reading file at %s: %s", path, err)
}
return b, nil
}
func (s *localStorage) ListKeys() ([]string, error) {
keys := []string{}
err := filepath.Walk(s.config.StorageConfig.BasePath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
keys = append(keys, path)
}
return nil
})
if err != nil {
return nil, err
}
return keys, nil
}
func (s *localStorage) RemoveFileAt(path string) error {
return os.Remove(path)
}

View File

@ -9,6 +9,43 @@ type MockStorage struct {
mock.Mock
}
// ListKeys provides a mock function with given fields:
func (_m *MockStorage) ListKeys() ([]string, error) {
ret := _m.Called()
var r0 []string
if rf, ok := ret.Get(0).(func() []string); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RemoveFileAt provides a mock function with given fields: path
func (_m *MockStorage) RemoveFileAt(path string) error {
ret := _m.Called(path)
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(path)
} else {
r0 = ret.Error(0)
}
return r0
}
// RetrieveFileFrom provides a mock function with given fields: path
func (_m *MockStorage) RetrieveFileFrom(path string) ([]byte, error) {
ret := _m.Called(path)

View File

@ -25,4 +25,6 @@ package storage
type Storage interface {
StoreFileAt(path string, data []byte) error
RetrieveFileFrom(path string) ([]byte, error)
ListKeys() ([]string, error)
RemoveFileAt(path string) error
}