Skip to content

Commit 6bf72ef

Browse files
committed
merge tests
1 parent 4bfcf22 commit 6bf72ef

File tree

5 files changed

+368
-399
lines changed

5 files changed

+368
-399
lines changed
Lines changed: 368 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,368 @@
1+
// Copyright 2025 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package integration
5+
6+
import (
7+
"fmt"
8+
"net/http"
9+
"slices"
10+
"testing"
11+
12+
actions_model "code.gitea.io/gitea/models/actions"
13+
auth_model "code.gitea.io/gitea/models/auth"
14+
"code.gitea.io/gitea/models/db"
15+
api "code.gitea.io/gitea/modules/structs"
16+
"code.gitea.io/gitea/tests"
17+
18+
"github.com/stretchr/testify/assert"
19+
"github.com/stretchr/testify/require"
20+
)
21+
22+
func TestAPIActionsRunner(t *testing.T) {
23+
t.Run("AdminRunner", testActionsRunnerAdmin)
24+
t.Run("UserRunner", testActionsRunnerUser)
25+
t.Run("OwnerRunner", testActionsRunnerOwner)
26+
t.Run("RepoRunner", testActionsRunnerRepo)
27+
}
28+
29+
func testActionsRunnerAdmin(t *testing.T) {
30+
defer tests.PrepareTestEnv(t)()
31+
adminUsername := "user1"
32+
token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
33+
req := NewRequest(t, "POST", "/api/v1/admin/actions/runners/registration-token").AddTokenAuth(token)
34+
tokenResp := MakeRequest(t, req, http.StatusOK)
35+
var registrationToken struct {
36+
Token string `json:"token"`
37+
}
38+
DecodeJSON(t, tokenResp, &registrationToken)
39+
assert.NotEmpty(t, registrationToken.Token)
40+
41+
req = NewRequest(t, "GET", "/api/v1/admin/actions/runners").AddTokenAuth(token)
42+
runnerListResp := MakeRequest(t, req, http.StatusOK)
43+
runnerList := api.ActionRunnersResponse{}
44+
DecodeJSON(t, runnerListResp, &runnerList)
45+
46+
assert.Len(t, runnerList.Entries, 4)
47+
48+
idx := slices.IndexFunc(runnerList.Entries, func(e *api.ActionRunner) bool { return e.ID == 34349 })
49+
require.NotEqual(t, -1, idx)
50+
expectedRunner := runnerList.Entries[idx]
51+
assert.Equal(t, "runner_to_be_deleted", expectedRunner.Name)
52+
assert.False(t, expectedRunner.Ephemeral)
53+
assert.Len(t, expectedRunner.Labels, 2)
54+
assert.Equal(t, "runner_to_be_deleted", expectedRunner.Labels[0].Name)
55+
assert.Equal(t, "linux", expectedRunner.Labels[1].Name)
56+
57+
// Verify all returned runners can be requested and deleted
58+
for _, runnerEntry := range runnerList.Entries {
59+
// Verify get the runner by id
60+
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token)
61+
runnerResp := MakeRequest(t, req, http.StatusOK)
62+
63+
runner := api.ActionRunner{}
64+
DecodeJSON(t, runnerResp, &runner)
65+
66+
assert.Equal(t, runnerEntry.Name, runner.Name)
67+
assert.Equal(t, runnerEntry.ID, runner.ID)
68+
assert.Equal(t, runnerEntry.Ephemeral, runner.Ephemeral)
69+
assert.ElementsMatch(t, runnerEntry.Labels, runner.Labels)
70+
71+
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token)
72+
MakeRequest(t, req, http.StatusNoContent)
73+
74+
// Verify runner deletion
75+
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token)
76+
MakeRequest(t, req, http.StatusNotFound)
77+
}
78+
}
79+
80+
func testActionsRunnerUser(t *testing.T) {
81+
defer tests.PrepareTestEnv(t)()
82+
userUsername := "user1"
83+
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteUser)
84+
req := NewRequest(t, "POST", "/api/v1/user/actions/runners/registration-token").AddTokenAuth(token)
85+
tokenResp := MakeRequest(t, req, http.StatusOK)
86+
var registrationToken struct {
87+
Token string `json:"token"`
88+
}
89+
DecodeJSON(t, tokenResp, &registrationToken)
90+
assert.NotEmpty(t, registrationToken.Token)
91+
92+
req = NewRequest(t, "GET", "/api/v1/user/actions/runners").AddTokenAuth(token)
93+
runnerListResp := MakeRequest(t, req, http.StatusOK)
94+
runnerList := api.ActionRunnersResponse{}
95+
DecodeJSON(t, runnerListResp, &runnerList)
96+
97+
assert.Len(t, runnerList.Entries, 1)
98+
assert.Equal(t, "runner_to_be_deleted-user", runnerList.Entries[0].Name)
99+
assert.Equal(t, int64(34346), runnerList.Entries[0].ID)
100+
assert.False(t, runnerList.Entries[0].Ephemeral)
101+
assert.Len(t, runnerList.Entries[0].Labels, 2)
102+
assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
103+
assert.Equal(t, "linux", runnerList.Entries[0].Labels[1].Name)
104+
105+
// Verify get the runner by id
106+
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
107+
runnerResp := MakeRequest(t, req, http.StatusOK)
108+
109+
runner := api.ActionRunner{}
110+
DecodeJSON(t, runnerResp, &runner)
111+
112+
assert.Equal(t, "runner_to_be_deleted-user", runner.Name)
113+
assert.Equal(t, int64(34346), runner.ID)
114+
assert.False(t, runner.Ephemeral)
115+
assert.Len(t, runner.Labels, 2)
116+
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
117+
assert.Equal(t, "linux", runner.Labels[1].Name)
118+
119+
// Verify delete the runner by id
120+
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
121+
MakeRequest(t, req, http.StatusNoContent)
122+
123+
// Verify runner deletion
124+
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
125+
MakeRequest(t, req, http.StatusNotFound)
126+
}
127+
128+
func testActionsRunnerOwner(t *testing.T) {
129+
defer tests.PrepareTestEnv(t)()
130+
131+
t.Run("GetRunner", func(t *testing.T) {
132+
userUsername := "user2"
133+
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
134+
// Verify get the runner by id with read scope
135+
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347)).AddTokenAuth(token)
136+
runnerResp := MakeRequest(t, req, http.StatusOK)
137+
138+
runner := api.ActionRunner{}
139+
DecodeJSON(t, runnerResp, &runner)
140+
141+
assert.Equal(t, "runner_to_be_deleted-org", runner.Name)
142+
assert.Equal(t, int64(34347), runner.ID)
143+
assert.False(t, runner.Ephemeral)
144+
assert.Len(t, runner.Labels, 2)
145+
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
146+
assert.Equal(t, "linux", runner.Labels[1].Name)
147+
})
148+
149+
t.Run("Access", func(t *testing.T) {
150+
userUsername := "user2"
151+
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteOrganization)
152+
req := NewRequest(t, "POST", "/api/v1/orgs/org3/actions/runners/registration-token").AddTokenAuth(token)
153+
tokenResp := MakeRequest(t, req, http.StatusOK)
154+
var registrationToken struct {
155+
Token string `json:"token"`
156+
}
157+
DecodeJSON(t, tokenResp, &registrationToken)
158+
assert.NotEmpty(t, registrationToken.Token)
159+
160+
req = NewRequest(t, "GET", "/api/v1/orgs/org3/actions/runners").AddTokenAuth(token)
161+
runnerListResp := MakeRequest(t, req, http.StatusOK)
162+
runnerList := api.ActionRunnersResponse{}
163+
DecodeJSON(t, runnerListResp, &runnerList)
164+
165+
assert.Len(t, runnerList.Entries, 1)
166+
assert.Equal(t, "runner_to_be_deleted-org", runnerList.Entries[0].Name)
167+
assert.Equal(t, int64(34347), runnerList.Entries[0].ID)
168+
assert.False(t, runnerList.Entries[0].Ephemeral)
169+
assert.Len(t, runnerList.Entries[0].Labels, 2)
170+
assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
171+
assert.Equal(t, "linux", runnerList.Entries[0].Labels[1].Name)
172+
173+
// Verify get the runner by id
174+
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
175+
runnerResp := MakeRequest(t, req, http.StatusOK)
176+
177+
runner := api.ActionRunner{}
178+
DecodeJSON(t, runnerResp, &runner)
179+
180+
assert.Equal(t, "runner_to_be_deleted-org", runner.Name)
181+
assert.Equal(t, int64(34347), runner.ID)
182+
assert.False(t, runner.Ephemeral)
183+
assert.Len(t, runner.Labels, 2)
184+
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
185+
assert.Equal(t, "linux", runner.Labels[1].Name)
186+
187+
// Verify delete the runner by id
188+
// TODO: what's the different from the test "DeleteNoConflictWhileJobIsDone"?
189+
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
190+
MakeRequest(t, req, http.StatusNoContent)
191+
192+
// Verify runner deletion
193+
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
194+
MakeRequest(t, req, http.StatusNotFound)
195+
})
196+
197+
t.Run("DeleteReadScopeForbidden", func(t *testing.T) {
198+
userUsername := "user2"
199+
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
200+
201+
// Verify delete the runner by id is forbidden with read scope
202+
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347)).AddTokenAuth(token)
203+
MakeRequest(t, req, http.StatusForbidden)
204+
})
205+
206+
t.Run("GetRepoScopeForbidden", func(t *testing.T) {
207+
userUsername := "user2"
208+
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
209+
// Verify get the runner by id with read scope
210+
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347)).AddTokenAuth(token)
211+
MakeRequest(t, req, http.StatusForbidden)
212+
})
213+
214+
t.Run("GetAdminRunner", func(t *testing.T) {
215+
userUsername := "user2"
216+
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
217+
// Verify get a runner by id of different entity is not found
218+
// runner.EditableInContext(ownerID, repoID) false
219+
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34349)).AddTokenAuth(token)
220+
MakeRequest(t, req, http.StatusNotFound)
221+
})
222+
223+
t.Run("DeleteAdminRunner", func(t *testing.T) {
224+
userUsername := "user2"
225+
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteOrganization)
226+
// Verify delete a runner by id of different entity is not found
227+
// runner.EditableInContext(ownerID, repoID) false
228+
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34349)).AddTokenAuth(token)
229+
MakeRequest(t, req, http.StatusNotFound)
230+
})
231+
232+
t.Run("DeleteNoConflictWhileJobIsDone", func(t *testing.T) {
233+
userUsername := "user2"
234+
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteOrganization)
235+
236+
_, err := db.GetEngine(t.Context()).Insert(&actions_model.ActionTask{
237+
RunnerID: 34347,
238+
Status: actions_model.StatusSuccess,
239+
})
240+
assert.NoError(t, err)
241+
242+
// FIXME: CI fails because the runner has been deleted in the "Access" test
243+
// Verify delete the runner by id is ok
244+
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347)).AddTokenAuth(token)
245+
MakeRequest(t, req, http.StatusNoContent)
246+
})
247+
}
248+
249+
func testActionsRunnerRepo(t *testing.T) {
250+
defer tests.PrepareTestEnv(t)()
251+
252+
t.Run("GetRunner", func(t *testing.T) {
253+
userUsername := "user2"
254+
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
255+
// Verify get the runner by id with read scope
256+
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348)).AddTokenAuth(token)
257+
runnerResp := MakeRequest(t, req, http.StatusOK)
258+
259+
runner := api.ActionRunner{}
260+
DecodeJSON(t, runnerResp, &runner)
261+
262+
assert.Equal(t, "runner_to_be_deleted-repo1", runner.Name)
263+
assert.Equal(t, int64(34348), runner.ID)
264+
assert.False(t, runner.Ephemeral)
265+
assert.Len(t, runner.Labels, 2)
266+
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
267+
assert.Equal(t, "linux", runner.Labels[1].Name)
268+
})
269+
270+
t.Run("Access", func(t *testing.T) {
271+
userUsername := "user2"
272+
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteRepository)
273+
req := NewRequest(t, "POST", "/api/v1/repos/user2/repo1/actions/runners/registration-token").AddTokenAuth(token)
274+
tokenResp := MakeRequest(t, req, http.StatusOK)
275+
var registrationToken struct {
276+
Token string `json:"token"`
277+
}
278+
DecodeJSON(t, tokenResp, &registrationToken)
279+
assert.NotEmpty(t, registrationToken.Token)
280+
281+
req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/actions/runners").AddTokenAuth(token)
282+
runnerListResp := MakeRequest(t, req, http.StatusOK)
283+
runnerList := api.ActionRunnersResponse{}
284+
DecodeJSON(t, runnerListResp, &runnerList)
285+
286+
assert.Len(t, runnerList.Entries, 1)
287+
assert.Equal(t, "runner_to_be_deleted-repo1", runnerList.Entries[0].Name)
288+
assert.Equal(t, int64(34348), runnerList.Entries[0].ID)
289+
assert.False(t, runnerList.Entries[0].Ephemeral)
290+
assert.Len(t, runnerList.Entries[0].Labels, 2)
291+
assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
292+
assert.Equal(t, "linux", runnerList.Entries[0].Labels[1].Name)
293+
294+
// Verify get the runner by id
295+
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
296+
runnerResp := MakeRequest(t, req, http.StatusOK)
297+
298+
runner := api.ActionRunner{}
299+
DecodeJSON(t, runnerResp, &runner)
300+
301+
assert.Equal(t, "runner_to_be_deleted-repo1", runner.Name)
302+
assert.Equal(t, int64(34348), runner.ID)
303+
assert.False(t, runner.Ephemeral)
304+
assert.Len(t, runner.Labels, 2)
305+
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
306+
assert.Equal(t, "linux", runner.Labels[1].Name)
307+
308+
// Verify delete the runner by id
309+
// TODO: what's the different from the test "DeleteNoConflictWhileJobIsDone"?
310+
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
311+
MakeRequest(t, req, http.StatusNoContent)
312+
313+
// Verify runner deletion
314+
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
315+
MakeRequest(t, req, http.StatusNotFound)
316+
})
317+
318+
t.Run("DeleteReadScopeForbidden", func(t *testing.T) {
319+
userUsername := "user2"
320+
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
321+
322+
// Verify delete the runner by id is forbidden with read scope
323+
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348)).AddTokenAuth(token)
324+
MakeRequest(t, req, http.StatusForbidden)
325+
})
326+
327+
t.Run("GetOrganizationScopeForbidden", func(t *testing.T) {
328+
userUsername := "user2"
329+
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
330+
// Verify get the runner by id with read scope
331+
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348)).AddTokenAuth(token)
332+
MakeRequest(t, req, http.StatusForbidden)
333+
})
334+
335+
t.Run("GetAdminRunnerNotFound", func(t *testing.T) {
336+
userUsername := "user2"
337+
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
338+
// Verify get a runner by id of different entity is not found
339+
// runner.EditableInContext(ownerID, repoID) false
340+
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34349)).AddTokenAuth(token)
341+
MakeRequest(t, req, http.StatusNotFound)
342+
})
343+
344+
t.Run("DeleteAdminRunnerNotFound", func(t *testing.T) {
345+
userUsername := "user2"
346+
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteRepository)
347+
// Verify delete a runner by id of different entity is not found
348+
// runner.EditableInContext(ownerID, repoID) false
349+
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34349)).AddTokenAuth(token)
350+
MakeRequest(t, req, http.StatusNotFound)
351+
})
352+
353+
t.Run("DeleteNoConflictWhileJobIsDone", func(t *testing.T) {
354+
userUsername := "user2"
355+
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteRepository)
356+
357+
_, err := db.GetEngine(t.Context()).Insert(&actions_model.ActionTask{
358+
RunnerID: 34348,
359+
Status: actions_model.StatusSuccess,
360+
})
361+
assert.NoError(t, err)
362+
363+
// FIXME: CI fails because the runner has been deleted in the "Access" test
364+
// Verify delete the runner by id is ok
365+
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348)).AddTokenAuth(token)
366+
MakeRequest(t, req, http.StatusNoContent)
367+
})
368+
}

0 commit comments

Comments
 (0)