summaryrefslogtreecommitdiff
path: root/internal/filter
diff options
context:
space:
mode:
authorLibravatar kim <grufwub@gmail.com>2025-07-04 15:30:39 +0200
committerLibravatar kim <gruf@noreply.codeberg.org>2025-07-04 15:30:39 +0200
commit66e1ec14aa07e115580afc8e1399677f3b54eeda (patch)
tree54aabccf5c0540abbfdea771745f354e05c8eb1a /internal/filter
parent[bugfix] set correct scope for StatusFavePOSTHandler (#4310) (diff)
downloadgotosocial-66e1ec14aa07e115580afc8e1399677f3b54eeda.tar.xz
[chore] move status filtering from type converter (#4306)
This finalizes the moving status filtering out of the type converter, and into its own `./internal/filter/` subpkg :) Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4306 Co-authored-by: kim <grufwub@gmail.com> Co-committed-by: kim <grufwub@gmail.com>
Diffstat (limited to 'internal/filter')
-rw-r--r--internal/filter/status/status.go31
-rw-r--r--internal/filter/status/status_test.go201
2 files changed, 227 insertions, 5 deletions
diff --git a/internal/filter/status/status.go b/internal/filter/status/status.go
index 5f997129d..e38131ae3 100644
--- a/internal/filter/status/status.go
+++ b/internal/filter/status/status.go
@@ -25,6 +25,7 @@ import (
apimodel "code.superseriousbusiness.org/gotosocial/internal/api/model"
"code.superseriousbusiness.org/gotosocial/internal/cache"
+ "code.superseriousbusiness.org/gotosocial/internal/gtscontext"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
)
@@ -159,6 +160,31 @@ func (f *Filter) getStatusFilterResults(
return results, nil
}
+ // Check if status is boost.
+ if status.BoostOfID != "" {
+ if status.BoostOf == nil {
+ var err error
+
+ // Ensure original status is loaded on boost.
+ status.BoostOf, err = f.state.DB.GetStatusByID(
+ gtscontext.SetBarebones(ctx),
+ status.BoostOfID,
+ )
+ if err != nil {
+ return results, gtserror.Newf("error getting boosted status of %s: %w", status.URI, err)
+ }
+ }
+
+ // From here look at details
+ // for original boosted status.
+ status = status.BoostOf
+ }
+
+ // For proper status filtering we need all fields populated.
+ if err := f.state.DB.PopulateStatus(ctx, status); err != nil {
+ return results, gtserror.Newf("error populating status: %w", err)
+ }
+
// Get the string fields status is
// filterable on for keyword matching.
fields := getFilterableFields(status)
@@ -169,11 +195,6 @@ func (f *Filter) getStatusFilterResults(
return results, gtserror.Newf("error getting account filters: %w", err)
}
- // For proper status filtering we need all fields populated.
- if err := f.state.DB.PopulateStatus(ctx, status); err != nil {
- return results, gtserror.Newf("error populating status: %w", err)
- }
-
// Generate result for each filter.
for _, filter := range filters {
diff --git a/internal/filter/status/status_test.go b/internal/filter/status/status_test.go
new file mode 100644
index 000000000..e81b7d34e
--- /dev/null
+++ b/internal/filter/status/status_test.go
@@ -0,0 +1,201 @@
+// 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 status_test
+
+import (
+ "testing"
+
+ apimodel "code.superseriousbusiness.org/gotosocial/internal/api/model"
+ "code.superseriousbusiness.org/gotosocial/internal/filter/status"
+ "code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
+ "code.superseriousbusiness.org/gotosocial/internal/id"
+ "code.superseriousbusiness.org/gotosocial/internal/state"
+ "code.superseriousbusiness.org/gotosocial/internal/typeutils"
+ "code.superseriousbusiness.org/gotosocial/testrig"
+ "github.com/stretchr/testify/suite"
+)
+
+type StatusFilterTestSuite struct {
+ suite.Suite
+
+ state state.State
+ filter *status.Filter
+ converter *typeutils.Converter
+
+ testAccounts map[string]*gtsmodel.Account
+ testFilters map[string]*gtsmodel.Filter
+ testStatuses map[string]*gtsmodel.Status
+}
+
+func (suite *StatusFilterTestSuite) SetupTest() {
+ suite.state.Caches.Init()
+
+ testrig.InitTestConfig()
+ testrig.InitTestLog()
+
+ db := testrig.NewTestDB(&suite.state)
+ suite.state.DB = db
+
+ suite.filter = status.NewFilter(&suite.state)
+
+ suite.converter = typeutils.NewConverter(&suite.state)
+
+ suite.testAccounts = testrig.NewTestAccounts()
+ suite.testFilters = testrig.NewTestFilters()
+ suite.testStatuses = testrig.NewTestStatuses()
+
+ testrig.StandardDBSetup(suite.state.DB, nil)
+}
+
+func (suite *StatusFilterTestSuite) TearDownTest() {
+ testrig.StandardDBTeardown(suite.state.DB)
+ testrig.StopWorkers(&suite.state)
+}
+
+func (suite *StatusFilterTestSuite) TestHideFilteredStatus() {
+ filtered, hide, err := suite.testFilterStatus(gtsmodel.FilterActionHide, false)
+ suite.NoError(err)
+ suite.True(hide)
+ suite.Empty(filtered)
+}
+
+func (suite *StatusFilterTestSuite) TestWarnFilteredStatus() {
+ filtered, hide, err := suite.testFilterStatus(gtsmodel.FilterActionWarn, false)
+ suite.NoError(err)
+ suite.False(hide)
+ suite.NotEmpty(filtered)
+}
+
+func (suite *StatusFilterTestSuite) TestHideFilteredBoost() {
+ filtered, hide, err := suite.testFilterStatus(gtsmodel.FilterActionHide, true)
+ suite.NoError(err)
+ suite.True(hide)
+ suite.Empty(filtered)
+}
+
+func (suite *StatusFilterTestSuite) TestWarnFilteredBoost() {
+ filtered, hide, err := suite.testFilterStatus(gtsmodel.FilterActionWarn, true)
+ suite.NoError(err)
+ suite.False(hide)
+ suite.NotEmpty(filtered)
+}
+
+func (suite *StatusFilterTestSuite) TestHashtagWholewordStatusFiltered() {
+ suite.testFilteredStatusWithHashtag(true, false)
+}
+
+func (suite *StatusFilterTestSuite) TestHashtagWholewordBoostFiltered() {
+ suite.testFilteredStatusWithHashtag(true, true)
+}
+
+func (suite *StatusFilterTestSuite) TestHashtagAnywhereStatusFiltered() {
+ suite.testFilteredStatusWithHashtag(false, false)
+}
+
+func (suite *StatusFilterTestSuite) TestHashtagAnywhereBoostFiltered() {
+ suite.testFilteredStatusWithHashtag(false, true)
+}
+
+func (suite *StatusFilterTestSuite) testFilterStatus(action gtsmodel.FilterAction, boost bool) ([]apimodel.FilterResult, bool, error) {
+ ctx := suite.T().Context()
+
+ status := suite.testStatuses["admin_account_status_1"]
+ status.Content += " fnord"
+ status.Text += " fnord"
+
+ if boost {
+ // Modify a fixture boost into a boost of the above status.
+ boost := suite.testStatuses["admin_account_status_4"]
+ boost.BoostOf = status
+ boost.BoostOfID = status.ID
+ status = boost
+ }
+
+ requester := suite.testAccounts["local_account_1"]
+
+ filter := suite.testFilters["local_account_1_filter_1"]
+ filter.Action = action
+
+ err := suite.state.DB.UpdateFilter(ctx, filter, "action")
+ suite.NoError(err)
+
+ return suite.filter.StatusFilterResultsInContext(ctx,
+ requester,
+ status,
+ gtsmodel.FilterContextHome,
+ )
+}
+
+func (suite *StatusFilterTestSuite) testFilteredStatusWithHashtag(wholeword, boost bool) {
+ ctx := suite.T().Context()
+
+ status := new(gtsmodel.Status)
+ *status = *suite.testStatuses["admin_account_status_1"]
+ status.Content = `<p>doggo doggin' it</p><p><a href="https://example.test/tags/dogsofmastodon" class="mention hashtag" rel="tag nofollow noreferrer noopener" target="_blank">#<span>dogsofmastodon</span></a></p>`
+
+ if boost {
+ boost, err := suite.converter.StatusToBoost(
+ suite.T().Context(),
+ status,
+ suite.testAccounts["admin_account"],
+ "",
+ )
+ suite.NoError(err)
+ status = boost
+ }
+
+ var err error
+
+ requester := suite.testAccounts["local_account_1"]
+
+ filter := &gtsmodel.Filter{
+ ID: id.NewULID(),
+ Title: id.NewULID(),
+ AccountID: requester.ID,
+ Action: gtsmodel.FilterActionWarn,
+ Contexts: gtsmodel.FilterContexts(gtsmodel.FilterContextHome),
+ }
+
+ filterKeyword := &gtsmodel.FilterKeyword{
+ ID: id.NewULID(),
+ FilterID: filter.ID,
+ Keyword: "#dogsofmastodon",
+ WholeWord: &wholeword,
+ }
+
+ filter.KeywordIDs = []string{filterKeyword.ID}
+
+ err = suite.state.DB.PutFilterKeyword(ctx, filterKeyword)
+ suite.NoError(err)
+
+ err = suite.state.DB.PutFilter(ctx, filter)
+ suite.NoError(err)
+
+ filtered, hide, err := suite.filter.StatusFilterResultsInContext(ctx,
+ requester,
+ status,
+ gtsmodel.FilterContextHome,
+ )
+ suite.NoError(err)
+ suite.False(hide)
+ suite.NotEmpty(filtered)
+}
+
+func TestStatusFilterTestSuite(t *testing.T) {
+ suite.Run(t, new(StatusFilterTestSuite))
+}