summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/processing/timeline/home_test.go154
-rw-r--r--internal/processing/timeline/public_test.go91
-rw-r--r--internal/processing/timeline/timeline_test.go2
-rw-r--r--internal/timeline/get.go13
4 files changed, 260 insertions, 0 deletions
diff --git a/internal/processing/timeline/home_test.go b/internal/processing/timeline/home_test.go
new file mode 100644
index 000000000..c73c209a3
--- /dev/null
+++ b/internal/processing/timeline/home_test.go
@@ -0,0 +1,154 @@
+// 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 timeline_test
+
+import (
+ "context"
+ "testing"
+
+ "github.com/stretchr/testify/suite"
+ apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
+ "github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/id"
+ "github.com/superseriousbusiness/gotosocial/internal/oauth"
+ tlprocessor "github.com/superseriousbusiness/gotosocial/internal/processing/timeline"
+ "github.com/superseriousbusiness/gotosocial/internal/timeline"
+ "github.com/superseriousbusiness/gotosocial/internal/typeutils"
+ "github.com/superseriousbusiness/gotosocial/internal/util"
+)
+
+type HomeTestSuite struct {
+ TimelineStandardTestSuite
+}
+
+func (suite *HomeTestSuite) SetupTest() {
+ suite.TimelineStandardTestSuite.SetupTest()
+
+ suite.state.Timelines.Home = timeline.NewManager(
+ tlprocessor.HomeTimelineGrab(&suite.state),
+ tlprocessor.HomeTimelineFilter(&suite.state, visibility.NewFilter(&suite.state)),
+ tlprocessor.HomeTimelineStatusPrepare(&suite.state, typeutils.NewConverter(&suite.state)),
+ tlprocessor.SkipInsert(),
+ )
+ if err := suite.state.Timelines.Home.Start(); err != nil {
+ suite.FailNow(err.Error())
+ }
+}
+
+func (suite *HomeTestSuite) TearDownTest() {
+ if err := suite.state.Timelines.Home.Stop(); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ suite.TimelineStandardTestSuite.TearDownTest()
+}
+
+// A timeline containing a status hidden due to filtering should return other statuses with no error.
+func (suite *HomeTestSuite) TestHomeTimelineGetHideFiltered() {
+ var (
+ ctx = context.Background()
+ requester = suite.testAccounts["local_account_1"]
+ authed = &oauth.Auth{Account: requester}
+ maxID = ""
+ sinceID = ""
+ minID = "01F8MHAAY43M6RJ473VQFCVH36" // 1 before filteredStatus
+ limit = 40
+ local = false
+ filteredStatus = suite.testStatuses["admin_account_status_2"]
+ filteredStatusFound = false
+ filterID = id.NewULID()
+ filter = &gtsmodel.Filter{
+ ID: filterID,
+ AccountID: requester.ID,
+ Title: "timeline filtering test",
+ Action: gtsmodel.FilterActionHide,
+ Statuses: []*gtsmodel.FilterStatus{
+ {
+ ID: id.NewULID(),
+ AccountID: requester.ID,
+ FilterID: filterID,
+ StatusID: filteredStatus.ID,
+ },
+ },
+ ContextHome: util.Ptr(true),
+ ContextNotifications: util.Ptr(false),
+ ContextPublic: util.Ptr(false),
+ ContextThread: util.Ptr(false),
+ ContextAccount: util.Ptr(false),
+ }
+ )
+
+ // Fetch the timeline to make sure the status we're going to filter is in that section of it.
+ resp, errWithCode := suite.timeline.HomeTimelineGet(
+ ctx,
+ authed,
+ maxID,
+ sinceID,
+ minID,
+ limit,
+ local,
+ )
+ suite.NoError(errWithCode)
+ for _, item := range resp.Items {
+ if item.(*apimodel.Status).ID == filteredStatus.ID {
+ filteredStatusFound = true
+ break
+ }
+ }
+ if !filteredStatusFound {
+ suite.FailNow("precondition failed: status we would filter isn't present in unfiltered timeline")
+ }
+ // Prune the timeline to drop cached prepared statuses, a side effect of this precondition check.
+ if _, err := suite.state.Timelines.Home.Prune(ctx, requester.ID, 0, 0); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ // Create a filter to hide one status on the timeline.
+ if err := suite.db.PutFilter(ctx, filter); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ // Fetch the timeline again with the filter in place.
+ resp, errWithCode = suite.timeline.HomeTimelineGet(
+ ctx,
+ authed,
+ maxID,
+ sinceID,
+ minID,
+ limit,
+ local,
+ )
+
+ // We should have some statuses even though one status was filtered out.
+ suite.NoError(errWithCode)
+ suite.NotEmpty(resp.Items)
+ // The filtered status should not be there.
+ filteredStatusFound = false
+ for _, item := range resp.Items {
+ if item.(*apimodel.Status).ID == filteredStatus.ID {
+ filteredStatusFound = true
+ break
+ }
+ }
+ suite.False(filteredStatusFound)
+}
+
+func TestHomeTestSuite(t *testing.T) {
+ suite.Run(t, new(HomeTestSuite))
+}
diff --git a/internal/processing/timeline/public_test.go b/internal/processing/timeline/public_test.go
index f14fee1b9..6b01c9849 100644
--- a/internal/processing/timeline/public_test.go
+++ b/internal/processing/timeline/public_test.go
@@ -22,6 +22,10 @@ import (
"testing"
"github.com/stretchr/testify/suite"
+ apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/id"
+ "github.com/superseriousbusiness/gotosocial/internal/util"
)
type PublicTestSuite struct {
@@ -91,6 +95,93 @@ func (suite *PublicTestSuite) TestPublicTimelineGetNotEmpty() {
suite.Equal(`http://localhost:8080/api/v1/timelines/public?limit=1&min_id=01HE7XJ1CG84TBKH5V9XKBVGF5&local=false`, resp.PrevLink)
}
+// A timeline containing a status hidden due to filtering should return other statuses with no error.
+func (suite *PublicTestSuite) TestPublicTimelineGetHideFiltered() {
+ var (
+ ctx = context.Background()
+ requester = suite.testAccounts["local_account_1"]
+ maxID = ""
+ sinceID = ""
+ minID = "01F8MHAAY43M6RJ473VQFCVH36" // 1 before filteredStatus
+ limit = 10
+ local = false
+ filteredStatus = suite.testStatuses["admin_account_status_2"]
+ filteredStatusFound = false
+ filterID = id.NewULID()
+ filter = &gtsmodel.Filter{
+ ID: filterID,
+ AccountID: requester.ID,
+ Title: "timeline filtering test",
+ Action: gtsmodel.FilterActionHide,
+ Statuses: []*gtsmodel.FilterStatus{
+ {
+ ID: id.NewULID(),
+ AccountID: requester.ID,
+ FilterID: filterID,
+ StatusID: filteredStatus.ID,
+ },
+ },
+ ContextHome: util.Ptr(false),
+ ContextNotifications: util.Ptr(false),
+ ContextPublic: util.Ptr(true),
+ ContextThread: util.Ptr(false),
+ ContextAccount: util.Ptr(false),
+ }
+ )
+
+ // Fetch the timeline to make sure the status we're going to filter is in that section of it.
+ resp, errWithCode := suite.timeline.PublicTimelineGet(
+ ctx,
+ requester,
+ maxID,
+ sinceID,
+ minID,
+ limit,
+ local,
+ )
+ suite.NoError(errWithCode)
+ for _, item := range resp.Items {
+ if item.(*apimodel.Status).ID == filteredStatus.ID {
+ filteredStatusFound = true
+ break
+ }
+ }
+ if !filteredStatusFound {
+ suite.FailNow("precondition failed: status we would filter isn't present in unfiltered timeline")
+ }
+ // The public timeline has no prepared status cache and doesn't need to be pruned,
+ // as in the home timeline version of this test.
+
+ // Create a filter to hide one status on the timeline.
+ if err := suite.db.PutFilter(ctx, filter); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ // Fetch the timeline again with the filter in place.
+ resp, errWithCode = suite.timeline.PublicTimelineGet(
+ ctx,
+ requester,
+ maxID,
+ sinceID,
+ minID,
+ limit,
+ local,
+ )
+
+ // We should have some statuses even though one status was filtered out.
+ suite.NoError(errWithCode)
+ suite.NotEmpty(resp.Items)
+ // The filtered status should not be there.
+ filteredStatusFound = false
+ for _, item := range resp.Items {
+ if item.(*apimodel.Status).ID == filteredStatus.ID {
+ filteredStatusFound = true
+ break
+ }
+ }
+ suite.False(filteredStatusFound)
+}
+
func TestPublicTestSuite(t *testing.T) {
suite.Run(t, new(PublicTestSuite))
}
diff --git a/internal/processing/timeline/timeline_test.go b/internal/processing/timeline/timeline_test.go
index 79626830e..593bfb8f3 100644
--- a/internal/processing/timeline/timeline_test.go
+++ b/internal/processing/timeline/timeline_test.go
@@ -35,6 +35,7 @@ type TimelineStandardTestSuite struct {
// standard suite models
testAccounts map[string]*gtsmodel.Account
+ testStatuses map[string]*gtsmodel.Status
// module being tested
timeline timeline.Processor
@@ -42,6 +43,7 @@ type TimelineStandardTestSuite struct {
func (suite *TimelineStandardTestSuite) SetupSuite() {
suite.testAccounts = testrig.NewTestAccounts()
+ suite.testStatuses = testrig.NewTestStatuses()
}
func (suite *TimelineStandardTestSuite) SetupTest() {
diff --git a/internal/timeline/get.go b/internal/timeline/get.go
index 93c869e73..06ee8c174 100644
--- a/internal/timeline/get.go
+++ b/internal/timeline/get.go
@@ -25,6 +25,7 @@ import (
"codeberg.org/gruf/go-kv"
"github.com/superseriousbusiness/gotosocial/internal/db"
+ statusfilter "github.com/superseriousbusiness/gotosocial/internal/filter/status"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/log"
@@ -246,6 +247,12 @@ func (t *timeline) getXBetweenIDs(ctx context.Context, amount int, behindID stri
// race condition? That's OK, we can do it now.
prepared, err := t.prepareFunction(ctx, t.timelineID, entry.itemID)
if err != nil {
+ if errors.Is(err, statusfilter.ErrHideStatus) {
+ // This item has been filtered out by the requesting user's filters.
+ // Remove it and skip past it.
+ removeElements = append(removeElements, e)
+ return true, nil
+ }
if errors.Is(err, db.ErrNoEntries) {
// ErrNoEntries means something has been deleted,
// so we'll likely not be able to ever prepare this.
@@ -340,6 +347,12 @@ func (t *timeline) getXBetweenIDs(ctx context.Context, amount int, behindID stri
// race condition? That's OK, we can do it now.
prepared, err := t.prepareFunction(ctx, t.timelineID, entry.itemID)
if err != nil {
+ if errors.Is(err, statusfilter.ErrHideStatus) {
+ // This item has been filtered out by the requesting user's filters.
+ // Remove it and skip past it.
+ removeElements = append(removeElements, e)
+ continue
+ }
if errors.Is(err, db.ErrNoEntries) {
// ErrNoEntries means something has been deleted,
// so we'll likely not be able to ever prepare this.