summaryrefslogtreecommitdiff
path: root/internal/visibility/home_timeline_test.go
diff options
context:
space:
mode:
authorLibravatar kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com>2023-03-28 14:03:14 +0100
committerLibravatar GitHub <noreply@github.com>2023-03-28 14:03:14 +0100
commitde6e3e5f2a8ea639d76e310a11cb9bc093fef3a9 (patch)
treee2b7044e22c943425a4d351a02f862fbde783657 /internal/visibility/home_timeline_test.go
parent[feature] Add list command to admin account (#1648) (diff)
downloadgotosocial-de6e3e5f2a8ea639d76e310a11cb9bc093fef3a9.tar.xz
[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility * invalidate visibility cache items on account / status deletes * fix requester ID passed to visibility cache nil ptr * de-interface caches, fix home / public timeline caching + visibility * finish adding code comments for visibility filter * fix angry goconst linter warnings * actually finish adding filter visibility code comments for timeline functions * move home timeline status author check to after visibility * remove now-unused code * add more code comments * add TODO code comment, update printed cache start names * update printed cache names on stop * start adding separate follow(request) delete db functions, add specific visibility cache tests * add relationship type caching * fix getting local account follows / followed-bys, other small codebase improvements * simplify invalidation using cache hooks, add more GetAccountBy___() functions * fix boosting to return 404 if not boostable but no error (to not leak status ID) * remove dead code * improved placement of cache invalidation * update license headers * add example follow, follow-request config entries * add example visibility cache configuration to config file * use specific PutFollowRequest() instead of just Put() * add tests for all GetAccountBy() * add GetBlockBy() tests * update block to check primitive fields * update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests * fix copy-pasted code * update envparsing test * whitespace * fix bun struct tag * add license header to gtscontext * fix old license header * improved error creation to not use fmt.Errorf() when not needed * fix various rebase conflicts, fix account test * remove commented-out code, fix-up mention caching * fix mention select bun statement * ensure mention target account populated, pass in context to customrenderer logging * remove more uncommented code, fix typeutil test * add statusfave database model caching * add status fave cache configuration * add status fave cache example config * woops, catch missed error. nice catch linter! * add back testrig panic on nil db * update example configuration to match defaults, slight tweak to cache configuration defaults * update envparsing test with new defaults * fetch followingget to use the follow target account * use accounnt.IsLocal() instead of empty domain check * use constants for the cache visibility type check * use bun.In() for notification type restriction in db query * include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable()) * use bun query building for nested select statements to ensure working with postgres * update public timeline future status checks to match visibility filter * same as previous, for home timeline * update public timeline tests to dynamically check for appropriate statuses * migrate accounts to allow unique constraint on public_key * provide minimal account with publicKey --------- Signed-off-by: kim <grufwub@gmail.com> Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
Diffstat (limited to 'internal/visibility/home_timeline_test.go')
-rw-r--r--internal/visibility/home_timeline_test.go334
1 files changed, 334 insertions, 0 deletions
diff --git a/internal/visibility/home_timeline_test.go b/internal/visibility/home_timeline_test.go
new file mode 100644
index 000000000..b81f8fe4c
--- /dev/null
+++ b/internal/visibility/home_timeline_test.go
@@ -0,0 +1,334 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package visibility_test
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/suite"
+ "github.com/superseriousbusiness/gotosocial/internal/ap"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/testrig"
+)
+
+type StatusStatusHomeTimelineableTestSuite struct {
+ FilterStandardTestSuite
+}
+
+func (suite *StatusStatusHomeTimelineableTestSuite) TestOwnStatusHomeTimelineable() {
+ testStatus := suite.testStatuses["local_account_1_status_1"]
+ testAccount := suite.testAccounts["local_account_1"]
+ ctx := context.Background()
+
+ timelineable, err := suite.filter.StatusHomeTimelineable(ctx, testAccount, testStatus)
+ suite.NoError(err)
+
+ suite.True(timelineable)
+}
+
+func (suite *StatusStatusHomeTimelineableTestSuite) TestFollowingStatusHomeTimelineable() {
+ testStatus := suite.testStatuses["local_account_2_status_1"]
+ testAccount := suite.testAccounts["local_account_1"]
+ ctx := context.Background()
+
+ timelineable, err := suite.filter.StatusHomeTimelineable(ctx, testAccount, testStatus)
+ suite.NoError(err)
+
+ suite.True(timelineable)
+}
+
+func (suite *StatusStatusHomeTimelineableTestSuite) TestNotFollowingStatusHomeTimelineable() {
+ testStatus := suite.testStatuses["remote_account_1_status_1"]
+ testAccount := suite.testAccounts["local_account_1"]
+ ctx := context.Background()
+
+ timelineable, err := suite.filter.StatusHomeTimelineable(ctx, testAccount, testStatus)
+ suite.NoError(err)
+
+ suite.False(timelineable)
+}
+
+func (suite *StatusStatusHomeTimelineableTestSuite) TestStatusTooNewNotTimelineable() {
+ testStatus := &gtsmodel.Status{}
+ *testStatus = *suite.testStatuses["local_account_1_status_1"]
+
+ testStatus.CreatedAt = time.Now().Add(25 * time.Hour)
+
+ testAccount := suite.testAccounts["local_account_1"]
+ ctx := context.Background()
+
+ timelineable, err := suite.filter.StatusHomeTimelineable(ctx, testAccount, testStatus)
+ suite.NoError(err)
+
+ suite.False(timelineable)
+}
+
+func (suite *StatusStatusHomeTimelineableTestSuite) TestStatusNotTooNewTimelineable() {
+ testStatus := &gtsmodel.Status{}
+ *testStatus = *suite.testStatuses["local_account_1_status_1"]
+
+ testStatus.CreatedAt = time.Now().Add(23 * time.Hour)
+
+ testAccount := suite.testAccounts["local_account_1"]
+ ctx := context.Background()
+
+ timelineable, err := suite.filter.StatusHomeTimelineable(ctx, testAccount, testStatus)
+ suite.NoError(err)
+
+ suite.True(timelineable)
+}
+
+func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly() {
+ ctx := context.Background()
+
+ // This scenario makes sure that we don't timeline a status which is a followers-only
+ // reply to a followers-only status TO A FOLLOWERS-ONLY STATUS owned by someone the
+ // timeline owner account doesn't follow.
+ //
+ // In other words, remote_account_1 posts a followers-only status, which local_account_1 replies to;
+ // THEN, local_account_1 replies to their own reply. None of these statuses should appear to
+ // local_account_2 since they don't follow the original parent.
+ //
+ // See: https://github.com/superseriousbusiness/gotosocial/issues/501
+
+ originalStatusParent := suite.testAccounts["remote_account_1"]
+ replyingAccount := suite.testAccounts["local_account_1"]
+ timelineOwnerAccount := suite.testAccounts["local_account_2"]
+
+ // put a followers-only status by remote_account_1 in the db
+ originalStatus := &gtsmodel.Status{
+ ID: "01G3957TS7XE2CMDKFG3MZPWAF",
+ URI: "http://fossbros-anonymous.io/users/foss_satan/statuses/01G3957TS7XE2CMDKFG3MZPWAF",
+ URL: "http://fossbros-anonymous.io/@foss_satan/statuses/01G3957TS7XE2CMDKFG3MZPWAF",
+ Content: "didn't expect dog",
+ CreatedAt: testrig.TimeMustParse("2021-09-20T12:40:37+02:00"),
+ UpdatedAt: testrig.TimeMustParse("2021-09-20T12:40:37+02:00"),
+ Local: testrig.FalseBool(),
+ AccountURI: "http://fossbros-anonymous.io/users/foss_satan",
+ AccountID: originalStatusParent.ID,
+ InReplyToID: "",
+ InReplyToAccountID: "",
+ InReplyToURI: "",
+ BoostOfID: "",
+ ContentWarning: "",
+ Visibility: gtsmodel.VisibilityFollowersOnly,
+ Sensitive: testrig.FalseBool(),
+ Language: "en",
+ CreatedWithApplicationID: "",
+ Federated: testrig.TrueBool(),
+ Boostable: testrig.TrueBool(),
+ Replyable: testrig.TrueBool(),
+ Likeable: testrig.TrueBool(),
+ ActivityStreamsType: ap.ObjectNote,
+ }
+ if err := suite.db.PutStatus(ctx, originalStatus); err != nil {
+ suite.FailNow(err.Error())
+ }
+ // this status should not be hometimelineable for local_account_2
+ originalStatusTimelineable, err := suite.filter.StatusHomeTimelineable(ctx, timelineOwnerAccount, originalStatus)
+ suite.NoError(err)
+ suite.False(originalStatusTimelineable)
+
+ // now a followers-only reply from zork
+ firstReplyStatus := &gtsmodel.Status{
+ ID: "01G395ESAYPK9161QSQEZKATJN",
+ URI: "http://localhost:8080/users/the_mighty_zork/statuses/01G395ESAYPK9161QSQEZKATJN",
+ URL: "http://localhost:8080/@the_mighty_zork/statuses/01G395ESAYPK9161QSQEZKATJN",
+ Content: "nbnbdy expects dog",
+ CreatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
+ UpdatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
+ Local: testrig.FalseBool(),
+ AccountURI: "http://localhost:8080/users/the_mighty_zork",
+ AccountID: replyingAccount.ID,
+ InReplyToID: originalStatus.ID,
+ InReplyToAccountID: originalStatusParent.ID,
+ InReplyToURI: originalStatus.URI,
+ BoostOfID: "",
+ ContentWarning: "",
+ Visibility: gtsmodel.VisibilityFollowersOnly,
+ Sensitive: testrig.FalseBool(),
+ Language: "en",
+ CreatedWithApplicationID: "",
+ Federated: testrig.TrueBool(),
+ Boostable: testrig.TrueBool(),
+ Replyable: testrig.TrueBool(),
+ Likeable: testrig.TrueBool(),
+ ActivityStreamsType: ap.ObjectNote,
+ }
+ if err := suite.db.PutStatus(ctx, firstReplyStatus); err != nil {
+ suite.FailNow(err.Error())
+ }
+ // this status should be hometimelineable for local_account_2
+ firstReplyStatusTimelineable, err := suite.filter.StatusHomeTimelineable(ctx, timelineOwnerAccount, firstReplyStatus)
+ suite.NoError(err)
+ suite.False(firstReplyStatusTimelineable)
+
+ // now a followers-only reply from zork to the status they just replied to
+ secondReplyStatus := &gtsmodel.Status{
+ ID: "01G395NZQZGJYRBAES57KYZ7XP",
+ URI: "http://localhost:8080/users/the_mighty_zork/statuses/01G395NZQZGJYRBAES57KYZ7XP",
+ URL: "http://localhost:8080/@the_mighty_zork/statuses/01G395NZQZGJYRBAES57KYZ7XP",
+ Content: "*nobody",
+ CreatedAt: testrig.TimeMustParse("2021-09-20T12:42:37+02:00"),
+ UpdatedAt: testrig.TimeMustParse("2021-09-20T12:42:37+02:00"),
+ Local: testrig.FalseBool(),
+ AccountURI: "http://localhost:8080/users/the_mighty_zork",
+ AccountID: replyingAccount.ID,
+ InReplyToID: firstReplyStatus.ID,
+ InReplyToAccountID: replyingAccount.ID,
+ InReplyToURI: firstReplyStatus.URI,
+ BoostOfID: "",
+ ContentWarning: "",
+ Visibility: gtsmodel.VisibilityFollowersOnly,
+ Sensitive: testrig.FalseBool(),
+ Language: "en",
+ CreatedWithApplicationID: "",
+ Federated: testrig.TrueBool(),
+ Boostable: testrig.TrueBool(),
+ Replyable: testrig.TrueBool(),
+ Likeable: testrig.TrueBool(),
+ ActivityStreamsType: ap.ObjectNote,
+ }
+ if err := suite.db.PutStatus(ctx, secondReplyStatus); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ // this status should ALSO not be hometimelineable for local_account_2
+ secondReplyStatusTimelineable, err := suite.filter.StatusHomeTimelineable(ctx, timelineOwnerAccount, secondReplyStatus)
+ suite.NoError(err)
+ suite.False(secondReplyStatusTimelineable)
+}
+
+func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnlocked() {
+ ctx := context.Background()
+
+ // This scenario is exactly the same as the above test, but for a mix of unlocked + public posts
+
+ originalStatusParent := suite.testAccounts["remote_account_1"]
+ replyingAccount := suite.testAccounts["local_account_1"]
+ timelineOwnerAccount := suite.testAccounts["local_account_2"]
+
+ // put an unlocked status by remote_account_1 in the db
+ originalStatus := &gtsmodel.Status{
+ ID: "01G3957TS7XE2CMDKFG3MZPWAF",
+ URI: "http://fossbros-anonymous.io/users/foss_satan/statuses/01G3957TS7XE2CMDKFG3MZPWAF",
+ URL: "http://fossbros-anonymous.io/@foss_satan/statuses/01G3957TS7XE2CMDKFG3MZPWAF",
+ Content: "didn't expect dog",
+ CreatedAt: testrig.TimeMustParse("2021-09-20T12:40:37+02:00"),
+ UpdatedAt: testrig.TimeMustParse("2021-09-20T12:40:37+02:00"),
+ Local: testrig.FalseBool(),
+ AccountURI: "http://fossbros-anonymous.io/users/foss_satan",
+ AccountID: originalStatusParent.ID,
+ InReplyToID: "",
+ InReplyToAccountID: "",
+ InReplyToURI: "",
+ BoostOfID: "",
+ ContentWarning: "",
+ Visibility: gtsmodel.VisibilityUnlocked,
+ Sensitive: testrig.FalseBool(),
+ Language: "en",
+ CreatedWithApplicationID: "",
+ Federated: testrig.TrueBool(),
+ Boostable: testrig.TrueBool(),
+ Replyable: testrig.TrueBool(),
+ Likeable: testrig.TrueBool(),
+ ActivityStreamsType: ap.ObjectNote,
+ }
+ if err := suite.db.PutStatus(ctx, originalStatus); err != nil {
+ suite.FailNow(err.Error())
+ }
+ // this status should not be hometimelineable for local_account_2
+ originalStatusTimelineable, err := suite.filter.StatusHomeTimelineable(ctx, timelineOwnerAccount, originalStatus)
+ suite.NoError(err)
+ suite.False(originalStatusTimelineable)
+
+ // now a public reply from zork
+ firstReplyStatus := &gtsmodel.Status{
+ ID: "01G395ESAYPK9161QSQEZKATJN",
+ URI: "http://localhost:8080/users/the_mighty_zork/statuses/01G395ESAYPK9161QSQEZKATJN",
+ URL: "http://localhost:8080/@the_mighty_zork/statuses/01G395ESAYPK9161QSQEZKATJN",
+ Content: "nbnbdy expects dog",
+ CreatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
+ UpdatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
+ Local: testrig.FalseBool(),
+ AccountURI: "http://localhost:8080/users/the_mighty_zork",
+ AccountID: replyingAccount.ID,
+ InReplyToID: originalStatus.ID,
+ InReplyToAccountID: originalStatusParent.ID,
+ InReplyToURI: originalStatus.URI,
+ BoostOfID: "",
+ ContentWarning: "",
+ Visibility: gtsmodel.VisibilityPublic,
+ Sensitive: testrig.FalseBool(),
+ Language: "en",
+ CreatedWithApplicationID: "",
+ Federated: testrig.TrueBool(),
+ Boostable: testrig.TrueBool(),
+ Replyable: testrig.TrueBool(),
+ Likeable: testrig.TrueBool(),
+ ActivityStreamsType: ap.ObjectNote,
+ }
+ if err := suite.db.PutStatus(ctx, firstReplyStatus); err != nil {
+ suite.FailNow(err.Error())
+ }
+ // this status should not be hometimelineable for local_account_2
+ firstReplyStatusTimelineable, err := suite.filter.StatusHomeTimelineable(ctx, timelineOwnerAccount, firstReplyStatus)
+ suite.NoError(err)
+ suite.False(firstReplyStatusTimelineable)
+
+ // now an unlocked reply from zork to the status they just replied to
+ secondReplyStatus := &gtsmodel.Status{
+ ID: "01G395NZQZGJYRBAES57KYZ7XP",
+ URI: "http://localhost:8080/users/the_mighty_zork/statuses/01G395NZQZGJYRBAES57KYZ7XP",
+ URL: "http://localhost:8080/@the_mighty_zork/statuses/01G395NZQZGJYRBAES57KYZ7XP",
+ Content: "*nobody",
+ CreatedAt: testrig.TimeMustParse("2021-09-20T12:42:37+02:00"),
+ UpdatedAt: testrig.TimeMustParse("2021-09-20T12:42:37+02:00"),
+ Local: testrig.FalseBool(),
+ AccountURI: "http://localhost:8080/users/the_mighty_zork",
+ AccountID: replyingAccount.ID,
+ InReplyToID: firstReplyStatus.ID,
+ InReplyToAccountID: replyingAccount.ID,
+ InReplyToURI: firstReplyStatus.URI,
+ BoostOfID: "",
+ ContentWarning: "",
+ Visibility: gtsmodel.VisibilityUnlocked,
+ Sensitive: testrig.FalseBool(),
+ Language: "en",
+ CreatedWithApplicationID: "",
+ Federated: testrig.TrueBool(),
+ Boostable: testrig.TrueBool(),
+ Replyable: testrig.TrueBool(),
+ Likeable: testrig.TrueBool(),
+ ActivityStreamsType: ap.ObjectNote,
+ }
+ if err := suite.db.PutStatus(ctx, secondReplyStatus); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ // this status should ALSO not be hometimelineable for local_account_2
+ secondReplyStatusTimelineable, err := suite.filter.StatusHomeTimelineable(ctx, timelineOwnerAccount, secondReplyStatus)
+ suite.NoError(err)
+ suite.False(secondReplyStatusTimelineable)
+}
+
+func TestStatusHomeTimelineableTestSuite(t *testing.T) {
+ suite.Run(t, new(StatusStatusHomeTimelineableTestSuite))
+}