fromclientapi_test.go (10479B)
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 processing_test 19 20 import ( 21 "context" 22 "encoding/json" 23 "errors" 24 "testing" 25 26 "github.com/stretchr/testify/suite" 27 "github.com/superseriousbusiness/gotosocial/internal/ap" 28 apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" 29 "github.com/superseriousbusiness/gotosocial/internal/db" 30 "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" 31 "github.com/superseriousbusiness/gotosocial/internal/messages" 32 "github.com/superseriousbusiness/gotosocial/internal/stream" 33 "github.com/superseriousbusiness/gotosocial/testrig" 34 ) 35 36 type FromClientAPITestSuite struct { 37 ProcessingStandardTestSuite 38 } 39 40 // This test ensures that when admin_account posts a new 41 // status, it ends up in the correct streaming timelines 42 // of local_account_1, which follows it. 43 func (suite *FromClientAPITestSuite) TestProcessStreamNewStatus() { 44 var ( 45 ctx = context.Background() 46 postingAccount = suite.testAccounts["admin_account"] 47 receivingAccount = suite.testAccounts["local_account_1"] 48 testList = suite.testLists["local_account_1_list_1"] 49 streams = suite.openStreams(ctx, receivingAccount, []string{testList.ID}) 50 homeStream = streams[stream.TimelineHome] 51 listStream = streams[stream.TimelineList+":"+testList.ID] 52 ) 53 54 // Make a new status from admin account. 55 newStatus := >smodel.Status{ 56 ID: "01FN4B2F88TF9676DYNXWE1WSS", 57 URI: "http://localhost:8080/users/admin/statuses/01FN4B2F88TF9676DYNXWE1WSS", 58 URL: "http://localhost:8080/@admin/statuses/01FN4B2F88TF9676DYNXWE1WSS", 59 Content: "this status should stream :)", 60 AttachmentIDs: []string{}, 61 TagIDs: []string{}, 62 MentionIDs: []string{}, 63 EmojiIDs: []string{}, 64 CreatedAt: testrig.TimeMustParse("2021-10-20T11:36:45Z"), 65 UpdatedAt: testrig.TimeMustParse("2021-10-20T11:36:45Z"), 66 Local: testrig.TrueBool(), 67 AccountURI: "http://localhost:8080/users/admin", 68 AccountID: "01F8MH17FWEB39HZJ76B6VXSKF", 69 InReplyToID: "", 70 BoostOfID: "", 71 ContentWarning: "", 72 Visibility: gtsmodel.VisibilityFollowersOnly, 73 Sensitive: testrig.FalseBool(), 74 Language: "en", 75 CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F", 76 Federated: testrig.FalseBool(), 77 Boostable: testrig.TrueBool(), 78 Replyable: testrig.TrueBool(), 79 Likeable: testrig.TrueBool(), 80 ActivityStreamsType: ap.ObjectNote, 81 } 82 83 // Put the status in the db first, to mimic what 84 // would have already happened earlier up the flow. 85 if err := suite.db.PutStatus(ctx, newStatus); err != nil { 86 suite.FailNow(err.Error()) 87 } 88 89 // Process the new status. 90 if err := suite.processor.ProcessFromClientAPI(ctx, messages.FromClientAPI{ 91 APObjectType: ap.ObjectNote, 92 APActivityType: ap.ActivityCreate, 93 GTSModel: newStatus, 94 OriginAccount: postingAccount, 95 }); err != nil { 96 suite.FailNow(err.Error()) 97 } 98 99 // Check message in home stream. 100 homeMsg := <-homeStream.Messages 101 suite.Equal(stream.EventTypeUpdate, homeMsg.Event) 102 suite.EqualValues([]string{stream.TimelineHome}, homeMsg.Stream) 103 suite.Empty(homeStream.Messages) // Stream should now be empty. 104 105 // Check status from home stream. 106 homeStreamStatus := &apimodel.Status{} 107 if err := json.Unmarshal([]byte(homeMsg.Payload), homeStreamStatus); err != nil { 108 suite.FailNow(err.Error()) 109 } 110 suite.Equal(newStatus.ID, homeStreamStatus.ID) 111 suite.Equal(newStatus.Content, homeStreamStatus.Content) 112 113 // Check message in list stream. 114 listMsg := <-listStream.Messages 115 suite.Equal(stream.EventTypeUpdate, listMsg.Event) 116 suite.EqualValues([]string{stream.TimelineList + ":" + testList.ID}, listMsg.Stream) 117 suite.Empty(listStream.Messages) // Stream should now be empty. 118 119 // Check status from list stream. 120 listStreamStatus := &apimodel.Status{} 121 if err := json.Unmarshal([]byte(listMsg.Payload), listStreamStatus); err != nil { 122 suite.FailNow(err.Error()) 123 } 124 suite.Equal(newStatus.ID, listStreamStatus.ID) 125 suite.Equal(newStatus.Content, listStreamStatus.Content) 126 } 127 128 func (suite *FromClientAPITestSuite) TestProcessStatusDelete() { 129 var ( 130 ctx = context.Background() 131 deletingAccount = suite.testAccounts["local_account_1"] 132 receivingAccount = suite.testAccounts["local_account_2"] 133 deletedStatus = suite.testStatuses["local_account_1_status_1"] 134 boostOfDeletedStatus = suite.testStatuses["admin_account_status_4"] 135 streams = suite.openStreams(ctx, receivingAccount, nil) 136 homeStream = streams[stream.TimelineHome] 137 ) 138 139 // Delete the status from the db first, to mimic what 140 // would have already happened earlier up the flow 141 if err := suite.db.DeleteStatusByID(ctx, deletedStatus.ID); err != nil { 142 suite.FailNow(err.Error()) 143 } 144 145 // Process the status delete. 146 if err := suite.processor.ProcessFromClientAPI(ctx, messages.FromClientAPI{ 147 APObjectType: ap.ObjectNote, 148 APActivityType: ap.ActivityDelete, 149 GTSModel: deletedStatus, 150 OriginAccount: deletingAccount, 151 }); err != nil { 152 suite.FailNow(err.Error()) 153 } 154 155 // Stream should have the delete of admin's boost in it now. 156 msg := <-homeStream.Messages 157 suite.Equal(stream.EventTypeDelete, msg.Event) 158 suite.Equal(boostOfDeletedStatus.ID, msg.Payload) 159 suite.EqualValues([]string{stream.TimelineHome}, msg.Stream) 160 161 // Stream should also have the delete of the message itself in it. 162 msg = <-homeStream.Messages 163 suite.Equal(stream.EventTypeDelete, msg.Event) 164 suite.Equal(deletedStatus.ID, msg.Payload) 165 suite.EqualValues([]string{stream.TimelineHome}, msg.Stream) 166 167 // Stream should now be empty. 168 suite.Empty(homeStream.Messages) 169 170 // Boost should no longer be in the database. 171 if !testrig.WaitFor(func() bool { 172 _, err := suite.db.GetStatusByID(ctx, boostOfDeletedStatus.ID) 173 return errors.Is(err, db.ErrNoEntries) 174 }) { 175 suite.FailNow("timed out waiting for status delete") 176 } 177 } 178 179 func (suite *FromClientAPITestSuite) TestProcessNewStatusWithNotification() { 180 var ( 181 ctx = context.Background() 182 postingAccount = suite.testAccounts["admin_account"] 183 receivingAccount = suite.testAccounts["local_account_1"] 184 streams = suite.openStreams(ctx, receivingAccount, nil) 185 notifStream = streams[stream.TimelineNotifications] 186 ) 187 188 // Update the follow from receiving account -> posting account so 189 // that receiving account wants notifs when posting account posts. 190 follow := >smodel.Follow{} 191 *follow = *suite.testFollows["local_account_1_admin_account"] 192 follow.Notify = testrig.TrueBool() 193 if err := suite.db.UpdateFollow(ctx, follow); err != nil { 194 suite.FailNow(err.Error()) 195 } 196 197 // Make a new status from admin account. 198 newStatus := >smodel.Status{ 199 ID: "01FN4B2F88TF9676DYNXWE1WSS", 200 URI: "http://localhost:8080/users/admin/statuses/01FN4B2F88TF9676DYNXWE1WSS", 201 URL: "http://localhost:8080/@admin/statuses/01FN4B2F88TF9676DYNXWE1WSS", 202 Content: "this status should create a notification", 203 AttachmentIDs: []string{}, 204 TagIDs: []string{}, 205 MentionIDs: []string{}, 206 EmojiIDs: []string{}, 207 CreatedAt: testrig.TimeMustParse("2021-10-20T11:36:45Z"), 208 UpdatedAt: testrig.TimeMustParse("2021-10-20T11:36:45Z"), 209 Local: testrig.TrueBool(), 210 AccountURI: "http://localhost:8080/users/admin", 211 AccountID: "01F8MH17FWEB39HZJ76B6VXSKF", 212 InReplyToID: "", 213 BoostOfID: "", 214 ContentWarning: "", 215 Visibility: gtsmodel.VisibilityFollowersOnly, 216 Sensitive: testrig.FalseBool(), 217 Language: "en", 218 CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F", 219 Federated: testrig.FalseBool(), 220 Boostable: testrig.TrueBool(), 221 Replyable: testrig.TrueBool(), 222 Likeable: testrig.TrueBool(), 223 ActivityStreamsType: ap.ObjectNote, 224 } 225 226 // Put the status in the db first, to mimic what 227 // would have already happened earlier up the flow. 228 if err := suite.db.PutStatus(ctx, newStatus); err != nil { 229 suite.FailNow(err.Error()) 230 } 231 232 // Process the new status. 233 if err := suite.processor.ProcessFromClientAPI(ctx, messages.FromClientAPI{ 234 APObjectType: ap.ObjectNote, 235 APActivityType: ap.ActivityCreate, 236 GTSModel: newStatus, 237 OriginAccount: postingAccount, 238 }); err != nil { 239 suite.FailNow(err.Error()) 240 } 241 242 // Wait for a notification to appear for the status. 243 if !testrig.WaitFor(func() bool { 244 _, err := suite.db.GetNotification( 245 ctx, 246 gtsmodel.NotificationStatus, 247 receivingAccount.ID, 248 postingAccount.ID, 249 newStatus.ID, 250 ) 251 return err == nil 252 }) { 253 suite.FailNow("timed out waiting for new status notification") 254 } 255 256 // Check message in notification stream. 257 notifMsg := <-notifStream.Messages 258 suite.Equal(stream.EventTypeNotification, notifMsg.Event) 259 suite.EqualValues([]string{stream.TimelineNotifications}, notifMsg.Stream) 260 suite.Empty(notifStream.Messages) // Stream should now be empty. 261 262 // Check notif. 263 notif := &apimodel.Notification{} 264 if err := json.Unmarshal([]byte(notifMsg.Payload), notif); err != nil { 265 suite.FailNow(err.Error()) 266 } 267 suite.Equal(newStatus.ID, notif.Status.ID) 268 } 269 270 func TestFromClientAPITestSuite(t *testing.T) { 271 suite.Run(t, &FromClientAPITestSuite{}) 272 }