| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 |
- // Copyright 2025 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package integration
-
- import (
- "fmt"
- "net/http"
- "slices"
- "testing"
-
- auth_model "code.gitea.io/gitea/models/auth"
- api "code.gitea.io/gitea/modules/structs"
- "code.gitea.io/gitea/tests"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
-
- func TestAPIActionsRunner(t *testing.T) {
- t.Run("AdminRunner", testActionsRunnerAdmin)
- t.Run("UserRunner", testActionsRunnerUser)
- t.Run("OwnerRunner", testActionsRunnerOwner)
- t.Run("RepoRunner", testActionsRunnerRepo)
- }
-
- func testActionsRunnerAdmin(t *testing.T) {
- defer tests.PrepareTestEnv(t)()
- adminUsername := "user1"
- token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
- req := NewRequest(t, "POST", "/api/v1/admin/actions/runners/registration-token").AddTokenAuth(token)
- tokenResp := MakeRequest(t, req, http.StatusOK)
- var registrationToken struct {
- Token string `json:"token"`
- }
- DecodeJSON(t, tokenResp, ®istrationToken)
- assert.NotEmpty(t, registrationToken.Token)
-
- req = NewRequest(t, "GET", "/api/v1/admin/actions/runners").AddTokenAuth(token)
- runnerListResp := MakeRequest(t, req, http.StatusOK)
- runnerList := api.ActionRunnersResponse{}
- DecodeJSON(t, runnerListResp, &runnerList)
-
- idx := slices.IndexFunc(runnerList.Entries, func(e *api.ActionRunner) bool { return e.ID == 34349 })
- require.NotEqual(t, -1, idx)
- expectedRunner := runnerList.Entries[idx]
- assert.Equal(t, "runner_to_be_deleted", expectedRunner.Name)
- assert.False(t, expectedRunner.Ephemeral)
- assert.Len(t, expectedRunner.Labels, 2)
- assert.Equal(t, "runner_to_be_deleted", expectedRunner.Labels[0].Name)
- assert.Equal(t, "linux", expectedRunner.Labels[1].Name)
-
- // Verify all returned runners can be requested and deleted
- for _, runnerEntry := range runnerList.Entries {
- // Verify get the runner by id
- req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token)
- runnerResp := MakeRequest(t, req, http.StatusOK)
-
- runner := api.ActionRunner{}
- DecodeJSON(t, runnerResp, &runner)
-
- assert.Equal(t, runnerEntry.Name, runner.Name)
- assert.Equal(t, runnerEntry.ID, runner.ID)
- assert.Equal(t, runnerEntry.Ephemeral, runner.Ephemeral)
- assert.ElementsMatch(t, runnerEntry.Labels, runner.Labels)
-
- req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusNoContent)
-
- // Verify runner deletion
- req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusNotFound)
- }
- }
-
- func testActionsRunnerUser(t *testing.T) {
- defer tests.PrepareTestEnv(t)()
- userUsername := "user1"
- token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteUser)
- req := NewRequest(t, "POST", "/api/v1/user/actions/runners/registration-token").AddTokenAuth(token)
- tokenResp := MakeRequest(t, req, http.StatusOK)
- var registrationToken struct {
- Token string `json:"token"`
- }
- DecodeJSON(t, tokenResp, ®istrationToken)
- assert.NotEmpty(t, registrationToken.Token)
-
- req = NewRequest(t, "GET", "/api/v1/user/actions/runners").AddTokenAuth(token)
- runnerListResp := MakeRequest(t, req, http.StatusOK)
- runnerList := api.ActionRunnersResponse{}
- DecodeJSON(t, runnerListResp, &runnerList)
-
- assert.Len(t, runnerList.Entries, 1)
- assert.Equal(t, "runner_to_be_deleted-user", runnerList.Entries[0].Name)
- assert.Equal(t, int64(34346), runnerList.Entries[0].ID)
- assert.False(t, runnerList.Entries[0].Ephemeral)
- assert.Len(t, runnerList.Entries[0].Labels, 2)
- assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
- assert.Equal(t, "linux", runnerList.Entries[0].Labels[1].Name)
-
- // Verify get the runner by id
- req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
- runnerResp := MakeRequest(t, req, http.StatusOK)
-
- runner := api.ActionRunner{}
- DecodeJSON(t, runnerResp, &runner)
-
- assert.Equal(t, "runner_to_be_deleted-user", runner.Name)
- assert.Equal(t, int64(34346), runner.ID)
- assert.False(t, runner.Ephemeral)
- assert.Len(t, runner.Labels, 2)
- assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
- assert.Equal(t, "linux", runner.Labels[1].Name)
-
- // Verify delete the runner by id
- req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusNoContent)
-
- // Verify runner deletion
- req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusNotFound)
- }
-
- func testActionsRunnerOwner(t *testing.T) {
- defer tests.PrepareTestEnv(t)()
-
- t.Run("GetRunner", func(t *testing.T) {
- userUsername := "user2"
- token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
- // Verify get the runner by id with read scope
- req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347)).AddTokenAuth(token)
- runnerResp := MakeRequest(t, req, http.StatusOK)
-
- runner := api.ActionRunner{}
- DecodeJSON(t, runnerResp, &runner)
-
- assert.Equal(t, "runner_to_be_deleted-org", runner.Name)
- assert.Equal(t, int64(34347), runner.ID)
- assert.False(t, runner.Ephemeral)
- assert.Len(t, runner.Labels, 2)
- assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
- assert.Equal(t, "linux", runner.Labels[1].Name)
- })
-
- t.Run("Access", func(t *testing.T) {
- userUsername := "user2"
- token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteOrganization)
- req := NewRequest(t, "POST", "/api/v1/orgs/org3/actions/runners/registration-token").AddTokenAuth(token)
- tokenResp := MakeRequest(t, req, http.StatusOK)
- var registrationToken struct {
- Token string `json:"token"`
- }
- DecodeJSON(t, tokenResp, ®istrationToken)
- assert.NotEmpty(t, registrationToken.Token)
-
- req = NewRequest(t, "GET", "/api/v1/orgs/org3/actions/runners").AddTokenAuth(token)
- runnerListResp := MakeRequest(t, req, http.StatusOK)
- runnerList := api.ActionRunnersResponse{}
- DecodeJSON(t, runnerListResp, &runnerList)
-
- idx := slices.IndexFunc(runnerList.Entries, func(e *api.ActionRunner) bool { return e.ID == 34347 })
- require.NotEqual(t, -1, idx)
- expectedRunner := runnerList.Entries[idx]
-
- require.NotNil(t, expectedRunner)
- assert.Equal(t, "runner_to_be_deleted-org", expectedRunner.Name)
- assert.Equal(t, int64(34347), expectedRunner.ID)
- assert.False(t, expectedRunner.Ephemeral)
- assert.Len(t, expectedRunner.Labels, 2)
- assert.Equal(t, "runner_to_be_deleted", expectedRunner.Labels[0].Name)
- assert.Equal(t, "linux", expectedRunner.Labels[1].Name)
-
- // Verify get the runner by id
- req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token)
- runnerResp := MakeRequest(t, req, http.StatusOK)
-
- runner := api.ActionRunner{}
- DecodeJSON(t, runnerResp, &runner)
-
- assert.Equal(t, "runner_to_be_deleted-org", runner.Name)
- assert.Equal(t, int64(34347), runner.ID)
- assert.False(t, runner.Ephemeral)
- assert.Len(t, runner.Labels, 2)
- assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
- assert.Equal(t, "linux", runner.Labels[1].Name)
-
- // Verify delete the runner by id
- req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusNoContent)
-
- // Verify runner deletion
- req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", expectedRunner.ID)).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusNotFound)
- })
-
- t.Run("DeleteReadScopeForbidden", func(t *testing.T) {
- userUsername := "user2"
- token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
-
- // Verify delete the runner by id is forbidden with read scope
- req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347)).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusForbidden)
- })
-
- t.Run("GetRepoScopeForbidden", func(t *testing.T) {
- userUsername := "user2"
- token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
- // Verify get the runner by id with read scope
- req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347)).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusForbidden)
- })
-
- t.Run("GetAdminRunner", func(t *testing.T) {
- userUsername := "user2"
- token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
- // Verify get a runner by id of different entity is not found
- // runner.EditableInContext(ownerID, repoID) false
- req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34349)).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusNotFound)
- })
-
- t.Run("DeleteAdminRunner", func(t *testing.T) {
- userUsername := "user2"
- token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteOrganization)
- // Verify delete a runner by id of different entity is not found
- // runner.EditableInContext(ownerID, repoID) false
- req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34349)).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusNotFound)
- })
- }
-
- func testActionsRunnerRepo(t *testing.T) {
- defer tests.PrepareTestEnv(t)()
-
- t.Run("GetRunner", func(t *testing.T) {
- userUsername := "user2"
- token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
- // Verify get the runner by id with read scope
- req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348)).AddTokenAuth(token)
- runnerResp := MakeRequest(t, req, http.StatusOK)
-
- runner := api.ActionRunner{}
- DecodeJSON(t, runnerResp, &runner)
-
- assert.Equal(t, "runner_to_be_deleted-repo1", runner.Name)
- assert.Equal(t, int64(34348), runner.ID)
- assert.False(t, runner.Ephemeral)
- assert.Len(t, runner.Labels, 2)
- assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
- assert.Equal(t, "linux", runner.Labels[1].Name)
- })
-
- t.Run("Access", func(t *testing.T) {
- userUsername := "user2"
- token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteRepository)
- req := NewRequest(t, "POST", "/api/v1/repos/user2/repo1/actions/runners/registration-token").AddTokenAuth(token)
- tokenResp := MakeRequest(t, req, http.StatusOK)
- var registrationToken struct {
- Token string `json:"token"`
- }
- DecodeJSON(t, tokenResp, ®istrationToken)
- assert.NotEmpty(t, registrationToken.Token)
-
- req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/actions/runners").AddTokenAuth(token)
- runnerListResp := MakeRequest(t, req, http.StatusOK)
- runnerList := api.ActionRunnersResponse{}
- DecodeJSON(t, runnerListResp, &runnerList)
-
- assert.Len(t, runnerList.Entries, 1)
- assert.Equal(t, "runner_to_be_deleted-repo1", runnerList.Entries[0].Name)
- assert.Equal(t, int64(34348), runnerList.Entries[0].ID)
- assert.False(t, runnerList.Entries[0].Ephemeral)
- assert.Len(t, runnerList.Entries[0].Labels, 2)
- assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
- assert.Equal(t, "linux", runnerList.Entries[0].Labels[1].Name)
-
- // Verify get the runner by id
- req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
- runnerResp := MakeRequest(t, req, http.StatusOK)
-
- runner := api.ActionRunner{}
- DecodeJSON(t, runnerResp, &runner)
-
- assert.Equal(t, "runner_to_be_deleted-repo1", runner.Name)
- assert.Equal(t, int64(34348), runner.ID)
- assert.False(t, runner.Ephemeral)
- assert.Len(t, runner.Labels, 2)
- assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
- assert.Equal(t, "linux", runner.Labels[1].Name)
-
- // Verify delete the runner by id
- req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusNoContent)
-
- // Verify runner deletion
- req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusNotFound)
- })
-
- t.Run("DeleteReadScopeForbidden", func(t *testing.T) {
- userUsername := "user2"
- token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
-
- // Verify delete the runner by id is forbidden with read scope
- req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348)).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusForbidden)
- })
-
- t.Run("GetOrganizationScopeForbidden", func(t *testing.T) {
- userUsername := "user2"
- token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
- // Verify get the runner by id with read scope
- req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348)).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusForbidden)
- })
-
- t.Run("GetAdminRunnerNotFound", func(t *testing.T) {
- userUsername := "user2"
- token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
- // Verify get a runner by id of different entity is not found
- // runner.EditableInContext(ownerID, repoID) false
- req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34349)).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusNotFound)
- })
-
- t.Run("DeleteAdminRunnerNotFound", func(t *testing.T) {
- userUsername := "user2"
- token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteRepository)
- // Verify delete a runner by id of different entity is not found
- // runner.EditableInContext(ownerID, repoID) false
- req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34349)).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusNotFound)
- })
-
- t.Run("DeleteAdminRunnerNotFoundUnknownID", func(t *testing.T) {
- userUsername := "user2"
- token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteRepository)
- // Verify delete a runner by unknown id is not found
- req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 4384797347934)).AddTokenAuth(token)
- MakeRequest(t, req, http.StatusNotFound)
- })
- }
|