diff --git a/models/actions/run_job_list.go b/models/actions/run_job_list.go index cbcb4beb8e..afc754f26a 100644 --- a/models/actions/run_job_list.go +++ b/models/actions/run_job_list.go @@ -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 } diff --git a/models/actions/status.go b/models/actions/status.go index f4357af731..e42c221121 100644 --- a/models/actions/status.go +++ b/models/actions/status.go @@ -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 +} diff --git a/models/fixtures/action_run.yml b/models/fixtures/action_run.yml index 7a7bf34197..5b6f89ae0e 100644 --- a/models/fixtures/action_run.yml +++ b/models/fixtures/action_run.yml @@ -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 diff --git a/models/fixtures/repo_unit.yml b/models/fixtures/repo_unit.yml index cd49a51796..773f238645 100644 --- a/models/fixtures/repo_unit.yml +++ b/models/fixtures/repo_unit.yml @@ -795,3 +795,10 @@ type: 10 config: "{}" created_unix: 946684810 + +- + id: 115 + repo_id: 63 + type: 10 + config: "{}" + created_unix: 946684810 diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index 5941f82299..c383fa43ac 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -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: '[]' diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml index 82a1d28023..1a03185cf1 100644 --- a/models/fixtures/user.yml +++ b/models/fixtures/user.yml @@ -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 diff --git a/modules/structs/repo_actions.go b/modules/structs/repo_actions.go index b13f344738..981baddb91 100644 --- a/modules/structs/repo_actions.go +++ b/modules/structs/repo_actions.go @@ -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"` +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 18624df246..e371ebb28b 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -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() { diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go index a39d4836e1..2f0f2c17cd 100644 --- a/routers/api/v1/repo/action.go +++ b/routers/api/v1/repo/action.go @@ -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) +} diff --git a/routers/api/v1/swagger/repo.go b/routers/api/v1/swagger/repo.go index bf7e2cc0c3..629f207ca3 100644 --- a/routers/api/v1/swagger/repo.go +++ b/routers/api/v1/swagger/repo.go @@ -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"` +} diff --git a/services/convert/convert.go b/services/convert/convert.go index 2ea24a1b51..45e5a1994b 100644 --- a/services/convert/convert.go +++ b/services/convert/convert.go @@ -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) diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 888b39ec82..d2957a0f86 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -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": { diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/COMMIT_EDITMSG b/tests/gitea-repositories-meta/user2/test_action_run_search.git/COMMIT_EDITMSG new file mode 100644 index 0000000000..6be072ebc0 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/test_action_run_search.git/COMMIT_EDITMSG @@ -0,0 +1 @@ +fix: bug diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/FETCH_HEAD b/tests/gitea-repositories-meta/user2/test_action_run_search.git/FETCH_HEAD new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/HEAD b/tests/gitea-repositories-meta/user2/test_action_run_search.git/HEAD new file mode 100644 index 0000000000..b870d82622 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/test_action_run_search.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/main diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/config b/tests/gitea-repositories-meta/user2/test_action_run_search.git/config new file mode 100644 index 0000000000..515f483629 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/test_action_run_search.git/config @@ -0,0 +1,5 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/description b/tests/gitea-repositories-meta/user2/test_action_run_search.git/description new file mode 100644 index 0000000000..498b267a8c --- /dev/null +++ b/tests/gitea-repositories-meta/user2/test_action_run_search.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/index b/tests/gitea-repositories-meta/user2/test_action_run_search.git/index new file mode 100644 index 0000000000..cca2ada835 Binary files /dev/null and b/tests/gitea-repositories-meta/user2/test_action_run_search.git/index differ diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/info/exclude b/tests/gitea-repositories-meta/user2/test_action_run_search.git/info/exclude new file mode 100644 index 0000000000..a5196d1be8 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/test_action_run_search.git/info/exclude @@ -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] +# *~ diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/logs/HEAD b/tests/gitea-repositories-meta/user2/test_action_run_search.git/logs/HEAD new file mode 100644 index 0000000000..51870db479 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/test_action_run_search.git/logs/HEAD @@ -0,0 +1,7 @@ +0000000000000000000000000000000000000000 97f29ee599c373c729132a5c46a046978311e0ee User 2 1745918688 +0200 commit (initial): initial +97f29ee599c373c729132a5c46a046978311e0ee 97f29ee599c373c729132a5c46a046978311e0ee User 2 1745921505 +0200 checkout: moving from master to main +97f29ee599c373c729132a5c46a046978311e0ee 97f29ee599c373c729132a5c46a046978311e0ee User 2 1745922102 +0200 checkout: moving from main to bugfix-1 +97f29ee599c373c729132a5c46a046978311e0ee 35c5cddfc19397501ec8f4f7bb808a7c8f04445f User 2 1745922142 +0200 commit: fix: bug +35c5cddfc19397501ec8f4f7bb808a7c8f04445f 97f29ee599c373c729132a5c46a046978311e0ee User 2 1745922150 +0200 checkout: moving from bugfix-1 to main +97f29ee599c373c729132a5c46a046978311e0ee 35c5cddfc19397501ec8f4f7bb808a7c8f04445f User 2 1745922198 +0200 checkout: moving from main to bugfix-1 +35c5cddfc19397501ec8f4f7bb808a7c8f04445f 97f29ee599c373c729132a5c46a046978311e0ee User 2 1745922272 +0200 checkout: moving from bugfix-1 to main diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/logs/refs/heads/bugfix-1 b/tests/gitea-repositories-meta/user2/test_action_run_search.git/logs/refs/heads/bugfix-1 new file mode 100644 index 0000000000..d8977c9d15 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/test_action_run_search.git/logs/refs/heads/bugfix-1 @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 97f29ee599c373c729132a5c46a046978311e0ee User 2 1745922102 +0200 branch: Created from HEAD +97f29ee599c373c729132a5c46a046978311e0ee 35c5cddfc19397501ec8f4f7bb808a7c8f04445f User 2 1745922142 +0200 commit: fix: bug diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/logs/refs/heads/main b/tests/gitea-repositories-meta/user2/test_action_run_search.git/logs/refs/heads/main new file mode 100644 index 0000000000..30a780778b --- /dev/null +++ b/tests/gitea-repositories-meta/user2/test_action_run_search.git/logs/refs/heads/main @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 97f29ee599c373c729132a5c46a046978311e0ee User 2 1745921505 +0200 branch: Created from HEAD diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/05/daa94891793ad0f183273cfb9902b903782e2a b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/05/daa94891793ad0f183273cfb9902b903782e2a new file mode 100644 index 0000000000..741f3a665f Binary files /dev/null and b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/05/daa94891793ad0f183273cfb9902b903782e2a differ diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/18/722d8d68a60af7e66213ef4a15d05dae365f2b b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/18/722d8d68a60af7e66213ef4a15d05dae365f2b new file mode 100644 index 0000000000..cececf6633 Binary files /dev/null and b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/18/722d8d68a60af7e66213ef4a15d05dae365f2b differ diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/35/c5cddfc19397501ec8f4f7bb808a7c8f04445f b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/35/c5cddfc19397501ec8f4f7bb808a7c8f04445f new file mode 100644 index 0000000000..3f921384ba Binary files /dev/null and b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/35/c5cddfc19397501ec8f4f7bb808a7c8f04445f differ diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/76/ebce62dde5d6c40cfdda5782ff171f02a618b9 b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/76/ebce62dde5d6c40cfdda5782ff171f02a618b9 new file mode 100644 index 0000000000..920604316e Binary files /dev/null and b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/76/ebce62dde5d6c40cfdda5782ff171f02a618b9 differ diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/7f/56afb8a0b9ffbc39e8edd537f252412254d112 b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/7f/56afb8a0b9ffbc39e8edd537f252412254d112 new file mode 100644 index 0000000000..4dad68d944 Binary files /dev/null and b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/7f/56afb8a0b9ffbc39e8edd537f252412254d112 differ diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/8d/6860a6d6bb80661d308b060509ef41f099c390 b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/8d/6860a6d6bb80661d308b060509ef41f099c390 new file mode 100644 index 0000000000..893f680834 Binary files /dev/null and b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/8d/6860a6d6bb80661d308b060509ef41f099c390 differ diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/8f/2069876727126f91aa756d84caa59f93670dfc b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/8f/2069876727126f91aa756d84caa59f93670dfc new file mode 100644 index 0000000000..92663ca712 Binary files /dev/null and b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/8f/2069876727126f91aa756d84caa59f93670dfc differ diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/97/f29ee599c373c729132a5c46a046978311e0ee b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/97/f29ee599c373c729132a5c46a046978311e0ee new file mode 100644 index 0000000000..9a835a73c4 Binary files /dev/null and b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/97/f29ee599c373c729132a5c46a046978311e0ee differ diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/a5/3a19de56c7594844459c2951497c15dba8adee b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/a5/3a19de56c7594844459c2951497c15dba8adee new file mode 100644 index 0000000000..44b7d5774d Binary files /dev/null and b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/a5/3a19de56c7594844459c2951497c15dba8adee differ diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/a6/2cdde48ab95bb49294c30cf0971ce2511008c5 b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/a6/2cdde48ab95bb49294c30cf0971ce2511008c5 new file mode 100644 index 0000000000..e6f7dccf30 Binary files /dev/null and b/tests/gitea-repositories-meta/user2/test_action_run_search.git/objects/a6/2cdde48ab95bb49294c30cf0971ce2511008c5 differ diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/refs/heads/bugfix-1 b/tests/gitea-repositories-meta/user2/test_action_run_search.git/refs/heads/bugfix-1 new file mode 100644 index 0000000000..ae12ad5d79 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/test_action_run_search.git/refs/heads/bugfix-1 @@ -0,0 +1 @@ +35c5cddfc19397501ec8f4f7bb808a7c8f04445f diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/refs/heads/main b/tests/gitea-repositories-meta/user2/test_action_run_search.git/refs/heads/main new file mode 100644 index 0000000000..d46cce7027 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/test_action_run_search.git/refs/heads/main @@ -0,0 +1 @@ +97f29ee599c373c729132a5c46a046978311e0ee diff --git a/tests/gitea-repositories-meta/user2/test_action_run_search.git/refs/tags/v1 b/tests/gitea-repositories-meta/user2/test_action_run_search.git/refs/tags/v1 new file mode 100644 index 0000000000..d46cce7027 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/test_action_run_search.git/refs/tags/v1 @@ -0,0 +1 @@ +97f29ee599c373c729132a5c46a046978311e0ee diff --git a/tests/integration/api_repo_actions_test.go b/tests/integration/api_repo_actions_test.go index 43de376421..a7b66d6d62 100644 --- a/tests/integration/api_repo_actions_test.go +++ b/tests/integration/api_repo_actions_test.go @@ -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) + }) + } +}