feat: make action runs available in api (#7699)

## Summary

Inspired by https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#list-workflow-runs-for-a-repository and https://docs.github.com/en/rest/actions/workflow-runs?apiVersion=2022-11-28#get-a-workflow-run

## 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...
  - [ ] 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/7699): <!--number 7699 --><!--line 0 --><!--description bWFrZSBhY3Rpb24gcnVucyBhdmFpbGFibGUgaW4gYXBp-->make action runs available in api<!--description-->
<!--end release-notes-assistant-->

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7699
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: klausfyhn <klausfyhn@gmail.com>
Co-committed-by: klausfyhn <klausfyhn@gmail.com>
This commit is contained in:
klausfyhn 2025-06-02 22:05:12 +02:00 committed by Earl Warren
parent 85c054c412
commit fc35915a28
36 changed files with 730 additions and 1 deletions

View file

@ -54,6 +54,8 @@ type FindRunJobOptions struct {
CommitSHA string
Statuses []Status
UpdatedBefore timeutil.TimeStamp
Events []string // []webhook_module.HookEventType
RunNumber int64
}
func (opts FindRunJobOptions) ToConds() builder.Cond {
@ -76,5 +78,11 @@ func (opts FindRunJobOptions) ToConds() builder.Cond {
if opts.UpdatedBefore > 0 {
cond = cond.And(builder.Lt{"updated": opts.UpdatedBefore})
}
if len(opts.Events) > 0 {
cond = cond.And(builder.In("event", opts.Events))
}
if opts.RunNumber > 0 {
cond = cond.And(builder.Eq{"`index`": opts.RunNumber})
}
return cond
}

View file

@ -34,6 +34,15 @@ var statusNames = map[Status]string{
StatusBlocked: "blocked",
}
var nameToStatus = make(map[string]Status, len(statusNames))
func init() {
// Populate name to status lookup map
for status, name := range statusNames {
nameToStatus[name] = status
}
}
// String returns the string name of the Status
func (s Status) String() string {
return statusNames[s]
@ -102,3 +111,8 @@ func (s Status) AsResult() runnerv1.Result {
}
return runnerv1.Result_RESULT_UNSPECIFIED
}
func StatusFromString(name string) (Status, bool) {
status, exists := nameToStatus[name]
return status, exists
}

View file

@ -471,3 +471,64 @@
need_approval: 0
approved_by: 0
event_payload: '{"head_commit":{"id":"5f22f7d0d95d614d25a5b68592adb345a4b5c7fd"}}'
# GET action run(s) test
-
id: 892
title: "successful push run"
repo_id: 63
owner_id: 2
workflow_id: "success.yaml"
index: 1
trigger_user_id: 2
ref: "refs/heads/main"
commit_sha: "97f29ee599c373c729132a5c46a046978311e0ee"
event: "push"
is_fork_pull_request: 0
status: 1 # success
started: 1683636528
stopped: 1683636626
created: 1683636108
updated: 1683636626
need_approval: 0
approved_by: 0
-
id: 893
title: "failed pull_request run"
repo_id: 63
owner_id: 2
workflow_id: "failed.yaml"
index: 2
trigger_user_id: 2
ref: "refs/heads/bugfix-1"
commit_sha: "35c5cddfc19397501ec8f4f7bb808a7c8f04445f"
event: "pull_request"
is_fork_pull_request: 0
status: 2 # failure
started: 1683636528
stopped: 1683636626
created: 1683636108
updated: 1683636626
need_approval: 0
approved_by: 0
-
id: 894
title: "running workflow_dispatch run"
repo_id: 63
owner_id: 2
workflow_id: "running.yaml"
index: 3
trigger_user_id: 2
ref: "refs/heads/main"
commit_sha: "97f29ee599c373c729132a5c46a046978311e0ee"
event: "workflow_dispatch"
is_fork_pull_request: 0
status: 6 # running
started: 1683636528
created: 1683636108
updated: 1683636626
need_approval: 0
approved_by: 0

View file

@ -795,3 +795,10 @@
type: 10
config: "{}"
created_unix: 946684810
-
id: 115
repo_id: 63
type: 10
config: "{}"
created_unix: 946684810

View file

@ -1889,3 +1889,35 @@
is_fsck_enabled: true
close_issues_via_commit_in_any_branch: false
topics: '[]'
-
id: 63
owner_id: 2
owner_name: user2
lower_name: test_action_run_search
name: test_action_run_search
default_branch: main
num_watches: 0
num_stars: 0
num_forks: 0
num_issues: 0
num_closed_issues: 0
num_pulls: 0
num_closed_pulls: 0
num_milestones: 0
num_closed_milestones: 0
num_projects: 0
num_closed_projects: 0
is_private: true
is_empty: false
is_archived: false
is_mirror: false
status: 0
is_fork: false
fork_id: 0
is_template: false
template_id: 0
size: 0
is_fsck_enabled: true
close_issues_via_commit_in_any_branch: false
topics: '[]'

View file

@ -70,7 +70,7 @@
num_followers: 2
num_following: 1
num_stars: 2
num_repos: 17
num_repos: 18
num_teams: 0
num_members: 0
visibility: 0

View file

@ -32,3 +32,23 @@ type ActionTaskResponse struct {
Entries []*ActionTask `json:"workflow_runs"`
TotalCount int64 `json:"total_count"`
}
// ActionRun represents an ActionRun
type ActionRun struct {
ID int64 `json:"id"`
Name string `json:"name"`
RunNumber int64 `json:"run_number"`
Event string `json:"event"`
Status string `json:"status"`
HeadBranch string `json:"head_branch"`
HeadSHA string `json:"head_sha"`
WorkflowID string `json:"workflow_id"`
URL string `json:"url"`
TriggeringActor *User `json:"triggering_actor"`
}
// ListActionRunResponse return a list of ActionRun
type ListActionRunResponse struct {
Entries []*ActionRun `json:"workflow_runs"`
TotalCount int64 `json:"total_count"`
}

View file

@ -1172,6 +1172,10 @@ func Routes() *web.Route {
}, reqToken(), reqAdmin())
m.Group("/actions", func() {
m.Get("/tasks", repo.ListActionTasks)
m.Group("/runs", func() {
m.Get("", repo.ListActionRuns)
m.Get("/{run_id}", repo.GetActionRun)
})
m.Group("/workflows", func() {
m.Group("/{workflowname}", func() {

View file

@ -5,6 +5,7 @@ package repo
import (
"errors"
"fmt"
"net/http"
actions_model "forgejo.org/models/actions"
@ -694,3 +695,160 @@ func DispatchWorkflow(ctx *context.APIContext) {
ctx.JSON(http.StatusNoContent, nil)
}
}
// ListActionRuns return a filtered list of ActionRun
func ListActionRuns(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/actions/runs repository ListActionRuns
// ---
// summary: List a repository's action runs
// 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: page
// in: query
// description: page number of results to return (1-based)
// type: integer
// - name: limit
// in: query
// description: page size of results, default maximum page size is 50
// type: integer
// - name: event
// in: query
// description: Returns workflow run triggered by the specified events. For example, `push`, `pull_request` or `workflow_dispatch`.
// type: array
// items:
// type: string
// - name: status
// in: query
// description: |
// Returns workflow runs with the check run status or conclusion that is specified. For example, a conclusion can be success or a status can be in_progress. Only Forgejo Actions can set a status of waiting, pending, or requested.
// type: array
// items:
// type: string
// enum: [unknown, waiting, running, success, failure, cancelled, skipped, blocked]
// - name: run_number
// in: query
// description: |
// Returns the workflow run associated with the run number.
// type: integer
// format: int64
// - name: head_sha
// in: query
// description: Only returns workflow runs that are associated with the specified head_sha.
// type: string
// responses:
// "200":
// "$ref": "#/responses/ActionRunList"
// "400":
// "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/forbidden"
statusStrs := ctx.FormStrings("status")
statuses := make([]actions_model.Status, len(statusStrs))
for i, s := range statusStrs {
if status, exists := actions_model.StatusFromString(s); exists {
statuses[i] = status
} else {
ctx.Error(http.StatusBadRequest, "StatusFromString", fmt.Sprintf("unknown status: %s", s))
return
}
}
runs, total, err := db.FindAndCount[actions_model.ActionRun](ctx, &actions_model.FindRunJobOptions{
ListOptions: utils.GetListOptions(ctx),
OwnerID: ctx.Repo.Owner.ID,
RepoID: ctx.Repo.Repository.ID,
Events: ctx.FormStrings("event"),
Statuses: statuses,
RunNumber: ctx.FormInt64("run_number"),
CommitSHA: ctx.FormString("head_sha"),
})
if err != nil {
ctx.Error(http.StatusInternalServerError, "ListActionRuns", err)
return
}
res := new(api.ListActionRunResponse)
res.TotalCount = total
res.Entries = make([]*api.ActionRun, len(runs))
for i, r := range runs {
cr, err := convert.ToActionRun(ctx, r)
if err != nil {
ctx.Error(http.StatusInternalServerError, "ToActionRun", err)
return
}
res.Entries[i] = cr
}
ctx.JSON(http.StatusOK, &res)
}
// GetActionRun get one action instance
func GetActionRun(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/actions/runs/{run_id} repository ActionRun
// ---
// summary: Get an action run
// 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: run_id
// in: path
// description: id of the action run
// type: integer
// format: int64
// required: true
// responses:
// "200":
// "$ref": "#/responses/ActionRun"
// "400":
// "$ref": "#/responses/error"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
run, err := actions_model.GetRunByID(ctx, ctx.ParamsInt64(":run_id"))
if err != nil {
if errors.Is(err, util.ErrNotExist) {
ctx.Error(http.StatusNotFound, "GetRunById", err)
} else {
ctx.Error(http.StatusInternalServerError, "GetRunByID", err)
}
return
}
if ctx.Repo.Repository.ID != run.RepoID {
ctx.Error(http.StatusNotFound, "GetRunById", util.ErrNotExist)
return
}
res, err := convert.ToActionRun(ctx, run)
if err != nil {
ctx.Error(http.StatusInternalServerError, "ToActionRun", err)
return
}
ctx.JSON(http.StatusOK, res)
}

View file

@ -455,3 +455,17 @@ type swaggerSyncForkInfo struct {
// in:body
Body []api.SyncForkInfo `json:"body"`
}
// ActionRunList
// swagger:response ActionRunList
type swaggerRepoActionRunList struct {
// in:body
Body api.ListActionRunResponse `json:"body"`
}
// ActionRun
// swagger:response ActionRun
type swaggerRepoActionRun struct {
// in:body
Body api.ActionRun `json:"body"`
}

View file

@ -222,6 +222,29 @@ func ToActionTask(ctx context.Context, t *actions_model.ActionTask) (*api.Action
}, nil
}
// ToActionRun convert a actions_model.ActionRun to an api.ActionRun
func ToActionRun(ctx context.Context, r *actions_model.ActionRun) (*api.ActionRun, error) {
if err := r.LoadAttributes(ctx); err != nil {
return nil, err
}
url := strings.TrimSuffix(setting.AppURL, "/") + r.Link()
actor := ToUser(ctx, r.TriggerUser, nil)
return &api.ActionRun{
ID: r.ID,
Name: r.Title,
HeadBranch: r.PrettyRef(),
HeadSHA: r.CommitSHA,
RunNumber: r.Index,
Event: r.TriggerEvent,
Status: r.Status.String(),
WorkflowID: r.WorkflowID,
URL: url,
TriggeringActor: actor,
}, nil
}
// ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification
func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerification {
verif := asymkey_model.ParseCommitWithSignature(ctx, c)

View file

@ -4904,6 +4904,148 @@
}
}
},
"/repos/{owner}/{repo}/actions/runs": {
"get": {
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "List a repository's action runs",
"operationId": "ListActionRuns",
"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": "integer",
"description": "page number of results to return (1-based)",
"name": "page",
"in": "query"
},
{
"type": "integer",
"description": "page size of results, default maximum page size is 50",
"name": "limit",
"in": "query"
},
{
"type": "array",
"items": {
"type": "string"
},
"description": "Returns workflow run triggered by the specified events. For example, `push`, `pull_request` or `workflow_dispatch`.",
"name": "event",
"in": "query"
},
{
"type": "array",
"items": {
"enum": [
"unknown",
"waiting",
"running",
"success",
"failure",
"cancelled",
"skipped",
"blocked"
],
"type": "string"
},
"description": "Returns workflow runs with the check run status or conclusion that is specified. For example, a conclusion can be success or a status can be in_progress. Only Forgejo Actions can set a status of waiting, pending, or requested.\n",
"name": "status",
"in": "query"
},
{
"type": "integer",
"format": "int64",
"description": "Returns the workflow run associated with the run number.\n",
"name": "run_number",
"in": "query"
},
{
"type": "string",
"description": "Only returns workflow runs that are associated with the specified head_sha.",
"name": "head_sha",
"in": "query"
}
],
"responses": {
"200": {
"$ref": "#/responses/ActionRunList"
},
"400": {
"$ref": "#/responses/error"
},
"403": {
"$ref": "#/responses/forbidden"
}
}
}
},
"/repos/{owner}/{repo}/actions/runs/{run_id}": {
"get": {
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "Get an action run",
"operationId": "ActionRun",
"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": "integer",
"format": "int64",
"description": "id of the action run",
"name": "run_id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"$ref": "#/responses/ActionRun"
},
"400": {
"$ref": "#/responses/error"
},
"403": {
"$ref": "#/responses/forbidden"
},
"404": {
"$ref": "#/responses/notFound"
}
}
}
},
"/repos/{owner}/{repo}/actions/secrets": {
"get": {
"produces": [
@ -20925,6 +21067,54 @@
},
"x-go-package": "forgejo.org/modules/structs"
},
"ActionRun": {
"description": "ActionRun represents an ActionRun",
"type": "object",
"properties": {
"event": {
"type": "string",
"x-go-name": "Event"
},
"head_branch": {
"type": "string",
"x-go-name": "HeadBranch"
},
"head_sha": {
"type": "string",
"x-go-name": "HeadSHA"
},
"id": {
"type": "integer",
"format": "int64",
"x-go-name": "ID"
},
"name": {
"type": "string",
"x-go-name": "Name"
},
"run_number": {
"type": "integer",
"format": "int64",
"x-go-name": "RunNumber"
},
"status": {
"type": "string",
"x-go-name": "Status"
},
"triggering_actor": {
"$ref": "#/definitions/User"
},
"url": {
"type": "string",
"x-go-name": "URL"
},
"workflow_id": {
"type": "string",
"x-go-name": "WorkflowID"
}
},
"x-go-package": "forgejo.org/modules/structs"
},
"ActionRunJob": {
"description": "ActionRunJob represents a job of a run",
"type": "object",
@ -25381,6 +25571,25 @@
},
"x-go-package": "forgejo.org/modules/structs"
},
"ListActionRunResponse": {
"description": "ListActionRunResponse return a list of ActionRun",
"type": "object",
"properties": {
"total_count": {
"type": "integer",
"format": "int64",
"x-go-name": "TotalCount"
},
"workflow_runs": {
"type": "array",
"items": {
"$ref": "#/definitions/ActionRun"
},
"x-go-name": "Entries"
}
},
"x-go-package": "forgejo.org/modules/structs"
},
"MarkdownOption": {
"description": "MarkdownOption markdown options",
"type": "object",
@ -28585,6 +28794,18 @@
}
}
},
"ActionRun": {
"description": "ActionRun",
"schema": {
"$ref": "#/definitions/ActionRun"
}
},
"ActionRunList": {
"description": "ActionRunList",
"schema": {
"$ref": "#/definitions/ListActionRunResponse"
}
},
"ActionVariable": {
"description": "ActionVariable",
"schema": {

View file

@ -0,0 +1 @@
ref: refs/heads/main

View file

@ -0,0 +1,5 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true

View file

@ -0,0 +1 @@
Unnamed repository; edit this file 'description' to name the repository.

View file

@ -0,0 +1,6 @@
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~

View file

@ -0,0 +1,7 @@
0000000000000000000000000000000000000000 97f29ee599c373c729132a5c46a046978311e0ee User 2 <user2@example.com> 1745918688 +0200 commit (initial): initial
97f29ee599c373c729132a5c46a046978311e0ee 97f29ee599c373c729132a5c46a046978311e0ee User 2 <user2@example.com> 1745921505 +0200 checkout: moving from master to main
97f29ee599c373c729132a5c46a046978311e0ee 97f29ee599c373c729132a5c46a046978311e0ee User 2 <user2@example.com> 1745922102 +0200 checkout: moving from main to bugfix-1
97f29ee599c373c729132a5c46a046978311e0ee 35c5cddfc19397501ec8f4f7bb808a7c8f04445f User 2 <user2@example.com> 1745922142 +0200 commit: fix: bug
35c5cddfc19397501ec8f4f7bb808a7c8f04445f 97f29ee599c373c729132a5c46a046978311e0ee User 2 <user2@example.com> 1745922150 +0200 checkout: moving from bugfix-1 to main
97f29ee599c373c729132a5c46a046978311e0ee 35c5cddfc19397501ec8f4f7bb808a7c8f04445f User 2 <user2@example.com> 1745922198 +0200 checkout: moving from main to bugfix-1
35c5cddfc19397501ec8f4f7bb808a7c8f04445f 97f29ee599c373c729132a5c46a046978311e0ee User 2 <user2@example.com> 1745922272 +0200 checkout: moving from bugfix-1 to main

View file

@ -0,0 +1,2 @@
0000000000000000000000000000000000000000 97f29ee599c373c729132a5c46a046978311e0ee User 2 <user2@example.com> 1745922102 +0200 branch: Created from HEAD
97f29ee599c373c729132a5c46a046978311e0ee 35c5cddfc19397501ec8f4f7bb808a7c8f04445f User 2 <user2@example.com> 1745922142 +0200 commit: fix: bug

View file

@ -0,0 +1 @@
0000000000000000000000000000000000000000 97f29ee599c373c729132a5c46a046978311e0ee User 2 <user2@example.com> 1745921505 +0200 branch: Created from HEAD

View file

@ -0,0 +1 @@
35c5cddfc19397501ec8f4f7bb808a7c8f04445f

View file

@ -0,0 +1 @@
97f29ee599c373c729132a5c46a046978311e0ee

View file

@ -0,0 +1 @@
97f29ee599c373c729132a5c46a046978311e0ee

View file

@ -101,3 +101,143 @@ jobs:
assert.Len(t, run.Jobs, 2)
})
}
func TestAPIGetListActionRun(t *testing.T) {
defer tests.PrepareTestEnv(t)()
var (
runIDs = []int64{892, 893, 894}
dbRuns = make(map[int64]*actions_model.ActionRun, 3)
)
for _, id := range runIDs {
dbRuns[id] = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: id})
}
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: dbRuns[runIDs[0]].RepoID})
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
token := getUserToken(t, user.LowerName, auth_model.AccessTokenScopeWriteRepository)
testqueries := []struct {
name string
query string
expectedIDs []int64
}{
{
name: "No query parameters",
query: "",
expectedIDs: runIDs,
},
{
name: "Search for workflow_dispatch events",
query: "?event=workflow_dispatch",
expectedIDs: []int64{894},
},
{
name: "Search for multiple events",
query: "?event=workflow_dispatch&event=push",
expectedIDs: []int64{892, 894},
},
{
name: "Search for failed status",
query: "?status=failure",
expectedIDs: []int64{893},
},
{
name: "Search for multiple statuses",
query: "?status=failure&status=running",
expectedIDs: []int64{893, 894},
},
{
name: "Search for num_nr",
query: "?run_number=1",
expectedIDs: []int64{892},
},
{
name: "Search for sha",
query: "?head_sha=97f29ee599c373c729132a5c46a046978311e0ee",
expectedIDs: []int64{892, 894},
},
}
for _, tt := range testqueries {
t.Run(tt.name, func(t *testing.T) {
req := NewRequest(t, http.MethodGet,
fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs%s",
repo.OwnerName, repo.Name, tt.query,
),
)
req.AddTokenAuth(token)
res := MakeRequest(t, req, http.StatusOK)
apiRuns := new(api.ListActionRunResponse)
DecodeJSON(t, res, apiRuns)
assert.Equal(t, int64(len(tt.expectedIDs)), apiRuns.TotalCount)
assert.Len(t, apiRuns.Entries, len(tt.expectedIDs))
resultIDs := make([]int64, apiRuns.TotalCount)
for i, run := range apiRuns.Entries {
resultIDs[i] = run.ID
}
assert.ElementsMatch(t, tt.expectedIDs, resultIDs)
})
}
}
func TestAPIGetActionRun(t *testing.T) {
defer tests.PrepareTestEnv(t)()
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 63})
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
token := getUserToken(t, user.LowerName, auth_model.AccessTokenScopeWriteRepository)
testqueries := []struct {
name string
runID int64
expectedStatus int
}{
{
name: "existing return ok",
runID: 892,
expectedStatus: http.StatusOK,
},
{
name: "non existing run",
runID: 9876543210, // I hope this run will not exists, else just change it to another.
expectedStatus: http.StatusNotFound,
},
{
name: "existing run but wrong repo should not be found",
runID: 891,
expectedStatus: http.StatusNotFound,
},
}
for _, tt := range testqueries {
t.Run(tt.name, func(t *testing.T) {
req := NewRequest(t, http.MethodGet,
fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs/%d",
repo.OwnerName, repo.Name, tt.runID,
),
)
req.AddTokenAuth(token)
res := MakeRequest(t, req, tt.expectedStatus)
// Only interested in the data if 200 OK
if tt.expectedStatus != http.StatusOK {
return
}
dbRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: tt.runID})
apiRun := new(api.ActionRun)
DecodeJSON(t, res, apiRun)
assert.Equal(t, dbRun.Index, apiRun.RunNumber)
assert.Equal(t, dbRun.Status.String(), apiRun.Status)
assert.Equal(t, dbRun.CommitSHA, apiRun.HeadSHA)
assert.Equal(t, dbRun.TriggerUserID, apiRun.TriggeringActor.ID)
})
}
}