commit f0c9f4169be6c5c7dd913f348cdd294a19038d63 parent 1461adfd3a8c0f12610b43a9da14662ba9738fa8 Author: tobi <31960611+tsmethurst@users.noreply.github.com> Date: Mon, 23 May 2022 17:40:03 +0200 [bugfix] Fix multiple dereferences of boosted status causing media duplication (#589) * add some announces to test models * start on announce test logic * test federatingDB.Announce * change signature of GetRemoteStatus * remove 'refresh' logic and replace it with refetch * go fmt * remove timeline manager from processor test * make zork created at determinate * test get account statuses * test get + serialize zork * make account keys determinate * make admin accountCreate time determinate * test account to as * init test config before test log * test status to frontend * remove daft Within check * hack around a bit * use index of slice Diffstat:
18 files changed, 323 insertions(+), 98 deletions(-)
diff --git a/internal/api/client/account/accountverify_test.go b/internal/api/client/account/accountverify_test.go @@ -65,8 +65,6 @@ func (suite *AccountVerifyTestSuite) TestAccountVerifyGet() { createdAt, err := time.Parse(time.RFC3339, apimodelAccount.CreatedAt) suite.NoError(err) - lastStatusAt, err := time.Parse(time.RFC3339, apimodelAccount.LastStatusAt) - suite.NoError(err) suite.Equal(testAccount.ID, apimodelAccount.ID) suite.Equal(testAccount.Username, apimodelAccount.Username) @@ -83,7 +81,6 @@ func (suite *AccountVerifyTestSuite) TestAccountVerifyGet() { suite.Equal(2, apimodelAccount.FollowersCount) suite.Equal(2, apimodelAccount.FollowingCount) suite.Equal(5, apimodelAccount.StatusesCount) - suite.WithinDuration(time.Now(), lastStatusAt, 5*time.Minute) suite.EqualValues(gtsmodel.VisibilityPublic, apimodelAccount.Source.Privacy) suite.Equal(testAccount.Language, apimodelAccount.Source.Language) suite.Equal(testAccount.NoteRaw, apimodelAccount.Source.Note) diff --git a/internal/db/bundb/account_test.go b/internal/db/bundb/account_test.go @@ -34,6 +34,12 @@ type AccountTestSuite struct { BunDBStandardTestSuite } +func (suite *AccountTestSuite) TestGetAccountStatuses() { + statuses, err := suite.db.GetAccountStatuses(context.Background(), suite.testAccounts["local_account_1"].ID, 20, false, false, "", "", false, false, false) + suite.NoError(err) + suite.Len(statuses, 5) +} + func (suite *AccountTestSuite) TestGetAccountByIDWithExtras() { account, err := suite.db.GetAccountByID(context.Background(), suite.testAccounts["local_account_1"].ID) if err != nil { diff --git a/internal/federation/dereference.go b/internal/federation/dereference.go @@ -30,8 +30,8 @@ func (f *federator) GetRemoteAccount(ctx context.Context, username string, remot return f.dereferencer.GetRemoteAccount(ctx, username, remoteAccountID, blocking, refresh) } -func (f *federator) GetRemoteStatus(ctx context.Context, username string, remoteStatusID *url.URL, refresh, includeParent bool) (*gtsmodel.Status, ap.Statusable, bool, error) { - return f.dereferencer.GetRemoteStatus(ctx, username, remoteStatusID, refresh, includeParent) +func (f *federator) GetRemoteStatus(ctx context.Context, username string, remoteStatusID *url.URL, refetch, includeParent bool) (*gtsmodel.Status, ap.Statusable, error) { + return f.dereferencer.GetRemoteStatus(ctx, username, remoteStatusID, refetch, includeParent) } func (f *federator) EnrichRemoteStatus(ctx context.Context, username string, status *gtsmodel.Status, includeParent bool) (*gtsmodel.Status, error) { diff --git a/internal/federation/dereferencing/announce.go b/internal/federation/dereferencing/announce.go @@ -46,7 +46,7 @@ func (d *deref) DereferenceAnnounce(ctx context.Context, announce *gtsmodel.Stat return fmt.Errorf("DereferenceAnnounce: error dereferencing thread of boosted status: %s", err) } - boostedStatus, _, _, err := d.GetRemoteStatus(ctx, requestingUsername, boostedStatusURI, false, true) + boostedStatus, _, err := d.GetRemoteStatus(ctx, requestingUsername, boostedStatusURI, false, true) if err != nil { return fmt.Errorf("DereferenceAnnounce: error dereferencing remote status with id %s: %s", announce.BoostOf.URI, err) } diff --git a/internal/federation/dereferencing/dereferencer.go b/internal/federation/dereferencing/dereferencer.go @@ -35,7 +35,7 @@ import ( type Dereferencer interface { GetRemoteAccount(ctx context.Context, username string, remoteAccountID *url.URL, blocking bool, refresh bool) (*gtsmodel.Account, error) - GetRemoteStatus(ctx context.Context, username string, remoteStatusID *url.URL, refresh, includeParent bool) (*gtsmodel.Status, ap.Statusable, bool, error) + GetRemoteStatus(ctx context.Context, username string, remoteStatusID *url.URL, refetch, includeParent bool) (*gtsmodel.Status, ap.Statusable, error) EnrichRemoteStatus(ctx context.Context, username string, status *gtsmodel.Status, includeParent bool) (*gtsmodel.Status, error) GetRemoteInstance(ctx context.Context, username string, remoteInstanceURI *url.URL) (*gtsmodel.Instance, error) diff --git a/internal/federation/dereferencing/status.go b/internal/federation/dereferencing/status.go @@ -53,79 +53,63 @@ func (d *deref) EnrichRemoteStatus(ctx context.Context, username string, status } // GetRemoteStatus completely dereferences a remote status, converts it to a GtS model status, -// puts it in the database, and returns it to a caller. The boolean indicates whether the status is new -// to us or not. If we haven't seen the status before, bool will be true. If we have seen the status before, -// it will be false. +// puts it in the database, and returns it to a caller. // -// If refresh is true, then even if we have the status in our database already, it will be dereferenced from its -// remote representation, as will its owner. +// If refetch is true, then regardless of whether we have the original status in the database or not, +// the ap.Statusable representation of the status will be dereferenced and returned. // -// If a dereference was performed, then the function also returns the ap.Statusable representation for further processing. +// If refetch is false, the ap.Statusable will only be returned if this is a new status, so callers +// should check whether or not this is nil. // // SIDE EFFECTS: remote status will be stored in the database, and the remote status owner will also be stored. -func (d *deref) GetRemoteStatus(ctx context.Context, username string, remoteStatusID *url.URL, refresh, includeParent bool) (*gtsmodel.Status, ap.Statusable, bool, error) { - new := true - - // check if we already have the status in our db +func (d *deref) GetRemoteStatus(ctx context.Context, username string, remoteStatusID *url.URL, refetch, includeParent bool) (*gtsmodel.Status, ap.Statusable, error) { maybeStatus, err := d.db.GetStatusByURI(ctx, remoteStatusID.String()) - if err == nil { - // we've seen this status before so it's not new - new = false - - // if we're not being asked to refresh, we can just return the maybeStatus as-is and avoid doing any external calls - if !refresh { - return maybeStatus, nil, new, nil - } + if err == nil && !refetch { + // we already had the status and we aren't being asked to refetch the AP representation + return maybeStatus, nil, nil } statusable, err := d.dereferenceStatusable(ctx, username, remoteStatusID) if err != nil { - return nil, statusable, new, fmt.Errorf("GetRemoteStatus: error dereferencing statusable: %s", err) + return nil, nil, fmt.Errorf("GetRemoteStatus: error dereferencing statusable: %s", err) + } + + if maybeStatus != nil && refetch { + // we already had the status and we've successfully fetched the AP representation as requested + return maybeStatus, statusable, nil } + // from here on out we can consider this to be a 'new' status because we didn't have the status in the db already accountURI, err := ap.ExtractAttributedTo(statusable) if err != nil { - return nil, statusable, new, fmt.Errorf("GetRemoteStatus: error extracting attributedTo: %s", err) + return nil, nil, fmt.Errorf("GetRemoteStatus: error extracting attributedTo: %s", err) } - // do this so we know we have the remote account of the status in the db _, err = d.GetRemoteAccount(ctx, username, accountURI, true, false) if err != nil { - return nil, statusable, new, fmt.Errorf("GetRemoteStatus: couldn't derive status author: %s", err) + return nil, nil, fmt.Errorf("GetRemoteStatus: couldn't get status author: %s", err) } gtsStatus, err := d.typeConverter.ASStatusToStatus(ctx, statusable) if err != nil { - return nil, statusable, new, fmt.Errorf("GetRemoteStatus: error converting statusable to status: %s", err) + return nil, statusable, fmt.Errorf("GetRemoteStatus: error converting statusable to status: %s", err) } - if new { - ulid, err := id.NewULIDFromTime(gtsStatus.CreatedAt) - if err != nil { - return nil, statusable, new, fmt.Errorf("GetRemoteStatus: error generating new id for status: %s", err) - } - gtsStatus.ID = ulid - - if err := d.populateStatusFields(ctx, gtsStatus, username, includeParent); err != nil { - return nil, statusable, new, fmt.Errorf("GetRemoteStatus: error populating status fields: %s", err) - } - - if err := d.db.PutStatus(ctx, gtsStatus); err != nil { - return nil, statusable, new, fmt.Errorf("GetRemoteStatus: error putting new status: %s", err) - } - } else { - gtsStatus.ID = maybeStatus.ID + ulid, err := id.NewULIDFromTime(gtsStatus.CreatedAt) + if err != nil { + return nil, nil, fmt.Errorf("GetRemoteStatus: error generating new id for status: %s", err) + } + gtsStatus.ID = ulid - if err := d.populateStatusFields(ctx, gtsStatus, username, includeParent); err != nil { - return nil, statusable, new, fmt.Errorf("GetRemoteStatus: error populating status fields: %s", err) - } + if err := d.populateStatusFields(ctx, gtsStatus, username, includeParent); err != nil { + return nil, nil, fmt.Errorf("GetRemoteStatus: error populating status fields: %s", err) + } - if err := d.db.UpdateByPrimaryKey(ctx, gtsStatus); err != nil { - return nil, statusable, new, fmt.Errorf("GetRemoteStatus: error updating status: %s", err) - } + if err := d.db.PutStatus(ctx, gtsStatus); err != nil { + return nil, nil, fmt.Errorf("GetRemoteStatus: error putting new status: %s", err) } - return gtsStatus, statusable, new, nil + return gtsStatus, statusable, nil } func (d *deref) dereferenceStatusable(ctx context.Context, username string, remoteStatusID *url.URL) (ap.Statusable, error) { @@ -429,14 +413,9 @@ func (d *deref) populateStatusRepliedTo(ctx context.Context, status *gtsmodel.St return err } - // see if we have the status in our db already - replyToStatus, err := d.db.GetStatusByURI(ctx, status.InReplyToURI) + replyToStatus, _, err := d.GetRemoteStatus(ctx, requestingUsername, statusURI, false, false) if err != nil { - // Status was not in the DB, try fetch - replyToStatus, _, _, err = d.GetRemoteStatus(ctx, requestingUsername, statusURI, false, false) - if err != nil { - return fmt.Errorf("populateStatusRepliedTo: couldn't get reply to status with uri %s: %s", status.InReplyToURI, err) - } + return fmt.Errorf("populateStatusRepliedTo: couldn't get reply to status with uri %s: %s", status.InReplyToURI, err) } // we have the status diff --git a/internal/federation/dereferencing/status_test.go b/internal/federation/dereferencing/status_test.go @@ -38,11 +38,9 @@ func (suite *StatusTestSuite) TestDereferenceSimpleStatus() { fetchingAccount := suite.testAccounts["local_account_1"] statusURL := testrig.URLMustParse("https://unknown-instance.com/users/brand_new_person/statuses/01FE4NTHKWW7THT67EF10EB839") - status, statusable, new, err := suite.dereferencer.GetRemoteStatus(context.Background(), fetchingAccount.Username, statusURL, false, false) + status, _, err := suite.dereferencer.GetRemoteStatus(context.Background(), fetchingAccount.Username, statusURL, false, false) suite.NoError(err) suite.NotNil(status) - suite.NotNil(statusable) - suite.True(new) // status values should be set suite.Equal("https://unknown-instance.com/users/brand_new_person/statuses/01FE4NTHKWW7THT67EF10EB839", status.URI) @@ -80,11 +78,9 @@ func (suite *StatusTestSuite) TestDereferenceStatusWithMention() { fetchingAccount := suite.testAccounts["local_account_1"] statusURL := testrig.URLMustParse("https://unknown-instance.com/users/brand_new_person/statuses/01FE5Y30E3W4P7TRE0R98KAYQV") - status, statusable, new, err := suite.dereferencer.GetRemoteStatus(context.Background(), fetchingAccount.Username, statusURL, false, false) + status, _, err := suite.dereferencer.GetRemoteStatus(context.Background(), fetchingAccount.Username, statusURL, false, false) suite.NoError(err) suite.NotNil(status) - suite.NotNil(statusable) - suite.True(new) // status values should be set suite.Equal("https://unknown-instance.com/users/brand_new_person/statuses/01FE5Y30E3W4P7TRE0R98KAYQV", status.URI) @@ -135,11 +131,9 @@ func (suite *StatusTestSuite) TestDereferenceStatusWithImageAndNoContent() { fetchingAccount := suite.testAccounts["local_account_1"] statusURL := testrig.URLMustParse("https://turnip.farm/users/turniplover6969/statuses/70c53e54-3146-42d5-a630-83c8b6c7c042") - status, statusable, new, err := suite.dereferencer.GetRemoteStatus(context.Background(), fetchingAccount.Username, statusURL, false, false) + status, _, err := suite.dereferencer.GetRemoteStatus(context.Background(), fetchingAccount.Username, statusURL, false, false) suite.NoError(err) suite.NotNil(status) - suite.NotNil(statusable) - suite.True(new) // status values should be set suite.Equal("https://turnip.farm/users/turniplover6969/statuses/70c53e54-3146-42d5-a630-83c8b6c7c042", status.URI) diff --git a/internal/federation/dereferencing/thread.go b/internal/federation/dereferencing/thread.go @@ -52,9 +52,9 @@ func (d *deref) DereferenceThread(ctx context.Context, username string, statusIR } // first make sure we have this status in our db - _, statusable, _, err := d.GetRemoteStatus(ctx, username, statusIRI, true, false) + _, statusable, err := d.GetRemoteStatus(ctx, username, statusIRI, true, false) if err != nil { - return fmt.Errorf("DereferenceThread: error getting status with id %s: %s", statusIRI.String(), err) + return fmt.Errorf("DereferenceThread: error getting initial status with id %s: %s", statusIRI.String(), err) } // first iterate up through ancestors, dereferencing if necessary as we go @@ -106,9 +106,8 @@ func (d *deref) iterateAncestors(ctx context.Context, username string, statusIRI return d.iterateAncestors(ctx, username, *nextIRI) } - // If we reach here, we're looking at a remote status -- make sure we have it in our db by calling GetRemoteStatus - // We call it with refresh to true because we want the statusable representation to parse inReplyTo from. - _, statusable, _, err := d.GetRemoteStatus(ctx, username, &statusIRI, true, false) + // If we reach here, we're looking at a remote status + _, statusable, err := d.GetRemoteStatus(ctx, username, &statusIRI, true, false) if err != nil { l.Debugf("error getting remote status: %s", err) return nil @@ -220,8 +219,8 @@ pageLoop: foundReplies++ // get the remote statusable and put it in the db - _, statusable, new, err := d.GetRemoteStatus(ctx, username, itemURI, false, false) - if new && err == nil && statusable != nil { + _, statusable, err := d.GetRemoteStatus(ctx, username, itemURI, false, false) + if err == nil { // now iterate descendants of *that* status if err := d.iterateDescendants(ctx, username, *itemURI, statusable); err != nil { continue diff --git a/internal/federation/federatingdb/announce_test.go b/internal/federation/federatingdb/announce_test.go @@ -0,0 +1,93 @@ +/* + GoToSocial + Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org + + 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 federatingdb_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/activity/streams/vocab" + "github.com/superseriousbusiness/gotosocial/internal/ap" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +type AnnounceTestSuite struct { + FederatingDBTestSuite +} + +func (suite *AnnounceTestSuite) TestNewAnnounce() { + receivingAccount1 := suite.testAccounts["local_account_1"] + announcingAccount := suite.testAccounts["remote_account_1"] + + ctx := createTestContext(receivingAccount1, announcingAccount) + announce1 := suite.testActivities["announce_forwarded_1_zork"] + + err := suite.federatingDB.Announce(ctx, announce1.Activity.(vocab.ActivityStreamsAnnounce)) + suite.NoError(err) + + // should be a message heading to the processor now, which we can intercept here + msg := <-suite.fromFederator + suite.Equal(ap.ActivityAnnounce, msg.APObjectType) + suite.Equal(ap.ActivityCreate, msg.APActivityType) + + boost, ok := msg.GTSModel.(*gtsmodel.Status) + suite.True(ok) + suite.Equal(announcingAccount.ID, boost.AccountID) + + // only the URI will be set on the boosted status because it still needs to be dereferenced + suite.NotEmpty(boost.BoostOf.URI) +} + +func (suite *AnnounceTestSuite) TestAnnounceTwice() { + receivingAccount1 := suite.testAccounts["local_account_1"] + receivingAccount2 := suite.testAccounts["local_account_2"] + + announcingAccount := suite.testAccounts["remote_account_1"] + + ctx1 := createTestContext(receivingAccount1, announcingAccount) + announce1 := suite.testActivities["announce_forwarded_1_zork"] + + err := suite.federatingDB.Announce(ctx1, announce1.Activity.(vocab.ActivityStreamsAnnounce)) + suite.NoError(err) + + // should be a message heading to the processor now, which we can intercept here + msg := <-suite.fromFederator + suite.Equal(ap.ActivityAnnounce, msg.APObjectType) + suite.Equal(ap.ActivityCreate, msg.APActivityType) + boost, ok := msg.GTSModel.(*gtsmodel.Status) + suite.True(ok) + suite.Equal(announcingAccount.ID, boost.AccountID) + + // only the URI will be set on the boosted status because it still needs to be dereferenced + suite.NotEmpty(boost.BoostOf.URI) + + ctx2 := createTestContext(receivingAccount2, announcingAccount) + announce2 := suite.testActivities["announce_forwarded_1_turtle"] + + err = suite.federatingDB.Announce(ctx2, announce2.Activity.(vocab.ActivityStreamsAnnounce)) + suite.NoError(err) + + // since this is a repeat announce with the same URI, just delivered to a different inbox, + // we should have nothing in the messages channel... + suite.Empty(suite.fromFederator) +} + +func TestAnnounceTestSuite(t *testing.T) { + suite.Run(t, &AnnounceTestSuite{}) +} diff --git a/internal/federation/federatingdb/federatingdb_test.go b/internal/federation/federatingdb/federatingdb_test.go @@ -63,8 +63,9 @@ func (suite *FederatingDBTestSuite) SetupSuite() { } func (suite *FederatingDBTestSuite) SetupTest() { - testrig.InitTestLog() testrig.InitTestConfig() + testrig.InitTestLog() + suite.fedWorker = concurrency.NewWorkerPool[messages.FromFederator](-1, -1) suite.fromFederator = make(chan messages.FromFederator, 10) suite.fedWorker.SetProcessor(func(ctx context.Context, msg messages.FromFederator) error { diff --git a/internal/federation/federator.go b/internal/federation/federator.go @@ -62,7 +62,7 @@ type Federator interface { GetRemoteAccount(ctx context.Context, username string, remoteAccountID *url.URL, blocking bool, refresh bool) (*gtsmodel.Account, error) - GetRemoteStatus(ctx context.Context, username string, remoteStatusID *url.URL, refresh, includeParent bool) (*gtsmodel.Status, ap.Statusable, bool, error) + GetRemoteStatus(ctx context.Context, username string, remoteStatusID *url.URL, refetch, includeParent bool) (*gtsmodel.Status, ap.Statusable, error) EnrichRemoteStatus(ctx context.Context, username string, status *gtsmodel.Status, includeParent bool) (*gtsmodel.Status, error) GetRemoteInstance(ctx context.Context, username string, remoteInstanceURI *url.URL) (*gtsmodel.Instance, error) diff --git a/internal/processing/fromfederator.go b/internal/processing/fromfederator.go @@ -108,7 +108,7 @@ func (p *processor) processCreateStatusFromFederator(ctx context.Context, federa return errors.New("ProcessFromFederator: status was not pinned to federatorMsg, and neither was an IRI for us to dereference") } var err error - status, _, _, err = p.federator.GetRemoteStatus(ctx, federatorMsg.ReceivingAccount.Username, federatorMsg.APIri, false, false) + status, _, err = p.federator.GetRemoteStatus(ctx, federatorMsg.ReceivingAccount.Username, federatorMsg.APIri, false, false) if err != nil { return err } diff --git a/internal/processing/processor_test.go b/internal/processing/processor_test.go @@ -38,7 +38,6 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/messages" "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/processing" - "github.com/superseriousbusiness/gotosocial/internal/timeline" "github.com/superseriousbusiness/gotosocial/internal/transport" "github.com/superseriousbusiness/gotosocial/internal/typeutils" "github.com/superseriousbusiness/gotosocial/testrig" @@ -54,7 +53,6 @@ type ProcessingStandardTestSuite struct { transportController transport.Controller federator federation.Federator oauthServer oauth.Server - timelineManager timeline.Manager emailSender email.Sender // standard suite models diff --git a/internal/processing/search.go b/internal/processing/search.go @@ -127,7 +127,7 @@ func (p *processor) searchStatusByURI(ctx context.Context, authed *oauth.Auth, u // we don't have it locally so dereference it if we're allowed to if resolve { - status, _, _, err := p.federator.GetRemoteStatus(ctx, authed.Account.Username, uri, true, true) + status, _, err := p.federator.GetRemoteStatus(ctx, authed.Account.Username, uri, false, true) if err == nil { if err := p.federator.DereferenceRemoteThread(ctx, authed.Account.Username, uri); err != nil { // try to deref the thread while we're here diff --git a/internal/typeutils/converter_test.go b/internal/typeutils/converter_test.go @@ -427,8 +427,8 @@ type TypeUtilsTestSuite struct { } func (suite *TypeUtilsTestSuite) SetupSuite() { - testrig.InitTestLog() testrig.InitTestConfig() + testrig.InitTestLog() suite.db = testrig.NewTestDB() suite.testAccounts = testrig.NewTestAccounts() diff --git a/internal/typeutils/internaltoas_test.go b/internal/typeutils/internaltoas_test.go @@ -21,7 +21,7 @@ package typeutils_test import ( "context" "encoding/json" - "fmt" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -45,8 +45,11 @@ func (suite *InternalToASTestSuite) TestAccountToAS() { bytes, err := json.Marshal(ser) suite.NoError(err) - fmt.Println(string(bytes)) - // TODO: write assertions here, rn we're just eyeballing the output + // trim off everything up to 'discoverable'; + // this is necessary because the order of multiple 'context' entries is not determinate + trimmed := strings.Split(string(bytes), "\"discoverable\"")[1] + + suite.Equal(`:true,"featured":"http://localhost:8080/users/the_mighty_zork/collections/featured","followers":"http://localhost:8080/users/the_mighty_zork/followers","following":"http://localhost:8080/users/the_mighty_zork/following","icon":{"mediaType":"image/jpeg","type":"Image","url":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg"},"id":"http://localhost:8080/users/the_mighty_zork","image":{"mediaType":"image/jpeg","type":"Image","url":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg"},"inbox":"http://localhost:8080/users/the_mighty_zork/inbox","manuallyApprovesFollowers":false,"name":"original zork (he/they)","outbox":"http://localhost:8080/users/the_mighty_zork/outbox","preferredUsername":"the_mighty_zork","publicKey":{"id":"http://localhost:8080/users/the_mighty_zork/main-key","owner":"http://localhost:8080/users/the_mighty_zork","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwXTcOAvM1Jiw5Ffpk0qn\nr0cwbNvFe/5zQ+Tp7tumK/ZnT37o7X0FUEXrxNi+dkhmeJ0gsaiN+JQGNUewvpSk\nPIAXKvi908aSfCGjs7bGlJCJCuDuL5d6m7hZnP9rt9fJc70GElPpG0jc9fXwlz7T\nlsPb2ecatmG05Y4jPwdC+oN4MNCv9yQzEvCVMzl76EJaM602kIHC1CISn0rDFmYd\n9rSN7XPlNJw1F6PbpJ/BWQ+pXHKw3OEwNTETAUNYiVGnZU+B7a7bZC9f6/aPbJuV\nt8Qmg+UnDvW1Y8gmfHnxaWG2f5TDBvCHmcYtucIZPLQD4trAozC4ryqlmCWQNKbt\n0wIDAQAB\n-----END PUBLIC KEY-----\n"},"summary":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","type":"Person","url":"http://localhost:8080/@the_mighty_zork"}`, trimmed) } func (suite *InternalToASTestSuite) TestOutboxToASCollection() { diff --git a/internal/typeutils/internaltofrontend_test.go b/internal/typeutils/internaltofrontend_test.go @@ -0,0 +1,58 @@ +/* + GoToSocial + Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org + + 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 typeutils_test + +import ( + "context" + "encoding/json" + "testing" + + "github.com/stretchr/testify/suite" +) + +type InternalToFrontendTestSuite struct { + TypeUtilsTestSuite +} + +func (suite *InternalToFrontendTestSuite) TestAccountToFrontend() { + testAccount := suite.testAccounts["local_account_1"] // take zork for this test + apiAccount, err := suite.typeconverter.AccountToAPIAccountPublic(context.Background(), testAccount) + suite.NoError(err) + suite.NotNil(apiAccount) + + b, err := json.Marshal(apiAccount) + suite.NoError(err) + suite.Equal(`{"id":"01F8MH1H7YV1Z7D2C8K2730QBF","username":"the_mighty_zork","acct":"the_mighty_zork","display_name":"original zork (he/they)","locked":false,"bot":false,"created_at":"2022-05-20T11:09:18Z","note":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","url":"http://localhost:8080/@the_mighty_zork","avatar":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg","avatar_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpeg","header":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","header_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","followers_count":2,"following_count":2,"statuses_count":5,"last_status_at":"2022-05-20T11:37:55Z","emojis":[],"fields":[]}`, string(b)) +} + +func (suite *InternalToFrontendTestSuite) TestStatusToFrontend() { + testStatus := suite.testStatuses["admin_account_status_1"] + requestingAccount := suite.testAccounts["local_account_1"] + apiStatus, err := suite.typeconverter.StatusToAPIStatus(context.Background(), testStatus, requestingAccount) + suite.NoError(err) + + b, err := json.Marshal(apiStatus) + suite.NoError(err) + + suite.Equal(`{"id":"01F8MH75CBF9JFX4ZAD54N0W0R","created_at":"2021-10-20T11:36:45Z","sensitive":false,"spoiler_text":"","visibility":"public","language":"en","uri":"http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R","url":"http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R","replies_count":0,"reblogs_count":0,"favourites_count":1,"favourited":true,"reblogged":false,"muted":false,"bookmarked":false,"content":"hello world! #welcome ! first post on the instance :rainbow: !","application":{"name":"superseriousbusiness","website":"https://superserious.business"},"account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"","header_static":"","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37Z","emojis":[],"fields":[]},"media_attachments":[{"id":"01F8MH6NEM8D7527KZAECTCR76","type":"image","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg","preview_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/small/01F8MH6NEM8D7527KZAECTCR76.jpeg","meta":{"original":{"width":1200,"height":630,"size":"1200x630","aspect":1.9047619},"small":{"width":256,"height":134,"size":"256x134","aspect":1.9104477},"focus":{"x":0,"y":0}},"description":"Black and white image of some 50's style text saying: Welcome On Board","blurhash":"LNJRdVM{00Rj%Mayt7j[4nWBofRj"}],"mentions":[],"tags":[{"name":"welcome","url":"http://localhost:8080/tags/welcome"}],"emojis":[{"shortcode":"rainbow","url":"http://localhost:8080/fileserver/01F8MH261H1KSV3GW3016GZRY3/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","static_url":"http://localhost:8080/fileserver/01F8MH261H1KSV3GW3016GZRY3/emoji/static/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","visible_in_picker":true}],"card":null,"poll":null,"text":""}`, string(b)) +} + +func TestInternalToFrontendTestSuite(t *testing.T) { + suite.Run(t, new(InternalToFrontendTestSuite)) +} diff --git a/testrig/testmodels.go b/testrig/testmodels.go @@ -31,6 +31,7 @@ import ( "net/http" "net/url" "os" + "sort" "strings" "time" @@ -318,8 +319,8 @@ func NewTestAccounts() map[string]*gtsmodel.Account { NoteRaw: "", Memorial: false, MovedToAccountID: "", - CreatedAt: time.Now().Add(-72 * time.Hour), - UpdatedAt: time.Now().Add(-72 * time.Hour), + CreatedAt: TimeMustParse("2022-05-17T13:10:59Z"), + UpdatedAt: TimeMustParse("2022-05-17T13:10:59Z"), Bot: false, Reason: "", Locked: false, @@ -357,8 +358,8 @@ func NewTestAccounts() map[string]*gtsmodel.Account { NoteRaw: "hey yo this is my profile!", Memorial: false, MovedToAccountID: "", - CreatedAt: time.Now().Add(-48 * time.Hour), - UpdatedAt: time.Now().Add(-48 * time.Hour), + CreatedAt: TimeMustParse("2022-05-20T11:09:18Z"), + UpdatedAt: TimeMustParse("2022-05-20T11:09:18Z"), Bot: false, Reason: "I wanna be on this damned webbed site so bad! Please! Wow", Locked: false, @@ -496,6 +497,15 @@ func NewTestAccounts() map[string]*gtsmodel.Account { }, } + var accountsSorted []*gtsmodel.Account + for _, a := range accounts { + accountsSorted = append(accountsSorted, a) + } + + sort.Slice(accountsSorted, func(i, j int) bool { + return accountsSorted[i].ID > accountsSorted[j].ID + }) + preserializedKeys := []string{ "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDGj2wLnDIHnP6wjJ+WmIhp7NGAaKWwfxBWfdMFR+Y0ilkK5ld5igT45UHAmzN3v4HcwHGGpPITD9caDYj5YaGOX+dSdGLgXWwItR0j+ivrHEJmvz8hG6z9wKEZKUUrRw7Ob72S0LOsreq98bjdiWJKHNka27slqQjGyhLQtcg6pe1CLJtnuJH4GEMLj7jJB3/Mqv3vl5CQZ+Js0bXfgw5TF/x/Bzq/8qsxQ1vnmYHJsR0eLPEuDJOvoFPiJZytI09S7qBEJL5PDeVSfjQi3o71sqOzZlEL0b0Ny48rfo/mwJAdkmfcnydRDxeGUEqpAWICCOdUL0+W3/fCffaRZsk1AgMBAAECggEAUuyO6QJgeoF8dGsmMxSc0/ANRp1tpRpLznNZ77ipUYP9z+mG2sFjdjb4kOHASuB18aWFRAAbAQ76fGzuqYe2muk+iFcG/EDH35MUCnRuZxA0QwjX6pHOW2NZZFKyCnLwohJUj74Na65ufMk4tXysydrmaKsfq4i+m5bE6NkiOCtbXsjUGVdJKzkT6X1gEyEPEHgrgVZz9OpRY5nwjZBMcFI6EibFnWdehcuCQLESIX9ll/QzGvTJ1p8xeVJs2ktLWKQ38RewwucNYVLVJmxS1LCPP8x+yHVkOxD66eIncY26sjX+VbyICkaG/ZjKBuoOekOq/T+b6q5ESxWUNfcu+QKBgQDmt3WVBrW6EXKtN1MrVyBoSfn9WHyf8Rfb84t5iNtaWGSyPZK/arUw1DRbI0TdPjct//wMWoUU2/uqcPSzudTaPena3oxjKReXso1hcynHqboCaXJMxWSqDQLumbrVY05C1WFSyhRY0iQS5fIrNzD4+6rmeC2Aj5DKNW5Atda8dwKBgQDcUdhQfjL9SmzzIeAqJUBIfSSI2pSTsZrnrvMtSMkYJbzwYrUdhIVxaS4hXuQYmGgwonLctyvJxVxEMnf+U0nqPgJHE9nGQb5BbK6/LqxBWRJQlc+W6EYodIwvtE5B4JNkPE5757u+xlDdHe2zGUGXSIf4IjBNbSpCu6RcFsGOswKBgEnr4gqbmcJCMOH65fTu930yppxbq6J7Vs+sWrXX+aAazjilrc0S3XcFprjEth3E/10HtbQnlJg4W4wioOSs19wNFk6AG67xzZNXLCFbCrnkUarQKkUawcQSYywbqVcReFPFlmc2RAqpWdGMR2k9R72etQUe4EVeul9veyHUoTbFAoGBAKj3J9NLhaVVb8ri3vzThsJRHzTJlYrTeb5XIO5I1NhtEMK2oLobiQ+aH6O+F2Z5c+Zgn4CABdf/QSyYHAhzLcu0dKC4K5rtjpC0XiwHClovimk9C3BrgGrEP0LSn/XL2p3T1kkWRpkflKKPsl1ZcEEqggSdi7fFkdSN/ZYWaakbAoGBALWVGpA/vXmaZEV/hTDdtDnIHj6RXfKHCsfnyI7AdjUX4gokzdcEvFsEIoI+nnXR/PIAvwqvQw4wiUqQnp2VB8r73YZvW/0npnsidQw3ZjqnyvZ9X8y80nYs7DjSlaG0A8huy2TUdFnJyCMWby30g82kf0b/lhotJg4d3fIDou51", "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC6q61hiC7OhlMz7JNnLiL/RwOaFC8955GDvwSMH9Zw3oguWH9nLqkmlJ98cnqRG9ZC0qVo6Gagl7gv6yOHDwD4xZI8JoV2ZfNdDzq4QzoBIzMtRsbSS4IvrF3JP+kDH1tim+CbRMBxiFJgLgS6yeeQlLNvBW+CIYzmeCimZ6CWCr91rZPIprUIdjvhxrM9EQU072Pmzn2gpGM6K5gAReN+LtP+VSBC61x7GQJxBaJNtk11PXkgG99EdFi9vvgEBbM9bdcawvf8jxvjgsgdaDx/1cypDdnaL8eistmyv1YI67bKvrSPCEh55b90hl3o3vW4W5G4gcABoyORON96Y+i9AgMBAAECggEBAKp+tyNH0QiMo13fjFpHR2vFnsKSAPwXj063nx2kzqXUeqlp5yOE+LXmNSzjGpOCy1XJM474BRRUvsP1jkODLq4JNiF+RZP4Vij/CfDWZho33jxSUrIsiUGluxtfJiHV+A++s4zdZK/NhP+XyHYah0gEqUaTvl8q6Zhu0yH5sDCZHDLxDBpgiT5qD3lli8/o2xzzBdaibZdjQyHi9v5Yi3+ysly1tmfmqnkXSsevAubwJu504WxvDUSo7hPpG4a8Xb8ODqL738GIF2UY/olCcGkWqTQEr2pOqG9XbMmlUWnxG62GCfK6KtGfIzCyBBkGO2PZa9aPhVnv2bkYxI4PkLkCgYEAzAp7xH88UbSX31suDRa4jZwgtzhJLeyc3YxO5C4XyWZ89oWrA30V1KvfVwFRavYRJW07a+r0moba+0E1Nj5yZVXPOVu0bWd9ZyMbdH2L6MRZoJWU5bUOwyruulRCkqASZbWo4G05NOVesOyY1bhZGE7RyUW0vOo8tSyyRQ8nUGMCgYEA6jTQbDry4QkUP9tDhvc8+LsobIF1mPLEJui+mT98+9IGar6oeVDKekmNDO0Dx2+miLfjMNhCb5qUc8g036ZsekHt2WuQKunADua0coB00CebMdr6AQFf7QOQ/RuA+/gPJ5G0GzWB3YOQ5gE88tTCO/jBfmikVOZvLtgXUGjo3F8CgYEAl2poMoehQZjc41mMsRXdWukztgPE+pmORzKqENbLvB+cOG01XV9j5fCtyqklvFRioP2QjSNM5aeRtcbMMDbjOaQWJaCSImYcP39kDmxkeRXM1UhruJNGIzsm8Ys55Al53ZSTgAhN3Z0hSfYp7N/i7hD/yXc7Cr5g0qoamPkH2bUCgYApf0oeoyM9tDoeRl9knpHzEFZNQ3LusrUGn96FkLY4eDIi371CIYp+uGGBlM1CnQnI16wtj2PWGnGLQkH8DqTR1LSr/V8B+4DIIyB92TzZVOsunjoFy5SPjj42WpU0D/O/cxWSbJyh/xnBZx7Bd+kibyT5nNjhIiM5DZiz6qK3yQKBgAOO/MFKHKpKOXrtafbqCyculG/ope2u4eBveHKO6ByWcUSbuD9ebtr7Lu5AC5tKUJLkSyRx4EHk71bqP1yOITj8z9wQWdVyLxtVtyj9SUkUNvGwIj+F7NJ5VgHzWVZtvYWDCzrfxkEhKk3DRIIVjqmEohJcaOZoZ2Q/f8sjlId6", @@ -505,7 +515,7 @@ func NewTestAccounts() map[string]*gtsmodel.Account { "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDSIsx0TsUCeSHXDYPzViqRwB/wZhBkj5f0Mrc+Q0yogUmiTcubYQcf/xj9LOvtArJ+8/rori0j8aFX17jZqtFyDDINyhICT+i5bk1ZKPt/uH/H5oFpjtsL+bCoOF8F4AUeELExH0dO3uwl8v9fPZZ3AZEGj6UB6Ru13LON7fKHt+JT6s9jNtUIUpHUDg2GZYv9gLFGDDm9H91Yervl8yF6VWbK+7pcVyhlz5wqHR/qNUiyUXhiie+veiJc9ipCU7RriNEuehvF12d3rRIOK/wRsFAG4LxufJS8Shu8VJrOBlKzsufqjDZtnZb8SrTY0EjLJpslMf67zRDD1kEDpq4jAgMBAAECggEBAMeKxe2YMxpjHpBRRECZTTk0YN/ue5iShrAcTMeyLqRAiUS3bSXyIErw+bDIrIxXKFrHoja71x+vvw9kSSNhQxxymkFf5nQNn6geJxMIiLJC6AxSRgeP4U/g3jEPvqQck592KFzGH/e0Vji/JGMzX6NIeIfrdbx3uJmcp2CaWNkoOs7UYV5VbNDaIWYcgptQS9hJpCQ+cuMov7scXE88uKtwAl+0VVopNr/XA7vV+npsESBCt3dfnp6poA13ldfqReLdPTmDWH7Z8QrTIagrfPi5mKpxksTYyC0/quKyk4yTj8Ge5GWmsXCHtyf19NX7reeJa8MjEWonYDCdnqReDoECgYEA8R5OHNIGC6yw6ZyTuyEt2epXwUj0h2Z9d+JAT9ndRGK9xdMqJt4acjxfcEck2wjv9BuNLr5YvLc4CYiOgyqJHNt5c5Ys5rJEOgBZ2IFoaoXZNom2LEtr583T4RFXp/Id8ix85D6EZj8Hp6OvZygQFwEYQexY383hZZh5enkorUECgYEA3xr3u/SbttM86ib1RP1uuON9ZURfzpmrr2ubSWiRDqwift0T2HesdhWi6xDGjzGyeT5e7irf1BsBKUq2dp/wFX6+15A6eV12C7PvC4N8u3NJwGBdvCmufh5wZ19rerelaB7+vG9c+Nbw9h1BbDi8MlGs06oVSawvwUzp2oVKLmMCgYEAq1RFXOU/tnv3GYhQ0N86nWWPBaC5YJzK+qyh1huQxk8DWdY6VXPshs+vYTCsV5d6KZKKN3S5yR7Hir6lxT4sP30UR7WmIib5o90r+lO5xjdlqQMhl0fgXM48h+iyyHuaG8LQ274whhazccM1l683/6Cfg/hVDnJUfsRhTU1aQgECgYBrZPTZcf6+u+I3qHcqNYBl2YPUCly/+7LsJzVB2ebxlCSqwsq5yamn0fRxiMq7xSVvPXm+1b6WwEUH1mIMqiKMhk1hQJkVMMsRCRVJioqxROa8hua4G6xWI1riN8lp8hraCwl+NXEgi37ESgLjEFBvPGegH+BNbWgzeU2clcrGlwKBgHBxlFLf6AjDxjR8Z5dnZVPyvLOUjejs5nsLdOfONJ8F/MU0PoKFWdBavhbnwXwium6NvcearnhbWL758sKooZviQL6m/sKDGWMq3O8SCnX+TKTEOw+kLLFn4L3sT02WaHYg+C5iVEDdGlsXSehhI2e7hBoTulE/zbUkbA3+wlmv", } - if diff := len(accounts) - len(preserializedKeys); diff > 0 { + if diff := len(accountsSorted) - len(preserializedKeys); diff > 0 { keyStrings := make([]string, diff) for i := 0; i < diff; i++ { priv, _ := rsa.GenerateKey(rand.Reader, 2048) @@ -515,9 +525,7 @@ func NewTestAccounts() map[string]*gtsmodel.Account { panic(fmt.Sprintf("mismatch between number of hardcoded test RSA keys and accounts used for test data. Insert the following generated key[s]: \n%+v", keyStrings)) } - // generate keys for each account - i := 0 - for _, v := range accounts { + for i, v := range accountsSorted { premadeBytes, err := base64.StdEncoding.DecodeString(preserializedKeys[i]) if err != nil { panic(err) @@ -532,8 +540,8 @@ func NewTestAccounts() map[string]*gtsmodel.Account { } v.PrivateKey = priv v.PublicKey = &priv.PublicKey - i++ } + return accounts } @@ -1174,8 +1182,8 @@ func NewTestStatuses() map[string]*gtsmodel.Status { URL: "http://localhost:8080/@the_mighty_zork/statuses/01FCTA44PW9H1TB328S9AQXKDS", Content: "hi!", AttachmentIDs: []string{}, - CreatedAt: time.Now().Add(-1 * time.Minute), - UpdatedAt: time.Now().Add(-1 * time.Minute), + CreatedAt: TimeMustParse("2022-05-20T11:37:55Z"), + UpdatedAt: TimeMustParse("2022-05-20T11:37:55Z"), Local: true, AccountURI: "http://localhost:8080/users/the_mighty_zork", AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF", @@ -1636,7 +1644,13 @@ func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]Activit nil, false, []vocab.ActivityStreamsMention{}, - nil, + []vocab.ActivityStreamsImage{ + newAPImage( + URLMustParse("http://example.org/users/some_user/statuses/afaba698-5740-4e32-a702-af61aa543bc1/attachment1.jpeg"), + "image/jpeg", + "trent reznor looking handsome as balls", + "LEDara58O=t5EMSOENEN9]}?aK%0"), + }, ) createForwardedMessage := WrapAPNoteInCreate( URLMustParse("http://example.org/users/some_user/statuses/afaba698-5740-4e32-a702-af61aa543bc1/activity"), @@ -1645,6 +1659,33 @@ func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]Activit forwardedMessage) createForwardedMessageSig, createForwardedMessageDigest, createForwardedMessageDate := GetSignatureForActivity(createForwardedMessage, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURI)) + announceForwarded1Zork := newAPAnnounce( + URLMustParse("http://fossbros-anonymous.io/users/foss_satan/first_announce"), + URLMustParse("http://fossbros-anonymous.io/users/foss_satan"), + time.Now(), + URLMustParse("http://fossbros-anonymous.io/users/foss_satan/followers"), + forwardedMessage, + ) + announceForwarded1ZorkSig, announceForwarded1ZorkDigest, announceForwarded1ZorkDate := GetSignatureForActivity(announceForwarded1Zork, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURI)) + + announceForwarded1Turtle := newAPAnnounce( + URLMustParse("http://fossbros-anonymous.io/users/foss_satan/first_announce"), + URLMustParse("http://fossbros-anonymous.io/users/foss_satan"), + time.Now(), + URLMustParse("http://fossbros-anonymous.io/users/foss_satan/followers"), + forwardedMessage, + ) + announceForwarded1TurtleSig, announceForwarded1TurtleDigest, announceForwarded1TurtleDate := GetSignatureForActivity(announceForwarded1Turtle, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_2"].InboxURI)) + + announceForwarded2Zork := newAPAnnounce( + URLMustParse("http://fossbros-anonymous.io/users/foss_satan/second_announce"), + URLMustParse("http://fossbros-anonymous.io/users/foss_satan"), + time.Now(), + URLMustParse("http://fossbros-anonymous.io/users/foss_satan/followers"), + forwardedMessage, + ) + announceForwarded2ZorkSig, announceForwarded2ZorkDigest, announceForwarded2ZorkDate := GetSignatureForActivity(announceForwarded2Zork, accounts["remote_account_1"].PublicKeyURI, accounts["remote_account_1"].PrivateKey, URLMustParse(accounts["local_account_1"].InboxURI)) + return map[string]ActivityWithSignature{ "dm_for_zork": { Activity: createDmForZork, @@ -1670,6 +1711,24 @@ func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]Activit DigestHeader: createForwardedMessageDigest, DateHeader: createForwardedMessageDate, }, + "announce_forwarded_1_zork": { + Activity: announceForwarded1Zork, + SignatureHeader: announceForwarded1ZorkSig, + DigestHeader: announceForwarded1ZorkDigest, + DateHeader: announceForwarded1ZorkDate, + }, + "announce_forwarded_1_turtle": { + Activity: announceForwarded1Turtle, + SignatureHeader: announceForwarded1TurtleSig, + DigestHeader: announceForwarded1TurtleDigest, + DateHeader: announceForwarded1TurtleDate, + }, + "announce_forwarded_2_zork": { + Activity: announceForwarded2Zork, + SignatureHeader: announceForwarded2ZorkSig, + DigestHeader: announceForwarded2ZorkDigest, + DateHeader: announceForwarded2ZorkDate, + }, } } @@ -2585,3 +2644,41 @@ func WrapAPNoteInCreate(createID *url.URL, createActor *url.URL, createPublished return create } + +func newAPAnnounce(announceID *url.URL, announceActor *url.URL, announcePublished time.Time, announceTo *url.URL, announceNote vocab.ActivityStreamsNote) vocab.ActivityStreamsAnnounce { + announce := streams.NewActivityStreamsAnnounce() + + if announceID != nil { + id := streams.NewJSONLDIdProperty() + id.Set(announceID) + announce.SetJSONLDId(id) + } + + if announceActor != nil { + actor := streams.NewActivityStreamsActorProperty() + actor.AppendIRI(announceActor) + announce.SetActivityStreamsActor(actor) + } + + if !announcePublished.IsZero() { + published := streams.NewActivityStreamsPublishedProperty() + published.Set(announcePublished) + announce.SetActivityStreamsPublished(published) + } + + to := streams.NewActivityStreamsToProperty() + to.AppendIRI(announceTo) + announce.SetActivityStreamsTo(announceNote.GetActivityStreamsTo()) + + cc := streams.NewActivityStreamsCcProperty() + cc.AppendIRI(announceNote.GetActivityStreamsAttributedTo().Begin().GetIRI()) + announce.SetActivityStreamsCc(cc) + + if announceNote != nil { + noteIRI := streams.NewActivityStreamsObjectProperty() + noteIRI.AppendIRI(announceNote.GetJSONLDId().Get()) + announce.SetActivityStreamsObject(noteIRI) + } + + return announce +}