gtsocial-umbx

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

statuscreate_test.go (17802B)


      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 statuses_test
     19 
     20 import (
     21 	"context"
     22 	"encoding/json"
     23 	"fmt"
     24 	"io/ioutil"
     25 	"net/http"
     26 	"net/http/httptest"
     27 	"net/url"
     28 	"testing"
     29 
     30 	"github.com/stretchr/testify/suite"
     31 	"github.com/superseriousbusiness/gotosocial/internal/api/client/statuses"
     32 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
     33 	"github.com/superseriousbusiness/gotosocial/internal/db"
     34 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
     35 	"github.com/superseriousbusiness/gotosocial/internal/oauth"
     36 	"github.com/superseriousbusiness/gotosocial/testrig"
     37 )
     38 
     39 type StatusCreateTestSuite struct {
     40 	StatusStandardTestSuite
     41 }
     42 
     43 const (
     44 	statusWithLinksAndTags = "#test alright, should be able to post #links with fragments in them now, let's see........\n\nhttps://docs.gotosocial.org/en/latest/user_guide/posts/#links\n\n#gotosocial\n\n(tobi remember to pull the docker image challenge)"
     45 	statusMarkdown         = "# Title\n\n## Smaller title\n\nThis is a post written in [markdown](https://www.markdownguide.org/)\n\n<img src=\"https://d33wubrfki0l68.cloudfront.net/f1f475a6fda1c2c4be4cac04033db5c3293032b4/513a4/assets/images/markdown-mark-white.svg\"/>"
     46 	statusMarkdownExpected = "<h1>Title</h1><h2>Smaller title</h2><p>This is a post written in <a href=\"https://www.markdownguide.org/\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">markdown</a></p><img src=\"https://d33wubrfki0l68.cloudfront.net/f1f475a6fda1c2c4be4cac04033db5c3293032b4/513a4/assets/images/markdown-mark-white.svg\" crossorigin=\"anonymous\">"
     47 )
     48 
     49 // Post a new status with some custom visibility settings
     50 func (suite *StatusCreateTestSuite) TestPostNewStatus() {
     51 	t := suite.testTokens["local_account_1"]
     52 	oauthToken := oauth.DBTokenToToken(t)
     53 
     54 	// setup
     55 	recorder := httptest.NewRecorder()
     56 	ctx, _ := testrig.CreateGinTestContext(recorder, nil)
     57 	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
     58 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
     59 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
     60 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
     61 	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil) // the endpoint we're hitting
     62 	ctx.Request.Header.Set("accept", "application/json")
     63 	ctx.Request.Form = url.Values{
     64 		"status":       {"this is a brand new status! #helloworld"},
     65 		"spoiler_text": {"hello hello"},
     66 		"sensitive":    {"true"},
     67 		"visibility":   {string(apimodel.VisibilityMutualsOnly)},
     68 		"likeable":     {"false"},
     69 		"replyable":    {"false"},
     70 		"federated":    {"false"},
     71 	}
     72 	suite.statusModule.StatusCreatePOSTHandler(ctx)
     73 
     74 	// check response
     75 
     76 	// 1. we should have OK from our call to the function
     77 	suite.EqualValues(http.StatusOK, recorder.Code)
     78 
     79 	result := recorder.Result()
     80 	defer result.Body.Close()
     81 	b, err := ioutil.ReadAll(result.Body)
     82 	suite.NoError(err)
     83 
     84 	statusReply := &apimodel.Status{}
     85 	err = json.Unmarshal(b, statusReply)
     86 	suite.NoError(err)
     87 
     88 	suite.Equal("hello hello", statusReply.SpoilerText)
     89 	suite.Equal("<p>this is a brand new status! <a href=\"http://localhost:8080/tags/helloworld\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\">#<span>helloworld</span></a></p>", statusReply.Content)
     90 	suite.True(statusReply.Sensitive)
     91 	suite.Equal(apimodel.VisibilityPrivate, statusReply.Visibility) // even though we set this status to mutuals only, it should serialize to private, because the mastodon api has no idea about mutuals_only
     92 	suite.Len(statusReply.Tags, 1)
     93 	suite.Equal(apimodel.Tag{
     94 		Name: "helloworld",
     95 		URL:  "http://localhost:8080/tags/helloworld",
     96 	}, statusReply.Tags[0])
     97 
     98 	gtsTag := &gtsmodel.Tag{}
     99 	err = suite.db.GetWhere(context.Background(), []db.Where{{Key: "name", Value: "helloworld"}}, gtsTag)
    100 	suite.NoError(err)
    101 	suite.Equal(statusReply.Account.ID, gtsTag.FirstSeenFromAccountID)
    102 }
    103 
    104 func (suite *StatusCreateTestSuite) TestPostNewStatusMarkdown() {
    105 	// set default post language of account 1 to markdown
    106 	testAccount := suite.testAccounts["local_account_1"]
    107 	testAccount.StatusContentType = "text/markdown"
    108 	a := testAccount
    109 
    110 	err := suite.db.UpdateAccount(context.Background(), a)
    111 	if err != nil {
    112 		suite.FailNow(err.Error())
    113 	}
    114 	suite.Equal(a.StatusContentType, "text/markdown")
    115 
    116 	t := suite.testTokens["local_account_1"]
    117 	oauthToken := oauth.DBTokenToToken(t)
    118 
    119 	recorder := httptest.NewRecorder()
    120 	ctx, _ := testrig.CreateGinTestContext(recorder, nil)
    121 	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
    122 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
    123 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
    124 	ctx.Set(oauth.SessionAuthorizedAccount, a)
    125 
    126 	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil)
    127 	ctx.Request.Header.Set("accept", "application/json")
    128 	ctx.Request.Form = url.Values{
    129 		"status":     {statusMarkdown},
    130 		"visibility": {string(apimodel.VisibilityPublic)},
    131 	}
    132 	suite.statusModule.StatusCreatePOSTHandler(ctx)
    133 
    134 	suite.EqualValues(http.StatusOK, recorder.Code)
    135 
    136 	result := recorder.Result()
    137 	defer result.Body.Close()
    138 	b, err := ioutil.ReadAll(result.Body)
    139 	suite.NoError(err)
    140 
    141 	statusReply := &apimodel.Status{}
    142 	err = json.Unmarshal(b, statusReply)
    143 	suite.NoError(err)
    144 
    145 	suite.Equal(statusMarkdownExpected, statusReply.Content)
    146 }
    147 
    148 // mention an account that is not yet known to the instance -- it should be looked up and put in the db
    149 func (suite *StatusCreateTestSuite) TestMentionUnknownAccount() {
    150 	// first remove remote account 1 from the database so it gets looked up again
    151 	remoteAccount := suite.testAccounts["remote_account_1"]
    152 	err := suite.db.DeleteAccount(context.Background(), remoteAccount.ID)
    153 	suite.NoError(err)
    154 
    155 	t := suite.testTokens["local_account_1"]
    156 	oauthToken := oauth.DBTokenToToken(t)
    157 
    158 	// setup
    159 	recorder := httptest.NewRecorder()
    160 	ctx, _ := testrig.CreateGinTestContext(recorder, nil)
    161 	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
    162 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
    163 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
    164 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
    165 	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil) // the endpoint we're hitting
    166 	ctx.Request.Header.Set("accept", "application/json")
    167 	ctx.Request.Form = url.Values{
    168 		"status":     {"hello @brand_new_person@unknown-instance.com"},
    169 		"visibility": {string(apimodel.VisibilityPublic)},
    170 	}
    171 	suite.statusModule.StatusCreatePOSTHandler(ctx)
    172 
    173 	suite.EqualValues(http.StatusOK, recorder.Code)
    174 
    175 	result := recorder.Result()
    176 	defer result.Body.Close()
    177 	b, err := ioutil.ReadAll(result.Body)
    178 	suite.NoError(err)
    179 
    180 	statusReply := &apimodel.Status{}
    181 	err = json.Unmarshal(b, statusReply)
    182 	suite.NoError(err)
    183 
    184 	// if the status is properly formatted, that means the account has been put in the db
    185 	suite.Equal(`<p>hello <span class="h-card"><a href="https://unknown-instance.com/@brand_new_person" class="u-url mention" rel="nofollow noreferrer noopener" target="_blank">@<span>brand_new_person</span></a></span></p>`, statusReply.Content)
    186 	suite.Equal(apimodel.VisibilityPublic, statusReply.Visibility)
    187 }
    188 
    189 func (suite *StatusCreateTestSuite) TestPostAnotherNewStatus() {
    190 	t := suite.testTokens["local_account_1"]
    191 	oauthToken := oauth.DBTokenToToken(t)
    192 
    193 	// setup
    194 	recorder := httptest.NewRecorder()
    195 	ctx, _ := testrig.CreateGinTestContext(recorder, nil)
    196 	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
    197 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
    198 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
    199 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
    200 	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil) // the endpoint we're hitting
    201 	ctx.Request.Header.Set("accept", "application/json")
    202 	ctx.Request.Form = url.Values{
    203 		"status": {statusWithLinksAndTags},
    204 	}
    205 	suite.statusModule.StatusCreatePOSTHandler(ctx)
    206 
    207 	// check response
    208 
    209 	// 1. we should have OK from our call to the function
    210 	suite.EqualValues(http.StatusOK, recorder.Code)
    211 
    212 	result := recorder.Result()
    213 	defer result.Body.Close()
    214 	b, err := ioutil.ReadAll(result.Body)
    215 	suite.NoError(err)
    216 
    217 	statusReply := &apimodel.Status{}
    218 	err = json.Unmarshal(b, statusReply)
    219 	suite.NoError(err)
    220 
    221 	suite.Equal("<p><a href=\"http://localhost:8080/tags/test\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\">#<span>test</span></a> alright, should be able to post <a href=\"http://localhost:8080/tags/links\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\">#<span>links</span></a> with fragments in them now, let's see........<br><br><a href=\"https://docs.gotosocial.org/en/latest/user_guide/posts/#links\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">https://docs.gotosocial.org/en/latest/user_guide/posts/#links</a><br><br><a href=\"http://localhost:8080/tags/gotosocial\" class=\"mention hashtag\" rel=\"tag nofollow noreferrer noopener\" target=\"_blank\">#<span>gotosocial</span></a><br><br>(tobi remember to pull the docker image challenge)</p>", statusReply.Content)
    222 }
    223 
    224 func (suite *StatusCreateTestSuite) TestPostNewStatusWithEmoji() {
    225 	t := suite.testTokens["local_account_1"]
    226 	oauthToken := oauth.DBTokenToToken(t)
    227 
    228 	// setup
    229 	recorder := httptest.NewRecorder()
    230 	ctx, _ := testrig.CreateGinTestContext(recorder, nil)
    231 	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
    232 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
    233 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
    234 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
    235 	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil) // the endpoint we're hitting
    236 	ctx.Request.Header.Set("accept", "application/json")
    237 	ctx.Request.Form = url.Values{
    238 		"status": {"here is a rainbow emoji a few times! :rainbow: :rainbow: :rainbow: \n here's an emoji that isn't in the db: :test_emoji: "},
    239 	}
    240 	suite.statusModule.StatusCreatePOSTHandler(ctx)
    241 
    242 	suite.EqualValues(http.StatusOK, recorder.Code)
    243 
    244 	result := recorder.Result()
    245 	defer result.Body.Close()
    246 	b, err := ioutil.ReadAll(result.Body)
    247 	suite.NoError(err)
    248 
    249 	statusReply := &apimodel.Status{}
    250 	err = json.Unmarshal(b, statusReply)
    251 	suite.NoError(err)
    252 
    253 	suite.Equal("", statusReply.SpoilerText)
    254 	suite.Equal("<p>here is a rainbow emoji a few times! :rainbow: :rainbow: :rainbow:<br>here's an emoji that isn't in the db: :test_emoji:</p>", statusReply.Content)
    255 
    256 	suite.Len(statusReply.Emojis, 1)
    257 	apiEmoji := statusReply.Emojis[0]
    258 	gtsEmoji := testrig.NewTestEmojis()["rainbow"]
    259 
    260 	suite.Equal(gtsEmoji.Shortcode, apiEmoji.Shortcode)
    261 	suite.Equal(gtsEmoji.ImageURL, apiEmoji.URL)
    262 	suite.Equal(gtsEmoji.ImageStaticURL, apiEmoji.StaticURL)
    263 }
    264 
    265 // Try to reply to a status that doesn't exist
    266 func (suite *StatusCreateTestSuite) TestReplyToNonexistentStatus() {
    267 	t := suite.testTokens["local_account_1"]
    268 	oauthToken := oauth.DBTokenToToken(t)
    269 
    270 	// setup
    271 	recorder := httptest.NewRecorder()
    272 	ctx, _ := testrig.CreateGinTestContext(recorder, nil)
    273 	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
    274 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
    275 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
    276 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
    277 	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil) // the endpoint we're hitting
    278 	ctx.Request.Header.Set("accept", "application/json")
    279 	ctx.Request.Form = url.Values{
    280 		"status":         {"this is a reply to a status that doesn't exist"},
    281 		"spoiler_text":   {"don't open cuz it won't work"},
    282 		"in_reply_to_id": {"3759e7ef-8ee1-4c0c-86f6-8b70b9ad3d50"},
    283 	}
    284 	suite.statusModule.StatusCreatePOSTHandler(ctx)
    285 
    286 	// check response
    287 
    288 	suite.EqualValues(http.StatusBadRequest, recorder.Code)
    289 
    290 	result := recorder.Result()
    291 	defer result.Body.Close()
    292 	b, err := ioutil.ReadAll(result.Body)
    293 	suite.NoError(err)
    294 	suite.Equal(`{"error":"Bad Request: status with id 3759e7ef-8ee1-4c0c-86f6-8b70b9ad3d50 not replyable because it doesn't exist"}`, string(b))
    295 }
    296 
    297 // Post a reply to the status of a local user that allows replies.
    298 func (suite *StatusCreateTestSuite) TestReplyToLocalStatus() {
    299 	t := suite.testTokens["local_account_1"]
    300 	oauthToken := oauth.DBTokenToToken(t)
    301 
    302 	// setup
    303 	recorder := httptest.NewRecorder()
    304 	ctx, _ := testrig.CreateGinTestContext(recorder, nil)
    305 	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
    306 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
    307 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
    308 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
    309 	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil) // the endpoint we're hitting
    310 	ctx.Request.Header.Set("accept", "application/json")
    311 	ctx.Request.Form = url.Values{
    312 		"status":         {fmt.Sprintf("hello @%s this reply should work!", testrig.NewTestAccounts()["local_account_2"].Username)},
    313 		"in_reply_to_id": {testrig.NewTestStatuses()["local_account_2_status_1"].ID},
    314 	}
    315 	suite.statusModule.StatusCreatePOSTHandler(ctx)
    316 
    317 	// check response
    318 	suite.EqualValues(http.StatusOK, recorder.Code)
    319 
    320 	result := recorder.Result()
    321 	defer result.Body.Close()
    322 	b, err := ioutil.ReadAll(result.Body)
    323 	suite.NoError(err)
    324 
    325 	statusReply := &apimodel.Status{}
    326 	err = json.Unmarshal(b, statusReply)
    327 	suite.NoError(err)
    328 
    329 	suite.Equal("", statusReply.SpoilerText)
    330 	suite.Equal(fmt.Sprintf("<p>hello <span class=\"h-card\"><a href=\"http://localhost:8080/@%s\" class=\"u-url mention\" rel=\"nofollow noreferrer noopener\" target=\"_blank\">@<span>%s</span></a></span> this reply should work!</p>", testrig.NewTestAccounts()["local_account_2"].Username, testrig.NewTestAccounts()["local_account_2"].Username), statusReply.Content)
    331 	suite.False(statusReply.Sensitive)
    332 	suite.Equal(apimodel.VisibilityPublic, statusReply.Visibility)
    333 	suite.Equal(testrig.NewTestStatuses()["local_account_2_status_1"].ID, *statusReply.InReplyToID)
    334 	suite.Equal(testrig.NewTestAccounts()["local_account_2"].ID, *statusReply.InReplyToAccountID)
    335 	suite.Len(statusReply.Mentions, 1)
    336 }
    337 
    338 // Take a media file which is currently not associated with a status, and attach it to a new status.
    339 func (suite *StatusCreateTestSuite) TestAttachNewMediaSuccess() {
    340 	t := suite.testTokens["local_account_1"]
    341 	oauthToken := oauth.DBTokenToToken(t)
    342 
    343 	attachment := suite.testAttachments["local_account_1_unattached_1"]
    344 
    345 	// setup
    346 	recorder := httptest.NewRecorder()
    347 	ctx, _ := testrig.CreateGinTestContext(recorder, nil)
    348 	ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
    349 	ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
    350 	ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
    351 	ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
    352 	ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil) // the endpoint we're hitting
    353 	ctx.Request.Header.Set("accept", "application/json")
    354 	ctx.Request.Form = url.Values{
    355 		"status":      {"here's an image attachment"},
    356 		"media_ids[]": {attachment.ID},
    357 	}
    358 	suite.statusModule.StatusCreatePOSTHandler(ctx)
    359 
    360 	// check response
    361 	suite.EqualValues(http.StatusOK, recorder.Code)
    362 
    363 	result := recorder.Result()
    364 	defer result.Body.Close()
    365 	b, err := ioutil.ReadAll(result.Body)
    366 	suite.NoError(err)
    367 
    368 	statusResponse := &apimodel.Status{}
    369 	err = json.Unmarshal(b, statusResponse)
    370 	suite.NoError(err)
    371 
    372 	suite.Equal("", statusResponse.SpoilerText)
    373 	suite.Equal("<p>here's an image attachment</p>", statusResponse.Content)
    374 	suite.False(statusResponse.Sensitive)
    375 	suite.Equal(apimodel.VisibilityPublic, statusResponse.Visibility)
    376 
    377 	// there should be one media attachment
    378 	suite.Len(statusResponse.MediaAttachments, 1)
    379 
    380 	// get the updated media attachment from the database
    381 	gtsAttachment, err := suite.db.GetAttachmentByID(context.Background(), statusResponse.MediaAttachments[0].ID)
    382 	suite.NoError(err)
    383 
    384 	// convert it to a api attachment
    385 	gtsAttachmentAsapi, err := suite.tc.AttachmentToAPIAttachment(context.Background(), gtsAttachment)
    386 	suite.NoError(err)
    387 
    388 	// compare it with what we have now
    389 	suite.EqualValues(statusResponse.MediaAttachments[0], gtsAttachmentAsapi)
    390 
    391 	// the status id of the attachment should now be set to the id of the status we just created
    392 	suite.Equal(statusResponse.ID, gtsAttachment.StatusID)
    393 }
    394 
    395 func TestStatusCreateTestSuite(t *testing.T) {
    396 	suite.Run(t, new(StatusCreateTestSuite))
    397 }