summaryrefslogtreecommitdiffstats
path: root/tests/integration/repo_fork_test.go
blob: b2e40671ba0b29f5d758266012648347543effba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
// Copyright 2017 The Gitea Authors. All rights reserved.
// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
// SPDX-License-Identifier: MIT

package integration

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"

	"code.gitea.io/gitea/models/db"
	repo_model "code.gitea.io/gitea/models/repo"
	"code.gitea.io/gitea/models/unittest"
	user_model "code.gitea.io/gitea/models/user"
	"code.gitea.io/gitea/modules/setting"
	"code.gitea.io/gitea/modules/structs"
	"code.gitea.io/gitea/modules/test"
	"code.gitea.io/gitea/routers"
	repo_service "code.gitea.io/gitea/services/repository"
	"code.gitea.io/gitea/tests"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func testRepoFork(t *testing.T, session *TestSession, ownerName, repoName, forkOwnerName, forkRepoName string) *httptest.ResponseRecorder {
	t.Helper()

	forkOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: forkOwnerName})

	// Step0: check the existence of the to-fork repo
	req := NewRequestf(t, "GET", "/%s/%s", forkOwnerName, forkRepoName)
	session.MakeRequest(t, req, http.StatusNotFound)

	// Step1: visit the /fork page
	forkURL := fmt.Sprintf("/%s/%s/fork", ownerName, repoName)
	req = NewRequest(t, "GET", forkURL)
	resp := session.MakeRequest(t, req, http.StatusOK)

	// Step2: fill the form of the forking
	htmlDoc := NewHTMLParser(t, resp.Body)
	link, exists := htmlDoc.doc.Find(fmt.Sprintf("form.ui.form[action=\"%s\"]", forkURL)).Attr("action")
	assert.True(t, exists, "The template has changed")
	_, exists = htmlDoc.doc.Find(fmt.Sprintf(".owner.dropdown .item[data-value=\"%d\"]", forkOwner.ID)).Attr("data-value")
	assert.True(t, exists, "Fork owner %q is not present in select box", forkOwnerName)
	req = NewRequestWithValues(t, "POST", link, map[string]string{
		"_csrf":     htmlDoc.GetCSRF(),
		"uid":       fmt.Sprintf("%d", forkOwner.ID),
		"repo_name": forkRepoName,
	})
	session.MakeRequest(t, req, http.StatusSeeOther)

	// Step3: check the existence of the forked repo
	req = NewRequestf(t, "GET", "/%s/%s", forkOwnerName, forkRepoName)
	resp = session.MakeRequest(t, req, http.StatusOK)

	return resp
}

func testRepoForkLegacyRedirect(t *testing.T, session *TestSession, ownerName, repoName string) {
	t.Helper()

	owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: ownerName})
	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: owner.ID, Name: repoName})

	// Visit the /repo/fork/:id url
	req := NewRequestf(t, "GET", "/repo/fork/%d", repo.ID)
	resp := session.MakeRequest(t, req, http.StatusMovedPermanently)

	assert.Equal(t, repo.Link()+"/fork", resp.Header().Get("Location"))
}

