diff options
Diffstat (limited to 'internal/processing/account')
-rw-r--r-- | internal/processing/account/account_test.go | 4 | ||||
-rw-r--r-- | internal/processing/account/follow.go | 92 | ||||
-rw-r--r-- | internal/processing/account/follow_test.go | 140 |
3 files changed, 224 insertions, 12 deletions
diff --git a/internal/processing/account/account_test.go b/internal/processing/account/account_test.go index eed6ad7e3..5d48d1210 100644 --- a/internal/processing/account/account_test.go +++ b/internal/processing/account/account_test.go @@ -59,6 +59,7 @@ type AccountStandardTestSuite struct { testApplications map[string]*gtsmodel.Application testUsers map[string]*gtsmodel.User testAccounts map[string]*gtsmodel.Account + testFollows map[string]*gtsmodel.Follow testAttachments map[string]*gtsmodel.MediaAttachment testStatuses map[string]*gtsmodel.Status @@ -72,6 +73,7 @@ func (suite *AccountStandardTestSuite) SetupSuite() { suite.testApplications = testrig.NewTestApplications() suite.testUsers = testrig.NewTestUsers() suite.testAccounts = testrig.NewTestAccounts() + suite.testFollows = testrig.NewTestFollows() suite.testAttachments = testrig.NewTestAttachments() suite.testStatuses = testrig.NewTestStatuses() } @@ -80,8 +82,8 @@ func (suite *AccountStandardTestSuite) SetupTest() { suite.state.Caches.Init() testrig.StartWorkers(&suite.state) - testrig.InitTestLog() testrig.InitTestConfig() + testrig.InitTestLog() suite.db = testrig.NewTestDB(&suite.state) suite.state.DB = suite.db diff --git a/internal/processing/account/follow.go b/internal/processing/account/follow.go index ab8fecd94..1aed92e75 100644 --- a/internal/processing/account/follow.go +++ b/internal/processing/account/follow.go @@ -25,6 +25,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/ap" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtscontext" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/id" @@ -40,24 +41,47 @@ func (p *Processor) FollowCreate(ctx context.Context, requestingAccount *gtsmode } // Check if a follow exists already. - if follows, err := p.state.DB.IsFollowing(ctx, requestingAccount.ID, targetAccount.ID); err != nil { - err = fmt.Errorf("FollowCreate: db error checking follow: %w", err) + if follow, err := p.state.DB.GetFollow( + gtscontext.SetBarebones(ctx), + requestingAccount.ID, + targetAccount.ID, + ); err != nil && !errors.Is(err, db.ErrNoEntries) { + err = fmt.Errorf("FollowCreate: db error checking existing follow: %w", err) return nil, gtserror.NewErrorInternalError(err) - } else if follows { - // Already follows, just return current relationship. - return p.RelationshipGet(ctx, requestingAccount, form.ID) + } else if follow != nil { + // Already follows, update if necessary + return relationship. + return p.updateFollow( + ctx, + requestingAccount, + form, + follow.ShowReblogs, + follow.Notify, + func(columns ...string) error { return p.state.DB.UpdateFollow(ctx, follow, columns...) }, + ) } // Check if a follow request exists already. - if followRequested, err := p.state.DB.IsFollowRequested(ctx, requestingAccount.ID, targetAccount.ID); err != nil { - err = fmt.Errorf("FollowCreate: db error checking follow request: %w", err) + if followRequest, err := p.state.DB.GetFollowRequest( + gtscontext.SetBarebones(ctx), + requestingAccount.ID, + targetAccount.ID, + ); err != nil && !errors.Is(err, db.ErrNoEntries) { + err = fmt.Errorf("FollowCreate: db error checking existing follow request: %w", err) return nil, gtserror.NewErrorInternalError(err) - } else if followRequested { - // Already follow requested, just return current relationship. - return p.RelationshipGet(ctx, requestingAccount, form.ID) + } else if followRequest != nil { + // Already requested, update if necessary + return relationship. + return p.updateFollow( + ctx, + requestingAccount, + form, + followRequest.ShowReblogs, + followRequest.Notify, + func(columns ...string) error { return p.state.DB.UpdateFollowRequest(ctx, followRequest, columns...) }, + ) } - // Create and store a new follow request. + // Neither follows nor follow requests, so + // create and store a new follow request. followID, err := id.NewRandomULID() if err != nil { return nil, gtserror.NewErrorInternalError(err) @@ -129,6 +153,52 @@ func (p *Processor) FollowRemove(ctx context.Context, requestingAccount *gtsmode Utility functions. */ +// updateFollow is a utility function for updating an existing +// follow or followRequest with the parameters provided in the +// given form. If nothing changes, this function is a no-op and +// will just return the existing relationship between follow +// origin and follow target account. +func (p *Processor) updateFollow( + ctx context.Context, + requestingAccount *gtsmodel.Account, + form *apimodel.AccountFollowRequest, + currentShowReblogs *bool, + currentNotify *bool, + update func(...string) error, +) (*apimodel.Relationship, gtserror.WithCode) { + + if form.Reblogs == nil && form.Notify == nil { + // There's nothing to update. + return p.RelationshipGet(ctx, requestingAccount, form.ID) + } + + // Including "updated_at", max 3 columns may change. + columns := make([]string, 0, 3) + + // Check what we need to update (if anything). + if newReblogs := form.Reblogs; newReblogs != nil && *newReblogs != *currentShowReblogs { + *currentShowReblogs = *newReblogs + columns = append(columns, "show_reblogs") + } + + if newNotify := form.Notify; newNotify != nil && *newNotify != *currentNotify { + *currentNotify = *newNotify + columns = append(columns, "notify") + } + + if len(columns) == 0 { + // Nothing actually changed. + return p.RelationshipGet(ctx, requestingAccount, form.ID) + } + + if err := update(columns...); err != nil { + err = fmt.Errorf("updateFollow: error updating existing follow (request): %w", err) + return nil, gtserror.NewErrorInternalError(err) + } + + return p.RelationshipGet(ctx, requestingAccount, form.ID) +} + // getFollowTarget is a convenience function which: // - Checks if account is trying to follow/unfollow itself. // - Returns not found if there's a block in place between accounts. diff --git a/internal/processing/account/follow_test.go b/internal/processing/account/follow_test.go new file mode 100644 index 000000000..70a28eea2 --- /dev/null +++ b/internal/processing/account/follow_test.go @@ -0,0 +1,140 @@ +// 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 account_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/suite" + apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" + "github.com/superseriousbusiness/gotosocial/testrig" +) + +type FollowTestSuite struct { + AccountStandardTestSuite +} + +func (suite *FollowTestSuite) TestUpdateExistingFollowChangeBoth() { + ctx := context.Background() + requestingAccount := suite.testAccounts["local_account_1"] + targetAccount := suite.testAccounts["admin_account"] + + // Change both Reblogs and Notify. + // Trace logs should show a query similar to this: + // UPDATE "follows" AS "follow" SET "show_reblogs" = FALSE, "notify" = TRUE, "updated_at" = '2023-04-09 11:42:39.424705+00:00' WHERE ("follow"."id" = '01F8PY8RHWRQZV038T4E8T9YK8') + relationship, err := suite.accountProcessor.FollowCreate(ctx, requestingAccount, &apimodel.AccountFollowRequest{ + ID: targetAccount.ID, + Reblogs: testrig.FalseBool(), + Notify: testrig.TrueBool(), + }) + + if err != nil { + suite.FailNow(err.Error()) + } + + suite.False(relationship.ShowingReblogs) + suite.True(relationship.Notifying) +} + +func (suite *FollowTestSuite) TestUpdateExistingFollowChangeNotifyIgnoreReblogs() { + ctx := context.Background() + requestingAccount := suite.testAccounts["local_account_1"] + targetAccount := suite.testAccounts["admin_account"] + + // Change Notify, ignore Reblogs. + // Trace logs should show a query similar to this: + // UPDATE "follows" AS "follow" SET "notify" = TRUE, "updated_at" = '2023-04-09 11:40:33.827858+00:00' WHERE ("follow"."id" = '01F8PY8RHWRQZV038T4E8T9YK8') + relationship, err := suite.accountProcessor.FollowCreate(ctx, requestingAccount, &apimodel.AccountFollowRequest{ + ID: targetAccount.ID, + Notify: testrig.TrueBool(), + }) + + if err != nil { + suite.FailNow(err.Error()) + } + + suite.True(relationship.ShowingReblogs) + suite.True(relationship.Notifying) +} + +func (suite *FollowTestSuite) TestUpdateExistingFollowChangeNotifySetReblogs() { + ctx := context.Background() + requestingAccount := suite.testAccounts["local_account_1"] + targetAccount := suite.testAccounts["admin_account"] + + // Change Notify, set Reblogs to same value as before. + // Trace logs should show a query similar to this: + // UPDATE "follows" AS "follow" SET "notify" = TRUE, "updated_at" = '2023-04-09 11:40:33.827858+00:00' WHERE ("follow"."id" = '01F8PY8RHWRQZV038T4E8T9YK8') + relationship, err := suite.accountProcessor.FollowCreate(ctx, requestingAccount, &apimodel.AccountFollowRequest{ + ID: targetAccount.ID, + Notify: testrig.TrueBool(), + Reblogs: testrig.TrueBool(), + }) + + if err != nil { + suite.FailNow(err.Error()) + } + + suite.True(relationship.ShowingReblogs) + suite.True(relationship.Notifying) +} + +func (suite *FollowTestSuite) TestUpdateExistingFollowChangeNothing() { + ctx := context.Background() + requestingAccount := suite.testAccounts["local_account_1"] + targetAccount := suite.testAccounts["admin_account"] + + // Set Notify and Reblogs to same values as before. + // Trace logs should show no update query. + relationship, err := suite.accountProcessor.FollowCreate(ctx, requestingAccount, &apimodel.AccountFollowRequest{ + ID: targetAccount.ID, + Notify: testrig.FalseBool(), + Reblogs: testrig.TrueBool(), + }) + + if err != nil { + suite.FailNow(err.Error()) + } + + suite.True(relationship.ShowingReblogs) + suite.False(relationship.Notifying) +} + +func (suite *FollowTestSuite) TestUpdateExistingFollowSetNothing() { + ctx := context.Background() + requestingAccount := suite.testAccounts["local_account_1"] + targetAccount := suite.testAccounts["admin_account"] + + // Don't set Notify or Reblogs. + // Trace logs should show no update query. + relationship, err := suite.accountProcessor.FollowCreate(ctx, requestingAccount, &apimodel.AccountFollowRequest{ + ID: targetAccount.ID, + }) + + if err != nil { + suite.FailNow(err.Error()) + } + + suite.True(relationship.ShowingReblogs) + suite.False(relationship.Notifying) +} + +func TestFollowTestS(t *testing.T) { + suite.Run(t, new(FollowTestSuite)) +} |