From 2552bb7b6ec51a094c9d800589991b1556fba798 Mon Sep 17 00:00:00 2001
From: Gusted <postmaster@gusted.xyz>
Date: Tue, 28 Nov 2023 19:30:08 +0100
Subject: [PATCH] [GITEA] Only pass selected repository IDs to pagination

- Backport of https://codeberg.org/forgejo/forgejo/pulls/1848
- `ReposParam` is passed to the pagination as value for the `repos`
query. It should paginate to other pages with only the selected
repositories, which was currently not the case, but was already the case
for the links in the selectable items.
- Fix the wrong value being passed for issues/pulls lists.
- Fix the formatting of repository query value for milestones lists.
- Added integration testing.
- Resolves https://codeberg.org/forgejo/forgejo/issues/1836

(cherry picked from commit c648e5ab3a341b97807b9a1c4cf312d4acdc08d4)
---
 routers/web/user/home.go       | 17 ++++++-
 tests/integration/user_test.go | 85 ++++++++++++++++++++++++++++++++++
 2 files changed, 100 insertions(+), 2 deletions(-)

diff --git a/routers/web/user/home.go b/routers/web/user/home.go
index d0d24d7ed1..4c4a57734c 100644
--- a/routers/web/user/home.go
+++ b/routers/web/user/home.go
@@ -315,9 +315,18 @@ func Milestones(ctx *context.Context) {
 	ctx.Data["RepoIDs"] = repoIDs
 	ctx.Data["IsShowClosed"] = isShowClosed
 
+	// Convert []int64 to string
+	reposParam, err := json.Marshal(repoIDs)
+	if err != nil {
+		ctx.ServerError("json.Marshal", err)
+		return
+	}
+
+	ctx.Data["ReposParam"] = string(reposParam)
+
 	pager := context.NewPagination(pagerCount, setting.UI.IssuePagingNum, page, 5)
 	pager.AddParam(ctx, "q", "Keyword")
-	pager.AddParam(ctx, "repos", "RepoIDs")
+	pager.AddParam(ctx, "repos", "ReposParam")
 	pager.AddParam(ctx, "sort", "SortType")
 	pager.AddParam(ctx, "state", "State")
 	ctx.Data["Page"] = pager
@@ -690,7 +699,11 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) {
 	}
 
 	// Convert []int64 to string
-	reposParam, _ := json.Marshal(opts.RepoIDs)
+	reposParam, err := json.Marshal(selectedRepoIDs)
+	if err != nil {
+		ctx.ServerError("json.Marshal", err)
+		return
+	}
 
 	ctx.Data["ReposParam"] = string(reposParam)
 
