// 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 . package bundb_test import ( "context" "testing" "time" "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/testrig" ) type TimelineTestSuite struct { BunDBStandardTestSuite } func getFutureStatus() *gtsmodel.Status { theDistantFuture := time.Now().Add(876600 * time.Hour) id, err := id.NewULIDFromTime(theDistantFuture) if err != nil { panic(err) } return >smodel.Status{ ID: id, URI: "http://localhost:8080/users/admin/statuses/" + id, URL: "http://localhost:8080/@admin/statuses/" + id, Content: "it's the future, wooooooooooooooooooooooooooooooooo", Text: "it's the future, wooooooooooooooooooooooooooooooooo", AttachmentIDs: []string{}, TagIDs: []string{}, MentionIDs: []string{}, EmojiIDs: []string{}, CreatedAt: theDistantFuture, UpdatedAt: theDistantFuture, Local: testrig.TrueBool(), AccountURI: "http://localhost:8080/users/admin", AccountID: "01F8MH17FWEB39HZJ76B6VXSKF", InReplyToID: "", BoostOfID: "", ContentWarning: "", Visibility: gtsmodel.VisibilityPublic, Sensitive: testrig.FalseBool(), Language: "en", CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F", Federated: testrig.TrueBool(), Boostable: testrig.TrueBool(), Replyable: testrig.TrueBool(), Likeable: testrig.TrueBool(), ActivityStreamsType: ap.ObjectNote, } } func (suite *TimelineTestSuite) publicCount() int { var publicCount int for _, status := range suite.testStatuses { if status.Visibility == gtsmodel.VisibilityPublic && status.BoostOfID == "" { publicCount++ } } return publicCount } func (suite *TimelineTestSuite) checkStatuses(statuses []*gtsmodel.Status, maxID string, minID string, expectedLength int) { if l := len(statuses); l != expectedLength { suite.FailNow("", "expected %d statuses in slice, got %d", expectedLength, l) } else if l == 0 { // Can't test empty slice. return } // Check ordering + bounds of statuses. highest := statuses[0].ID for _, status := range statuses { id := status.ID if id >= maxID { suite.FailNow("", "%s greater than maxID %s", id, maxID) } if id <= minID { suite.FailNow("", "%s smaller than minID %s", id, minID) } if id > highest { suite.FailNow("", "statuses in slice were not ordered highest -> lowest ID") } highest = id } } func (suite *TimelineTestSuite) TestGetPublicTimeline() { ctx := context.Background() s, err := suite.db.GetPublicTimeline(ctx, "", "", "", 20, false) if err != nil { suite.FailNow(err.Error()) } suite.checkStatuses(s, id.Highest, id.Lowest, suite.publicCount()) } func (suite *TimelineTestSuite) TestGetPublicTimelineWithFutureStatus() { ctx := context.Background() // Insert a status set far in the // future, it shouldn't be retrieved. futureStatus := getFutureStatus() if err := suite.db.PutStatus(ctx, futureStatus); err != nil { suite.FailNow(err.Error()) } s, err := suite.db.GetPublicTimeline(ctx, "", "", "", 20, false) if err != nil { suite.FailNow(err.Error()) } suite.NotContains(s, futureStatus) suite.checkStatuses(s, id.Highest, id.Lowest, suite.publicCount()) } func (suite *TimelineTestSuite) TestGetHomeTimeline() { var ( ctx = context.Background() viewingAccount = suite.testAccounts["local_account_1"] ) s, err := suite.db.GetHomeTimeline(ctx, viewingAccount.ID, "", "", "", 20, false) if err != nil { suite.FailNow(err.Error()) } suite.checkStatuses(s, id.Highest, id.Lowest, 16) } func (suite *TimelineTestSuite) TestGetHomeTimelineWithFutureStatus() { var ( ctx = context.Background() viewingAccount = suite.testAccounts["local_account_1"] ) // Insert a status set far in the // future, it shouldn't be retrieved. futureStatus := getFutureStatus() if err := suite.db.PutStatus(ctx, futureStatus); err != nil { suite.FailNow(err.Error()) } s, err := suite.db.GetHomeTimeline(ctx, viewingAccount.ID, "", "", "", 20, false) if err != nil { suite.FailNow(err.Error()) } suite.NotContains(s, futureStatus) suite.checkStatuses(s, id.Highest, id.Lowest, 16) } func (suite *TimelineTestSuite) TestGetHomeTimelineBackToFront() { var ( ctx = context.Background() viewingAccount = suite.testAccounts["local_account_1"] ) s, err := suite.db.GetHomeTimeline(ctx, viewingAccount.ID, "", "", id.Lowest, 5, false) if err != nil { suite.FailNow(err.Error()) } suite.checkStatuses(s, id.Highest, id.Lowest, 5) suite.Equal("01F8MHAYFKS4KMXF8K5Y1C0KRN", s[0].ID) suite.Equal("01F8MH75CBF9JFX4ZAD54N0W0R", s[len(s)-1].ID) } func (suite *TimelineTestSuite) TestGetHomeTimelineFromHighest() { var ( ctx = context.Background() viewingAccount = suite.testAccounts["local_account_1"] ) s, err := suite.db.GetHomeTimeline(ctx, viewingAccount.ID, id.Highest, "", "", 5, false) if err != nil { suite.FailNow(err.Error()) } suite.checkStatuses(s, id.Highest, id.Lowest, 5) suite.Equal("01G36SF3V6Y6V5BF9P4R7PQG7G", s[0].ID) suite.Equal("01FCTA44PW9H1TB328S9AQXKDS", s[len(s)-1].ID) } func (suite *TimelineTestSuite) TestGetListTimelineNoParams() { var ( ctx = context.Background() list = suite.testLists["local_account_1_list_1"] ) s, err := suite.db.GetListTimeline(ctx, list.ID, "", "", "", 20) if err != nil { suite.FailNow(err.Error()) } suite.checkStatuses(s, id.Highest, id.Lowest, 11) } func (suite *TimelineTestSuite) TestGetListTimelineMaxID() { var ( ctx = context.Background() list = suite.testLists["local_account_1_list_1"] ) s, err := suite.db.GetListTimeline(ctx, list.ID, id.Highest, "", "", 5) if err != nil { suite.FailNow(err.Error()) } suite.checkStatuses(s, id.Highest, id.Lowest, 5) suite.Equal("01G36SF3V6Y6V5BF9P4R7PQG7G", s[0].ID) suite.Equal("01FCQSQ667XHJ9AV9T27SJJSX5", s[len(s)-1].ID) } func (suite *TimelineTestSuite) TestGetListTimelineMinID() { var ( ctx = context.Background() list = suite.testLists["local_account_1_list_1"] ) s, err := suite.db.GetListTimeline(ctx, list.ID, "", "", id.Lowest, 5) if err != nil { suite.FailNow(err.Error()) } suite.checkStatuses(s, id.Highest, id.Lowest, 5) suite.Equal("01F8MHC8VWDRBQR0N1BATDDEM5", s[0].ID) suite.Equal("01F8MH75CBF9JFX4ZAD54N0W0R", s[len(s)-1].ID) } func (suite *TimelineTestSuite) TestGetListTimelineMinIDPagingUp() { var ( ctx = context.Background() list = suite.testLists["local_account_1_list_1"] ) s, err := suite.db.GetListTimeline(ctx, list.ID, "", "", "01F8MHC8VWDRBQR0N1BATDDEM5", 5) if err != nil { suite.FailNow(err.Error()) } suite.checkStatuses(s, id.Highest, "01F8MHC8VWDRBQR0N1BATDDEM5", 5) suite.Equal("01G20ZM733MGN8J344T4ZDDFY1", s[0].ID) suite.Equal("01F8MHCP5P2NWYQ416SBA0XSEV", s[len(s)-1].ID) } func (suite *TimelineTestSuite) TestGetTagTimelineNoParams() { var ( ctx = context.Background() tag = suite.testTags["welcome"] ) s, err := suite.db.GetTagTimeline(ctx, tag.ID, "", "", "", 1) if err != nil { suite.FailNow(err.Error()) } suite.checkStatuses(s, id.Highest, id.Lowest, 1) suite.Equal("01F8MH75CBF9JFX4ZAD54N0W0R", s[0].ID) } func TestTimelineTestSuite(t *testing.T) { suite.Run(t, new(TimelineTestSuite)) }