gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

mediacreate_test.go (14088B)


      1 // GoToSocial
      2 // Copyright (C) GoToSocial Authors admin@gotosocial.org
      3 // SPDX-License-Identifier: AGPL-3.0-or-later
      4 //
      5 // This program is free software: you can redistribute it and/or modify
      6 // it under the terms of the GNU Affero General Public License as published by
      7 // the Free Software Foundation, either version 3 of the License, or
      8 // (at your option) any later version.
      9 //
     10 // This program is distributed in the hope that it will be useful,
     11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 // GNU Affero General Public License for more details.
     14 //
     15 // You should have received a copy of the GNU Affero General Public License
     16 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 
     18 package media_test
     19 
     20 import (
     21 	"bytes"
     22 	"context"
     23 	"crypto/rand"
     24 	"encoding/base64"
     25 	"encoding/json"
     26 	"fmt"
     27 	"io/ioutil"
     28 	"net/http"
     29 	"net/http/httptest"
     30 	"testing"
     31 
     32 	"github.com/stretchr/testify/suite"
     33 	mediamodule "github.com/superseriousbusiness/gotosocial/internal/api/client/media"
     34 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
     35 	"github.com/superseriousbusiness/gotosocial/internal/config"
     36 	"github.com/superseriousbusiness/gotosocial/internal/db"
     37 	"github.com/superseriousbusiness/gotosocial/internal/email"
     38 	"github.com/superseriousbusiness/gotosocial/internal/federation"
     39 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
     40 	"github.com/superseriousbusiness/gotosocial/internal/log"
     41 	"github.com/superseriousbusiness/gotosocial/internal/media"
     42 	"github.com/superseriousbusiness/gotosocial/internal/oauth"
     43 	"github.com/superseriousbusiness/gotosocial/internal/processing"
     44 	"github.com/superseriousbusiness/gotosocial/internal/state"
     45 	"github.com/superseriousbusiness/gotosocial/internal/storage"
     46 	"github.com/superseriousbusiness/gotosocial/internal/typeutils"
     47 	"github.com/superseriousbusiness/gotosocial/internal/visibility"
     48 	"github.com/superseriousbusiness/gotosocial/testrig"
     49 )
     50 
     51 type MediaCreateTestSuite struct {
     52 	// standard suite interfaces
     53 	suite.Suite
     54 	db           db.DB
     55 	storage      *storage.Driver
     56 	mediaManager *media.Manager
     57 	federator    federation.Federator
     58 	tc           typeutils.TypeConverter
     59 	oauthServer  oauth.Server
     60 	emailSender  email.Sender
     61 	processor    *processing.Processor
     62 	state        state.State
     63 
     64 	// standard suite models
     65 	testTokens       map[string]*gtsmodel.Token
     66 	testClients      map[string]*gtsmodel.Client
     67 	testApplications map[string]*gtsmodel.Application
     68 	testUsers        map[string]*gtsmodel.User
     69 	testAccounts     map[string]*gtsmodel.Account
     70 	testAttachments  map[string]*gtsmodel.MediaAttachment
     71 
     72 	// item being tested
     73 	mediaModule *mediamodule.Module
     74 }
     75 
     76 /*
     77 	TEST INFRASTRUCTURE
     78 */
     79 
     80 func (suite *MediaCreateTestSuite) SetupSuite() {
     81 	suite.state.Caches.Init()
     82 	testrig.StartWorkers(&suite.state)
     83 
     84 	// setup standard items
     85 	testrig.InitTestConfig()
     86 	testrig.InitTestLog()
     87 
     88 	suite.db = testrig.NewTestDB(&suite.state)
     89 	suite.state.DB = suite.db
     90 	suite.storage = testrig.NewInMemoryStorage()
     91 	suite.state.Storage = suite.storage
     92 
     93 	suite.tc = testrig.NewTestTypeConverter(suite.db)
     94 
     95 	testrig.StartTimelines(
     96 		&suite.state,
     97 		visibility.NewFilter(&suite.state),
     98 		suite.tc,
     99 	)
    100 
    101 	suite.mediaManager = testrig.NewTestMediaManager(&suite.state)
    102 	suite.oauthServer = testrig.NewTestOauthServer(suite.db)
    103 	suite.federator = testrig.NewTestFederator(&suite.state, testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")), suite.mediaManager)
    104 	suite.emailSender = testrig.NewEmailSender("../../../../web/template/", nil)
    105 	suite.processor = testrig.NewTestProcessor(&suite.state, suite.federator, suite.emailSender, suite.mediaManager)
    106 
    107 	// setup module being tested
    108 	suite.mediaModule = mediamodule.New(suite.processor)
    109 }
    110 
    111 func (suite *MediaCreateTestSuite) TearDownSuite() {
    112 	if err := suite.db.Stop(context.Background()); err != nil {
    113 		log.Panicf(nil, "error closing db connection: %s", err)
    114 	}
    115 	testrig.StopWorkers(&suite.state)
    116 }
    117 
    118 func (suite *MediaCreateTestSuite) SetupTest() {
    119 	suite.state.Caches.Init()
    120 
    121 	testrig.StandardDBSetup(suite.db, nil)
    122 	testrig.StandardStorageSetup(suite.storage, "../../../../testrig/media")
    123 
    124 	suite.testTokens = testrig.NewTestTokens()
    125 	suite.testClients = testrig.NewTestClients()
    126 	suite.testApplications = testrig.NewTestApplications()
    127 	suite.testUsers = testrig.NewTestUsers()
    128 	suite.testAccounts = testrig.NewTestAccounts()
    129 	suite.testAttachments = testrig.NewTestAttachments()
    130 }
    131 
    132 func (suite *MediaCreateTestSuite) TearDownTest() {
    133 	testrig.StandardDBTeardown(suite.db)
    134 	testrig.StandardStorageTeardown(suite.storage)
    135 }
    136 
    137 /*
    138 	ACTUAL TESTS
    139 */
    140 
    141 func (suite *MediaCreateTestSuite) TestMediaCreateSuccessful() {
    142 	// set up the context for the request
    143 	t := suite.testTokens["local_account_1"]
    144 	oauthToken := oauth.DBTokenToToken(t)
    145 	recorder := httptest.NewRecorder()
    146 	ctx, _ := testrig.CreateGinTestContext(recorder, nil)
    147 	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
    148 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
    149 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
    150 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
    151 
    152 	// see what's in storage *before* the request
    153 	var storageKeysBeforeRequest []string
    154 	if err := suite.storage.WalkKeys(ctx, func(ctx context.Context, key string) error {
    155 		storageKeysBeforeRequest = append(storageKeysBeforeRequest, key)
    156 		return nil
    157 	}); err != nil {
    158 		panic(err)
    159 	}
    160 
    161 	// create the request
    162 	buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string]string{
    163 		"description": "this is a test image -- a cool background from somewhere",
    164 		"focus":       "-0.5,0.5",
    165 	})
    166 	if err != nil {
    167 		panic(err)
    168 	}
    169 	ctx.Request = httptest.NewRequest(http.MethodPost, "http://localhost:8080/api/v1/media", bytes.NewReader(buf.Bytes())) // the endpoint we're hitting
    170 	ctx.Request.Header.Set("Content-Type", w.FormDataContentType())
    171 	ctx.Request.Header.Set("accept", "application/json")
    172 	ctx.AddParam(mediamodule.APIVersionKey, mediamodule.APIv1)
    173 
    174 	// do the actual request
    175 	suite.mediaModule.MediaCreatePOSTHandler(ctx)
    176 
    177 	// check what's in storage *after* the request
    178 	var storageKeysAfterRequest []string
    179 	if err := suite.storage.WalkKeys(ctx, func(ctx context.Context, key string) error {
    180 		storageKeysAfterRequest = append(storageKeysAfterRequest, key)
    181 		return nil
    182 	}); err != nil {
    183 		panic(err)
    184 	}
    185 
    186 	// check response
    187 	suite.EqualValues(http.StatusOK, recorder.Code)
    188 
    189 	result := recorder.Result()
    190 	defer result.Body.Close()
    191 	b, err := ioutil.ReadAll(result.Body)
    192 	suite.NoError(err)
    193 	fmt.Println(string(b))
    194 
    195 	attachmentReply := &apimodel.Attachment{}
    196 	err = json.Unmarshal(b, attachmentReply)
    197 	suite.NoError(err)
    198 
    199 	suite.Equal("this is a test image -- a cool background from somewhere", *attachmentReply.Description)
    200 	suite.Equal("image", attachmentReply.Type)
    201 	suite.EqualValues(apimodel.MediaMeta{
    202 		Original: apimodel.MediaDimensions{
    203 			Width:  1920,
    204 			Height: 1080,
    205 			Size:   "1920x1080",
    206 			Aspect: 1.7777778,
    207 		},
    208 		Small: apimodel.MediaDimensions{
    209 			Width:  512,
    210 			Height: 288,
    211 			Size:   "512x288",
    212 			Aspect: 1.7777778,
    213 		},
    214 		Focus: &apimodel.MediaFocus{
    215 			X: -0.5,
    216 			Y: 0.5,
    217 		},
    218 	}, attachmentReply.Meta)
    219 	suite.Equal("LiBzRk#6V[WF_NvzV@WY_3rqV@a$", attachmentReply.Blurhash)
    220 	suite.NotEmpty(attachmentReply.ID)
    221 	suite.NotEmpty(attachmentReply.URL)
    222 	suite.NotEmpty(attachmentReply.PreviewURL)
    223 	suite.Equal(len(storageKeysBeforeRequest)+2, len(storageKeysAfterRequest)) // 2 images should be added to storage: the original and the thumbnail
    224 }
    225 
    226 func (suite *MediaCreateTestSuite) TestMediaCreateSuccessfulV2() {
    227 	// set up the context for the request
    228 	t := suite.testTokens["local_account_1"]
    229 	oauthToken := oauth.DBTokenToToken(t)
    230 	recorder := httptest.NewRecorder()
    231 	ctx, _ := testrig.CreateGinTestContext(recorder, nil)
    232 	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
    233 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
    234 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
    235 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
    236 
    237 	// see what's in storage *before* the request
    238 	var storageKeysBeforeRequest []string
    239 	if err := suite.storage.WalkKeys(ctx, func(ctx context.Context, key string) error {
    240 		storageKeysBeforeRequest = append(storageKeysBeforeRequest, key)
    241 		return nil
    242 	}); err != nil {
    243 		panic(err)
    244 	}
    245 
    246 	// create the request
    247 	buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string]string{
    248 		"description": "this is a test image -- a cool background from somewhere",
    249 		"focus":       "-0.5,0.5",
    250 	})
    251 	if err != nil {
    252 		panic(err)
    253 	}
    254 	ctx.Request = httptest.NewRequest(http.MethodPost, "http://localhost:8080/api/v2/media", bytes.NewReader(buf.Bytes())) // the endpoint we're hitting
    255 	ctx.Request.Header.Set("Content-Type", w.FormDataContentType())
    256 	ctx.Request.Header.Set("accept", "application/json")
    257 	ctx.AddParam(mediamodule.APIVersionKey, mediamodule.APIv2)
    258 
    259 	// do the actual request
    260 	suite.mediaModule.MediaCreatePOSTHandler(ctx)
    261 
    262 	// check what's in storage *after* the request
    263 	var storageKeysAfterRequest []string
    264 	if err := suite.storage.WalkKeys(ctx, func(ctx context.Context, key string) error {
    265 		storageKeysAfterRequest = append(storageKeysAfterRequest, key)
    266 		return nil
    267 	}); err != nil {
    268 		panic(err)
    269 	}
    270 
    271 	// check response
    272 	suite.EqualValues(http.StatusOK, recorder.Code)
    273 
    274 	result := recorder.Result()
    275 	defer result.Body.Close()
    276 	b, err := ioutil.ReadAll(result.Body)
    277 	suite.NoError(err)
    278 	fmt.Println(string(b))
    279 
    280 	attachmentReply := &apimodel.Attachment{}
    281 	err = json.Unmarshal(b, attachmentReply)
    282 	suite.NoError(err)
    283 
    284 	suite.Equal("this is a test image -- a cool background from somewhere", *attachmentReply.Description)
    285 	suite.Equal("image", attachmentReply.Type)
    286 	suite.EqualValues(apimodel.MediaMeta{
    287 		Original: apimodel.MediaDimensions{
    288 			Width:  1920,
    289 			Height: 1080,
    290 			Size:   "1920x1080",
    291 			Aspect: 1.7777778,
    292 		},
    293 		Small: apimodel.MediaDimensions{
    294 			Width:  512,
    295 			Height: 288,
    296 			Size:   "512x288",
    297 			Aspect: 1.7777778,
    298 		},
    299 		Focus: &apimodel.MediaFocus{
    300 			X: -0.5,
    301 			Y: 0.5,
    302 		},
    303 	}, attachmentReply.Meta)
    304 	suite.Equal("LiBzRk#6V[WF_NvzV@WY_3rqV@a$", attachmentReply.Blurhash)
    305 	suite.NotEmpty(attachmentReply.ID)
    306 	suite.Nil(attachmentReply.URL)
    307 	suite.NotEmpty(attachmentReply.PreviewURL)
    308 	suite.Equal(len(storageKeysBeforeRequest)+2, len(storageKeysAfterRequest)) // 2 images should be added to storage: the original and the thumbnail
    309 }
    310 
    311 func (suite *MediaCreateTestSuite) TestMediaCreateLongDescription() {
    312 	// set up the context for the request
    313 	t := suite.testTokens["local_account_1"]
    314 	oauthToken := oauth.DBTokenToToken(t)
    315 	recorder := httptest.NewRecorder()
    316 	ctx, _ := testrig.CreateGinTestContext(recorder, nil)
    317 	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
    318 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
    319 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
    320 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
    321 
    322 	// read a random string of a really long description
    323 	descriptionBytes := make([]byte, 5000)
    324 	if _, err := rand.Read(descriptionBytes); err != nil {
    325 		panic(err)
    326 	}
    327 	description := base64.RawStdEncoding.EncodeToString(descriptionBytes)
    328 
    329 	// create the request
    330 	buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string]string{
    331 		"description": description,
    332 		"focus":       "-0.5,0.5",
    333 	})
    334 	if err != nil {
    335 		panic(err)
    336 	}
    337 	ctx.Request = httptest.NewRequest(http.MethodPost, "http://localhost:8080/api/v1/media", bytes.NewReader(buf.Bytes())) // the endpoint we're hitting
    338 	ctx.Request.Header.Set("Content-Type", w.FormDataContentType())
    339 	ctx.Request.Header.Set("accept", "application/json")
    340 	ctx.AddParam(mediamodule.APIVersionKey, mediamodule.APIv1)
    341 
    342 	// do the actual request
    343 	suite.mediaModule.MediaCreatePOSTHandler(ctx)
    344 
    345 	// check response
    346 	suite.EqualValues(http.StatusBadRequest, recorder.Code)
    347 
    348 	result := recorder.Result()
    349 	defer result.Body.Close()
    350 	b, err := ioutil.ReadAll(result.Body)
    351 	suite.NoError(err)
    352 
    353 	suite.Equal(`{"error":"Bad Request: image description length must be between 0 and 500 characters (inclusive), but provided image description was 6667 chars"}`, string(b))
    354 }
    355 
    356 func (suite *MediaCreateTestSuite) TestMediaCreateTooShortDescription() {
    357 	// set the min description length
    358 	config.SetMediaDescriptionMinChars(500)
    359 
    360 	// set up the context for the request
    361 	t := suite.testTokens["local_account_1"]
    362 	oauthToken := oauth.DBTokenToToken(t)
    363 	recorder := httptest.NewRecorder()
    364 	ctx, _ := testrig.CreateGinTestContext(recorder, nil)
    365 	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
    366 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
    367 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
    368 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
    369 
    370 	// create the request
    371 	buf, w, err := testrig.CreateMultipartFormData("file", "../../../../testrig/media/test-jpeg.jpg", map[string]string{
    372 		"description": "", // provide an empty description
    373 		"focus":       "-0.5,0.5",
    374 	})
    375 	if err != nil {
    376 		panic(err)
    377 	}
    378 	ctx.Request = httptest.NewRequest(http.MethodPost, "http://localhost:8080/api/v1/media", bytes.NewReader(buf.Bytes())) // the endpoint we're hitting
    379 	ctx.Request.Header.Set("Content-Type", w.FormDataContentType())
    380 	ctx.Request.Header.Set("accept", "application/json")
    381 	ctx.AddParam(mediamodule.APIVersionKey, mediamodule.APIv1)
    382 
    383 	// do the actual request
    384 	suite.mediaModule.MediaCreatePOSTHandler(ctx)
    385 
    386 	// check response -- there should be no error because minimum description length is checked on *UPDATE*, not initial upload
    387 	suite.EqualValues(http.StatusOK, recorder.Code)
    388 }
    389 
    390 func TestMediaCreateTestSuite(t *testing.T) {
    391 	suite.Run(t, new(MediaCreateTestSuite))
    392 }