| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | // 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 <http://www.gnu.org/licenses/>. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package paging_test | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"math/rand" | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 	"slices" | 
					
						
							| 
									
										
										
										
											2024-05-01 12:29:42 +01:00
										 |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 	"testing" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-26 15:34:10 +02:00
										 |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/paging" | 
					
						
							| 
									
										
										
										
											2024-05-01 12:29:42 +01:00
										 |  |  | 	"github.com/stretchr/testify/assert" | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type Case struct { | 
					
						
							|  |  |  | 	// Name is the test case name. | 
					
						
							|  |  |  | 	Name string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Page to use for test. | 
					
						
							|  |  |  | 	Page *paging.Page | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Input contains test case input ID slice. | 
					
						
							|  |  |  | 	Input []string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Expect contains expected test case output. | 
					
						
							|  |  |  | 	Expect []string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // CreateCase creates a new test case with random input for function defining test page parameters and expected output. | 
					
						
							|  |  |  | func CreateCase(name string, getParams func([]string) (input []string, page *paging.Page, expect []string)) Case { | 
					
						
							|  |  |  | 	i := randRd.Intn(100) | 
					
						
							|  |  |  | 	in := generateSlice(i) | 
					
						
							|  |  |  | 	input, page, expect := getParams(in) | 
					
						
							|  |  |  | 	return Case{ | 
					
						
							|  |  |  | 		Name:   name, | 
					
						
							|  |  |  | 		Page:   page, | 
					
						
							|  |  |  | 		Input:  input, | 
					
						
							|  |  |  | 		Expect: expect, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestPage(t *testing.T) { | 
					
						
							|  |  |  | 	for _, c := range cases { | 
					
						
							|  |  |  | 		t.Run(c.Name, func(t *testing.T) { | 
					
						
							|  |  |  | 			// Page the input slice. | 
					
						
							|  |  |  | 			out := c.Page.Page(c.Input) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-01 12:29:42 +01:00
										 |  |  | 			// Check paged output is expected. | 
					
						
							|  |  |  | 			assert.Equal(t, c.Expect, out, | 
					
						
							|  |  |  | 				"input=%#v page=%v", c.Input, c.Page) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var cases = []Case{ | 
					
						
							|  |  |  | 	CreateCase("minID and maxID set", func(ids []string) ([]string, *paging.Page, []string) { | 
					
						
							|  |  |  | 		// Ensure input slice sorted ascending for min_id | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		slices.SortFunc(ids, ascending) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Select random indices in slice. | 
					
						
							| 
									
										
										
										
											2024-05-01 12:29:42 +01:00
										 |  |  | 		minIdx, maxIdx, _ := generateParams(len(ids)) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Select the boundaries. | 
					
						
							|  |  |  | 		minID := ids[minIdx] | 
					
						
							|  |  |  | 		maxID := ids[maxIdx] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Create expected output. | 
					
						
							|  |  |  | 		expect := slices.Clone(ids) | 
					
						
							|  |  |  | 		expect = cutLower(expect, minID) | 
					
						
							|  |  |  | 		expect = cutUpper(expect, maxID) | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		slices.Reverse(expect) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Return page and expected IDs. | 
					
						
							|  |  |  | 		return ids, &paging.Page{ | 
					
						
							| 
									
										
										
										
											2023-09-12 14:00:35 +01:00
										 |  |  | 			Min: paging.MinID(minID), | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 			Max: paging.MaxID(maxID), | 
					
						
							|  |  |  | 		}, expect | 
					
						
							|  |  |  | 	}), | 
					
						
							|  |  |  | 	CreateCase("minID, maxID and limit set", func(ids []string) ([]string, *paging.Page, []string) { | 
					
						
							|  |  |  | 		// Ensure input slice sorted ascending for min_id | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		slices.SortFunc(ids, ascending) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Select random parameters in slice. | 
					
						
							| 
									
										
										
										
											2024-05-01 12:29:42 +01:00
										 |  |  | 		minIdx, maxIdx, limit := generateParams(len(ids)) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Select the boundaries. | 
					
						
							|  |  |  | 		minID := ids[minIdx] | 
					
						
							|  |  |  | 		maxID := ids[maxIdx] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Create expected output. | 
					
						
							|  |  |  | 		expect := slices.Clone(ids) | 
					
						
							|  |  |  | 		expect = cutLower(expect, minID) | 
					
						
							|  |  |  | 		expect = cutUpper(expect, maxID) | 
					
						
							|  |  |  | 		if limit < len(expect) { | 
					
						
							|  |  |  | 			expect = expect[:limit] | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-05-01 12:29:42 +01:00
										 |  |  | 		slices.Reverse(expect) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Return page and expected IDs. | 
					
						
							|  |  |  | 		return ids, &paging.Page{ | 
					
						
							| 
									
										
										
										
											2023-09-12 14:00:35 +01:00
										 |  |  | 			Min:   paging.MinID(minID), | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 			Max:   paging.MaxID(maxID), | 
					
						
							|  |  |  | 			Limit: limit, | 
					
						
							|  |  |  | 		}, expect | 
					
						
							|  |  |  | 	}), | 
					
						
							|  |  |  | 	CreateCase("minID, maxID and too-large limit set", func(ids []string) ([]string, *paging.Page, []string) { | 
					
						
							|  |  |  | 		// Ensure input slice sorted ascending for min_id | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		slices.SortFunc(ids, ascending) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Select random parameters in slice. | 
					
						
							| 
									
										
										
										
											2024-05-01 12:29:42 +01:00
										 |  |  | 		minIdx, maxIdx, _ := generateParams(len(ids)) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Select the boundaries. | 
					
						
							|  |  |  | 		minID := ids[minIdx] | 
					
						
							|  |  |  | 		maxID := ids[maxIdx] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Create expected output. | 
					
						
							|  |  |  | 		expect := slices.Clone(ids) | 
					
						
							|  |  |  | 		expect = cutLower(expect, minID) | 
					
						
							|  |  |  | 		expect = cutUpper(expect, maxID) | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		slices.Reverse(expect) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Return page and expected IDs. | 
					
						
							|  |  |  | 		return ids, &paging.Page{ | 
					
						
							| 
									
										
										
										
											2023-09-12 14:00:35 +01:00
										 |  |  | 			Min:   paging.MinID(minID), | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 			Max:   paging.MaxID(maxID), | 
					
						
							|  |  |  | 			Limit: len(ids) * 2, | 
					
						
							|  |  |  | 		}, expect | 
					
						
							|  |  |  | 	}), | 
					
						
							|  |  |  | 	CreateCase("sinceID and maxID set", func(ids []string) ([]string, *paging.Page, []string) { | 
					
						
							|  |  |  | 		// Ensure input slice sorted descending for since_id | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		slices.SortFunc(ids, descending) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Select random indices in slice. | 
					
						
							| 
									
										
										
										
											2024-05-01 12:29:42 +01:00
										 |  |  | 		sinceIdx, maxIdx, _ := generateParams(len(ids)) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Select the boundaries. | 
					
						
							|  |  |  | 		sinceID := ids[sinceIdx] | 
					
						
							|  |  |  | 		maxID := ids[maxIdx] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Create expected output. | 
					
						
							|  |  |  | 		expect := slices.Clone(ids) | 
					
						
							|  |  |  | 		expect = cutLower(expect, maxID) | 
					
						
							|  |  |  | 		expect = cutUpper(expect, sinceID) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Return page and expected IDs. | 
					
						
							|  |  |  | 		return ids, &paging.Page{ | 
					
						
							| 
									
										
										
										
											2023-09-12 14:00:35 +01:00
										 |  |  | 			Min: paging.SinceID(sinceID), | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 			Max: paging.MaxID(maxID), | 
					
						
							|  |  |  | 		}, expect | 
					
						
							|  |  |  | 	}), | 
					
						
							|  |  |  | 	CreateCase("maxID set", func(ids []string) ([]string, *paging.Page, []string) { | 
					
						
							|  |  |  | 		// Ensure input slice sorted descending for max_id | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		slices.SortFunc(ids, descending) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Select random indices in slice. | 
					
						
							| 
									
										
										
										
											2024-05-01 12:29:42 +01:00
										 |  |  | 		_, maxIdx, _ := generateParams(len(ids)) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Select the boundaries. | 
					
						
							|  |  |  | 		maxID := ids[maxIdx] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Create expected output. | 
					
						
							|  |  |  | 		expect := slices.Clone(ids) | 
					
						
							|  |  |  | 		expect = cutLower(expect, maxID) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Return page and expected IDs. | 
					
						
							|  |  |  | 		return ids, &paging.Page{ | 
					
						
							|  |  |  | 			Max: paging.MaxID(maxID), | 
					
						
							|  |  |  | 		}, expect | 
					
						
							|  |  |  | 	}), | 
					
						
							|  |  |  | 	CreateCase("sinceID set", func(ids []string) ([]string, *paging.Page, []string) { | 
					
						
							|  |  |  | 		// Ensure input slice sorted descending for since_id | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		slices.SortFunc(ids, descending) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Select random indices in slice. | 
					
						
							| 
									
										
										
										
											2024-05-01 12:29:42 +01:00
										 |  |  | 		sinceIdx, _, _ := generateParams(len(ids)) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Select the boundaries. | 
					
						
							|  |  |  | 		sinceID := ids[sinceIdx] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Create expected output. | 
					
						
							|  |  |  | 		expect := slices.Clone(ids) | 
					
						
							|  |  |  | 		expect = cutUpper(expect, sinceID) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Return page and expected IDs. | 
					
						
							|  |  |  | 		return ids, &paging.Page{ | 
					
						
							| 
									
										
										
										
											2023-09-12 14:00:35 +01:00
										 |  |  | 			Min: paging.SinceID(sinceID), | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 		}, expect | 
					
						
							|  |  |  | 	}), | 
					
						
							|  |  |  | 	CreateCase("minID set", func(ids []string) ([]string, *paging.Page, []string) { | 
					
						
							|  |  |  | 		// Ensure input slice sorted ascending for min_id | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		slices.SortFunc(ids, ascending) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Select random indices in slice. | 
					
						
							| 
									
										
										
										
											2024-05-01 12:29:42 +01:00
										 |  |  | 		minIdx, _, _ := generateParams(len(ids)) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Select the boundaries. | 
					
						
							|  |  |  | 		minID := ids[minIdx] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Create expected output. | 
					
						
							|  |  |  | 		expect := slices.Clone(ids) | 
					
						
							|  |  |  | 		expect = cutLower(expect, minID) | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 		slices.Reverse(expect) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// Return page and expected IDs. | 
					
						
							|  |  |  | 		return ids, &paging.Page{ | 
					
						
							| 
									
										
										
										
											2023-09-12 14:00:35 +01:00
										 |  |  | 			Min: paging.MinID(minID), | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 		}, expect | 
					
						
							|  |  |  | 	}), | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // cutLower cuts off the lower part of the slice from `bound` downwards. | 
					
						
							|  |  |  | func cutLower(in []string, bound string) []string { | 
					
						
							|  |  |  | 	for i := 0; i < len(in); i++ { | 
					
						
							|  |  |  | 		if in[i] == bound { | 
					
						
							|  |  |  | 			return in[i+1:] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return in | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // cutUpper cuts off the upper part of the slice from `bound` onwards. | 
					
						
							|  |  |  | func cutUpper(in []string, bound string) []string { | 
					
						
							|  |  |  | 	for i := 0; i < len(in); i++ { | 
					
						
							|  |  |  | 		if in[i] == bound { | 
					
						
							|  |  |  | 			return in[:i] | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return in | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-01 12:29:42 +01:00
										 |  |  | // random reader according to current-time source seed. | 
					
						
							|  |  |  | var randRd = rand.New(rand.NewSource(time.Now().Unix())) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // generateParams ... | 
					
						
							|  |  |  | func generateParams(n int) (minIdx int, maxIdx int, limit int) { | 
					
						
							|  |  |  | 	maxIdx = max(1, randRd.Intn(n)) | 
					
						
							|  |  |  | 	minIdx = randRd.Intn(maxIdx) | 
					
						
							|  |  |  | 	limit = randRd.Intn(max(1, maxIdx-minIdx)) + 1 | 
					
						
							|  |  |  | 	return | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | // generateSlice generates a new slice of len containing ascending sorted slice. | 
					
						
							|  |  |  | func generateSlice(len int) []string { | 
					
						
							| 
									
										
										
										
											2024-05-01 12:29:42 +01:00
										 |  |  | 	if len <= 1 { | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 		// minimum testable | 
					
						
							|  |  |  | 		// pageable amount | 
					
						
							|  |  |  | 		len = 2 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	in := make([]string, len) | 
					
						
							|  |  |  | 	for i := 0; i < len; i++ { | 
					
						
							| 
									
										
										
										
											2024-05-01 12:29:42 +01:00
										 |  |  | 		in[i] = strconv.Itoa(i) | 
					
						
							| 
									
										
										
										
											2023-09-07 15:58:37 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	return in | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-01 12:29:42 +01:00
										 |  |  | func ascending(sa, sb string) int { | 
					
						
							|  |  |  | 	a, _ := strconv.Atoi(sa) | 
					
						
							|  |  |  | 	b, _ := strconv.Atoi(sb) | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 	if a > b { | 
					
						
							|  |  |  | 		return 1 | 
					
						
							|  |  |  | 	} else if a < b { | 
					
						
							|  |  |  | 		return -1 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return 0 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-01 12:29:42 +01:00
										 |  |  | func descending(sa, sb string) int { | 
					
						
							|  |  |  | 	a, _ := strconv.Atoi(sa) | 
					
						
							|  |  |  | 	b, _ := strconv.Atoi(sb) | 
					
						
							| 
									
										
										
										
											2023-11-20 12:22:28 +00:00
										 |  |  | 	if a < b { | 
					
						
							|  |  |  | 		return 1 | 
					
						
							|  |  |  | 	} else if a > b { | 
					
						
							|  |  |  | 		return -1 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return 0 | 
					
						
							|  |  |  | } |