statuspin_test.go (6394B)
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 "strconv" 28 "testing" 29 "time" 30 31 "github.com/stretchr/testify/suite" 32 "github.com/superseriousbusiness/gotosocial/internal/ap" 33 "github.com/superseriousbusiness/gotosocial/internal/api/client/statuses" 34 apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" 35 "github.com/superseriousbusiness/gotosocial/internal/config" 36 "github.com/superseriousbusiness/gotosocial/internal/gtserror" 37 "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" 38 "github.com/superseriousbusiness/gotosocial/internal/id" 39 "github.com/superseriousbusiness/gotosocial/internal/oauth" 40 "github.com/superseriousbusiness/gotosocial/testrig" 41 ) 42 43 type StatusPinTestSuite struct { 44 StatusStandardTestSuite 45 } 46 47 func (suite *StatusPinTestSuite) createPin( 48 expectedHTTPStatus int, 49 expectedBody string, 50 targetStatusID string, 51 ) (*apimodel.Status, error) { 52 // instantiate recorder + test context 53 recorder := httptest.NewRecorder() 54 ctx, _ := testrig.CreateGinTestContext(recorder, nil) 55 ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) 56 ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"])) 57 ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) 58 ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) 59 60 // create the request 61 ctx.Request = httptest.NewRequest(http.MethodPost, config.GetProtocol()+"://"+config.GetHost()+"/api/"+statuses.BasePath+"/"+targetStatusID+"/pin", nil) 62 ctx.Request.Header.Set("accept", "application/json") 63 ctx.AddParam(statuses.IDKey, targetStatusID) 64 65 // trigger the handler 66 suite.statusModule.StatusPinPOSTHandler(ctx) 67 68 // read the response 69 result := recorder.Result() 70 defer result.Body.Close() 71 72 b, err := ioutil.ReadAll(result.Body) 73 if err != nil { 74 return nil, err 75 } 76 77 errs := gtserror.MultiError{} 78 79 // check code + body 80 if resultCode := recorder.Code; expectedHTTPStatus != resultCode { 81 errs = append(errs, fmt.Sprintf("expected %d got %d", expectedHTTPStatus, resultCode)) 82 } 83 84 // if we got an expected body, return early 85 if expectedBody != "" && string(b) != expectedBody { 86 errs = append(errs, fmt.Sprintf("expected %s got %s", expectedBody, string(b))) 87 } 88 89 if len(errs) > 0 { 90 return nil, errs.Combine() 91 } 92 93 resp := &apimodel.Status{} 94 if err := json.Unmarshal(b, resp); err != nil { 95 return nil, err 96 } 97 98 return resp, nil 99 } 100 101 func (suite *StatusPinTestSuite) TestPinStatusPublicOK() { 102 // Pin an unpinned public status that this account owns. 103 targetStatus := suite.testStatuses["local_account_1_status_1"] 104 105 resp, err := suite.createPin(http.StatusOK, "", targetStatus.ID) 106 if err != nil { 107 suite.FailNow(err.Error()) 108 } 109 110 suite.True(resp.Pinned) 111 } 112 113 func (suite *StatusPinTestSuite) TestPinStatusFollowersOnlyOK() { 114 // Pin an unpinned followers only status that this account owns. 115 targetStatus := suite.testStatuses["local_account_1_status_5"] 116 117 resp, err := suite.createPin(http.StatusOK, "", targetStatus.ID) 118 if err != nil { 119 suite.FailNow(err.Error()) 120 } 121 122 suite.True(resp.Pinned) 123 } 124 125 func (suite *StatusPinTestSuite) TestPinStatusTwiceError() { 126 // Try to pin a status that's already been pinned. 127 targetStatus := >smodel.Status{} 128 *targetStatus = *suite.testStatuses["local_account_1_status_5"] 129 targetStatus.PinnedAt = time.Now() 130 131 if err := suite.db.UpdateStatus(context.Background(), targetStatus, "pinned_at"); err != nil { 132 suite.FailNow(err.Error()) 133 } 134 135 if _, err := suite.createPin( 136 http.StatusUnprocessableEntity, 137 `{"error":"Unprocessable Entity: status already pinned"}`, 138 targetStatus.ID, 139 ); err != nil { 140 suite.FailNow(err.Error()) 141 } 142 } 143 144 func (suite *StatusPinTestSuite) TestPinStatusOtherAccountError() { 145 // Try to pin a status that doesn't belong to us. 146 targetStatus := suite.testStatuses["admin_account_status_1"] 147 148 if _, err := suite.createPin( 149 http.StatusUnprocessableEntity, 150 `{"error":"Unprocessable Entity: status 01F8MH75CBF9JFX4ZAD54N0W0R does not belong to account 01F8MH1H7YV1Z7D2C8K2730QBF"}`, 151 targetStatus.ID, 152 ); err != nil { 153 suite.FailNow(err.Error()) 154 } 155 } 156 157 func (suite *StatusPinTestSuite) TestPinStatusTooManyPins() { 158 // Test pinning too many statuses. 159 testAccount := suite.testAccounts["local_account_1"] 160 161 // Spam 10 pinned statuses into the database. 162 ctx := context.Background() 163 for i := range make([]interface{}, 10) { 164 status := >smodel.Status{ 165 ID: id.NewULID(), 166 PinnedAt: time.Now(), 167 URL: "stub " + strconv.Itoa(i), 168 URI: "stub " + strconv.Itoa(i), 169 Local: testrig.TrueBool(), 170 AccountID: testAccount.ID, 171 AccountURI: testAccount.URI, 172 Visibility: gtsmodel.VisibilityPublic, 173 Federated: testrig.TrueBool(), 174 Boostable: testrig.TrueBool(), 175 Replyable: testrig.TrueBool(), 176 Likeable: testrig.TrueBool(), 177 ActivityStreamsType: ap.ObjectNote, 178 } 179 if err := suite.db.PutStatus(ctx, status); err != nil { 180 suite.FailNow(err.Error()) 181 } 182 } 183 184 // Try to pin one more status as a treat. 185 targetStatus := suite.testStatuses["local_account_1_status_1"] 186 if _, err := suite.createPin( 187 http.StatusUnprocessableEntity, 188 `{"error":"Unprocessable Entity: status pin limit exceeded, you've already pinned 10 status(es) out of 10"}`, 189 targetStatus.ID, 190 ); err != nil { 191 suite.FailNow(err.Error()) 192 } 193 } 194 195 func TestStatusPinTestSuite(t *testing.T) { 196 suite.Run(t, new(StatusPinTestSuite)) 197 }