diff options
Diffstat (limited to 'internal/federation')
-rw-r--r-- | internal/federation/dereferencing/attachment.go | 84 | ||||
-rw-r--r-- | internal/federation/dereferencing/dereferencer.go | 3 | ||||
-rw-r--r-- | internal/federation/dereferencing/dereferencer_test.go | 42 | ||||
-rw-r--r-- | internal/federation/dereferencing/status.go | 218 | ||||
-rw-r--r-- | internal/federation/dereferencing/status_test.go | 213 | ||||
-rw-r--r-- | internal/federation/federatingdb/db.go | 2 |
6 files changed, 479 insertions, 83 deletions
diff --git a/internal/federation/dereferencing/attachment.go b/internal/federation/dereferencing/attachment.go new file mode 100644 index 000000000..fd2e3cb8f --- /dev/null +++ b/internal/federation/dereferencing/attachment.go @@ -0,0 +1,84 @@ +/* + GoToSocial + Copyright (C) 2021 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 dereferencing + +import ( + "context" + "fmt" + "net/url" + + "github.com/sirupsen/logrus" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +func (d *deref) GetRemoteAttachment(ctx context.Context, requestingUsername string, remoteAttachmentURI *url.URL, ownerAccountID string, statusID string, expectedContentType string) (*gtsmodel.MediaAttachment, error) { + l := d.log.WithFields(logrus.Fields{ + "username": requestingUsername, + "remoteAttachmentURI": remoteAttachmentURI, + }) + + maybeAttachment := >smodel.MediaAttachment{} + where := []db.Where{ + { + Key: "remote_url", + Value: remoteAttachmentURI.String(), + }, + } + + if err := d.db.GetWhere(ctx, where, maybeAttachment); err == nil { + // we already the attachment in the database + l.Debugf("GetRemoteAttachment: attachment already exists with id %s", maybeAttachment.ID) + return maybeAttachment, nil + } + + a, err := d.RefreshAttachment(ctx, requestingUsername, remoteAttachmentURI, ownerAccountID, expectedContentType) + if err != nil { + return nil, fmt.Errorf("GetRemoteAttachment: error refreshing attachment: %s", err) + } + + a.StatusID = statusID + if err := d.db.Put(ctx, a); err != nil { + if err != db.ErrAlreadyExists { + return nil, fmt.Errorf("GetRemoteAttachment: error inserting attachment: %s", err) + } + } + + return a, nil +} + +func (d *deref) RefreshAttachment(ctx context.Context, requestingUsername string, remoteAttachmentURI *url.URL, ownerAccountID string, expectedContentType string) (*gtsmodel.MediaAttachment, error) { + // it just doesn't exist or we have to refresh + t, err := d.transportController.NewTransportForUsername(ctx, requestingUsername) + if err != nil { + return nil, fmt.Errorf("RefreshAttachment: error creating transport: %s", err) + } + + attachmentBytes, err := t.DereferenceMedia(ctx, remoteAttachmentURI, expectedContentType) + if err != nil { + return nil, fmt.Errorf("RefreshAttachment: error dereferencing media: %s", err) + } + + a, err := d.mediaHandler.ProcessAttachment(ctx, attachmentBytes, ownerAccountID, remoteAttachmentURI.String()) + if err != nil { + return nil, fmt.Errorf("RefreshAttachment: error processing attachment: %s", err) + } + + return a, nil +} diff --git a/internal/federation/dereferencing/dereferencer.go b/internal/federation/dereferencing/dereferencer.go index 71625ed88..4191bd283 100644 --- a/internal/federation/dereferencing/dereferencer.go +++ b/internal/federation/dereferencing/dereferencer.go @@ -43,6 +43,9 @@ type Dereferencer interface { GetRemoteInstance(ctx context.Context, username string, remoteInstanceURI *url.URL) (*gtsmodel.Instance, error) + GetRemoteAttachment(ctx context.Context, username string, remoteAttachmentURI *url.URL, ownerAccountID string, statusID string, expectedContentType string) (*gtsmodel.MediaAttachment, error) + RefreshAttachment(ctx context.Context, requestingUsername string, remoteAttachmentURI *url.URL, ownerAccountID string, expectedContentType string) (*gtsmodel.MediaAttachment, error) + DereferenceAnnounce(ctx context.Context, announce *gtsmodel.Status, requestingUsername string) error DereferenceThread(ctx context.Context, username string, statusIRI *url.URL) error diff --git a/internal/federation/dereferencing/dereferencer_test.go b/internal/federation/dereferencing/dereferencer_test.go new file mode 100644 index 000000000..299aba10a --- /dev/null +++ b/internal/federation/dereferencing/dereferencer_test.go @@ -0,0 +1,42 @@ +/* + GoToSocial + Copyright (C) 2021 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 dereferencing_test + +import ( + "github.com/go-fed/activity/streams/vocab" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/config" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/federation/dereferencing" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +type DereferencerStandardTestSuite struct { + suite.Suite + config *config.Config + db db.DB + log *logrus.Logger + + testRemoteStatuses map[string]vocab.ActivityStreamsNote + testRemoteAccounts map[string]vocab.ActivityStreamsPerson + testAccounts map[string]*gtsmodel.Account + + dereferencer dereferencing.Dereferencer +} diff --git a/internal/federation/dereferencing/status.go b/internal/federation/dereferencing/status.go index 93ead6523..3fa1e4133 100644 --- a/internal/federation/dereferencing/status.go +++ b/internal/federation/dereferencing/status.go @@ -24,12 +24,12 @@ import ( "errors" "fmt" "net/url" + "strings" "github.com/go-fed/activity/streams" "github.com/go-fed/activity/streams/vocab" "github.com/sirupsen/logrus" "github.com/superseriousbusiness/gotosocial/internal/ap" - "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/id" ) @@ -229,8 +229,7 @@ func (d *deref) dereferenceStatusable(ctx context.Context, username string, remo // 2. Hashtags. // 3. Emojis. // 4. Mentions. -// 5. Posting account. -// 6. Replied-to-status. +// 5. Replied-to-status. // // SIDE EFFECTS: // This function will deference all of the above, insert them in the database as necessary, @@ -243,117 +242,113 @@ func (d *deref) populateStatusFields(ctx context.Context, status *gtsmodel.Statu }) l.Debug("entering function") - // make sure we have a status URI and that the domain in question isn't blocked - statusURI, err := url.Parse(status.URI) + statusIRI, err := url.Parse(status.URI) if err != nil { - return fmt.Errorf("DereferenceStatusFields: couldn't parse status URI %s: %s", status.URI, err) - } - if blocked, err := d.db.IsDomainBlocked(ctx, statusURI.Host); blocked || err != nil { - return fmt.Errorf("DereferenceStatusFields: domain %s is blocked", statusURI.Host) + return fmt.Errorf("populateStatusFields: couldn't parse status URI %s: %s", status.URI, err) } - // we can continue -- create a new transport here because we'll probably need it - t, err := d.transportController.NewTransportForUsername(ctx, requestingUsername) + blocked, err := d.db.IsURIBlocked(ctx, statusIRI) if err != nil { - return fmt.Errorf("error creating transport: %s", err) + return fmt.Errorf("populateStatusFields: error checking blocked status of %s: %s", statusIRI, err) + } + if blocked { + return fmt.Errorf("populateStatusFields: domain %s is blocked", statusIRI) } // in case the status doesn't have an id yet (ie., it hasn't entered the database yet), then create one if status.ID == "" { newID, err := id.NewULIDFromTime(status.CreatedAt) if err != nil { - return err + return fmt.Errorf("populateStatusFields: error creating ulid for status: %s", err) } status.ID = newID } // 1. Media attachments. - // - // At this point we should know: - // * the media type of the file we're looking for (a.File.ContentType) - // * the blurhash (a.Blurhash) - // * the file type (a.Type) - // * the remote URL (a.RemoteURL) - // This should be enough to pass along to the media processor. - attachmentIDs := []string{} - for _, a := range status.Attachments { - l.Tracef("dereferencing attachment: %+v", a) - - // it might have been processed elsewhere so check first if it's already in the database or not - maybeAttachment := >smodel.MediaAttachment{} - err := d.db.GetWhere(ctx, []db.Where{{Key: "remote_url", Value: a.RemoteURL}}, maybeAttachment) - if err == nil { - // we already have it in the db, dereferenced, no need to do it again - l.Tracef("attachment already exists with id %s", maybeAttachment.ID) - attachmentIDs = append(attachmentIDs, maybeAttachment.ID) - continue - } - if err != db.ErrNoEntries { - // we have a real error - return fmt.Errorf("error checking db for existence of attachment with remote url %s: %s", a.RemoteURL, err) - } - // it just doesn't exist yet so carry on - l.Debug("attachment doesn't exist yet, calling ProcessRemoteAttachment", a) - deferencedAttachment, err := d.mediaHandler.ProcessRemoteAttachment(ctx, t, a, status.AccountID) - if err != nil { - l.Errorf("error dereferencing status attachment: %s", err) - continue - } - l.Debugf("dereferenced attachment: %+v", deferencedAttachment) - deferencedAttachment.StatusID = status.ID - deferencedAttachment.Description = a.Description - if err := d.db.Put(ctx, deferencedAttachment); err != nil { - return fmt.Errorf("error inserting dereferenced attachment with remote url %s: %s", a.RemoteURL, err) - } - attachmentIDs = append(attachmentIDs, deferencedAttachment.ID) + if err := d.populateStatusAttachments(ctx, status, requestingUsername); err != nil { + return fmt.Errorf("populateStatusFields: error populating status attachments: %s", err) } - status.AttachmentIDs = attachmentIDs // 2. Hashtags + // TODO // 3. Emojis + // TODO // 4. Mentions + if err := d.populateStatusMentions(ctx, status, requestingUsername); err != nil { + return fmt.Errorf("populateStatusFields: error populating status mentions: %s", err) + } + + // 5. Replied-to-status. + if err := d.populateStatusRepliedTo(ctx, status, requestingUsername); err != nil { + return fmt.Errorf("populateStatusFields: error populating status repliedTo: %s", err) + } + + return nil +} + +func (d *deref) populateStatusMentions(ctx context.Context, status *gtsmodel.Status, requestingUsername string) error { + l := d.log + // At this point, mentions should have the namestring and mentionedAccountURI set on them. - // - // We should dereference any accounts mentioned here which we don't have in our db yet, by their URI. + // We can use these to find the accounts. + mentionIDs := []string{} + newMentions := []*gtsmodel.Mention{} for _, m := range status.Mentions { if m.ID != "" { // we've already populated this mention, since it has an ID - l.Debug("mention already populated") + l.Debug("populateStatusMentions: mention already populated") + mentionIDs = append(mentionIDs, m.ID) + newMentions = append(newMentions, m) continue } if m.TargetAccountURI == "" { - // can't do anything with this mention - l.Debug("target URI not set on mention") + l.Debug("populateStatusMentions: target URI not set on mention") continue } targetAccountURI, err := url.Parse(m.TargetAccountURI) if err != nil { - l.Debugf("error parsing mentioned account uri %s: %s", m.TargetAccountURI, err) + l.Debugf("populateStatusMentions: error parsing mentioned account uri %s: %s", m.TargetAccountURI, err) continue } var targetAccount *gtsmodel.Account - if a, err := d.db.GetAccountByURL(ctx, targetAccountURI.String()); err == nil { - targetAccount = a - } else if a, _, err := d.GetRemoteAccount(ctx, requestingUsername, targetAccountURI, false); err == nil { - targetAccount = a + errs := []string{} + + // check if account is in the db already + if a, err := d.db.GetAccountByURL(ctx, targetAccountURI.String()); err != nil { + errs = append(errs, err.Error()) } else { - // we can't find the target account so bail - l.Debug("can't retrieve account targeted by mention") + l.Debugf("populateStatusMentions: got target account %s with id %s through GetAccountByURL", targetAccountURI, a.ID) + targetAccount = a + } + + if targetAccount == nil { + // we didn't find the account in our database already + // check if we can get the account remotely (dereference it) + if a, _, err := d.GetRemoteAccount(ctx, requestingUsername, targetAccountURI, false); err != nil { + errs = append(errs, err.Error()) + } else { + l.Debugf("populateStatusMentions: got target account %s with id %s through GetRemoteAccount", targetAccountURI, a.ID) + targetAccount = a + } + } + + if targetAccount == nil { + l.Debugf("populateStatusMentions: couldn't get target account %s: %s", m.TargetAccountURI, strings.Join(errs, " : ")) continue } mID, err := id.NewRandomULID() if err != nil { - return err + return fmt.Errorf("populateStatusMentions: error generating ulid: %s", err) } - m = >smodel.Mention{ + newMention := >smodel.Mention{ ID: mID, StatusID: status.ID, Status: m.Status, @@ -369,32 +364,91 @@ func (d *deref) populateStatusFields(ctx context.Context, status *gtsmodel.Statu TargetAccountURL: targetAccount.URL, } - if err := d.db.Put(ctx, m); err != nil { - return fmt.Errorf("error creating mention: %s", err) + if err := d.db.Put(ctx, newMention); err != nil { + return fmt.Errorf("populateStatusMentions: error creating mention: %s", err) } - mentionIDs = append(mentionIDs, m.ID) + + mentionIDs = append(mentionIDs, newMention.ID) + newMentions = append(newMentions, newMention) } + status.MentionIDs = mentionIDs + status.Mentions = newMentions - // status has replyToURI but we don't have an ID yet for the status it replies to + return nil +} + +func (d *deref) populateStatusAttachments(ctx context.Context, status *gtsmodel.Status, requestingUsername string) error { + l := d.log + + // At this point we should know: + // * the media type of the file we're looking for (a.File.ContentType) + // * the file type (a.Type) + // * the remote URL (a.RemoteURL) + // This should be enough to dereference the piece of media. + + attachmentIDs := []string{} + attachments := []*gtsmodel.MediaAttachment{} + + for _, a := range status.Attachments { + + aURL, err := url.Parse(a.RemoteURL) + if err != nil { + l.Errorf("populateStatusAttachments: couldn't parse attachment url %s: %s", a.RemoteURL, err) + continue + } + + attachment, err := d.GetRemoteAttachment(ctx, requestingUsername, aURL, status.AccountID, status.ID, a.File.ContentType) + if err != nil { + l.Errorf("populateStatusAttachments: couldn't get remote attachment %s: %s", a.RemoteURL, err) + } + + attachmentIDs = append(attachmentIDs, attachment.ID) + attachments = append(attachments, attachment) + } + + status.AttachmentIDs = attachmentIDs + status.Attachments = attachments + + return nil +} + +func (d *deref) populateStatusRepliedTo(ctx context.Context, status *gtsmodel.Status, requestingUsername string) error { if status.InReplyToURI != "" && status.InReplyToID == "" { statusURI, err := url.Parse(status.InReplyToURI) if err != nil { return err } - if replyToStatus, err := d.db.GetStatusByURI(ctx, status.InReplyToURI); err == nil { - // we have the status - status.InReplyToID = replyToStatus.ID - status.InReplyTo = replyToStatus - status.InReplyToAccountID = replyToStatus.AccountID - status.InReplyToAccount = replyToStatus.Account - } else if replyToStatus, _, _, err := d.GetRemoteStatus(ctx, requestingUsername, statusURI, false); err == nil { - // we got the status - status.InReplyToID = replyToStatus.ID - status.InReplyTo = replyToStatus - status.InReplyToAccountID = replyToStatus.AccountID - status.InReplyToAccount = replyToStatus.Account + + var replyToStatus *gtsmodel.Status + errs := []string{} + + // see if we have the status in our db already + if s, err := d.db.GetStatusByURI(ctx, status.InReplyToURI); err != nil { + errs = append(errs, err.Error()) + } else { + replyToStatus = s + } + + if replyToStatus == nil { + // didn't find the status in our db, try to get it remotely + if s, _, _, err := d.GetRemoteStatus(ctx, requestingUsername, statusURI, false); err != nil { + errs = append(errs, err.Error()) + } else { + replyToStatus = s + } } + + if replyToStatus == nil { + return fmt.Errorf("populateStatusRepliedTo: couldn't get reply to status with uri %s: %s", statusURI, strings.Join(errs, " : ")) + } + + // we have the status + status.InReplyToID = replyToStatus.ID + status.InReplyTo = replyToStatus + status.InReplyToAccountID = replyToStatus.AccountID + status.InReplyToAccount = replyToStatus.Account } + return nil } diff --git a/internal/federation/dereferencing/status_test.go b/internal/federation/dereferencing/status_test.go new file mode 100644 index 000000000..2d259682b --- /dev/null +++ b/internal/federation/dereferencing/status_test.go @@ -0,0 +1,213 @@ +/* + GoToSocial + Copyright (C) 2021 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 dereferencing_test + +import ( + "bytes" + "context" + "encoding/json" + "io" + "net/http" + "testing" + "time" + + "github.com/go-fed/activity/streams" + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/federation/dereferencing" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/transport" + "github.com/superseriousbusiness/gotosocial/testrig" +) + +type StatusTestSuite struct { + DereferencerStandardTestSuite +} + +// mockTransportController returns basically a miniature muxer, which returns a different +// value based on the request URL. It can be used to return remote statuses, profiles, etc, +// as though they were actually being dereferenced. If the URL doesn't correspond to any person +// or note or attachment that we have stored, then just a 200 code will be returned, with an empty body. +func (suite *StatusTestSuite) mockTransportController() transport.Controller { + do := func(req *http.Request) (*http.Response, error) { + suite.log.Debugf("received request for %s", req.URL) + + responseBytes := []byte{} + + note, ok := suite.testRemoteStatuses[req.URL.String()] + if ok { + // the request is for a note that we have stored + noteI, err := streams.Serialize(note) + if err != nil { + panic(err) + } + noteJson, err := json.Marshal(noteI) + if err != nil { + panic(err) + } + responseBytes = noteJson + } + + person, ok := suite.testRemoteAccounts[req.URL.String()] + if ok { + // the request is for a person that we have stored + personI, err := streams.Serialize(person) + if err != nil { + panic(err) + } + personJson, err := json.Marshal(personI) + if err != nil { + panic(err) + } + responseBytes = personJson + } + + if len(responseBytes) != 0 { + // we found something, so print what we're going to return + suite.log.Debugf("returning response %s", string(responseBytes)) + } + + reader := bytes.NewReader(responseBytes) + readCloser := io.NopCloser(reader) + response := &http.Response{ + StatusCode: 200, + Body: readCloser, + } + + return response, nil + } + mockClient := testrig.NewMockHTTPClient(do) + return testrig.NewTestTransportController(mockClient, suite.db) +} + +func (suite *StatusTestSuite) SetupSuite() { + suite.testAccounts = testrig.NewTestAccounts() + suite.testRemoteStatuses = testrig.NewTestFediStatuses() + suite.testRemoteAccounts = testrig.NewTestFediPeople() +} + +func (suite *StatusTestSuite) SetupTest() { + suite.config = testrig.NewTestConfig() + suite.db = testrig.NewTestDB() + suite.log = testrig.NewTestLog() + suite.dereferencer = dereferencing.NewDereferencer(suite.config, + suite.db, + testrig.NewTestTypeConverter(suite.db), + suite.mockTransportController(), + testrig.NewTestMediaHandler(suite.db, testrig.NewTestStorage()), + suite.log) + testrig.StandardDBSetup(suite.db, nil) +} + +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) + 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) + suite.Equal("https://unknown-instance.com/users/@brand_new_person/01FE4NTHKWW7THT67EF10EB839", status.URL) + suite.Equal("Hello world!", status.Content) + suite.Equal("https://unknown-instance.com/users/brand_new_person", status.AccountURI) + suite.False(status.Local) + suite.Empty(status.ContentWarning) + suite.Equal(gtsmodel.VisibilityPublic, status.Visibility) + suite.Equal(gtsmodel.ActivityStreamsNote, status.ActivityStreamsType) + + // status should be in the database + dbStatus, err := suite.db.GetStatusByURI(context.Background(), status.URI) + suite.NoError(err) + suite.Equal(status.ID, dbStatus.ID) + + // account should be in the database now too + account, err := suite.db.GetAccountByURI(context.Background(), status.AccountURI) + suite.NoError(err) + suite.NotNil(account) + suite.True(account.Discoverable) + suite.Equal("https://unknown-instance.com/users/brand_new_person", account.URI) + suite.Equal("hey I'm a new person, your instance hasn't seen me yet uwu", account.Note) + suite.Equal("Geoff Brando New Personson", account.DisplayName) + suite.Equal("brand_new_person", account.Username) + suite.NotNil(account.PublicKey) + suite.Nil(account.PrivateKey) +} + +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) + 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) + suite.Equal("https://unknown-instance.com/users/@brand_new_person/01FE5Y30E3W4P7TRE0R98KAYQV", status.URL) + suite.Equal("Hey @the_mighty_zork@localhost:8080 how's it going?", status.Content) + suite.Equal("https://unknown-instance.com/users/brand_new_person", status.AccountURI) + suite.False(status.Local) + suite.Empty(status.ContentWarning) + suite.Equal(gtsmodel.VisibilityPublic, status.Visibility) + suite.Equal(gtsmodel.ActivityStreamsNote, status.ActivityStreamsType) + + // status should be in the database + dbStatus, err := suite.db.GetStatusByURI(context.Background(), status.URI) + suite.NoError(err) + suite.Equal(status.ID, dbStatus.ID) + + // account should be in the database now too + account, err := suite.db.GetAccountByURI(context.Background(), status.AccountURI) + suite.NoError(err) + suite.NotNil(account) + suite.True(account.Discoverable) + suite.Equal("https://unknown-instance.com/users/brand_new_person", account.URI) + suite.Equal("hey I'm a new person, your instance hasn't seen me yet uwu", account.Note) + suite.Equal("Geoff Brando New Personson", account.DisplayName) + suite.Equal("brand_new_person", account.Username) + suite.NotNil(account.PublicKey) + suite.Nil(account.PrivateKey) + + // we should have a mention in the database + m := >smodel.Mention{} + err = suite.db.GetWhere(context.Background(), []db.Where{{Key: "status_id", Value: status.ID}}, m) + suite.NoError(err) + suite.NotNil(m) + suite.Equal(status.ID, m.StatusID) + suite.Equal(account.ID, m.OriginAccountID) + suite.Equal(fetchingAccount.ID, m.TargetAccountID) + suite.Equal(account.URI, m.OriginAccountURI) + suite.WithinDuration(time.Now(), m.CreatedAt, 5*time.Minute) + suite.WithinDuration(time.Now(), m.UpdatedAt, 5*time.Minute) + suite.False(m.Silent) +} + +func (suite *StatusTestSuite) TearDownTest() { + testrig.StandardDBTeardown(suite.db) +} + +func TestStatusTestSuite(t *testing.T) { + suite.Run(t, new(StatusTestSuite)) +} diff --git a/internal/federation/federatingdb/db.go b/internal/federation/federatingdb/db.go index 5f8b9ad90..8d41d082e 100644 --- a/internal/federation/federatingdb/db.go +++ b/internal/federation/federatingdb/db.go @@ -60,7 +60,7 @@ func New(db db.DB, config *config.Config, log *logrus.Logger) DB { db: db, config: config, log: log, - typeConverter: typeutils.NewConverter(config, db), + typeConverter: typeutils.NewConverter(config, db, log), } go fdb.cleanupLocks() return &fdb |