gitea源码

api_actions_runner_test.go 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. // Copyright 2025 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package integration
  4. import (
  5. "fmt"
  6. "net/http"
  7. "slices"
  8. "testing"
  9. auth_model "code.gitea.io/gitea/models/auth"
  10. api "code.gitea.io/gitea/modules/structs"
  11. "code.gitea.io/gitea/tests"
  12. "github.com/stretchr/testify/assert"
  13. "github.com/stretchr/testify/require"
  14. )
  15. func TestAPIActionsRunner(t *testing.T) {
  16. t.Run("AdminRunner", testActionsRunnerAdmin)
  17. t.Run("UserRunner", testActionsRunnerUser)
  18. t.Run("OwnerRunner", testActionsRunnerOwner)
  19. t.Run("RepoRunner", testActionsRunnerRepo)
  20. }
  21. func testActionsRunnerAdmin(t *testing.T) {
  22. defer tests.PrepareTestEnv(t)()
  23. adminUsername := "user1"
  24. token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
  25. req := NewRequest(t, "POST", "/api/v1/admin/actions/runners/registration-token").AddTokenAuth(token)
  26. tokenResp := MakeRequest(t, req, http.StatusOK)
  27. var registrationToken struct {
  28. Token string `json:"token"`
  29. }
  30. DecodeJSON(t, tokenResp, &registrationToken)
  31. assert.NotEmpty(t, registrationToken.Token)
  32. req = NewRequest(t, "GET", "/api/v1/admin/actions/runners").AddTokenAuth(token)
  33. runnerListResp := MakeRequest(t, req, http.StatusOK)
  34. runnerList := api.ActionRunnersResponse{}
  35. DecodeJSON(t, runnerListResp, &runnerList)
  36. idx := slices.IndexFunc(runnerList.Entries, func(e *api.ActionRunner) bool { return e.ID == 34349 })
  37. require.NotEqual(t, -1, idx)
  38. expectedRunner := runnerList.Entries[idx]
  39. assert.Equal(t, "runner_to_be_deleted", expectedRunner.Name)
  40. assert.False(t, expectedRunner.Ephemeral)
  41. assert.Len(t, expectedRunner.Labels, 2)
  42. assert.Equal(t, "runner_to_be_deleted", expectedRunner.Labels[0].Name)
  43. assert.Equal(t, "linux", expectedRunner.Labels[1].Name)
  44. // Verify all returned runners can be requested and deleted
  45. for _, runnerEntry := range runnerList.Entries {
  46. // Verify get the runner by id
  47. req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token)
  48. runnerResp := MakeRequest(t, req, http.StatusOK)
  49. runner := api.ActionRunner{}
  50. DecodeJSON(t, runnerResp, &runner)
  51. assert.Equal(t, runnerEntry.Name, runner.Name)
  52. assert.Equal(t, runnerEntry.ID, runner.ID)
  53. assert.Equal(t, runnerEntry.Ephemeral, runner.Ephemeral)
  54. assert.ElementsMatch(t, runnerEntry.Labels, runner.Labels)
  55. req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token)
  56. MakeRequest(t, req, http.StatusNoContent)
  57. // Verify runner deletion
  58. req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token)
  59. MakeRequest(t, req, http.StatusNotFound)
  60. }
  61. }
  62. func testActionsRunnerUser(t *testing.T) {
  63. defer tests.PrepareTestEnv(t)()
  64. userUsername := "user1"
  65. token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteUser)
  66. req := NewRequest(t, "POST", "/api/v1/user/actions/runners/registration-token").AddTokenAuth(token)
  67. tokenResp := MakeRequest(t, req, http.StatusOK)
  68. var registrationToken struct {
  69. Token string `json:"token"`
  70. }
  71. DecodeJSON(t, tokenResp, &registrationToken)
  72. assert.NotEmpty(t, registrationToken.Token)
  73. req = NewRequest(t, "GET", "/api/v1/user/actions/runners").AddTokenAuth(token)
  74. runnerListResp := MakeRequest(t, req, http.StatusOK)
  75. runnerList := api.ActionRunnersResponse{}
  76. DecodeJSON(t, runnerListResp, &runnerList)
  77. assert.Len(t, runnerList.Entries, 1)
  78. assert.Equal(t, "runner_to_be_deleted-user", runnerList.Entries[0].Name)
  79. assert.Equal(t, int64(34346), runnerList.Entries[0].ID)
  80. assert.False(t, runnerList.Entries[0].Ephemeral)
  81. assert.Len(t, runnerList.Entries[0].Labels, 2)
  82. assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
  83. assert.Equal(t, "linux", runnerList.Entries[0].Labels[1].Name)
  84. // Verify get the runner by id
  85. req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
  86. runnerResp := MakeRequest(t, req, http.StatusOK)
  87. runner := api.ActionRunner{}
  88. DecodeJSON(t, runnerResp, &runner)
  89. assert.Equal(t, "runner_to_be_deleted-user", runner.Name)
  90. assert.Equal(t, int64(34346), runner.ID)
  91. assert.False(t, runner.Ephemeral)
  92. assert.Len(t, runner.Labels, 2)
  93. assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
  94. assert.Equal(t, "linux", runner.Labels[1].Name)
  95. // Verify delete the runner by id
  96. req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
  97. MakeRequest(t, req, http.StatusNoContent)
  98. // Verify runner deletion
  99. req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
  100. MakeRequest(t, req, http.StatusNotFound)
  101. }
  102. func testActionsRunnerOwner(t *testing.T) {
  103. defer tests.PrepareTestEnv(t)()
  104. t.Run("GetRunner", func(t *testing.T) {
  105. userUsername := "user2"
  106. token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
  107. // Verify get the runner by id with read scope
  108. req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347)).AddTokenAuth(token)
  109. runnerResp := MakeRequest(t, req, http.StatusOK)
  110. runner := api.ActionRunner{}
  111. DecodeJSON(t, runnerResp, &runner)
  112. assert.Equal(t, "runner_to_be_deleted-org", runner.Name)
  113. assert.Equal(t, int64(34347), 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. t.Run("Access", func(t *testing.T) {
  120. userUsername := "user2"
  121. token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteOrganization)
  122. req := NewRequest(t, "POST", "/api/v1/orgs/org3/actions/runners/registration-token").AddTokenAuth(token)
  123. tokenResp := MakeRequest(t, req, http.StatusOK)
  124. var registrationToken struct {
  125. Token string `json:"token"`
  126. }
  127. DecodeJSON(t, tokenResp, &registrationToken)
  128. assert.NotEmpty(t, registrationToken.Token)
  129. req = NewRequest(t, "GET", "/api/v1/orgs/org3/actions/runners").AddTokenAuth(token)
  130. runnerListResp := MakeRequest(t, req, http.StatusOK)
  131. runnerList := api.ActionRunnersResponse{}
  132. DecodeJSON(t, runnerListResp, &runnerList)
  133. idx := slices.IndexFunc(runnerList.Entries, func(e *api.ActionRunner) bool { return e.ID == 34347 })
  134. require.NotEqual(t, -1, idx)
  135. expectedRunner := runnerList.Entries[idx]
  136. require.NotNil(t, expectedRunner)
  137. assert.Equal(t, "runner_to_be_deleted-org", expectedRunner.Name)
  138. assert.Equal(t, int64(34347), expectedRunner.ID)
  139. assert.False(t, expectedRunner.Ephemeral)
  140. assert.Len(t, expectedRunner.Labels, 2)
  141. assert.Equal(t, "runner_to_be_deleted", expectedRunner.Labels[0].Name)
  142. assert.Equal(t, "linux", expectedRunner.Labels[1].Name)
  143. // Verify get the runner by id
  144. req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token)
  145. runnerResp := MakeRequest(t, req, http.StatusOK)
  146. runner := api.ActionRunner{}
  147. DecodeJSON(t, runnerResp, &runner)
  148. assert.Equal(t, "runner_to_be_deleted-org", runner.Name)
  149. assert.Equal(t, int64(34347), runner.ID)
  150. assert.False(t, runner.Ephemeral)
  151. assert.Len(t, runner.Labels, 2)
  152. assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
  153. assert.Equal(t, "linux", runner.Labels[1].Name)
  154. // Verify delete the runner by id
  155. req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token)
  156. MakeRequest(t, req, http.StatusNoContent)
  157. // Verify runner deletion
  158. req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token)
  159. MakeRequest(t, req, http.StatusNotFound)
  160. })
  161. t.Run("DeleteReadScopeForbidden", func(t *testing.T) {
  162. userUsername := "user2"
  163. token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
  164. // Verify delete the runner by id is forbidden with read scope
  165. req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347)).AddTokenAuth(token)
  166. MakeRequest(t, req, http.StatusForbidden)
  167. })
  168. t.Run("GetRepoScopeForbidden", func(t *testing.T) {
  169. userUsername := "user2"
  170. token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
  171. // Verify get the runner by id with read scope
  172. req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347)).AddTokenAuth(token)
  173. MakeRequest(t, req, http.StatusForbidden)
  174. })
  175. t.Run("GetAdminRunner", func(t *testing.T) {
  176. userUsername := "user2"
  177. token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
  178. // Verify get a runner by id of different entity is not found
  179. // runner.EditableInContext(ownerID, repoID) false
  180. req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34349)).AddTokenAuth(token)
  181. MakeRequest(t, req, http.StatusNotFound)
  182. })
  183. t.Run("DeleteAdminRunner", func(t *testing.T) {
  184. userUsername := "user2"
  185. token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteOrganization)
  186. // Verify delete a runner by id of different entity is not found
  187. // runner.EditableInContext(ownerID, repoID) false
  188. req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34349)).AddTokenAuth(token)
  189. MakeRequest(t, req, http.StatusNotFound)
  190. })
  191. }
  192. func testActionsRunnerRepo(t *testing.T) {
  193. defer tests.PrepareTestEnv(t)()
  194. t.Run("GetRunner", func(t *testing.T) {
  195. userUsername := "user2"
  196. token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
  197. // Verify get the runner by id with read scope
  198. req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348)).AddTokenAuth(token)
  199. runnerResp := MakeRequest(t, req, http.StatusOK)
  200. runner := api.ActionRunner{}
  201. DecodeJSON(t, runnerResp, &runner)
  202. assert.Equal(t, "runner_to_be_deleted-repo1", runner.Name)
  203. assert.Equal(t, int64(34348), runner.ID)
  204. assert.False(t, runner.Ephemeral)
  205. assert.Len(t, runner.Labels, 2)
  206. assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
  207. assert.Equal(t, "linux", runner.Labels[1].Name)
  208. })
  209. t.Run("Access", func(t *testing.T) {
  210. userUsername := "user2"
  211. token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteRepository)
  212. req := NewRequest(t, "POST", "/api/v1/repos/user2/repo1/actions/runners/registration-token").AddTokenAuth(token)
  213. tokenResp := MakeRequest(t, req, http.StatusOK)
  214. var registrationToken struct {
  215. Token string `json:"token"`
  216. }
  217. DecodeJSON(t, tokenResp, &registrationToken)
  218. assert.NotEmpty(t, registrationToken.Token)
  219. req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/actions/runners").AddTokenAuth(token)
  220. runnerListResp := MakeRequest(t, req, http.StatusOK)
  221. runnerList := api.ActionRunnersResponse{}
  222. DecodeJSON(t, runnerListResp, &runnerList)
  223. assert.Len(t, runnerList.Entries, 1)
  224. assert.Equal(t, "runner_to_be_deleted-repo1", runnerList.Entries[0].Name)
  225. assert.Equal(t, int64(34348), runnerList.Entries[0].ID)
  226. assert.False(t, runnerList.Entries[0].Ephemeral)
  227. assert.Len(t, runnerList.Entries[0].Labels, 2)
  228. assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
  229. assert.Equal(t, "linux", runnerList.Entries[0].Labels[1].Name)
  230. // Verify get the runner by id
  231. req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
  232. runnerResp := MakeRequest(t, req, http.StatusOK)
  233. runner := api.ActionRunner{}
  234. DecodeJSON(t, runnerResp, &runner)
  235. assert.Equal(t, "runner_to_be_deleted-repo1", runner.Name)
  236. assert.Equal(t, int64(34348), runner.ID)
  237. assert.False(t, runner.Ephemeral)
  238. assert.Len(t, runner.Labels, 2)
  239. assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
  240. assert.Equal(t, "linux", runner.Labels[1].Name)
  241. // Verify delete the runner by id
  242. req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
  243. MakeRequest(t, req, http.StatusNoContent)
  244. // Verify runner deletion
  245. req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
  246. MakeRequest(t, req, http.StatusNotFound)
  247. })
  248. t.Run("DeleteReadScopeForbidden", func(t *testing.T) {
  249. userUsername := "user2"
  250. token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
  251. // Verify delete the runner by id is forbidden with read scope
  252. req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348)).AddTokenAuth(token)
  253. MakeRequest(t, req, http.StatusForbidden)
  254. })
  255. t.Run("GetOrganizationScopeForbidden", func(t *testing.T) {
  256. userUsername := "user2"
  257. token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
  258. // Verify get the runner by id with read scope
  259. req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348)).AddTokenAuth(token)
  260. MakeRequest(t, req, http.StatusForbidden)
  261. })
  262. t.Run("GetAdminRunnerNotFound", func(t *testing.T) {
  263. userUsername := "user2"
  264. token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
  265. // Verify get a runner by id of different entity is not found
  266. // runner.EditableInContext(ownerID, repoID) false
  267. req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34349)).AddTokenAuth(token)
  268. MakeRequest(t, req, http.StatusNotFound)
  269. })
  270. t.Run("DeleteAdminRunnerNotFound", func(t *testing.T) {
  271. userUsername := "user2"
  272. token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteRepository)
  273. // Verify delete a runner by id of different entity is not found
  274. // runner.EditableInContext(ownerID, repoID) false
  275. req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34349)).AddTokenAuth(token)
  276. MakeRequest(t, req, http.StatusNotFound)
  277. })
  278. t.Run("DeleteAdminRunnerNotFoundUnknownID", func(t *testing.T) {
  279. userUsername := "user2"
  280. token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteRepository)
  281. // Verify delete a runner by unknown id is not found
  282. req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 4384797347934)).AddTokenAuth(token)
  283. MakeRequest(t, req, http.StatusNotFound)
  284. })
  285. }