gtsocial-umbx

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

home_timeline_test.go (17104B)


      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 visibility_test
     19 
     20 import (
     21 	"context"
     22 	"testing"
     23 	"time"
     24 
     25 	"github.com/stretchr/testify/suite"
     26 	"github.com/superseriousbusiness/gotosocial/internal/ap"
     27 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
     28 	"github.com/superseriousbusiness/gotosocial/testrig"
     29 )
     30 
     31 type StatusStatusHomeTimelineableTestSuite struct {
     32 	FilterStandardTestSuite
     33 }
     34 
     35 func (suite *StatusStatusHomeTimelineableTestSuite) TestOwnStatusHomeTimelineable() {
     36 	testStatus := suite.testStatuses["local_account_1_status_1"]
     37 	testAccount := suite.testAccounts["local_account_1"]
     38 	ctx := context.Background()
     39 
     40 	timelineable, err := suite.filter.StatusHomeTimelineable(ctx, testAccount, testStatus)
     41 	suite.NoError(err)
     42 
     43 	suite.True(timelineable)
     44 }
     45 
     46 func (suite *StatusStatusHomeTimelineableTestSuite) TestFollowingStatusHomeTimelineable() {
     47 	testStatus := suite.testStatuses["local_account_2_status_1"]
     48 	testAccount := suite.testAccounts["local_account_1"]
     49 	ctx := context.Background()
     50 
     51 	timelineable, err := suite.filter.StatusHomeTimelineable(ctx, testAccount, testStatus)
     52 	suite.NoError(err)
     53 
     54 	suite.True(timelineable)
     55 }
     56 
     57 func (suite *StatusStatusHomeTimelineableTestSuite) TestNotFollowingStatusHomeTimelineable() {
     58 	testStatus := suite.testStatuses["remote_account_1_status_1"]
     59 	testAccount := suite.testAccounts["local_account_1"]
     60 	ctx := context.Background()
     61 
     62 	timelineable, err := suite.filter.StatusHomeTimelineable(ctx, testAccount, testStatus)
     63 	suite.NoError(err)
     64 
     65 	suite.False(timelineable)
     66 }
     67 
     68 func (suite *StatusStatusHomeTimelineableTestSuite) TestStatusTooNewNotTimelineable() {
     69 	testStatus := &gtsmodel.Status{}
     70 	*testStatus = *suite.testStatuses["local_account_1_status_1"]
     71 
     72 	testStatus.CreatedAt = time.Now().Add(25 * time.Hour)
     73 
     74 	testAccount := suite.testAccounts["local_account_1"]
     75 	ctx := context.Background()
     76 
     77 	timelineable, err := suite.filter.StatusHomeTimelineable(ctx, testAccount, testStatus)
     78 	suite.NoError(err)
     79 
     80 	suite.False(timelineable)
     81 }
     82 
     83 func (suite *StatusStatusHomeTimelineableTestSuite) TestStatusNotTooNewTimelineable() {
     84 	testStatus := &gtsmodel.Status{}
     85 	*testStatus = *suite.testStatuses["local_account_1_status_1"]
     86 
     87 	testStatus.CreatedAt = time.Now().Add(23 * time.Hour)
     88 
     89 	testAccount := suite.testAccounts["local_account_1"]
     90 	ctx := context.Background()
     91 
     92 	timelineable, err := suite.filter.StatusHomeTimelineable(ctx, testAccount, testStatus)
     93 	suite.NoError(err)
     94 
     95 	suite.True(timelineable)
     96 }
     97 
     98 func (suite *StatusStatusHomeTimelineableTestSuite) TestThread() {
     99 	ctx := context.Background()
    100 
    101 	threadParentAccount := suite.testAccounts["local_account_1"]
    102 	timelineOwnerAccount := suite.testAccounts["local_account_2"]
    103 	originalStatus := suite.testStatuses["local_account_1_status_1"]
    104 
    105 	// this status should be hometimelineable for local_account_2
    106 	originalStatusTimelineable, err := suite.filter.StatusHomeTimelineable(ctx, timelineOwnerAccount, originalStatus)
    107 	suite.NoError(err)
    108 	suite.True(originalStatusTimelineable)
    109 
    110 	// now a reply from the original status author to their own status
    111 	firstReplyStatus := &gtsmodel.Status{
    112 		ID:                       "01G395ESAYPK9161QSQEZKATJN",
    113 		URI:                      "http://localhost:8080/users/the_mighty_zork/statuses/01G395ESAYPK9161QSQEZKATJN",
    114 		URL:                      "http://localhost:8080/@the_mighty_zork/statuses/01G395ESAYPK9161QSQEZKATJN",
    115 		Content:                  "nbnbdy expects dog",
    116 		CreatedAt:                testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
    117 		UpdatedAt:                testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
    118 		Local:                    testrig.FalseBool(),
    119 		AccountURI:               "http://localhost:8080/users/the_mighty_zork",
    120 		AccountID:                threadParentAccount.ID,
    121 		InReplyToID:              originalStatus.ID,
    122 		InReplyToAccountID:       threadParentAccount.ID,
    123 		InReplyToURI:             originalStatus.URI,
    124 		BoostOfID:                "",
    125 		ContentWarning:           "",
    126 		Visibility:               gtsmodel.VisibilityFollowersOnly,
    127 		Sensitive:                testrig.FalseBool(),
    128 		Language:                 "en",
    129 		CreatedWithApplicationID: "",
    130 		Federated:                testrig.TrueBool(),
    131 		Boostable:                testrig.TrueBool(),
    132 		Replyable:                testrig.TrueBool(),
    133 		Likeable:                 testrig.TrueBool(),
    134 		ActivityStreamsType:      ap.ObjectNote,
    135 	}
    136 	if err := suite.db.PutStatus(ctx, firstReplyStatus); err != nil {
    137 		suite.FailNow(err.Error())
    138 	}
    139 
    140 	// this status should also be hometimelineable for local_account_2
    141 	firstReplyStatusTimelineable, err := suite.filter.StatusHomeTimelineable(ctx, timelineOwnerAccount, firstReplyStatus)
    142 	suite.NoError(err)
    143 	suite.True(firstReplyStatusTimelineable)
    144 }
    145 
    146 func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly() {
    147 	ctx := context.Background()
    148 
    149 	// This scenario makes sure that we don't timeline a status which is a followers-only
    150 	// reply to a followers-only status TO A FOLLOWERS-ONLY STATUS owned by someone the
    151 	// timeline owner account doesn't follow.
    152 	//
    153 	// In other words, remote_account_1 posts a followers-only status, which local_account_1 replies to;
    154 	// THEN, local_account_1 replies to their own reply. None of these statuses should appear to
    155 	// local_account_2 since they don't follow the original parent.
    156 	//
    157 	// See: https://github.com/superseriousbusiness/gotosocial/issues/501
    158 
    159 	originalStatusParent := suite.testAccounts["remote_account_1"]
    160 	replyingAccount := suite.testAccounts["local_account_1"]
    161 	timelineOwnerAccount := suite.testAccounts["local_account_2"]
    162 
    163 	// put a followers-only status by remote_account_1 in the db
    164 	originalStatus := &gtsmodel.Status{
    165 		ID:                       "01G3957TS7XE2CMDKFG3MZPWAF",
    166 		URI:                      "http://fossbros-anonymous.io/users/foss_satan/statuses/01G3957TS7XE2CMDKFG3MZPWAF",
    167 		URL:                      "http://fossbros-anonymous.io/@foss_satan/statuses/01G3957TS7XE2CMDKFG3MZPWAF",
    168 		Content:                  "didn't expect dog",
    169 		CreatedAt:                testrig.TimeMustParse("2021-09-20T12:40:37+02:00"),
    170 		UpdatedAt:                testrig.TimeMustParse("2021-09-20T12:40:37+02:00"),
    171 		Local:                    testrig.FalseBool(),
    172 		AccountURI:               "http://fossbros-anonymous.io/users/foss_satan",
    173 		AccountID:                originalStatusParent.ID,
    174 		InReplyToID:              "",
    175 		InReplyToAccountID:       "",
    176 		InReplyToURI:             "",
    177 		BoostOfID:                "",
    178 		ContentWarning:           "",
    179 		Visibility:               gtsmodel.VisibilityFollowersOnly,
    180 		Sensitive:                testrig.FalseBool(),
    181 		Language:                 "en",
    182 		CreatedWithApplicationID: "",
    183 		Federated:                testrig.TrueBool(),
    184 		Boostable:                testrig.TrueBool(),
    185 		Replyable:                testrig.TrueBool(),
    186 		Likeable:                 testrig.TrueBool(),
    187 		ActivityStreamsType:      ap.ObjectNote,
    188 	}
    189 	if err := suite.db.PutStatus(ctx, originalStatus); err != nil {
    190 		suite.FailNow(err.Error())
    191 	}
    192 	// this status should not be hometimelineable for local_account_2
    193 	originalStatusTimelineable, err := suite.filter.StatusHomeTimelineable(ctx, timelineOwnerAccount, originalStatus)
    194 	suite.NoError(err)
    195 	suite.False(originalStatusTimelineable)
    196 
    197 	// now a followers-only reply from zork
    198 	firstReplyStatus := &gtsmodel.Status{
    199 		ID:                       "01G395ESAYPK9161QSQEZKATJN",
    200 		URI:                      "http://localhost:8080/users/the_mighty_zork/statuses/01G395ESAYPK9161QSQEZKATJN",
    201 		URL:                      "http://localhost:8080/@the_mighty_zork/statuses/01G395ESAYPK9161QSQEZKATJN",
    202 		Content:                  "nbnbdy expects dog",
    203 		CreatedAt:                testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
    204 		UpdatedAt:                testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
    205 		Local:                    testrig.FalseBool(),
    206 		AccountURI:               "http://localhost:8080/users/the_mighty_zork",
    207 		AccountID:                replyingAccount.ID,
    208 		InReplyToID:              originalStatus.ID,
    209 		InReplyToAccountID:       originalStatusParent.ID,
    210 		InReplyToURI:             originalStatus.URI,
    211 		BoostOfID:                "",
    212 		ContentWarning:           "",
    213 		Visibility:               gtsmodel.VisibilityFollowersOnly,
    214 		Sensitive:                testrig.FalseBool(),
    215 		Language:                 "en",
    216 		CreatedWithApplicationID: "",
    217 		Federated:                testrig.TrueBool(),
    218 		Boostable:                testrig.TrueBool(),
    219 		Replyable:                testrig.TrueBool(),
    220 		Likeable:                 testrig.TrueBool(),
    221 		ActivityStreamsType:      ap.ObjectNote,
    222 	}
    223 	if err := suite.db.PutStatus(ctx, firstReplyStatus); err != nil {
    224 		suite.FailNow(err.Error())
    225 	}
    226 	// this status should be hometimelineable for local_account_2
    227 	firstReplyStatusTimelineable, err := suite.filter.StatusHomeTimelineable(ctx, timelineOwnerAccount, firstReplyStatus)
    228 	suite.NoError(err)
    229 	suite.False(firstReplyStatusTimelineable)
    230 
    231 	// now a followers-only reply from zork to the status they just replied to
    232 	secondReplyStatus := &gtsmodel.Status{
    233 		ID:                       "01G395NZQZGJYRBAES57KYZ7XP",
    234 		URI:                      "http://localhost:8080/users/the_mighty_zork/statuses/01G395NZQZGJYRBAES57KYZ7XP",
    235 		URL:                      "http://localhost:8080/@the_mighty_zork/statuses/01G395NZQZGJYRBAES57KYZ7XP",
    236 		Content:                  "*nobody",
    237 		CreatedAt:                testrig.TimeMustParse("2021-09-20T12:42:37+02:00"),
    238 		UpdatedAt:                testrig.TimeMustParse("2021-09-20T12:42:37+02:00"),
    239 		Local:                    testrig.FalseBool(),
    240 		AccountURI:               "http://localhost:8080/users/the_mighty_zork",
    241 		AccountID:                replyingAccount.ID,
    242 		InReplyToID:              firstReplyStatus.ID,
    243 		InReplyToAccountID:       replyingAccount.ID,
    244 		InReplyToURI:             firstReplyStatus.URI,
    245 		BoostOfID:                "",
    246 		ContentWarning:           "",
    247 		Visibility:               gtsmodel.VisibilityFollowersOnly,
    248 		Sensitive:                testrig.FalseBool(),
    249 		Language:                 "en",
    250 		CreatedWithApplicationID: "",
    251 		Federated:                testrig.TrueBool(),
    252 		Boostable:                testrig.TrueBool(),
    253 		Replyable:                testrig.TrueBool(),
    254 		Likeable:                 testrig.TrueBool(),
    255 		ActivityStreamsType:      ap.ObjectNote,
    256 	}
    257 	if err := suite.db.PutStatus(ctx, secondReplyStatus); err != nil {
    258 		suite.FailNow(err.Error())
    259 	}
    260 
    261 	// this status should ALSO not be hometimelineable for local_account_2
    262 	secondReplyStatusTimelineable, err := suite.filter.StatusHomeTimelineable(ctx, timelineOwnerAccount, secondReplyStatus)
    263 	suite.NoError(err)
    264 	suite.False(secondReplyStatusTimelineable)
    265 }
    266 
    267 func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnlocked() {
    268 	ctx := context.Background()
    269 
    270 	// This scenario is exactly the same as the above test, but for a mix of unlocked + public posts
    271 
    272 	originalStatusParent := suite.testAccounts["remote_account_1"]
    273 	replyingAccount := suite.testAccounts["local_account_1"]
    274 	timelineOwnerAccount := suite.testAccounts["local_account_2"]
    275 
    276 	// put an unlocked status by remote_account_1 in the db
    277 	originalStatus := &gtsmodel.Status{
    278 		ID:                       "01G3957TS7XE2CMDKFG3MZPWAF",
    279 		URI:                      "http://fossbros-anonymous.io/users/foss_satan/statuses/01G3957TS7XE2CMDKFG3MZPWAF",
    280 		URL:                      "http://fossbros-anonymous.io/@foss_satan/statuses/01G3957TS7XE2CMDKFG3MZPWAF",
    281 		Content:                  "didn't expect dog",
    282 		CreatedAt:                testrig.TimeMustParse("2021-09-20T12:40:37+02:00"),
    283 		UpdatedAt:                testrig.TimeMustParse("2021-09-20T12:40:37+02:00"),
    284 		Local:                    testrig.FalseBool(),
    285 		AccountURI:               "http://fossbros-anonymous.io/users/foss_satan",
    286 		AccountID:                originalStatusParent.ID,
    287 		InReplyToID:              "",
    288 		InReplyToAccountID:       "",
    289 		InReplyToURI:             "",
    290 		BoostOfID:                "",
    291 		ContentWarning:           "",
    292 		Visibility:               gtsmodel.VisibilityUnlocked,
    293 		Sensitive:                testrig.FalseBool(),
    294 		Language:                 "en",
    295 		CreatedWithApplicationID: "",
    296 		Federated:                testrig.TrueBool(),
    297 		Boostable:                testrig.TrueBool(),
    298 		Replyable:                testrig.TrueBool(),
    299 		Likeable:                 testrig.TrueBool(),
    300 		ActivityStreamsType:      ap.ObjectNote,
    301 	}
    302 	if err := suite.db.PutStatus(ctx, originalStatus); err != nil {
    303 		suite.FailNow(err.Error())
    304 	}
    305 	// this status should not be hometimelineable for local_account_2
    306 	originalStatusTimelineable, err := suite.filter.StatusHomeTimelineable(ctx, timelineOwnerAccount, originalStatus)
    307 	suite.NoError(err)
    308 	suite.False(originalStatusTimelineable)
    309 
    310 	// now a public reply from zork
    311 	firstReplyStatus := &gtsmodel.Status{
    312 		ID:                       "01G395ESAYPK9161QSQEZKATJN",
    313 		URI:                      "http://localhost:8080/users/the_mighty_zork/statuses/01G395ESAYPK9161QSQEZKATJN",
    314 		URL:                      "http://localhost:8080/@the_mighty_zork/statuses/01G395ESAYPK9161QSQEZKATJN",
    315 		Content:                  "nbnbdy expects dog",
    316 		CreatedAt:                testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
    317 		UpdatedAt:                testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
    318 		Local:                    testrig.FalseBool(),
    319 		AccountURI:               "http://localhost:8080/users/the_mighty_zork",
    320 		AccountID:                replyingAccount.ID,
    321 		InReplyToID:              originalStatus.ID,
    322 		InReplyToAccountID:       originalStatusParent.ID,
    323 		InReplyToURI:             originalStatus.URI,
    324 		BoostOfID:                "",
    325 		ContentWarning:           "",
    326 		Visibility:               gtsmodel.VisibilityPublic,
    327 		Sensitive:                testrig.FalseBool(),
    328 		Language:                 "en",
    329 		CreatedWithApplicationID: "",
    330 		Federated:                testrig.TrueBool(),
    331 		Boostable:                testrig.TrueBool(),
    332 		Replyable:                testrig.TrueBool(),
    333 		Likeable:                 testrig.TrueBool(),
    334 		ActivityStreamsType:      ap.ObjectNote,
    335 	}
    336 	if err := suite.db.PutStatus(ctx, firstReplyStatus); err != nil {
    337 		suite.FailNow(err.Error())
    338 	}
    339 	// this status should not be hometimelineable for local_account_2
    340 	firstReplyStatusTimelineable, err := suite.filter.StatusHomeTimelineable(ctx, timelineOwnerAccount, firstReplyStatus)
    341 	suite.NoError(err)
    342 	suite.False(firstReplyStatusTimelineable)
    343 
    344 	// now an unlocked reply from zork to the status they just replied to
    345 	secondReplyStatus := &gtsmodel.Status{
    346 		ID:                       "01G395NZQZGJYRBAES57KYZ7XP",
    347 		URI:                      "http://localhost:8080/users/the_mighty_zork/statuses/01G395NZQZGJYRBAES57KYZ7XP",
    348 		URL:                      "http://localhost:8080/@the_mighty_zork/statuses/01G395NZQZGJYRBAES57KYZ7XP",
    349 		Content:                  "*nobody",
    350 		CreatedAt:                testrig.TimeMustParse("2021-09-20T12:42:37+02:00"),
    351 		UpdatedAt:                testrig.TimeMustParse("2021-09-20T12:42:37+02:00"),
    352 		Local:                    testrig.FalseBool(),
    353 		AccountURI:               "http://localhost:8080/users/the_mighty_zork",
    354 		AccountID:                replyingAccount.ID,
    355 		InReplyToID:              firstReplyStatus.ID,
    356 		InReplyToAccountID:       replyingAccount.ID,
    357 		InReplyToURI:             firstReplyStatus.URI,
    358 		BoostOfID:                "",
    359 		ContentWarning:           "",
    360 		Visibility:               gtsmodel.VisibilityUnlocked,
    361 		Sensitive:                testrig.FalseBool(),
    362 		Language:                 "en",
    363 		CreatedWithApplicationID: "",
    364 		Federated:                testrig.TrueBool(),
    365 		Boostable:                testrig.TrueBool(),
    366 		Replyable:                testrig.TrueBool(),
    367 		Likeable:                 testrig.TrueBool(),
    368 		ActivityStreamsType:      ap.ObjectNote,
    369 	}
    370 	if err := suite.db.PutStatus(ctx, secondReplyStatus); err != nil {
    371 		suite.FailNow(err.Error())
    372 	}
    373 
    374 	// this status should ALSO not be hometimelineable for local_account_2
    375 	secondReplyStatusTimelineable, err := suite.filter.StatusHomeTimelineable(ctx, timelineOwnerAccount, secondReplyStatus)
    376 	suite.NoError(err)
    377 	suite.False(secondReplyStatusTimelineable)
    378 }
    379 
    380 func TestStatusHomeTimelineableTestSuite(t *testing.T) {
    381 	suite.Run(t, new(StatusStatusHomeTimelineableTestSuite))
    382 }