diff --git a/tests/integration/user_test.go b/tests/integration/user_test.go
index ddde415960..2144e7f92b 100644
--- a/tests/integration/user_test.go
+++ b/tests/integration/user_test.go
@@ -4,7 +4,9 @@
 package integration
 
 import (
+	"fmt"
 	"net/http"
+	"net/url"
 	"testing"
 
 	auth_model "code.gitea.io/gitea/models/auth"
@@ -297,3 +299,86 @@ func TestUserLocationMapLink(t *testing.T) {
 	htmlDoc := NewHTMLParser(t, resp.Body)
 	htmlDoc.AssertElement(t, `a[href="https://example/foo/A%2Fb"]`, true)
 }
+
+func TestPagination(t *testing.T) {
+	defer tests.PrepareTestEnv(t)()
+	// Force pagination to almost always happen.
+	defer test.MockVariableValue(&setting.UI.IssuePagingNum, 1)()
+
+	session := loginUser(t, "user2")
+
+	// Pagination links can be seen multiple times, due to 'last' or 'next' having
+	// the same link, so take that into consideration when writing asserts.
+	t.Run("Issues", func(t *testing.T) {
+		t.Run("No selected repositories", func(t *testing.T) {
+			defer tests.PrintCurrentTest(t)()
+
+			req := NewRequest(t, "GET", "/issues")
+			resp := session.MakeRequest(t, req, http.StatusOK)
+
+			htmlDoc := NewHTMLParser(t, resp.Body)
+			sel := htmlDoc.Find(`.pagination a.navigation[href="/issues?page=2&q=&type=your_repositories&repos=%5B%5D&sort=recentupdate&state=open&labels="]`)
+			assert.GreaterOrEqual(t, sel.Length(), 1)
+		})
+		t.Run("Selected repositories", func(t *testing.T) {
+			defer tests.PrintCurrentTest(t)()
+
+			escapedQuery := url.QueryEscape("[3,5]")
+			req := NewRequest(t, "GET", "/issues?repos="+escapedQuery)
+			resp := session.MakeRequest(t, req, http.StatusOK)
+
+			htmlDoc := NewHTMLParser(t, resp.Body)
+			sel := htmlDoc.Find(fmt.Sprintf(`.pagination a.navigation[href="/issues?page=2&q=&type=your_repositories&repos=%s&sort=recentupdate&state=open&labels="]`, escapedQuery))
+			assert.GreaterOrEqual(t, sel.Length(), 1)
+		})
+	})
+
+	t.Run("Pulls", func(t *testing.T) {
+		t.Run("No selected repositories", func(t *testing.T) {
+			defer tests.PrintCurrentTest(t)()
+
+			req := NewRequest(t, "GET", "/pulls")
+			resp := session.MakeRequest(t, req, http.StatusOK)
+
+			htmlDoc := NewHTMLParser(t, resp.Body)
+			sel := htmlDoc.Find(`.pagination a.navigation[href="/pulls?page=2&q=&type=your_repositories&repos=%5B%5D&sort=recentupdate&state=open&labels="]`)
+			assert.GreaterOrEqual(t, sel.Length(), 1)
+		})
+		t.Run("Selected repositories", func(t *testing.T) {
+			defer tests.PrintCurrentTest(t)()
+
+			escapedQuery := url.QueryEscape("[1,3]")
+			req := NewRequest(t, "GET", "/pulls?repos="+escapedQuery)
+			resp := session.MakeRequest(t, req, http.StatusOK)
+
+			htmlDoc := NewHTMLParser(t, resp.Body)
+			sel := htmlDoc.Find(fmt.Sprintf(`.pagination a.navigation[href="/pulls?page=2&q=&type=your_repositories&repos=%s&sort=recentupdate&state=open&labels="]`, escapedQuery))
+			assert.GreaterOrEqual(t, sel.Length(), 1)
+		})
+	})
+
+	t.Run("Milestones", func(t *testing.T) {
+		t.Run("No selected repositories", func(t *testing.T) {
+			defer tests.PrintCurrentTest(t)()
+
+			escapedQuery := url.QueryEscape("[42,1]")
+			req := NewRequest(t, "GET", "/milestones")
+			resp := session.MakeRequest(t, req, http.StatusOK)
+
+			htmlDoc := NewHTMLParser(t, resp.Body)
+			sel := htmlDoc.Find(fmt.Sprintf(`.pagination a.navigation[href="/milestones?page=2&q=&repos=%s&sort=&state=open"]`, escapedQuery))
+			assert.GreaterOrEqual(t, sel.Length(), 1)
+		})
+		t.Run("Selected repositories", func(t *testing.T) {
+			defer tests.PrintCurrentTest(t)()
+
+			escapedQuery := url.QueryEscape("[1]")
+			req := NewRequest(t, "GET", "/milestones?repos="+escapedQuery)
+			resp := session.MakeRequest(t, req, http.StatusOK)
+
+			htmlDoc := NewHTMLParser(t, resp.Body)
+			sel := htmlDoc.Find(fmt.Sprintf(`.pagination a.navigation[href="/milestones?page=2&q=&repos=%s&sort=&state=open"]`, escapedQuery))
+			assert.GreaterOrEqual(t, sel.Length(), 1)
+		})
+	})
+}