mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-30 21:42:24 -05:00 
			
		
		
		
	[bugfix] further paging mishaps (#2884)
* FURTHER paging shenanigans 🥲
* remove cursor logic from ToLinkURL()
* fix up paging tests
---------
Co-authored-by: tobi <tobi.smethurst@protonmail.com>
	
	
This commit is contained in:
		
					parent
					
						
							
								ec7c983e46
							
						
					
				
			
			
				commit
				
					
						a8254a40e7
					
				
			
		
					 5 changed files with 163 additions and 138 deletions
				
			
		|  | @ -161,7 +161,7 @@ func (suite *RepliesGetTestSuite) TestGetRepliesNext() { | ||||||
| 		"type":         "OrderedCollectionPage", | 		"type":         "OrderedCollectionPage", | ||||||
| 		"id":           targetStatus.URI + "/replies?limit=20&only_other_accounts=false", | 		"id":           targetStatus.URI + "/replies?limit=20&only_other_accounts=false", | ||||||
| 		"partOf":       targetStatus.URI + "/replies?only_other_accounts=false", | 		"partOf":       targetStatus.URI + "/replies?only_other_accounts=false", | ||||||
| 		"next":         "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY/replies?limit=20&min_id=01FF25D5Q0DH7CHD57CTRS6WK0&only_other_accounts=false", | 		"next":         "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY/replies?limit=20&max_id=01FF25D5Q0DH7CHD57CTRS6WK0&only_other_accounts=false", | ||||||
| 		"prev":         "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY/replies?limit=20&min_id=01FF25D5Q0DH7CHD57CTRS6WK0&only_other_accounts=false", | 		"prev":         "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY/replies?limit=20&min_id=01FF25D5Q0DH7CHD57CTRS6WK0&only_other_accounts=false", | ||||||
| 		"orderedItems": []string{"http://localhost:8080/users/admin/statuses/01FF25D5Q0DH7CHD57CTRS6WK0"}, | 		"orderedItems": []string{"http://localhost:8080/users/admin/statuses/01FF25D5Q0DH7CHD57CTRS6WK0"}, | ||||||
| 		"totalItems":   1, | 		"totalItems":   1, | ||||||
|  |  | ||||||
|  | @ -21,8 +21,7 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io/ioutil" | 	"io" | ||||||
| 	"math/rand" |  | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/http/httptest" | 	"net/http/httptest" | ||||||
| 	"net/url" | 	"net/url" | ||||||
|  | @ -42,9 +41,6 @@ import ( | ||||||
| 	"github.com/tomnomnom/linkheader" | 	"github.com/tomnomnom/linkheader" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // random reader according to current-time source seed. |  | ||||||
| var randRd = rand.New(rand.NewSource(time.Now().Unix())) |  | ||||||
| 
 |  | ||||||
| type FollowTestSuite struct { | type FollowTestSuite struct { | ||||||
| 	AccountStandardTestSuite | 	AccountStandardTestSuite | ||||||
| } | } | ||||||
|  | @ -76,33 +72,33 @@ func (suite *FollowTestSuite) TestFollowSelf() { | ||||||
| 	defer result.Body.Close() | 	defer result.Body.Close() | ||||||
| 
 | 
 | ||||||
| 	// check the response | 	// check the response | ||||||
| 	b, err := ioutil.ReadAll(result.Body) | 	b, err := io.ReadAll(result.Body) | ||||||
| 	_ = b | 	_ = b | ||||||
| 	assert.NoError(suite.T(), err) | 	assert.NoError(suite.T(), err) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *FollowTestSuite) TestGetFollowersPageBackwardLimit2() { | func (suite *FollowTestSuite) TestGetFollowersPageNewestToOldestLimit2() { | ||||||
| 	suite.testGetFollowersPage(2, "backward") | 	suite.testGetFollowersPage(2, "newestToOldest") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *FollowTestSuite) TestGetFollowersPageBackwardLimit4() { | func (suite *FollowTestSuite) TestGetFollowersPageNewestToOldestLimit4() { | ||||||
| 	suite.testGetFollowersPage(4, "backward") | 	suite.testGetFollowersPage(4, "newestToOldest") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *FollowTestSuite) TestGetFollowersPageBackwardLimit6() { | func (suite *FollowTestSuite) TestGetFollowersPageNewestToOldestLimit6() { | ||||||
| 	suite.testGetFollowersPage(6, "backward") | 	suite.testGetFollowersPage(6, "newestToOldest") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *FollowTestSuite) TestGetFollowersPageForwardLimit2() { | func (suite *FollowTestSuite) TestGetFollowersPageOldestToNewestLimit2() { | ||||||
| 	suite.testGetFollowersPage(2, "forward") | 	suite.testGetFollowersPage(2, "oldestToNewest") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *FollowTestSuite) TestGetFollowersPageForwardLimit4() { | func (suite *FollowTestSuite) TestGetFollowersPageOldestToNewestLimit4() { | ||||||
| 	suite.testGetFollowersPage(4, "forward") | 	suite.testGetFollowersPage(4, "oldestToNewest") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *FollowTestSuite) TestGetFollowersPageForwardLimit6() { | func (suite *FollowTestSuite) TestGetFollowersPageOldestToNewestLimit6() { | ||||||
| 	suite.testGetFollowersPage(6, "forward") | 	suite.testGetFollowersPage(6, "oldestToNewest") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string) { | func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string) { | ||||||
|  | @ -117,8 +113,11 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string) | ||||||
| 
 | 
 | ||||||
| 	var i int | 	var i int | ||||||
| 
 | 
 | ||||||
| 	for _, targetAccount := range suite.testAccounts { | 	// Have each account in the testrig follow the account | ||||||
| 		if targetAccount.ID == requestingAccount.ID { | 	// that is requesting their followers from the API. | ||||||
|  | 	for _, account := range suite.testAccounts { | ||||||
|  | 		targetAccount := requestingAccount | ||||||
|  | 		if account.ID == targetAccount.ID { | ||||||
| 			// we cannot be our own target... | 			// we cannot be our own target... | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  | @ -132,9 +131,9 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string) | ||||||
| 			ID:              id, | 			ID:              id, | ||||||
| 			CreatedAt:       now, | 			CreatedAt:       now, | ||||||
| 			UpdatedAt:       now, | 			UpdatedAt:       now, | ||||||
| 			URI:             fmt.Sprintf("%s/follow/%s", targetAccount.URI, id), | 			URI:             fmt.Sprintf("%s/follow/%s", account.URI, id), | ||||||
| 			AccountID:       targetAccount.ID, | 			AccountID:       account.ID, | ||||||
| 			TargetAccountID: requestingAccount.ID, | 			TargetAccountID: targetAccount.ID, | ||||||
| 		}) | 		}) | ||||||
| 		suite.NoError(err) | 		suite.NoError(err) | ||||||
| 
 | 
 | ||||||
|  | @ -152,15 +151,17 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string) | ||||||
| 	var query string | 	var query string | ||||||
| 
 | 
 | ||||||
| 	switch direction { | 	switch direction { | ||||||
| 	case "backward": | 	case "newestToOldest": | ||||||
| 		// Set the starting query to page backward from newest. | 		// Set the starting query to page from | ||||||
|  | 		// newest (ie., first entry in slice). | ||||||
| 		acc := expectAccounts[0].(*model.Account) | 		acc := expectAccounts[0].(*model.Account) | ||||||
| 		newest, _ := suite.db.GetFollow(ctx, acc.ID, requestingAccount.ID) | 		newest, _ := suite.db.GetFollow(ctx, acc.ID, requestingAccount.ID) | ||||||
| 		expectAccounts = expectAccounts[1:] | 		expectAccounts = expectAccounts[1:] | ||||||
| 		query = fmt.Sprintf("limit=%d&max_id=%s", limit, newest.ID) | 		query = fmt.Sprintf("limit=%d&max_id=%s", limit, newest.ID) | ||||||
| 
 | 
 | ||||||
| 	case "forward": | 	case "oldestToNewest": | ||||||
| 		// Set the starting query to page forward from the oldest. | 		// Set the starting query to page from | ||||||
|  | 		// oldest (ie., last entry in slice). | ||||||
| 		acc := expectAccounts[len(expectAccounts)-1].(*model.Account) | 		acc := expectAccounts[len(expectAccounts)-1].(*model.Account) | ||||||
| 		oldest, _ := suite.db.GetFollow(ctx, acc.ID, requestingAccount.ID) | 		oldest, _ := suite.db.GetFollow(ctx, acc.ID, requestingAccount.ID) | ||||||
| 		expectAccounts = expectAccounts[:len(expectAccounts)-1] | 		expectAccounts = expectAccounts[:len(expectAccounts)-1] | ||||||
|  | @ -208,9 +209,9 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string) | ||||||
| 		) | 		) | ||||||
| 
 | 
 | ||||||
| 		switch direction { | 		switch direction { | ||||||
| 		case "backward": | 		case "newestToOldest": | ||||||
| 			// When paging backwards (DESC) we: | 			// When paging newest to oldest (ie., first page to last page): | ||||||
| 			// - iter from end of received accounts | 			// - iter from start of received accounts | ||||||
| 			// - iterate backward through received accounts | 			// - iterate backward through received accounts | ||||||
| 			// - stop when we reach last index of received accounts | 			// - stop when we reach last index of received accounts | ||||||
| 			// - compare each received with the first index of expected accounts | 			// - compare each received with the first index of expected accounts | ||||||
|  | @ -221,8 +222,8 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string) | ||||||
| 			expect = func(i []interface{}) interface{} { return i[0] } | 			expect = func(i []interface{}) interface{} { return i[0] } | ||||||
| 			trunc = func(i []interface{}) []interface{} { return i[1:] } | 			trunc = func(i []interface{}) []interface{} { return i[1:] } | ||||||
| 
 | 
 | ||||||
| 		case "forward": | 		case "oldestToNewest": | ||||||
| 			// When paging forwards (ASC) we: | 			// When paging oldest to newest (ie., last page to first page): | ||||||
| 			// - iter from end of received accounts | 			// - iter from end of received accounts | ||||||
| 			// - iterate backward through received accounts | 			// - iterate backward through received accounts | ||||||
| 			// - stop when we reach first index of received accounts | 			// - stop when we reach first index of received accounts | ||||||
|  | @ -230,7 +231,7 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string) | ||||||
| 			// - after each compare, drop the last index of expected accounts | 			// - after each compare, drop the last index of expected accounts | ||||||
| 			start = func(i []*model.Account) int { return len(i) - 1 } | 			start = func(i []*model.Account) int { return len(i) - 1 } | ||||||
| 			iter = func(i int) int { return i - 1 } | 			iter = func(i int) int { return i - 1 } | ||||||
| 			check = func(idx int, i []*model.Account) bool { return idx >= 0 } | 			check = func(idx int, _ []*model.Account) bool { return idx >= 0 } | ||||||
| 			expect = func(i []interface{}) interface{} { return i[len(i)-1] } | 			expect = func(i []interface{}) interface{} { return i[len(i)-1] } | ||||||
| 			trunc = func(i []interface{}) []interface{} { return i[:len(i)-1] } | 			trunc = func(i []interface{}) []interface{} { return i[:len(i)-1] } | ||||||
| 		} | 		} | ||||||
|  | @ -256,7 +257,14 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string) | ||||||
| 		// Parse response link header values. | 		// Parse response link header values. | ||||||
| 		values := result.Header.Values("Link") | 		values := result.Header.Values("Link") | ||||||
| 		links := linkheader.ParseMultiple(values) | 		links := linkheader.ParseMultiple(values) | ||||||
| 		filteredLinks := links.FilterByRel("next") | 
 | ||||||
|  | 		var filteredLinks linkheader.Links | ||||||
|  | 		if direction == "newestToOldest" { | ||||||
|  | 			filteredLinks = links.FilterByRel("next") | ||||||
|  | 		} else { | ||||||
|  | 			filteredLinks = links.FilterByRel("prev") | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		suite.NotEmpty(filteredLinks, "no next link provided with more remaining accounts on page=%d", p) | 		suite.NotEmpty(filteredLinks, "no next link provided with more remaining accounts on page=%d", p) | ||||||
| 
 | 
 | ||||||
| 		// A ref link header was set. | 		// A ref link header was set. | ||||||
|  | @ -271,28 +279,28 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *FollowTestSuite) TestGetFollowingPageBackwardLimit2() { | func (suite *FollowTestSuite) TestGetFollowingPageNewestToOldestLimit2() { | ||||||
| 	suite.testGetFollowingPage(2, "backward") | 	suite.testGetFollowingPage(2, "newestToOldest") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *FollowTestSuite) TestGetFollowingPageBackwardLimit4() { | func (suite *FollowTestSuite) TestGetFollowingPageNewestToOldestLimit4() { | ||||||
| 	suite.testGetFollowingPage(4, "backward") | 	suite.testGetFollowingPage(4, "newestToOldest") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *FollowTestSuite) TestGetFollowingPageBackwardLimit6() { | func (suite *FollowTestSuite) TestGetFollowingPageNewestToOldestLimit6() { | ||||||
| 	suite.testGetFollowingPage(6, "backward") | 	suite.testGetFollowingPage(6, "newestToOldest") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *FollowTestSuite) TestGetFollowingPageForwardLimit2() { | func (suite *FollowTestSuite) TestGetFollowingPageOldestToNewestLimit2() { | ||||||
| 	suite.testGetFollowingPage(2, "forward") | 	suite.testGetFollowingPage(2, "oldestToNewest") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *FollowTestSuite) TestGetFollowingPageForwardLimit4() { | func (suite *FollowTestSuite) TestGetFollowingPageOldestToNewestLimit4() { | ||||||
| 	suite.testGetFollowingPage(4, "forward") | 	suite.testGetFollowingPage(4, "oldestToNewest") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *FollowTestSuite) TestGetFollowingPageForwardLimit6() { | func (suite *FollowTestSuite) TestGetFollowingPageOldestToNewestLimit6() { | ||||||
| 	suite.testGetFollowingPage(6, "forward") | 	suite.testGetFollowingPage(6, "oldestToNewest") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string) { | func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string) { | ||||||
|  | @ -307,8 +315,11 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string) | ||||||
| 
 | 
 | ||||||
| 	var i int | 	var i int | ||||||
| 
 | 
 | ||||||
|  | 	// Have the account that is requesting their following | ||||||
|  | 	// list from the API follow each account in the testrig. | ||||||
| 	for _, targetAccount := range suite.testAccounts { | 	for _, targetAccount := range suite.testAccounts { | ||||||
| 		if targetAccount.ID == requestingAccount.ID { | 		account := requestingAccount | ||||||
|  | 		if targetAccount.ID == account.ID { | ||||||
| 			// we cannot be our own target... | 			// we cannot be our own target... | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  | @ -322,8 +333,8 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string) | ||||||
| 			ID:              id, | 			ID:              id, | ||||||
| 			CreatedAt:       now, | 			CreatedAt:       now, | ||||||
| 			UpdatedAt:       now, | 			UpdatedAt:       now, | ||||||
| 			URI:             fmt.Sprintf("%s/follow/%s", requestingAccount.URI, id), | 			URI:             fmt.Sprintf("%s/follow/%s", account.URI, id), | ||||||
| 			AccountID:       requestingAccount.ID, | 			AccountID:       account.ID, | ||||||
| 			TargetAccountID: targetAccount.ID, | 			TargetAccountID: targetAccount.ID, | ||||||
| 		}) | 		}) | ||||||
| 		suite.NoError(err) | 		suite.NoError(err) | ||||||
|  | @ -342,15 +353,17 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string) | ||||||
| 	var query string | 	var query string | ||||||
| 
 | 
 | ||||||
| 	switch direction { | 	switch direction { | ||||||
| 	case "backward": | 	case "newestToOldest": | ||||||
| 		// Set the starting query to page backward from newest. | 		// Set the starting query to page from | ||||||
|  | 		// newest (ie., first entry in slice). | ||||||
| 		acc := expectAccounts[0].(*model.Account) | 		acc := expectAccounts[0].(*model.Account) | ||||||
| 		newest, _ := suite.db.GetFollow(ctx, requestingAccount.ID, acc.ID) | 		newest, _ := suite.db.GetFollow(ctx, requestingAccount.ID, acc.ID) | ||||||
| 		expectAccounts = expectAccounts[1:] | 		expectAccounts = expectAccounts[1:] | ||||||
| 		query = fmt.Sprintf("limit=%d&max_id=%s", limit, newest.ID) | 		query = fmt.Sprintf("limit=%d&max_id=%s", limit, newest.ID) | ||||||
| 
 | 
 | ||||||
| 	case "forward": | 	case "oldestToNewest": | ||||||
| 		// Set the starting query to page forward from the oldest. | 		// Set the starting query to page from | ||||||
|  | 		// oldest (ie., last entry in slice). | ||||||
| 		acc := expectAccounts[len(expectAccounts)-1].(*model.Account) | 		acc := expectAccounts[len(expectAccounts)-1].(*model.Account) | ||||||
| 		oldest, _ := suite.db.GetFollow(ctx, requestingAccount.ID, acc.ID) | 		oldest, _ := suite.db.GetFollow(ctx, requestingAccount.ID, acc.ID) | ||||||
| 		expectAccounts = expectAccounts[:len(expectAccounts)-1] | 		expectAccounts = expectAccounts[:len(expectAccounts)-1] | ||||||
|  | @ -397,9 +410,9 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string) | ||||||
| 		) | 		) | ||||||
| 
 | 
 | ||||||
| 		switch direction { | 		switch direction { | ||||||
| 		case "backward": | 		case "newestToOldest": | ||||||
| 			// When paging backwards (DESC) we: | 			// When paging newest to oldest (ie., first page to last page): | ||||||
| 			// - iter from end of received accounts | 			// - iter from start of received accounts | ||||||
| 			// - iterate backward through received accounts | 			// - iterate backward through received accounts | ||||||
| 			// - stop when we reach last index of received accounts | 			// - stop when we reach last index of received accounts | ||||||
| 			// - compare each received with the first index of expected accounts | 			// - compare each received with the first index of expected accounts | ||||||
|  | @ -410,8 +423,8 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string) | ||||||
| 			expect = func(i []interface{}) interface{} { return i[0] } | 			expect = func(i []interface{}) interface{} { return i[0] } | ||||||
| 			trunc = func(i []interface{}) []interface{} { return i[1:] } | 			trunc = func(i []interface{}) []interface{} { return i[1:] } | ||||||
| 
 | 
 | ||||||
| 		case "forward": | 		case "oldestToNewest": | ||||||
| 			// When paging forwards (ASC) we: | 			// When paging oldest to newest (ie., last page to first page): | ||||||
| 			// - iter from end of received accounts | 			// - iter from end of received accounts | ||||||
| 			// - iterate backward through received accounts | 			// - iterate backward through received accounts | ||||||
| 			// - stop when we reach first index of received accounts | 			// - stop when we reach first index of received accounts | ||||||
|  | @ -419,7 +432,7 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string) | ||||||
| 			// - after each compare, drop the last index of expected accounts | 			// - after each compare, drop the last index of expected accounts | ||||||
| 			start = func(i []*model.Account) int { return len(i) - 1 } | 			start = func(i []*model.Account) int { return len(i) - 1 } | ||||||
| 			iter = func(i int) int { return i - 1 } | 			iter = func(i int) int { return i - 1 } | ||||||
| 			check = func(idx int, i []*model.Account) bool { return idx >= 0 } | 			check = func(idx int, _ []*model.Account) bool { return idx >= 0 } | ||||||
| 			expect = func(i []interface{}) interface{} { return i[len(i)-1] } | 			expect = func(i []interface{}) interface{} { return i[len(i)-1] } | ||||||
| 			trunc = func(i []interface{}) []interface{} { return i[:len(i)-1] } | 			trunc = func(i []interface{}) []interface{} { return i[:len(i)-1] } | ||||||
| 		} | 		} | ||||||
|  | @ -445,7 +458,14 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string) | ||||||
| 		// Parse response link header values. | 		// Parse response link header values. | ||||||
| 		values := result.Header.Values("Link") | 		values := result.Header.Values("Link") | ||||||
| 		links := linkheader.ParseMultiple(values) | 		links := linkheader.ParseMultiple(values) | ||||||
| 		filteredLinks := links.FilterByRel("next") | 
 | ||||||
|  | 		var filteredLinks linkheader.Links | ||||||
|  | 		if direction == "newestToOldest" { | ||||||
|  | 			filteredLinks = links.FilterByRel("next") | ||||||
|  | 		} else { | ||||||
|  | 			filteredLinks = links.FilterByRel("prev") | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		suite.NotEmpty(filteredLinks, "no next link provided with more remaining accounts on page=%d", p) | 		suite.NotEmpty(filteredLinks, "no next link provided with more remaining accounts on page=%d", p) | ||||||
| 
 | 
 | ||||||
| 		// A ref link header was set. | 		// A ref link header was set. | ||||||
|  |  | ||||||
|  | @ -107,28 +107,28 @@ func (suite *GetTestSuite) TestGet() { | ||||||
| ]`, dst.String()) | ]`, dst.String()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *GetTestSuite) TestGetPageBackwardLimit2() { | func (suite *GetTestSuite) TestGetPageNewestToOldestLimit2() { | ||||||
| 	suite.testGetPage(2, "backward") | 	suite.testGetPage(2, "newestToOldest") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *GetTestSuite) TestGetPageBackwardLimit4() { | func (suite *GetTestSuite) TestGetPageNewestToOldestLimit4() { | ||||||
| 	suite.testGetPage(4, "backward") | 	suite.testGetPage(4, "newestToOldest") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *GetTestSuite) TestGetPageBackwardLimit6() { | func (suite *GetTestSuite) TestGetPageNewestToOldestLimit6() { | ||||||
| 	suite.testGetPage(6, "backward") | 	suite.testGetPage(6, "newestToOldest") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *GetTestSuite) TestGetPageForwardLimit2() { | func (suite *GetTestSuite) TestGetPageOldestToNewestLimit2() { | ||||||
| 	suite.testGetPage(2, "forward") | 	suite.testGetPage(2, "oldestToNewest") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *GetTestSuite) TestGetPageForwardLimit4() { | func (suite *GetTestSuite) TestGetPageOldestToNewestLimit4() { | ||||||
| 	suite.testGetPage(4, "forward") | 	suite.testGetPage(4, "oldestToNewest") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *GetTestSuite) TestGetPageForwardLimit6() { | func (suite *GetTestSuite) TestGetPageOldestToNewestLimit6() { | ||||||
| 	suite.testGetPage(6, "forward") | 	suite.testGetPage(6, "oldestToNewest") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (suite *GetTestSuite) testGetPage(limit int, direction string) { | func (suite *GetTestSuite) testGetPage(limit int, direction string) { | ||||||
|  | @ -143,8 +143,11 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) { | ||||||
| 
 | 
 | ||||||
| 	var i int | 	var i int | ||||||
| 
 | 
 | ||||||
| 	for _, targetAccount := range suite.testAccounts { | 	// Have each account in the testrig follow req the | ||||||
| 		if targetAccount.ID == requestingAccount.ID { | 	// account requesting their followers from the API. | ||||||
|  | 	for _, account := range suite.testAccounts { | ||||||
|  | 		targetAccount := requestingAccount | ||||||
|  | 		if account.ID == requestingAccount.ID { | ||||||
| 			// we cannot be our own target... | 			// we cannot be our own target... | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  | @ -158,9 +161,9 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) { | ||||||
| 			ID:              id, | 			ID:              id, | ||||||
| 			CreatedAt:       now, | 			CreatedAt:       now, | ||||||
| 			UpdatedAt:       now, | 			UpdatedAt:       now, | ||||||
| 			URI:             fmt.Sprintf("%s/follow/%s", targetAccount.URI, id), | 			URI:             fmt.Sprintf("%s/follow/%s", account.URI, id), | ||||||
| 			AccountID:       targetAccount.ID, | 			AccountID:       account.ID, | ||||||
| 			TargetAccountID: requestingAccount.ID, | 			TargetAccountID: targetAccount.ID, | ||||||
| 		}) | 		}) | ||||||
| 		suite.NoError(err) | 		suite.NoError(err) | ||||||
| 
 | 
 | ||||||
|  | @ -178,15 +181,17 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) { | ||||||
| 	var query string | 	var query string | ||||||
| 
 | 
 | ||||||
| 	switch direction { | 	switch direction { | ||||||
| 	case "backward": | 	case "newestToOldest": | ||||||
| 		// Set the starting query to page backward from newest. | 		// Set the starting query to page from | ||||||
|  | 		// newest (ie., first entry in slice). | ||||||
| 		acc := expectAccounts[0].(*model.Account) | 		acc := expectAccounts[0].(*model.Account) | ||||||
| 		newest, _ := suite.db.GetFollowRequest(ctx, acc.ID, requestingAccount.ID) | 		newest, _ := suite.db.GetFollowRequest(ctx, acc.ID, requestingAccount.ID) | ||||||
| 		expectAccounts = expectAccounts[1:] | 		expectAccounts = expectAccounts[1:] | ||||||
| 		query = fmt.Sprintf("limit=%d&max_id=%s", limit, newest.ID) | 		query = fmt.Sprintf("limit=%d&max_id=%s", limit, newest.ID) | ||||||
| 
 | 
 | ||||||
| 	case "forward": | 	case "oldestToNewest": | ||||||
| 		// Set the starting query to page forward from the oldest. | 		// Set the starting query to page from | ||||||
|  | 		// oldest (ie., last entry in slice). | ||||||
| 		acc := expectAccounts[len(expectAccounts)-1].(*model.Account) | 		acc := expectAccounts[len(expectAccounts)-1].(*model.Account) | ||||||
| 		oldest, _ := suite.db.GetFollowRequest(ctx, acc.ID, requestingAccount.ID) | 		oldest, _ := suite.db.GetFollowRequest(ctx, acc.ID, requestingAccount.ID) | ||||||
| 		expectAccounts = expectAccounts[:len(expectAccounts)-1] | 		expectAccounts = expectAccounts[:len(expectAccounts)-1] | ||||||
|  | @ -232,9 +237,9 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) { | ||||||
| 		) | 		) | ||||||
| 
 | 
 | ||||||
| 		switch direction { | 		switch direction { | ||||||
| 		case "backward": | 		case "newestToOldest": | ||||||
| 			// When paging backwards (DESC) we: | 			// When paging newest to oldest (ie., first page to last page): | ||||||
| 			// - iter from end of received accounts | 			// - iter from start of received accounts | ||||||
| 			// - iterate backward through received accounts | 			// - iterate backward through received accounts | ||||||
| 			// - stop when we reach last index of received accounts | 			// - stop when we reach last index of received accounts | ||||||
| 			// - compare each received with the first index of expected accounts | 			// - compare each received with the first index of expected accounts | ||||||
|  | @ -245,8 +250,8 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) { | ||||||
| 			expect = func(i []interface{}) interface{} { return i[0] } | 			expect = func(i []interface{}) interface{} { return i[0] } | ||||||
| 			trunc = func(i []interface{}) []interface{} { return i[1:] } | 			trunc = func(i []interface{}) []interface{} { return i[1:] } | ||||||
| 
 | 
 | ||||||
| 		case "forward": | 		case "oldestToNewest": | ||||||
| 			// When paging forwards (ASC) we: | 			// When paging oldest to newest (ie., last page to first page): | ||||||
| 			// - iter from end of received accounts | 			// - iter from end of received accounts | ||||||
| 			// - iterate backward through received accounts | 			// - iterate backward through received accounts | ||||||
| 			// - stop when we reach first index of received accounts | 			// - stop when we reach first index of received accounts | ||||||
|  | @ -254,7 +259,7 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) { | ||||||
| 			// - after each compare, drop the last index of expected accounts | 			// - after each compare, drop the last index of expected accounts | ||||||
| 			start = func(i []*model.Account) int { return len(i) - 1 } | 			start = func(i []*model.Account) int { return len(i) - 1 } | ||||||
| 			iter = func(i int) int { return i - 1 } | 			iter = func(i int) int { return i - 1 } | ||||||
| 			check = func(idx int, i []*model.Account) bool { return idx >= 0 } | 			check = func(idx int, _ []*model.Account) bool { return idx >= 0 } | ||||||
| 			expect = func(i []interface{}) interface{} { return i[len(i)-1] } | 			expect = func(i []interface{}) interface{} { return i[len(i)-1] } | ||||||
| 			trunc = func(i []interface{}) []interface{} { return i[:len(i)-1] } | 			trunc = func(i []interface{}) []interface{} { return i[:len(i)-1] } | ||||||
| 		} | 		} | ||||||
|  | @ -280,7 +285,14 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) { | ||||||
| 		// Parse response link header values. | 		// Parse response link header values. | ||||||
| 		values := result.Header.Values("Link") | 		values := result.Header.Values("Link") | ||||||
| 		links := linkheader.ParseMultiple(values) | 		links := linkheader.ParseMultiple(values) | ||||||
| 		filteredLinks := links.FilterByRel("next") | 
 | ||||||
|  | 		var filteredLinks linkheader.Links | ||||||
|  | 		if direction == "newestToOldest" { | ||||||
|  | 			filteredLinks = links.FilterByRel("next") | ||||||
|  | 		} else { | ||||||
|  | 			filteredLinks = links.FilterByRel("prev") | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		suite.NotEmpty(filteredLinks, "no next link provided with more remaining accounts on page=%d", p) | 		suite.NotEmpty(filteredLinks, "no next link provided with more remaining accounts on page=%d", p) | ||||||
| 
 | 
 | ||||||
| 		// A ref link header was set. | 		// A ref link header was set. | ||||||
|  |  | ||||||
|  | @ -54,6 +54,13 @@ func EitherMinID(minID, sinceID string) Boundary { | ||||||
| 				the cursor value, and max_id provides a | 				the cursor value, and max_id provides a | ||||||
| 				limiting value to the results. | 				limiting value to the results. | ||||||
| 
 | 
 | ||||||
|  | 				But to further complicate it... | ||||||
|  | 
 | ||||||
|  | 				The "next" and "prev" relative links provided | ||||||
|  | 				in the link header are ALWAYS DESCENDING. Which | ||||||
|  | 				means we will ALWAYS provide next=?max_id and | ||||||
|  | 				prev=?min_id. *shakes fist at mastodon api* | ||||||
|  | 
 | ||||||
| 	*/ | 	*/ | ||||||
| 	switch { | 	switch { | ||||||
| 	case minID != "": | 	case minID != "": | ||||||
|  | @ -67,7 +74,12 @@ func EitherMinID(minID, sinceID string) Boundary { | ||||||
| // SinceID ... | // SinceID ... | ||||||
| func SinceID(sinceID string) Boundary { | func SinceID(sinceID string) Boundary { | ||||||
| 	return Boundary{ | 	return Boundary{ | ||||||
| 		Name:  "since_id", | 		// even when a since_id query is | ||||||
|  | 		// provided, the next / prev rel | ||||||
|  | 		// links are DESCENDING with | ||||||
|  | 		// next:max_id and prev:min_id. | ||||||
|  | 		// so ALWAYS use min_id as name. | ||||||
|  | 		Name:  "min_id", | ||||||
| 		Value: sinceID, | 		Value: sinceID, | ||||||
| 		Order: OrderDescending, | 		Order: OrderDescending, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -202,8 +202,9 @@ func Page_PageFunc[WithID any](p *Page, in []WithID, get func(WithID) string) [] | ||||||
| 	return in | 	return in | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Next creates a new instance for the next returnable page, using | // Prev creates a new instance for the next returnable page, using | ||||||
| // given max value. This preserves original limit and max key name. | // given max value. This will always assume DESCENDING for Mastodon | ||||||
|  | // API compatibility, but in case of change it can support both. | ||||||
| func (p *Page) Next(lo, hi string) *Page { | func (p *Page) Next(lo, hi string) *Page { | ||||||
| 	if p == nil || lo == "" || hi == "" { | 	if p == nil || lo == "" || hi == "" { | ||||||
| 		// no paging. | 		// no paging. | ||||||
|  | @ -216,25 +217,22 @@ func (p *Page) Next(lo, hi string) *Page { | ||||||
| 	// Set original limit. | 	// Set original limit. | ||||||
| 	p2.Limit = p.Limit | 	p2.Limit = p.Limit | ||||||
| 
 | 
 | ||||||
| 	if p.order().Ascending() { | 	// NOTE: | ||||||
| 		// When ascending, next page | 	// We ALWAYS assume the order | ||||||
| 		// needs to start with min at | 	// when creating next / prev | ||||||
| 		// the next highest value. | 	// links is DESCENDING. It will | ||||||
| 		p2.Min = p.Min.new(hi) | 	// always use prev: ?max_name | ||||||
| 		p2.Max = p.Max.new("") |  | ||||||
| 	} else { |  | ||||||
| 		// When descending, next page |  | ||||||
| 		// needs to start with max at |  | ||||||
| 		// the next lowest value. |  | ||||||
| 	p2.Min = p.Min.new("") | 	p2.Min = p.Min.new("") | ||||||
| 	p2.Max = p.Max.new(lo) | 	p2.Max = p.Max.new(lo) | ||||||
| 	} | 	p2.Min.Order = OrderDescending | ||||||
|  | 	p2.Max.Order = OrderDescending | ||||||
| 
 | 
 | ||||||
| 	return p2 | 	return p2 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Prev creates a new instance for the prev returnable page, using | // Prev creates a new instance for the prev returnable page, using | ||||||
| // given min value. This preserves original limit and min key name. | // given min value. This will always assume DESCENDING for Mastodon | ||||||
|  | // API compatibility, but in case of change it can support both. | ||||||
| func (p *Page) Prev(lo, hi string) *Page { | func (p *Page) Prev(lo, hi string) *Page { | ||||||
| 	if p == nil || lo == "" || hi == "" { | 	if p == nil || lo == "" || hi == "" { | ||||||
| 		// no paging. | 		// no paging. | ||||||
|  | @ -247,19 +245,15 @@ func (p *Page) Prev(lo, hi string) *Page { | ||||||
| 	// Set original limit. | 	// Set original limit. | ||||||
| 	p2.Limit = p.Limit | 	p2.Limit = p.Limit | ||||||
| 
 | 
 | ||||||
| 	if p.order().Ascending() { | 	// NOTE: | ||||||
| 		// When ascending, prev page | 	// We ALWAYS assume the order | ||||||
| 		// needs to start with max at | 	// when creating next / prev | ||||||
| 		// the next lowest value. | 	// links is DESCENDING. It will | ||||||
| 		p2.Min = p.Min.new("") | 	// always use prev: ?min_name | ||||||
| 		p2.Max = p.Max.new(lo) |  | ||||||
| 	} else { |  | ||||||
| 		// When descending, next page |  | ||||||
| 		// needs to start with max at |  | ||||||
| 		// the next lowest value. |  | ||||||
| 	p2.Min = p.Min.new(hi) | 	p2.Min = p.Min.new(hi) | ||||||
| 	p2.Max = p.Max.new("") | 	p2.Max = p.Max.new("") | ||||||
| 	} | 	p2.Min.Order = OrderDescending | ||||||
|  | 	p2.Max.Order = OrderDescending | ||||||
| 
 | 
 | ||||||
| 	return p2 | 	return p2 | ||||||
| } | } | ||||||
|  | @ -289,27 +283,14 @@ func (p *Page) ToLinkURL(proto, host, path string, queryParams url.Values) *url. | ||||||
| 		queryParams = cloneQuery(queryParams) | 		queryParams = cloneQuery(queryParams) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var cursor string |  | ||||||
| 
 |  | ||||||
| 	// Depending on page ordering, the |  | ||||||
| 	// page will be cursored by either |  | ||||||
| 	// the min or max query parameter. |  | ||||||
| 	if p.order().Ascending() { |  | ||||||
| 		cursor = p.Min.Name |  | ||||||
| 	} else { |  | ||||||
| 		cursor = p.Max.Name |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if cursor != "" { |  | ||||||
| 	if p.Min.Value != "" { | 	if p.Min.Value != "" { | ||||||
| 		// Set page-minimum cursor value. | 		// Set page-minimum cursor value. | ||||||
| 			queryParams.Set(cursor, p.Min.Value) | 		queryParams.Set(p.Min.Name, p.Min.Value) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if p.Max.Value != "" { | 	if p.Max.Value != "" { | ||||||
| 		// Set page-maximum cursor value. | 		// Set page-maximum cursor value. | ||||||
| 			queryParams.Set(cursor, p.Max.Value) | 		queryParams.Set(p.Max.Name, p.Max.Value) | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if p.Limit > 0 { | 	if p.Limit > 0 { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue