emojiupdate_test.go (17112B)
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 admin_test 19 20 import ( 21 "context" 22 "encoding/json" 23 "io/ioutil" 24 "net/http" 25 "net/http/httptest" 26 "testing" 27 28 "github.com/stretchr/testify/suite" 29 "github.com/superseriousbusiness/gotosocial/internal/api/client/admin" 30 apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" 31 "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" 32 "github.com/superseriousbusiness/gotosocial/testrig" 33 ) 34 35 type EmojiUpdateTestSuite struct { 36 AdminStandardTestSuite 37 } 38 39 func (suite *EmojiUpdateTestSuite) TestEmojiUpdateNewCategory() { 40 testEmoji := >smodel.Emoji{} 41 *testEmoji = *suite.testEmojis["rainbow"] 42 43 // set up the request 44 requestBody, w, err := testrig.CreateMultipartFormData( 45 "", "", 46 map[string]string{ 47 "category": "New Category", // this category doesn't exist yet 48 "type": "modify", 49 }) 50 if err != nil { 51 panic(err) 52 } 53 bodyBytes := requestBody.Bytes() 54 recorder := httptest.NewRecorder() 55 ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) 56 ctx.AddParam(admin.IDKey, testEmoji.ID) 57 58 // call the handler 59 suite.adminModule.EmojiPATCHHandler(ctx) 60 61 // 1. we should have OK because our request was valid 62 suite.Equal(http.StatusOK, recorder.Code) 63 64 // 2. we should have no error message in the result body 65 result := recorder.Result() 66 defer result.Body.Close() 67 68 // check the response 69 b, err := ioutil.ReadAll(result.Body) 70 suite.NoError(err) 71 suite.NotEmpty(b) 72 73 // response should be an admin model emoji 74 adminEmoji := &apimodel.AdminEmoji{} 75 err = json.Unmarshal(b, adminEmoji) 76 suite.NoError(err) 77 78 // appropriate fields should be set 79 suite.Equal("rainbow", adminEmoji.Shortcode) 80 suite.NotEmpty(adminEmoji.URL) 81 suite.NotEmpty(adminEmoji.StaticURL) 82 suite.True(adminEmoji.VisibleInPicker) 83 84 // emoji should be in the db 85 dbEmoji, err := suite.db.GetEmojiByShortcodeDomain(context.Background(), adminEmoji.Shortcode, "") 86 suite.NoError(err) 87 88 // check fields on the emoji 89 suite.NotEmpty(dbEmoji.ID) 90 suite.Equal("rainbow", dbEmoji.Shortcode) 91 suite.Empty(dbEmoji.Domain) 92 suite.Empty(dbEmoji.ImageRemoteURL) 93 suite.Empty(dbEmoji.ImageStaticRemoteURL) 94 suite.Equal(adminEmoji.URL, dbEmoji.ImageURL) 95 suite.Equal(adminEmoji.StaticURL, dbEmoji.ImageStaticURL) 96 suite.NotEmpty(dbEmoji.ImagePath) 97 suite.NotEmpty(dbEmoji.ImageStaticPath) 98 suite.Equal("image/png", dbEmoji.ImageContentType) 99 suite.Equal("image/png", dbEmoji.ImageStaticContentType) 100 suite.Equal(36702, dbEmoji.ImageFileSize) 101 suite.Equal(10413, dbEmoji.ImageStaticFileSize) 102 suite.False(*dbEmoji.Disabled) 103 suite.NotEmpty(dbEmoji.URI) 104 suite.True(*dbEmoji.VisibleInPicker) 105 suite.NotEmpty(dbEmoji.CategoryID) 106 107 // emoji should be in storage 108 emojiBytes, err := suite.storage.Get(ctx, dbEmoji.ImagePath) 109 suite.NoError(err) 110 suite.Len(emojiBytes, dbEmoji.ImageFileSize) 111 emojiStaticBytes, err := suite.storage.Get(ctx, dbEmoji.ImageStaticPath) 112 suite.NoError(err) 113 suite.Len(emojiStaticBytes, dbEmoji.ImageStaticFileSize) 114 } 115 116 func (suite *EmojiUpdateTestSuite) TestEmojiUpdateSwitchCategory() { 117 testEmoji := >smodel.Emoji{} 118 *testEmoji = *suite.testEmojis["rainbow"] 119 120 // set up the request 121 requestBody, w, err := testrig.CreateMultipartFormData( 122 "", "", 123 map[string]string{ 124 "type": "modify", 125 "category": "cute stuff", 126 }) 127 if err != nil { 128 panic(err) 129 } 130 bodyBytes := requestBody.Bytes() 131 recorder := httptest.NewRecorder() 132 ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) 133 ctx.AddParam(admin.IDKey, testEmoji.ID) 134 135 // call the handler 136 suite.adminModule.EmojiPATCHHandler(ctx) 137 138 // 1. we should have OK because our request was valid 139 suite.Equal(http.StatusOK, recorder.Code) 140 141 // 2. we should have no error message in the result body 142 result := recorder.Result() 143 defer result.Body.Close() 144 145 // check the response 146 b, err := ioutil.ReadAll(result.Body) 147 suite.NoError(err) 148 suite.NotEmpty(b) 149 150 // response should be an admin model emoji 151 adminEmoji := &apimodel.AdminEmoji{} 152 err = json.Unmarshal(b, adminEmoji) 153 suite.NoError(err) 154 155 // appropriate fields should be set 156 suite.Equal("rainbow", adminEmoji.Shortcode) 157 suite.NotEmpty(adminEmoji.URL) 158 suite.NotEmpty(adminEmoji.StaticURL) 159 suite.True(adminEmoji.VisibleInPicker) 160 161 // emoji should be in the db 162 dbEmoji, err := suite.db.GetEmojiByShortcodeDomain(context.Background(), adminEmoji.Shortcode, "") 163 suite.NoError(err) 164 165 // check fields on the emoji 166 suite.NotEmpty(dbEmoji.ID) 167 suite.Equal("rainbow", dbEmoji.Shortcode) 168 suite.Empty(dbEmoji.Domain) 169 suite.Empty(dbEmoji.ImageRemoteURL) 170 suite.Empty(dbEmoji.ImageStaticRemoteURL) 171 suite.Equal(adminEmoji.URL, dbEmoji.ImageURL) 172 suite.Equal(adminEmoji.StaticURL, dbEmoji.ImageStaticURL) 173 suite.NotEmpty(dbEmoji.ImagePath) 174 suite.NotEmpty(dbEmoji.ImageStaticPath) 175 suite.Equal("image/png", dbEmoji.ImageContentType) 176 suite.Equal("image/png", dbEmoji.ImageStaticContentType) 177 suite.Equal(36702, dbEmoji.ImageFileSize) 178 suite.Equal(10413, dbEmoji.ImageStaticFileSize) 179 suite.False(*dbEmoji.Disabled) 180 suite.NotEmpty(dbEmoji.URI) 181 suite.True(*dbEmoji.VisibleInPicker) 182 suite.NotEmpty(dbEmoji.CategoryID) 183 184 // emoji should be in storage 185 emojiBytes, err := suite.storage.Get(ctx, dbEmoji.ImagePath) 186 suite.NoError(err) 187 suite.Len(emojiBytes, dbEmoji.ImageFileSize) 188 emojiStaticBytes, err := suite.storage.Get(ctx, dbEmoji.ImageStaticPath) 189 suite.NoError(err) 190 suite.Len(emojiStaticBytes, dbEmoji.ImageStaticFileSize) 191 } 192 193 func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyRemoteToLocal() { 194 testEmoji := >smodel.Emoji{} 195 *testEmoji = *suite.testEmojis["yell"] 196 197 // set up the request 198 requestBody, w, err := testrig.CreateMultipartFormData( 199 "", "", 200 map[string]string{ 201 "type": "copy", 202 "category": "emojis i stole", 203 "shortcode": "yell", 204 }) 205 if err != nil { 206 panic(err) 207 } 208 bodyBytes := requestBody.Bytes() 209 recorder := httptest.NewRecorder() 210 ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) 211 ctx.AddParam(admin.IDKey, testEmoji.ID) 212 213 // call the handler 214 suite.adminModule.EmojiPATCHHandler(ctx) 215 216 // 1. we should have OK because our request was valid 217 suite.Equal(http.StatusOK, recorder.Code) 218 219 // 2. we should have no error message in the result body 220 result := recorder.Result() 221 defer result.Body.Close() 222 223 // check the response 224 b, err := ioutil.ReadAll(result.Body) 225 suite.NoError(err) 226 suite.NotEmpty(b) 227 228 // response should be an admin model emoji 229 adminEmoji := &apimodel.AdminEmoji{} 230 err = json.Unmarshal(b, adminEmoji) 231 suite.NoError(err) 232 233 // appropriate fields should be set 234 suite.Equal("yell", adminEmoji.Shortcode) 235 suite.NotEmpty(adminEmoji.URL) 236 suite.NotEmpty(adminEmoji.StaticURL) 237 suite.True(adminEmoji.VisibleInPicker) 238 239 // emoji should be in the db 240 dbEmoji, err := suite.db.GetEmojiByShortcodeDomain(context.Background(), adminEmoji.Shortcode, "") 241 suite.NoError(err) 242 243 // check fields on the emoji 244 suite.NotEmpty(dbEmoji.ID) 245 suite.Equal("yell", dbEmoji.Shortcode) 246 suite.Empty(dbEmoji.Domain) 247 suite.Empty(dbEmoji.ImageRemoteURL) 248 suite.Empty(dbEmoji.ImageStaticRemoteURL) 249 suite.Equal(adminEmoji.URL, dbEmoji.ImageURL) 250 suite.Equal(adminEmoji.StaticURL, dbEmoji.ImageStaticURL) 251 suite.NotEmpty(dbEmoji.ImagePath) 252 suite.NotEmpty(dbEmoji.ImageStaticPath) 253 suite.Equal("image/png", dbEmoji.ImageContentType) 254 suite.Equal("image/png", dbEmoji.ImageStaticContentType) 255 suite.Equal(10889, dbEmoji.ImageFileSize) 256 suite.Equal(10672, dbEmoji.ImageStaticFileSize) 257 suite.False(*dbEmoji.Disabled) 258 suite.NotEmpty(dbEmoji.URI) 259 suite.True(*dbEmoji.VisibleInPicker) 260 suite.NotEmpty(dbEmoji.CategoryID) 261 262 // emoji should be in storage 263 emojiBytes, err := suite.storage.Get(ctx, dbEmoji.ImagePath) 264 suite.NoError(err) 265 suite.Len(emojiBytes, dbEmoji.ImageFileSize) 266 emojiStaticBytes, err := suite.storage.Get(ctx, dbEmoji.ImageStaticPath) 267 suite.NoError(err) 268 suite.Len(emojiStaticBytes, dbEmoji.ImageStaticFileSize) 269 } 270 271 func (suite *EmojiUpdateTestSuite) TestEmojiUpdateDisableEmoji() { 272 testEmoji := >smodel.Emoji{} 273 *testEmoji = *suite.testEmojis["yell"] 274 275 // set up the request 276 requestBody, w, err := testrig.CreateMultipartFormData( 277 "", "", 278 map[string]string{ 279 "type": "disable", 280 }) 281 if err != nil { 282 panic(err) 283 } 284 bodyBytes := requestBody.Bytes() 285 recorder := httptest.NewRecorder() 286 ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) 287 ctx.AddParam(admin.IDKey, testEmoji.ID) 288 289 // call the handler 290 suite.adminModule.EmojiPATCHHandler(ctx) 291 292 // 1. we should have OK because our request was valid 293 suite.Equal(http.StatusOK, recorder.Code) 294 295 // 2. we should have no error message in the result body 296 result := recorder.Result() 297 defer result.Body.Close() 298 299 // check the response 300 b, err := ioutil.ReadAll(result.Body) 301 suite.NoError(err) 302 suite.NotEmpty(b) 303 304 // response should be an admin model emoji 305 adminEmoji := &apimodel.AdminEmoji{} 306 err = json.Unmarshal(b, adminEmoji) 307 suite.NoError(err) 308 309 suite.True(adminEmoji.Disabled) 310 } 311 312 func (suite *EmojiUpdateTestSuite) TestEmojiUpdateDisableLocalEmoji() { 313 testEmoji := >smodel.Emoji{} 314 *testEmoji = *suite.testEmojis["rainbow"] 315 316 // set up the request 317 requestBody, w, err := testrig.CreateMultipartFormData( 318 "", "", 319 map[string]string{ 320 "type": "disable", 321 }) 322 if err != nil { 323 panic(err) 324 } 325 bodyBytes := requestBody.Bytes() 326 recorder := httptest.NewRecorder() 327 ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) 328 ctx.AddParam(admin.IDKey, testEmoji.ID) 329 330 // call the handler 331 suite.adminModule.EmojiPATCHHandler(ctx) 332 suite.Equal(http.StatusBadRequest, recorder.Code) 333 334 // 2. we should have no error message in the result body 335 result := recorder.Result() 336 defer result.Body.Close() 337 338 // check the response 339 b, err := ioutil.ReadAll(result.Body) 340 suite.NoError(err) 341 342 suite.Equal(`{"error":"Bad Request: emojiUpdateDisable: emoji 01F8MH9H8E4VG3KDYJR9EGPXCQ is not a remote emoji, cannot disable it via this endpoint"}`, string(b)) 343 } 344 345 func (suite *EmojiUpdateTestSuite) TestEmojiUpdateModifyRemoteEmoji() { 346 testEmoji := >smodel.Emoji{} 347 *testEmoji = *suite.testEmojis["yell"] 348 349 // set up the request 350 requestBody, w, err := testrig.CreateMultipartFormData( 351 "image", "../../../../testrig/media/kip-original.gif", 352 map[string]string{ 353 "type": "modify", 354 }) 355 if err != nil { 356 panic(err) 357 } 358 bodyBytes := requestBody.Bytes() 359 recorder := httptest.NewRecorder() 360 ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) 361 ctx.AddParam(admin.IDKey, testEmoji.ID) 362 363 // call the handler 364 suite.adminModule.EmojiPATCHHandler(ctx) 365 suite.Equal(http.StatusBadRequest, recorder.Code) 366 367 // 2. we should have no error message in the result body 368 result := recorder.Result() 369 defer result.Body.Close() 370 371 // check the response 372 b, err := ioutil.ReadAll(result.Body) 373 suite.NoError(err) 374 375 suite.Equal(`{"error":"Bad Request: emojiUpdateModify: emoji 01GD5KP5CQEE1R3X43Y1EHS2CW is not a local emoji, cannot do a modify action on it"}`, string(b)) 376 } 377 378 func (suite *EmojiUpdateTestSuite) TestEmojiUpdateModifyNoParams() { 379 testEmoji := >smodel.Emoji{} 380 *testEmoji = *suite.testEmojis["rainbow"] 381 382 // set up the request 383 requestBody, w, err := testrig.CreateMultipartFormData( 384 "", "", 385 map[string]string{ 386 "type": "modify", 387 }) 388 if err != nil { 389 panic(err) 390 } 391 bodyBytes := requestBody.Bytes() 392 recorder := httptest.NewRecorder() 393 ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) 394 ctx.AddParam(admin.IDKey, testEmoji.ID) 395 396 // call the handler 397 suite.adminModule.EmojiPATCHHandler(ctx) 398 suite.Equal(http.StatusBadRequest, recorder.Code) 399 400 // 2. we should have no error message in the result body 401 result := recorder.Result() 402 defer result.Body.Close() 403 404 // check the response 405 b, err := ioutil.ReadAll(result.Body) 406 suite.NoError(err) 407 408 suite.Equal(`{"error":"Bad Request: emoji action type was 'modify' but no image or category name was provided"}`, string(b)) 409 } 410 411 func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyLocalToLocal() { 412 testEmoji := >smodel.Emoji{} 413 *testEmoji = *suite.testEmojis["rainbow"] 414 415 // set up the request 416 requestBody, w, err := testrig.CreateMultipartFormData( 417 "", "", 418 map[string]string{ 419 "type": "copy", 420 "shortcode": "bottoms", 421 }) 422 if err != nil { 423 panic(err) 424 } 425 bodyBytes := requestBody.Bytes() 426 recorder := httptest.NewRecorder() 427 ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) 428 ctx.AddParam(admin.IDKey, testEmoji.ID) 429 430 // call the handler 431 suite.adminModule.EmojiPATCHHandler(ctx) 432 suite.Equal(http.StatusBadRequest, recorder.Code) 433 434 // 2. we should have no error message in the result body 435 result := recorder.Result() 436 defer result.Body.Close() 437 438 // check the response 439 b, err := ioutil.ReadAll(result.Body) 440 suite.NoError(err) 441 442 suite.Equal(`{"error":"Bad Request: emojiUpdateCopy: emoji 01F8MH9H8E4VG3KDYJR9EGPXCQ is not a remote emoji, cannot copy it to local"}`, string(b)) 443 } 444 445 func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyEmptyShortcode() { 446 testEmoji := >smodel.Emoji{} 447 *testEmoji = *suite.testEmojis["yell"] 448 449 // set up the request 450 requestBody, w, err := testrig.CreateMultipartFormData( 451 "", "", 452 map[string]string{ 453 "type": "copy", 454 "shortcode": "", 455 }) 456 if err != nil { 457 panic(err) 458 } 459 bodyBytes := requestBody.Bytes() 460 recorder := httptest.NewRecorder() 461 ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) 462 ctx.AddParam(admin.IDKey, testEmoji.ID) 463 464 // call the handler 465 suite.adminModule.EmojiPATCHHandler(ctx) 466 suite.Equal(http.StatusBadRequest, recorder.Code) 467 468 // 2. we should have no error message in the result body 469 result := recorder.Result() 470 defer result.Body.Close() 471 472 // check the response 473 b, err := ioutil.ReadAll(result.Body) 474 suite.NoError(err) 475 476 suite.Equal(`{"error":"Bad Request: shortcode did not pass validation, must be between 2 and 30 characters, letters, numbers, and underscores only"}`, string(b)) 477 } 478 479 func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyNoShortcode() { 480 testEmoji := >smodel.Emoji{} 481 *testEmoji = *suite.testEmojis["yell"] 482 483 // set up the request 484 requestBody, w, err := testrig.CreateMultipartFormData( 485 "", "", 486 map[string]string{ 487 "type": "copy", 488 }) 489 if err != nil { 490 panic(err) 491 } 492 bodyBytes := requestBody.Bytes() 493 recorder := httptest.NewRecorder() 494 ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) 495 ctx.AddParam(admin.IDKey, testEmoji.ID) 496 497 // call the handler 498 suite.adminModule.EmojiPATCHHandler(ctx) 499 suite.Equal(http.StatusBadRequest, recorder.Code) 500 501 // 2. we should have no error message in the result body 502 result := recorder.Result() 503 defer result.Body.Close() 504 505 // check the response 506 b, err := ioutil.ReadAll(result.Body) 507 suite.NoError(err) 508 509 suite.Equal(`{"error":"Bad Request: emoji action type was 'copy' but no shortcode was provided"}`, string(b)) 510 } 511 512 func (suite *EmojiUpdateTestSuite) TestEmojiUpdateCopyShortcodeAlreadyInUse() { 513 testEmoji := >smodel.Emoji{} 514 *testEmoji = *suite.testEmojis["yell"] 515 516 // set up the request 517 requestBody, w, err := testrig.CreateMultipartFormData( 518 "", "", 519 map[string]string{ 520 "type": "copy", 521 "shortcode": "rainbow", 522 }) 523 if err != nil { 524 panic(err) 525 } 526 bodyBytes := requestBody.Bytes() 527 recorder := httptest.NewRecorder() 528 ctx := suite.newContext(recorder, http.MethodPost, bodyBytes, admin.EmojiPathWithID, w.FormDataContentType()) 529 ctx.AddParam(admin.IDKey, testEmoji.ID) 530 531 // call the handler 532 suite.adminModule.EmojiPATCHHandler(ctx) 533 suite.Equal(http.StatusConflict, recorder.Code) 534 535 // 2. we should have no error message in the result body 536 result := recorder.Result() 537 defer result.Body.Close() 538 539 // check the response 540 b, err := ioutil.ReadAll(result.Body) 541 suite.NoError(err) 542 543 suite.Equal(`{"error":"Conflict: emojiUpdateCopy: emoji 01GD5KP5CQEE1R3X43Y1EHS2CW could not be copied, emoji with shortcode rainbow already exists on this instance"}`, string(b)) 544 } 545 546 func TestEmojiUpdateTestSuite(t *testing.T) { 547 suite.Run(t, &EmojiUpdateTestSuite{}) 548 }