1
0
Fork 0
mirror of https://codeberg.org/forgejo/forgejo.git synced 2025-01-20 16:50:28 -05:00

Add search action jobs for API routes, repo, org and global level (#6300)

This PR wants to improve information of the tasks waiting to be executed on a global, organization, user and repository leve.
The main motivation is explained here https://codeberg.org/forgejo/discussions/issues/241

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests

- I added test coverage for Go changes...
  - [x] in their respective `*_test.go` for unit tests.
  - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] I do not want this change to show in the release notes.
- [x] I want the title to show in the release notes with a link to this pull request.
- [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title.

<!--start release-notes-assistant-->

## Release notes
<!--URL:https://codeberg.org/forgejo/forgejo-->
- Features
  - [PR](https://codeberg.org/forgejo/forgejo/pulls/6300): <!--number 6300 --><!--line 0 --><!--description QWRkIHNlYXJjaCBhY3Rpb24gam9icyBmb3IgQVBJIHJvdXRlcywgcmVwbywgb3JnIGFuZCBnbG9iYWwgbGV2ZWw=-->Add search action jobs for API routes, repo, org and global level<!--description-->
<!--end release-notes-assistant-->

Co-authored-by: jaime merino <jaime.merino_mora@mail.schwarzª>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6300
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Jaime merino <cobak78@gmail.com>
Co-committed-by: Jaime merino <cobak78@gmail.com>
This commit is contained in:
Jaime merino 2025-01-14 11:17:42 +00:00 committed by Earl Warren
parent a013acb632
commit 9f842f0dec
22 changed files with 640 additions and 22 deletions

View file

@ -10,6 +10,7 @@ import (
"time"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
@ -71,6 +72,15 @@ func (job *ActionRunJob) LoadAttributes(ctx context.Context) error {
return job.Run.LoadAttributes(ctx)
}
func (job *ActionRunJob) ItRunsOn(labels []string) bool {
if len(labels) == 0 || len(job.RunsOn) == 0 {
return false
}
labelSet := make(container.Set[string])
labelSet.AddMultiple(labels...)
return labelSet.IsSubset(job.RunsOn)
}
func GetRunJobByID(ctx context.Context, id int64) (*ActionRunJob, error) {
var job ActionRunJob
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&job)

View file

@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
package actions
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestActionRunJob_ItRunsOn(t *testing.T) {
actionJob := ActionRunJob{RunsOn: []string{"ubuntu"}}
agentLabels := []string{"ubuntu", "node-20"}
assert.True(t, actionJob.ItRunsOn(agentLabels))
assert.False(t, actionJob.ItRunsOn([]string{}))
actionJob.RunsOn = append(actionJob.RunsOn, "node-20")
assert.True(t, actionJob.ItRunsOn(agentLabels))
agentLabels = []string{"ubuntu"}
assert.False(t, actionJob.ItRunsOn(agentLabels))
actionJob.RunsOn = []string{}
assert.False(t, actionJob.ItRunsOn(agentLabels))
}

View file

@ -12,7 +12,6 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
@ -245,7 +244,7 @@ func CreateTaskForRunner(ctx context.Context, runner *ActionRunner) (*ActionTask
var job *ActionRunJob
log.Trace("runner labels: %v", runner.AgentLabels)
for _, v := range jobs {
if isSubset(runner.AgentLabels, v.RunsOn) {
if v.ItRunsOn(runner.AgentLabels) {
job = v
break
}
@ -482,20 +481,6 @@ func FindOldTasksToExpire(ctx context.Context, olderThan timeutil.TimeStamp, lim
Find(&tasks)
}
func isSubset(set, subset []string) bool {
m := make(container.Set[string], len(set))
for _, v := range set {
m.Add(v)
}
for _, v := range subset {
if !m.Contains(v) {
return false
}
}
return true
}
func convertTimestamp(timestamp *timestamppb.Timestamp) timeutil.TimeStamp {
if timestamp.GetSeconds() == 0 && timestamp.GetNanos() == 0 {
return timeutil.TimeStamp(0)

View file

@ -50,7 +50,7 @@ type FindTaskOptions struct {
RepoID int64
OwnerID int64
CommitSHA string
Status Status
Status []Status
UpdatedBefore timeutil.TimeStamp
StartedBefore timeutil.TimeStamp
RunnerID int64
@ -67,8 +67,8 @@ func (opts FindTaskOptions) ToConds() builder.Cond {
if opts.CommitSHA != "" {
cond = cond.And(builder.Eq{"commit_sha": opts.CommitSHA})
}
if opts.Status > StatusUnknown {
cond = cond.And(builder.Eq{"status": opts.Status})
if opts.Status != nil {
cond = cond.And(builder.In("status", opts.Status))
}
if opts.UpdatedBefore > 0 {
cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore})

View file

@ -83,3 +83,48 @@
status: 1
started: 1683636528
stopped: 1683636626
-
id: 393
run_id: 891
repo_id: 1
owner_id: 1
commit_sha: 985f0301dba5e7b34be866819cd15ad3d8f508ee
is_fork_pull_request: 0
name: job_2
attempt: 1
job_id: job_2
task_id: 47
status: 5
runs_on: '["ubuntu-latest"]'
started: 1683636528
stopped: 1683636626
-
id: 394
run_id: 891
repo_id: 1
owner_id: 2
commit_sha: 985f0301dba5e7b34be866819cd15ad3d8f508ee
is_fork_pull_request: 0
name: job_2
attempt: 1
job_id: job_2
task_id: 47
status: 5
runs_on: '["debian-latest"]'
started: 1683636528
stopped: 1683636626
-
id: 395
run_id: 891
repo_id: 1
owner_id: 3
commit_sha: 985f0301dba5e7b34be866819cd15ad3d8f508ee
is_fork_pull_request: 0
name: job_2
attempt: 1
job_id: job_2
task_id: 47
status: 5
runs_on: '["fedora"]'
started: 1683636528
stopped: 1683636626

View file

@ -29,6 +29,15 @@ func (s Set[T]) AddMultiple(values ...T) {
}
}
func (s Set[T]) IsSubset(subset []T) bool {
for _, v := range subset {
if !s.Contains(v) {
return false
}
}
return true
}
// Contains determines whether a set contains the specified element.
// Returns true if the set contains the specified element; otherwise, false.
func (s Set[T]) Contains(value T) bool {

View file

@ -33,4 +33,9 @@ func TestSet(t *testing.T) {
assert.False(t, s.Contains("key1"))
assert.True(t, s.Contains("key6"))
assert.True(t, s.Contains("key7"))
assert.True(t, s.IsSubset([]string{"key6", "key7"}))
assert.False(t, s.IsSubset([]string{"key1"}))
assert.True(t, s.IsSubset([]string{}))
}

25
modules/structs/action.go Normal file
View file

@ -0,0 +1,25 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package structs
// ActionRunJob represents a job of a run
// swagger:model
type ActionRunJob struct {
// the action run job id
ID int64 `json:"id"`
// the repository id
RepoID int64 `json:"repo_id"`
// the owner id
OwnerID int64 `json:"owner_id"`
// the action run job name
Name string `json:"name"`
// the action run job needed ids
Needs []string `json:"needs"`
// the action run job labels to run on
RunsOn []string `json:"runs_on"`
// the action run job latest task id
TaskID int64 `json:"task_id"`
// the action run job status
Status string `json:"status"`
}

View file

@ -24,3 +24,23 @@ func GetRegistrationToken(ctx *context.APIContext) {
shared.GetRegistrationToken(ctx, 0, 0)
}
// SearchActionRunJobs return a list of actions jobs filtered by the provided parameters
func SearchActionRunJobs(ctx *context.APIContext) {
// swagger:operation GET /admin/runners/jobs admin adminSearchRunJobs
// ---
// summary: Search action jobs according filter conditions
// produces:
// - application/json
// parameters:
// - name: labels
// in: query
// description: a comma separated list of run job labels to search for
// type: string
// responses:
// "200":
// "$ref": "#/responses/RunJobList"
// "403":
// "$ref": "#/responses/forbidden"
shared.GetActionRunJobs(ctx, 0, 0)
}

View file

@ -822,6 +822,7 @@ func Routes() *web.Route {
m.Group("/runners", func() {
m.Get("/registration-token", reqToken(), reqChecker, act.GetRegistrationToken)
m.Get("/jobs", reqToken(), reqChecker, act.SearchActionRunJobs)
})
})
}
@ -975,6 +976,7 @@ func Routes() *web.Route {
m.Group("/runners", func() {
m.Get("/registration-token", reqToken(), user.GetRegistrationToken)
m.Get("/jobs", reqToken(), user.SearchActionRunJobs)
})
})
@ -1631,6 +1633,7 @@ func Routes() *web.Route {
})
m.Group("/runners", func() {
m.Get("/registration-token", admin.GetRegistrationToken)
m.Get("/jobs", admin.SearchActionRunJobs)
})
if setting.Quota.Enabled {
m.Group("/quota", func() {

View file

@ -189,6 +189,31 @@ func (Action) GetRegistrationToken(ctx *context.APIContext) {
shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
}
// SearchActionRunJobs return a list of actions jobs filtered by the provided parameters
func (Action) SearchActionRunJobs(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/actions/runners/jobs organization orgSearchRunJobs
// ---
// summary: Search for organization's action jobs according filter conditions
// produces:
// - application/json
// parameters:
// - name: org
// in: path
// description: name of the organization
// type: string
// required: true
// - name: labels
// in: query
// description: a comma separated list of run job labels to search for
// type: string
// responses:
// "200":
// "$ref": "#/responses/RunJobList"
// "403":
// "$ref": "#/responses/forbidden"
shared.GetActionRunJobs(ctx, ctx.Org.Organization.ID, 0)
}
// ListVariables list org-level variables
func (Action) ListVariables(ctx *context.APIContext) {
// swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList

View file

@ -507,6 +507,36 @@ func (Action) GetRegistrationToken(ctx *context.APIContext) {
shared.GetRegistrationToken(ctx, 0, ctx.Repo.Repository.ID)
}
// SearchActionRunJobs return a list of actions jobs filtered by the provided parameters
func (Action) SearchActionRunJobs(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/actions/runners/jobs repository repoSearchRunJobs
// ---
// summary: Search for repository's action jobs according filter conditions
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: labels
// in: query
// description: a comma separated list of run job labels to search for
// type: string
// responses:
// "200":
// "$ref": "#/responses/RunJobList"
// "403":
// "$ref": "#/responses/forbidden"
shared.GetActionRunJobs(ctx, 0, ctx.Repo.Repository.ID)
}
var _ actions_service.API = new(Action)
// Action implements actions_service.API

View file

@ -6,8 +6,11 @@ package shared
import (
"errors"
"net/http"
"strings"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
)
@ -30,3 +33,48 @@ func GetRegistrationToken(ctx *context.APIContext, ownerID, repoID int64) {
ctx.JSON(http.StatusOK, RegistrationToken{Token: token.Token})
}
// RunJobList is a list of action run jobs
// swagger:response RunJobList
type RunJobList struct {
// in:body
Body []*structs.ActionRunJob `json:"body"`
}
func GetActionRunJobs(ctx *context.APIContext, ownerID, repoID int64) {
labels := strings.Split(ctx.FormTrim("labels"), ",")
total, err := db.Find[actions_model.ActionRunJob](ctx, &actions_model.FindTaskOptions{
Status: []actions_model.Status{actions_model.StatusWaiting, actions_model.StatusRunning},
OwnerID: ownerID,
RepoID: repoID,
})
if err != nil {
ctx.Error(http.StatusInternalServerError, "CountWaitingActionRunJobs", err)
return
}
res := new(RunJobList)
res.Body = fromRunJobModelToResponse(total, labels)
ctx.JSON(http.StatusOK, res)
}
func fromRunJobModelToResponse(job []*actions_model.ActionRunJob, labels []string) []*structs.ActionRunJob {
var res []*structs.ActionRunJob
for i := range job {
if job[i].ItRunsOn(labels) {
res = append(res, &structs.ActionRunJob{
ID: job[i].ID,
RepoID: job[i].RepoID,
OwnerID: job[i].OwnerID,
Name: job[i].Name,
Needs: job[i].Needs,
RunsOn: job[i].RunsOn,
TaskID: job[i].TaskID,
Status: job[i].Status.String(),
})
}
}
return res
}

View file

@ -28,3 +28,25 @@ func GetRegistrationToken(ctx *context.APIContext) {
shared.GetRegistrationToken(ctx, ctx.Doer.ID, 0)
}
// SearchActionRunJobs return a list of actions jobs filtered by the provided parameters
func SearchActionRunJobs(ctx *context.APIContext) {
// swagger:operation GET /user/actions/runners/jobs user userSearchRunJobs
// ---
// summary: Search for user's action jobs according filter conditions
// produces:
// - application/json
// parameters:
// - name: labels
// in: query
// description: a comma separated list of run job labels to search for
// type: string
// responses:
// "200":
// "$ref": "#/responses/RunJobList"
// "401":
// "$ref": "#/responses/unauthorized"
// "403":
// "$ref": "#/responses/forbidden"
shared.GetActionRunJobs(ctx, ctx.Doer.ID, 0)
}

View file

@ -79,7 +79,7 @@ func RunnerDetails(ctx *context.Context, page int, runnerID, ownerID, repoID int
Page: page,
PageSize: 30,
},
Status: actions_model.StatusUnknown, // Unknown means all
Status: []actions_model.Status{actions_model.StatusUnknown}, // Unknown means all
RunnerID: runner.ID,
}

View file

@ -19,7 +19,7 @@ import (
// StopZombieTasks stops the task which have running status, but haven't been updated for a long time
func StopZombieTasks(ctx context.Context) error {
return stopTasks(ctx, actions_model.FindTaskOptions{
Status: actions_model.StatusRunning,
Status: []actions_model.Status{actions_model.StatusRunning},
UpdatedBefore: timeutil.TimeStamp(time.Now().Add(-setting.Actions.ZombieTaskTimeout).Unix()),
})
}
@ -27,7 +27,7 @@ func StopZombieTasks(ctx context.Context) error {
// StopEndlessTasks stops the tasks which have running status and continuous updates, but don't end for a long time
func StopEndlessTasks(ctx context.Context) error {
return stopTasks(ctx, actions_model.FindTaskOptions{
Status: actions_model.StatusRunning,
Status: []actions_model.Status{actions_model.StatusRunning},
StartedBefore: timeutil.TimeStamp(time.Now().Add(-setting.Actions.EndlessTaskTimeout).Unix()),
})
}

View file

@ -25,4 +25,6 @@ type API interface {
UpdateVariable(*context.APIContext)
// GetRegistrationToken get registration token
GetRegistrationToken(*context.APIContext)
// SearchActionRunJobs get pending Action run jobs
SearchActionRunJobs(*context.APIContext)
}

View file

@ -992,6 +992,34 @@
}
}
},
"/admin/runners/jobs": {
"get": {
"produces": [
"application/json"
],
"tags": [
"admin"
],
"summary": "Search action jobs according filter conditions",
"operationId": "adminSearchRunJobs",
"parameters": [
{
"type": "string",
"description": "a comma separated list of run job labels to search for",
"name": "labels",
"in": "query"
}
],
"responses": {
"200": {
"$ref": "#/responses/RunJobList"
},
"403": {
"$ref": "#/responses/forbidden"
}
}
}
},
"/admin/runners/registration-token": {
"get": {
"produces": [
@ -2284,6 +2312,41 @@
}
}
},
"/orgs/{org}/actions/runners/jobs": {
"get": {
"produces": [
"application/json"
],
"tags": [
"organization"
],
"summary": "Search for organization's action jobs according filter conditions",
"operationId": "orgSearchRunJobs",
"parameters": [
{
"type": "string",
"description": "name of the organization",
"name": "org",
"in": "path",
"required": true
},
{
"type": "string",
"description": "a comma separated list of run job labels to search for",
"name": "labels",
"in": "query"
}
],
"responses": {
"200": {
"$ref": "#/responses/RunJobList"
},
"403": {
"$ref": "#/responses/forbidden"
}
}
}
},
"/orgs/{org}/actions/runners/registration-token": {
"get": {
"produces": [
@ -4639,6 +4702,48 @@
}
}
},
"/repos/{owner}/{repo}/actions/runners/jobs": {
"get": {
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "Search for repository's action jobs according filter conditions",
"operationId": "repoSearchRunJobs",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
},
{
"type": "string",
"description": "a comma separated list of run job labels to search for",
"name": "labels",
"in": "query"
}
],
"responses": {
"200": {
"$ref": "#/responses/RunJobList"
},
"403": {
"$ref": "#/responses/forbidden"
}
}
}
},
"/repos/{owner}/{repo}/actions/runners/registration-token": {
"get": {
"produces": [
@ -17399,6 +17504,37 @@
}
}
},
"/user/actions/runners/jobs": {
"get": {
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "Search for user's action jobs according filter conditions",
"operationId": "userSearchRunJobs",
"parameters": [
{
"type": "string",
"description": "a comma separated list of run job labels to search for",
"name": "labels",
"in": "query"
}
],
"responses": {
"200": {
"$ref": "#/responses/RunJobList"
},
"401": {
"$ref": "#/responses/unauthorized"
},
"403": {
"$ref": "#/responses/forbidden"
}
}
}
},
"/user/actions/runners/registration-token": {
"get": {
"produces": [
@ -20387,6 +20523,63 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"ActionRunJob": {
"description": "ActionRunJob represents a job of a run",
"type": "object",
"properties": {
"id": {
"description": "the action run job id",
"type": "integer",
"format": "int64",
"x-go-name": "ID"
},
"name": {
"description": "the action run job name",
"type": "string",
"x-go-name": "Name"
},
"needs": {
"description": "the action run job needed ids",
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "Needs"
},
"owner_id": {
"description": "the owner id",
"type": "integer",
"format": "int64",
"x-go-name": "OwnerID"
},
"repo_id": {
"description": "the repository id",
"type": "integer",
"format": "int64",
"x-go-name": "RepoID"
},
"runs_on": {
"description": "the action run job labels to run on",
"type": "array",
"items": {
"type": "string"
},
"x-go-name": "RunsOn"
},
"status": {
"description": "the action run job status",
"type": "string",
"x-go-name": "Status"
},
"task_id": {
"description": "the action run job latest task id",
"type": "integer",
"format": "int64",
"x-go-name": "TaskID"
}
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"ActionTask": {
"description": "ActionTask represents a ActionTask",
"type": "object",
@ -28678,6 +28871,15 @@
}
}
},
"RunJobList": {
"description": "RunJobList is a list of action run jobs",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/ActionRunJob"
}
}
},
"SearchResults": {
"description": "SearchResults",
"schema": {

View file

@ -0,0 +1,39 @@
// Copyright 2024 The Forgejo Authors c/o Codeberg e.V.. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"net/http"
"testing"
actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/routers/api/v1/shared"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
func TestAPISearchActionJobs_GlobalRunner(t *testing.T) {
defer tests.PrepareTestEnv(t)()
job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 393})
adminUsername := "user1"
token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
req := NewRequest(
t,
"GET",
fmt.Sprintf("/api/v1/admin/runners/jobs?labels=%s", "ubuntu-latest"),
).AddTokenAuth(token)
res := MakeRequest(t, req, http.StatusOK)
var jobs shared.RunJobList
DecodeJSON(t, res, &jobs)
assert.Len(t, jobs.Body, 1)
assert.EqualValues(t, job.ID, jobs.Body[0].ID)
}

View file

@ -0,0 +1,38 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"net/http"
"testing"
actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/routers/api/v1/shared"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
func TestAPISearchActionJobs_OrgRunner(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user1")
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization)
job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 395})
req := NewRequest(t, "GET",
fmt.Sprintf("/api/v1/orgs/org3/actions/runners/jobs?labels=%s", "fedora")).
AddTokenAuth(token)
res := MakeRequest(t, req, http.StatusOK)
var jobs shared.RunJobList
DecodeJSON(t, res, &jobs)
assert.Len(t, jobs.Body, 1)
assert.EqualValues(t, job.ID, jobs.Body[0].ID)
}

View file

@ -0,0 +1,43 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"net/http"
"testing"
actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth"
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/routers/api/v1/shared"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
func TestAPISearchActionJobs_RepoRunner(t *testing.T) {
defer tests.PrepareTestEnv(t)()
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
token := getUserToken(t, user2.LowerName, auth_model.AccessTokenScopeWriteRepository)
job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 393})
req := NewRequestf(
t,
"GET",
"/api/v1/repos/%s/%s/actions/runners/jobs?labels=%s",
repo.OwnerName, repo.Name,
"ubuntu-latest",
).AddTokenAuth(token)
res := MakeRequest(t, req, http.StatusOK)
var jobs shared.RunJobList
DecodeJSON(t, res, &jobs)
assert.Len(t, jobs.Body, 1)
assert.EqualValues(t, job.ID, jobs.Body[0].ID)
}

View file

@ -0,0 +1,38 @@
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"net/http"
"testing"
actions_model "code.gitea.io/gitea/models/actions"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/routers/api/v1/shared"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
func TestAPISearchActionJobs_UserRunner(t *testing.T) {
defer tests.PrepareTestEnv(t)()
normalUsername := "user2"
session := loginUser(t, normalUsername)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser)
job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: 394})
req := NewRequest(t, "GET",
fmt.Sprintf("/api/v1/user/actions/runners/jobs?labels=%s", "debian-latest")).
AddTokenAuth(token)
res := MakeRequest(t, req, http.StatusOK)
var jobs shared.RunJobList
DecodeJSON(t, res, &jobs)
assert.Len(t, jobs.Body, 1)
assert.EqualValues(t, job.ID, jobs.Body[0].ID)
}