diff options
| author | 2025-05-06 13:30:23 +0000 | |
|---|---|---|
| committer | 2025-05-06 13:30:23 +0000 | |
| commit | 8264b63337e19e643f79190bfe93b7bd9a62acd4 (patch) | |
| tree | 1c7ca69270600e0eac0cdee611d503b7d1234b7c /internal/cache/timeline/status_test.go | |
| parent | [bugfix] broken nootel function signatures (#4142) (diff) | |
| download | gotosocial-8264b63337e19e643f79190bfe93b7bd9a62acd4.tar.xz | |
[bugfix] ensure timeline limit query is respected (#4141)
# Description
Fixes a bug in the new timeline code in which the limit query parameter wasn't always being fulfilled, in which case some clients like Tusky would then assume it didn't need to add a "load more" placeholder view even when there were more statuses to be loaded. This also fiddles around a bit in the logging middleware handler to add some more code comments, and add logging of full request URIs when it is safe to do so.
## Checklist
- [x] I/we have read the [GoToSocial contribution guidelines](https://codeberg.org/superseriousbusiness/gotosocial/src/branch/main/CONTRIBUTING.md).
- [x] I/we have discussed the proposed changes already, either in an issue on the repository, or in the Matrix chat.
- [x] I/we have not leveraged AI to create the proposed changes.
- [x] I/we have performed a self-review of added code.
- [x] I/we have written code that is legible and maintainable by others.
- [x] I/we have commented the added code, particularly in hard-to-understand areas.
- [ ] I/we have made any necessary changes to documentation.
- [x] I/we have added tests that cover new code.
- [x] I/we have run tests and they pass locally with the changes.
- [x] I/we have run `go fmt ./...` and `golangci-lint run`.
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4141
Co-authored-by: kim <grufwub@gmail.com>
Co-committed-by: kim <grufwub@gmail.com>
Diffstat (limited to 'internal/cache/timeline/status_test.go')
| -rw-r--r-- | internal/cache/timeline/status_test.go | 83 |
1 files changed, 83 insertions, 0 deletions
diff --git a/internal/cache/timeline/status_test.go b/internal/cache/timeline/status_test.go index 6a288d2ea..fc7e43da8 100644 --- a/internal/cache/timeline/status_test.go +++ b/internal/cache/timeline/status_test.go @@ -18,11 +18,16 @@ package timeline import ( + "context" + "fmt" "slices" "testing" apimodel "code.superseriousbusiness.org/gotosocial/internal/api/model" "code.superseriousbusiness.org/gotosocial/internal/gtsmodel" + "code.superseriousbusiness.org/gotosocial/internal/id" + "code.superseriousbusiness.org/gotosocial/internal/log" + "code.superseriousbusiness.org/gotosocial/internal/paging" "codeberg.org/gruf/go-structr" "github.com/stretchr/testify/assert" ) @@ -60,6 +65,46 @@ var testStatusMeta = []*StatusMeta{ }, } +func TestStatusTimelineLoadLimit(t *testing.T) { + var tt StatusTimeline + tt.Init(1000) + + // Prepare new context for the duration of this test. + ctx, cncl := context.WithCancel(context.Background()) + defer cncl() + + // Clone the input test status data. + data := slices.Clone(testStatusMeta) + + // Insert test data into timeline. + _ = tt.cache.Insert(data...) + + // Manually mark timeline as 'preloaded'. + tt.preloader.CheckPreload(tt.preloader.Done) + + // Craft a new page for selection, + // setting placeholder min / max values + // but in particular setting a limit + // HIGHER than currently cached values. + page := new(paging.Page) + page.Min = paging.MinID(id.Lowest) + page.Max = paging.MaxID(id.Highest) + page.Limit = len(data) + 10 + + // Load crafted page from the cache. This + // SHOULD load all cached entries, then + // generate an extra 10 statuses up to limit. + apiStatuses, _, _, err := tt.Load(ctx, + page, + loadGeneratedStatusPage, + loadStatusIDsFrom(data), + nil, // no filtering + func(status *gtsmodel.Status) (*apimodel.Status, error) { return new(apimodel.Status), nil }, + ) + assert.NoError(t, err) + assert.Len(t, apiStatuses, page.Limit) +} + func TestStatusTimelineUnprepare(t *testing.T) { var tt StatusTimeline tt.Init(1000) @@ -301,6 +346,44 @@ func TestStatusTimelineTrim(t *testing.T) { assert.Equal(t, before, tt.cache.Len()) } +// loadStatusIDsFrom imitates loading of statuses of given IDs from the database, instead selecting +// statuses with appropriate IDs from the given slice of status meta, converting them to statuses. +func loadStatusIDsFrom(data []*StatusMeta) func(ids []string) ([]*gtsmodel.Status, error) { + return func(ids []string) ([]*gtsmodel.Status, error) { + var statuses []*gtsmodel.Status + for _, id := range ids { + i := slices.IndexFunc(data, func(s *StatusMeta) bool { + return s.ID == id + }) + if i < 0 || i >= len(data) { + panic(fmt.Sprintf("could not find %s in %v", id, log.VarDump(data))) + } + statuses = append(statuses, >smodel.Status{ + ID: data[i].ID, + AccountID: data[i].AccountID, + BoostOfID: data[i].BoostOfID, + BoostOfAccountID: data[i].BoostOfAccountID, + }) + } + return statuses, nil + } +} + +// loadGeneratedStatusPage imitates loading of a given page of statuses, +// simply generating new statuses until the given page's limit is reached. +func loadGeneratedStatusPage(page *paging.Page) ([]*gtsmodel.Status, error) { + var statuses []*gtsmodel.Status + for range page.Limit { + statuses = append(statuses, >smodel.Status{ + ID: id.NewULID(), + AccountID: id.NewULID(), + BoostOfID: id.NewULID(), + BoostOfAccountID: id.NewULID(), + }) + } + return statuses, nil +} + // containsStatusID returns whether timeline contains a status with ID. func containsStatusID(t *StatusTimeline, id string) bool { return getStatusByID(t, id) != nil |
