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 }