func TestRepoFork(t *testing.T) {
	onGiteaRun(t, func(t *testing.T, u *url.URL) {
		user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user5"})
		session := loginUser(t, user5.Name)

		t.Run("by name", func(t *testing.T) {
			defer tests.PrintCurrentTest(t)()
			defer func() {
				repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user5.ID, Name: "repo1"})
				repo_service.DeleteRepository(db.DefaultContext, user5, repo, false)
			}()
			testRepoFork(t, session, "user2", "repo1", "user5", "repo1")
		})

		t.Run("legacy redirect", func(t *testing.T) {
			defer tests.PrintCurrentTest(t)()
			testRepoForkLegacyRedirect(t, session, "user2", "repo1")

			t.Run("private 404", func(t *testing.T) {
				defer tests.PrintCurrentTest(t)()

				// Make sure the repo we try to fork is private
				repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 31, IsPrivate: true})

				// user5 does not have access to user2/repo20
				req := NewRequestf(t, "GET", "/repo/fork/%d", repo.ID) // user2/repo20
				session.MakeRequest(t, req, http.StatusNotFound)
			})
			t.Run("authenticated private redirect", func(t *testing.T) {
				defer tests.PrintCurrentTest(t)()

				// Make sure the repo we try to fork is private
				repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 31, IsPrivate: true})

				// user1 has access to user2/repo20
				session := loginUser(t, "user1")
				req := NewRequestf(t, "GET", "/repo/fork/%d", repo.ID) // user2/repo20
				session.MakeRequest(t, req, http.StatusMovedPermanently)
			})
			t.Run("no code unit", func(t *testing.T) {
				defer tests.PrintCurrentTest(t)()

				// Make sure the repo we try to fork is private.
				// We're also choosing user15/big_test_private_2, because it has the Code unit disabled.
				repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 20, IsPrivate: true})

				// user1, even though an admin, can't fork a repo without a code unit.
				session := loginUser(t, "user1")
				req := NewRequestf(t, "GET", "/repo/fork/%d", repo.ID) // user15/big_test_private_2
				session.MakeRequest(t, req, http.StatusNotFound)
			})
		})

		t.Run("fork button", func(t *testing.T) {
			defer tests.PrintCurrentTest(t)()

			req := NewRequest(t, "GET", "/user2/repo1/issues")
			resp := MakeRequest(t, req, http.StatusOK)
			htmlDoc := NewHTMLParser(t, resp.Body)

			forkButton := htmlDoc.Find("a[href*='/forks']")
			assert.EqualValues(t, 1, forkButton.Length())

			href, _ := forkButton.Attr("href")
			assert.Equal(t, "/user2/repo1/forks", href)
			assert.Equal(t, "0", strings.TrimSpace(forkButton.Text()))

			t.Run("no fork button on empty repo", func(t *testing.T) {
				defer tests.PrintCurrentTest(t)()

				// Create an empty repository
				repo, err := repo_service.CreateRepository(db.DefaultContext, user5, user5, repo_service.CreateRepoOptions{
					Name:     "empty-repo",
					AutoInit: false,
				})
				defer func() {
					repo_service.DeleteRepository(db.DefaultContext, user5, repo, false)
				}()
				require.NoError(t, err)
				assert.NotEmpty(t, repo)

				// Load the repository home view
				req := NewRequest(t, "GET", repo.HTMLURL())
				resp := session.MakeRequest(t, req, http.StatusOK)
				htmlDoc := NewHTMLParser(t, resp.Body)

				// On an empty repo, the fork button is not present
				htmlDoc.AssertElement(t, ".basic.button[href*='/fork']", false)
			})
		})

		t.Run("DISABLE_FORKS", func(t *testing.T) {
			defer test.MockVariableValue(&setting.Repository.DisableForks, true)()
			defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()

			t.Run("fork button not present", func(t *testing.T) {
				defer tests.PrintCurrentTest(t)()

				// The "Fork" button should not appear on the repo home
				req := NewRequest(t, "GET", "/user2/repo1")
				resp := MakeRequest(t, req, http.StatusOK)
				htmlDoc := NewHTMLParser(t, resp.Body)
				htmlDoc.AssertElement(t, "[href=/user2/repo1/fork]", false)
			})

			t.Run("forking by URL", func(t *testing.T) {
				t.Run("by name", func(t *testing.T) {
					defer tests.PrintCurrentTest(t)()

					// Forking by URL should be Not Found
					req := NewRequest(t, "GET", "/user2/repo1/fork")
					session.MakeRequest(t, req, http.StatusNotFound)
				})

				t.Run("by legacy URL", func(t *testing.T) {
					defer tests.PrintCurrentTest(t)()

					// Forking by legacy URL should be Not Found
					repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) // user2/repo1
					req := NewRequestf(t, "GET", "/repo/fork/%d", repo.ID)
					session.MakeRequest(t, req, http.StatusNotFound)
				})
			})

			t.Run("fork listing", func(t *testing.T) {
				defer tests.PrintCurrentTest(t)()

				// Listing the forks should be Not Found, too
				req := NewRequest(t, "GET", "/user2/repo1/forks")
				MakeRequest(t, req, http.StatusNotFound)
			})
		})
	})
}

func TestRepoForkToOrg(t *testing.T) {
	onGiteaRun(t, func(t *testing.T, u *url.URL) {
		session := loginUser(t, "user2")
		org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"})

		t.Run("by name", func(t *testing.T) {
			defer tests.PrintCurrentTest(t)()
			defer func() {
				repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: org3.ID, Name: "repo1"})
				repo_service.DeleteRepository(db.DefaultContext, org3, repo, false)
			}()

			testRepoFork(t, session, "user2", "repo1", "org3", "repo1")

			// Check that no more forking is allowed as user2 owns repository
			//  and org3 organization that owner user2 is also now has forked this repository
			req := NewRequest(t, "GET", "/user2/repo1")
			resp := session.MakeRequest(t, req, http.StatusOK)
			htmlDoc := NewHTMLParser(t, resp.Body)
			_, exists := htmlDoc.doc.Find("a.ui.button[href^=\"/fork\"]").Attr("href")
			assert.False(t, exists, "Forking should not be allowed anymore")
		})

		t.Run("legacy redirect", func(t *testing.T) {
			defer tests.PrintCurrentTest(t)()

			testRepoForkLegacyRedirect(t, session, "user2", "repo1")
		})
	})
}

func TestForkListPrivateRepo(t *testing.T) {
	forkItemSelector := ".tw-flex.tw-items-center.tw-py-2"

	onGiteaRun(t, func(t *testing.T, u *url.URL) {
		session := loginUser(t, "user5")
		org23 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 23, Visibility: structs.VisibleTypePrivate})

		testRepoFork(t, session, "user2", "repo1", org23.Name, "repo1")

		t.Run("Anomynous", func(t *testing.T) {
			defer tests.PrintCurrentTest(t)()

			req := NewRequest(t, "GET", "/user2/repo1/forks")
			resp := MakeRequest(t, req, http.StatusOK)
			htmlDoc := NewHTMLParser(t, resp.Body)

			htmlDoc.AssertElement(t, forkItemSelector, false)
		})

		t.Run("Logged in", func(t *testing.T) {
			defer tests.PrintCurrentTest(t)()

			req := NewRequest(t, "GET", "/user2/repo1/forks")
			resp := session.MakeRequest(t, req, http.StatusOK)
			htmlDoc := NewHTMLParser(t, resp.Body)

			htmlDoc.AssertElement(t, forkItemSelector, true)
		})
	})
}