diff --git a/models/actions/run.go b/models/actions/run.go index b4f4d001715..a44b0ff343e 100644 --- a/models/actions/run.go +++ b/models/actions/run.go @@ -71,11 +71,11 @@ func init() { db.RegisterModel(new(ActionRunIndex)) } -func (run *ActionRun) HTMLURL() string { +func (run *ActionRun) HTMLURL(ctxOpt ...context.Context) string { if run.Repo == nil { return "" } - return fmt.Sprintf("%s/actions/runs/%d", run.Repo.HTMLURL(), run.ID) + return fmt.Sprintf("%s/actions/runs/%d", run.Repo.HTMLURL(ctxOpt...), run.ID) } func (run *ActionRun) Link() string { @@ -120,11 +120,7 @@ func (run *ActionRun) RefTooltip() string { } // LoadAttributes load Repo TriggerUser if not loaded -func (run *ActionRun) LoadAttributes(ctx context.Context) (err error) { - if run == nil { - return nil - } - +func (run *ActionRun) LoadAttributes(ctx context.Context) error { if err := run.LoadRepo(ctx); err != nil { return err } @@ -133,18 +129,19 @@ func (run *ActionRun) LoadAttributes(ctx context.Context) (err error) { return err } - if run.TriggerUser == nil { - run.TriggerUserID, run.TriggerUser, err = user_model.GetPossibleUserByID(ctx, run.TriggerUserID) - if err != nil { - return err - } - } + return run.LoadTriggerUser(ctx) +} - return nil +func (run *ActionRun) LoadTriggerUser(ctx context.Context) (err error) { + if run.TriggerUser != nil { + return nil + } + run.TriggerUserID, run.TriggerUser, err = user_model.GetPossibleUserByID(ctx, run.TriggerUserID) + return err } func (run *ActionRun) LoadRepo(ctx context.Context) error { - if run == nil || run.Repo != nil { + if run.Repo != nil { return nil } diff --git a/models/actions/run_attempt.go b/models/actions/run_attempt.go index 8ef2ddf00a3..857247b0689 100644 --- a/models/actions/run_attempt.go +++ b/models/actions/run_attempt.go @@ -51,10 +51,6 @@ func (attempt *ActionRunAttempt) Duration() time.Duration { } func (attempt *ActionRunAttempt) LoadAttributes(ctx context.Context) (err error) { - if attempt == nil { - return nil - } - if attempt.Run == nil { run, err := GetRunByRepoAndID(ctx, attempt.RepoID, attempt.RunID) if err != nil { diff --git a/models/actions/run_job.go b/models/actions/run_job.go index 09213299978..f0d41ef4b47 100644 --- a/models/actions/run_job.go +++ b/models/actions/run_job.go @@ -120,10 +120,6 @@ func (job *ActionRunJob) LoadRepo(ctx context.Context) error { // LoadAttributes load Run if not loaded func (job *ActionRunJob) LoadAttributes(ctx context.Context) error { - if job == nil { - return nil - } - if err := job.LoadRun(ctx); err != nil { return err } diff --git a/models/actions/run_job_list.go b/models/actions/run_job_list.go index e06b6beb9ec..db7554593dd 100644 --- a/models/actions/run_job_list.go +++ b/models/actions/run_job_list.go @@ -56,8 +56,10 @@ func (jobs ActionJobList) LoadRuns(ctx context.Context, withRepo bool) error { return err } for _, j := range jobs { - if j.RunID > 0 && j.Run == nil { + if j.Run == nil { j.Run = runs[j.RunID] + } + if j.Run != nil { j.Run.Repo = j.Repo } } diff --git a/models/actions/run_list.go b/models/actions/run_list.go index 82dc97f3e5a..0a0840648da 100644 --- a/models/actions/run_list.go +++ b/models/actions/run_list.go @@ -7,6 +7,7 @@ import ( "context" "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/translation" @@ -17,27 +18,39 @@ import ( type RunList []*ActionRun -// GetUserIDs returns a slice of user's id -func (runs RunList) GetUserIDs() []int64 { - return container.FilterSlice(runs, func(run *ActionRun) (int64, bool) { - return run.TriggerUserID, true - }) -} - func (runs RunList) LoadTriggerUser(ctx context.Context) error { - userIDs := runs.GetUserIDs() + userIDs := container.FilterSlice(runs, func(run *ActionRun) (int64, bool) { + return run.TriggerUserID, run.TriggerUser == nil + }) users := make(map[int64]*user_model.User, len(userIDs)) if err := db.GetEngine(ctx).In("id", userIDs).Find(&users); err != nil { return err } for _, run := range runs { - if run.TriggerUserID == user_model.ActionsUserID { - run.TriggerUser = user_model.NewActionsUser() - } else { - run.TriggerUser = users[run.TriggerUserID] - if run.TriggerUser == nil { - run.TriggerUser = user_model.NewGhostUser() - } + if run.TriggerUser != nil { + continue + } + run.TriggerUser = users[run.TriggerUserID] + if run.TriggerUserID < 0 { + run.TriggerUserID, run.TriggerUser, _ = user_model.GetPossibleUserByID(ctx, run.TriggerUserID) + } else if run.TriggerUser == nil { + run.TriggerUserID, run.TriggerUser, _ = user_model.GetPossibleUserByID(ctx, user_model.GhostUserID) + } + } + return nil +} + +func (runs RunList) LoadRepos(ctx context.Context) error { + repoIDs := container.FilterSlice(runs, func(run *ActionRun) (int64, bool) { + return run.RepoID, run.Repo == nil + }) + repos, err := repo_model.GetRepositoriesMapByIDs(ctx, repoIDs) + if err != nil { + return err + } + for _, run := range runs { + if run.Repo == nil { + run.Repo = repos[run.RepoID] } } return nil diff --git a/models/actions/task.go b/models/actions/task.go index 016f91a7bb3..7a97eadc798 100644 --- a/models/actions/task.go +++ b/models/actions/task.go @@ -125,9 +125,6 @@ func (task *ActionTask) LoadJob(ctx context.Context) error { // LoadAttributes load Job Steps if not loaded func (task *ActionTask) LoadAttributes(ctx context.Context) error { - if task == nil { - return nil - } if err := task.LoadJob(ctx); err != nil { return err } diff --git a/models/repo/repo.go b/models/repo/repo.go index 25207cc28b0..7814bb4876d 100644 --- a/models/repo/repo.go +++ b/models/repo/repo.go @@ -376,8 +376,9 @@ func (repo *Repository) CommitLink(commitID string) (result string) { } // APIURL returns the repository API URL -func (repo *Repository) APIURL() string { - return setting.AppURL + "api/v1/repos/" + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name) +func (repo *Repository) APIURL(ctxOpt ...context.Context) string { + ctx := util.OptionalArg(ctxOpt, context.TODO()) + return httplib.MakeAbsoluteURL(ctx, setting.AppSubURL+"/api/v1/repos/"+url.PathEscape(repo.OwnerName)+"/"+url.PathEscape(repo.Name)) } // GetCommitsCountCacheKey returns cache key used for commits count caching. diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 633aa77430f..a8bfa0965ec 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1272,7 +1272,7 @@ func Routes() *web.Router { m.Delete("", reqRepoWriter(unit.TypeActions), repo.DeleteArtifact) }) m.Get("/artifacts/{artifact_id}/zip", repo.DownloadArtifact) - }, reqRepoReader(unit.TypeActions), context.ReferencesGitRepo(true)) + }, reqRepoReader(unit.TypeActions)) m.Group("/keys", func() { m.Combo("").Get(repo.ListDeployKeys). Post(bind(api.CreateKeyOption{}), repo.CreateDeployKey) diff --git a/routers/api/v1/repo/action.go b/routers/api/v1/repo/action.go index 8a0be250da1..1d38cc2f534 100644 --- a/routers/api/v1/repo/action.go +++ b/routers/api/v1/repo/action.go @@ -848,6 +848,12 @@ func ListActionTasks(ctx *context.APIContext) { res := new(api.ActionTaskResponse) res.TotalCount = total + taskList := actions_model.TaskList(tasks) + if err := taskList.LoadAttributes(ctx); err != nil { + ctx.APIErrorInternal(err) + return + } + res.Entries = make([]*api.ActionTask, len(tasks)) for i := range tasks { convertedTask, err := convert.ToActionTask(ctx, tasks[i]) @@ -859,7 +865,7 @@ func ListActionTasks(ctx *context.APIContext) { } ctx.SetLinkHeader(total, listOptions.PageSize) - ctx.SetTotalCountHeader(total) // Duplicates api response field but it's better to set it for consistency + ctx.SetTotalCountHeader(total) // Duplicates api response field, but it's better to set it for consistency ctx.JSON(http.StatusOK, &res) } @@ -1155,6 +1161,7 @@ func getCurrentRepoActionRunByID(ctx *context.APIContext) *actions_model.ActionR ctx.APIErrorInternal(err) return nil } + run.Repo = ctx.Repo.Repository return run } @@ -1226,7 +1233,7 @@ func GetWorkflowRun(ctx *context.APIContext) { return } - convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, run, nil) + convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil) if err != nil { ctx.APIErrorInternal(err) return @@ -1275,7 +1282,7 @@ func GetWorkflowRunAttempt(ctx *context.APIContext) { return } - convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, run, attempt) + convertedRun, err := convert.ToActionWorkflowRun(ctx, run, attempt) if err != nil { ctx.APIErrorInternal(err) return @@ -1330,7 +1337,7 @@ func RerunWorkflowRun(ctx *context.APIContext) { return } - convertedRun, err := convert.ToActionWorkflowRun(ctx, ctx.Repo.Repository, run, nil) + convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil) if err != nil { ctx.APIErrorInternal(err) return diff --git a/routers/api/v1/shared/action.go b/routers/api/v1/shared/action.go index 1b12023d7a0..6f0c0248438 100644 --- a/routers/api/v1/shared/action.go +++ b/routers/api/v1/shared/action.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/setting" @@ -62,6 +63,12 @@ func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64, runAttemptI res := new(api.ActionWorkflowJobsResponse) res.TotalCount = total + jobList := actions_model.ActionJobList(jobs) + if err := jobList.LoadAttributes(ctx, true); err != nil { + ctx.APIErrorInternal(err) + return + } + res.Entries = make([]*api.ActionWorkflowJob, len(jobs)) isRepoLevel := repoID != 0 && ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == repoID @@ -70,11 +77,11 @@ func ListJobs(ctx *context.APIContext, ownerID, repoID, runID int64, runAttemptI if isRepoLevel { repository = ctx.Repo.Repository } else { - repository, err = repo_model.GetRepositoryByID(ctx, jobs[i].RepoID) - if err != nil { - ctx.APIErrorInternal(err) + if jobs[i].Run == nil || jobs[i].Run.Repo == nil { + ctx.APIErrorInternal(fmt.Errorf("job %d is missing its run or repository", jobs[i].ID)) return } + repository = jobs[i].Run.Repo } convertedWorkflowJob, err := convert.ToActionWorkflowJob(ctx, repository, nil, jobs[i]) @@ -169,21 +176,28 @@ func ListRuns(ctx *context.APIContext, ownerID, repoID int64) { res := new(api.ActionWorkflowRunsResponse) res.TotalCount = total - res.Entries = make([]*api.ActionWorkflowRun, len(runs)) - isRepoLevel := repoID != 0 && ctx.Repo != nil && ctx.Repo.Repository != nil && ctx.Repo.Repository.ID == repoID - for i := range runs { - var repository *repo_model.Repository - if isRepoLevel { - repository = ctx.Repo.Repository - } else { - repository, err = repo_model.GetRepositoryByID(ctx, runs[i].RepoID) - if err != nil { - ctx.APIErrorInternal(err) - return - } - } + runList := actions_model.RunList(runs) + if err := runList.LoadTriggerUser(ctx); err != nil { + ctx.APIErrorInternal(err) + return + } - convertedRun, err := convert.ToActionWorkflowRun(ctx, repository, runs[i], nil) + if err := runList.LoadRepos(ctx); err != nil { + ctx.APIErrorInternal(err) + return + } + repos := repo_model.RepositoryList(container.FilterSlice(runs, func(r *actions_model.ActionRun) (*repo_model.Repository, bool) { + return r.Repo, r.Repo != nil + })) + if err := repos.LoadOwners(ctx); err != nil { + ctx.APIErrorInternal(err) + return + } + + res.Entries = make([]*api.ActionWorkflowRun, len(runs)) + for i := range runs { + // TODO: load run attempts in batch + convertedRun, err := convert.ToActionWorkflowRun(ctx, runs[i], nil) if err != nil { ctx.APIErrorInternal(err) return diff --git a/services/actions/notifier.go b/services/actions/notifier.go index c3b2003b3cd..4b2e87afad2 100644 --- a/services/actions/notifier.go +++ b/services/actions/notifier.go @@ -815,7 +815,8 @@ func (n *actionsNotifier) WorkflowRunStatusUpdate(ctx context.Context, repo *rep log.Error("GetActionWorkflow: %v", err) return } - convertedRun, err := convert.ToActionWorkflowRun(ctx, repo, run, nil) + run.Repo = repo + convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil) if err != nil { log.Error("ToActionWorkflowRun: %v", err) return diff --git a/services/convert/action_test.go b/services/convert/action_test.go index 9ecb4a2ca64..9efc0e36a83 100644 --- a/services/convert/action_test.go +++ b/services/convert/action_test.go @@ -115,12 +115,12 @@ func TestToActionWorkflowRun_UsesTriggerEvent(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) run := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: 803}) - + run.Repo = repo // Scheduled runs keep Event as the registration event (push) and use TriggerEvent as the real trigger. run.Event = "push" run.TriggerEvent = "schedule" - apiRun, err := ToActionWorkflowRun(t.Context(), repo, run, nil) + apiRun, err := ToActionWorkflowRun(t.Context(), run, nil) require.NoError(t, err) assert.Equal(t, "schedule", apiRun.Event) } diff --git a/services/convert/convert.go b/services/convert/convert.go index 29f46d37ad5..b254d297859 100644 --- a/services/convert/convert.go +++ b/services/convert/convert.go @@ -29,6 +29,7 @@ import ( "code.gitea.io/gitea/modules/actions" "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -222,14 +223,18 @@ func ToTag(repo *repo_model.Repository, t *git.Tag) *api.Tag { } } -// ToActionTask convert a actions_model.ActionTask to an api.ActionTask +// ToActionTask convert an actions_model.ActionTask to an api.ActionTask func ToActionTask(ctx context.Context, t *actions_model.ActionTask) (*api.ActionTask, error) { - if err := t.LoadAttributes(ctx); err != nil { + // don't need Steps here, only need to load job and its run + if err := t.LoadJob(ctx); err != nil { + return nil, err + } + if err := t.Job.LoadRun(ctx); err != nil { + return nil, err + } + if err := t.Job.Run.LoadRepo(ctx); err != nil { return nil, err } - - url := strings.TrimSuffix(setting.AppURL, "/") + t.GetRunLink() - return &api.ActionTask{ ID: t.ID, Name: t.Job.Name, @@ -240,23 +245,25 @@ func ToActionTask(ctx context.Context, t *actions_model.ActionTask) (*api.Action DisplayTitle: t.Job.Run.Title, Status: t.Status.String(), WorkflowID: t.Job.Run.WorkflowID, - URL: url, + URL: httplib.MakeAbsoluteURL(ctx, t.Job.Run.Link()), CreatedAt: t.Created.AsLocalTime(), UpdatedAt: t.Updated.AsLocalTime(), RunStartedAt: t.Started.AsLocalTime(), }, nil } -func ToActionWorkflowRun(ctx context.Context, repo *repo_model.Repository, run *actions_model.ActionRun, attempt *actions_model.ActionRunAttempt) (*api.ActionWorkflowRun, error) { - if err := run.LoadAttributes(ctx); err != nil { +func ToActionWorkflowRun(ctx context.Context, run *actions_model.ActionRun, attempt *actions_model.ActionRunAttempt) (_ *api.ActionWorkflowRun, err error) { + if err := run.LoadRepo(ctx); err != nil { + return nil, err + } + if err := run.LoadTriggerUser(ctx); err != nil { return nil, err } if attempt == nil { - if latestAttempt, has, err := run.GetLatestAttempt(ctx); err != nil { + attempt, _, err = run.GetLatestAttempt(ctx) + if err != nil { return nil, err - } else if has { - attempt = latestAttempt } } @@ -272,6 +279,7 @@ func ToActionWorkflowRun(ctx context.Context, repo *repo_model.Repository, run * var previousAttemptURL *string if attempt != nil { + attempt.Run = run if err := attempt.LoadAttributes(ctx); err != nil { return nil, err } @@ -281,16 +289,15 @@ func ToActionWorkflowRun(ctx context.Context, repo *repo_model.Repository, run * completedAt = attempt.Stopped.AsLocalTime() triggerUser = attempt.TriggerUser if attempt.Attempt > 1 { - url := fmt.Sprintf("%s/actions/runs/%d/attempts/%d", repo.APIURL(), run.ID, attempt.Attempt-1) - previousAttemptURL = &url + previousAttemptURL = new(fmt.Sprintf("%s/actions/runs/%d/attempts/%d", run.Repo.APIURL(ctx), run.ID, attempt.Attempt-1)) } } return &api.ActionWorkflowRun{ ID: run.ID, - URL: fmt.Sprintf("%s/actions/runs/%d", repo.APIURL(), run.ID), + URL: fmt.Sprintf("%s/actions/runs/%d", run.Repo.APIURL(ctx), run.ID), PreviousAttemptURL: previousAttemptURL, - HTMLURL: run.HTMLURL(), + HTMLURL: run.HTMLURL(ctx), RunNumber: run.Index, RunAttempt: runAttempt, StartedAt: startedAt, @@ -302,7 +309,7 @@ func ToActionWorkflowRun(ctx context.Context, repo *repo_model.Repository, run * Status: status, Conclusion: conclusion, Path: fmt.Sprintf("%s@%s", run.WorkflowID, run.Ref), - Repository: ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeNone}), + Repository: ToRepo(ctx, run.Repo, access_model.Permission{AccessMode: perm.AccessModeNone}), TriggerActor: ToUser(ctx, triggerUser, nil), Actor: ToUser(ctx, actor, nil), }, nil @@ -400,11 +407,11 @@ func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task return &api.ActionWorkflowJob{ ID: job.ID, // missing api endpoint for this location - URL: fmt.Sprintf("%s/actions/jobs/%d", repo.APIURL(), job.ID), - HTMLURL: fmt.Sprintf("%s/jobs/%d", job.Run.HTMLURL(), job.ID), + URL: fmt.Sprintf("%s/actions/jobs/%d", repo.APIURL(ctx), job.ID), + HTMLURL: fmt.Sprintf("%s/jobs/%d", job.Run.HTMLURL(ctx), job.ID), RunID: job.RunID, // Missing api endpoint for this location, artifacts are available under a nested url - RunURL: fmt.Sprintf("%s/actions/runs/%d", repo.APIURL(), job.RunID), + RunURL: fmt.Sprintf("%s/actions/runs/%d", repo.APIURL(ctx), job.RunID), Name: job.Name, Labels: job.RunsOn, RunAttempt: job.Attempt, diff --git a/services/webhook/notifier.go b/services/webhook/notifier.go index d2575e9931f..7627935a321 100644 --- a/services/webhook/notifier.go +++ b/services/webhook/notifier.go @@ -1043,7 +1043,8 @@ func (*webhookNotifier) WorkflowRunStatusUpdate(ctx context.Context, repo *repo_ return } - convertedRun, err := convert.ToActionWorkflowRun(ctx, repo, run, nil) + run.Repo = repo + convertedRun, err := convert.ToActionWorkflowRun(ctx, run, nil) if err != nil { log.Error("ToActionWorkflowRun: %v", err) return diff --git a/tests/integration/api_actions_run_test.go b/tests/integration/api_actions_run_test.go index e2aeef7ec01..22fa6ba2eaf 100644 --- a/tests/integration/api_actions_run_test.go +++ b/tests/integration/api_actions_run_test.go @@ -15,17 +15,35 @@ import ( 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/modules/json" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestAPIActionsGetWorkflowRun(t *testing.T) { +func TestAPIActionsWorkflowRun(t *testing.T) { defer prepareTestEnvActionsArtifacts(t)() + t.Run("GetWorkflowRun", testAPIActionsGetWorkflowRun) + t.Run("GetWorkflowJob", testAPIActionsGetWorkflowJob) + t.Run("ListUserWorkflows", testAPIActionsListUserWorkflows) + t.Run("ListRepoWorkflows", testAPIActionsListRepoWorkflows) + t.Run("DeleteRunCheckPermission", testAPIActionsDeleteRunCheckPermission) + t.Run("DeleteRunRunning", testAPIActionsDeleteRunRunning) + t.Run("DeleteRunGeneral", testAPIActionsDeleteRunGeneral) + t.Run("RerunWorkflowRun", func(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testAPIActionsRerunWorkflowRun(t) + }) + t.Run("RerunWorkflowJob", func(t *testing.T) { + defer tests.PrepareTestEnv(t)() + testAPIActionsRerunWorkflowJob(t) + }) +} + +func testAPIActionsGetWorkflowRun(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, user.Name) @@ -56,13 +74,9 @@ func TestAPIActionsGetWorkflowRun(t *testing.T) { }) require.NoError(t, err) - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/jobs", repo.FullName())). - AddTokenAuth(token) + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/jobs", repo.FullName())).AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) - - var jobList api.ActionWorkflowJobsResponse - err = json.Unmarshal(resp.Body.Bytes(), &jobList) - require.NoError(t, err) + jobList := DecodeJSON(t, resp, &api.ActionWorkflowJobsResponse{}) job198Idx := slices.IndexFunc(jobList.Entries, func(job *api.ActionWorkflowJob) bool { return job.ID == 198 }) require.NotEqual(t, -1, job198Idx, "expected to find job 198 in run 795 jobs list") @@ -72,9 +86,7 @@ func TestAPIActionsGetWorkflowRun(t *testing.T) { }) } -func TestAPIActionsGetWorkflowJob(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() - +func testAPIActionsGetWorkflowJob(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, user.Name) @@ -91,9 +103,7 @@ func TestAPIActionsGetWorkflowJob(t *testing.T) { MakeRequest(t, req, http.StatusNotFound) } -func TestAPIActionsDeleteRunCheckPermission(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() - +func testAPIActionsDeleteRunCheckPermission(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, user.Name) @@ -101,9 +111,7 @@ func TestAPIActionsDeleteRunCheckPermission(t *testing.T) { testAPIActionsDeleteRun(t, repo, token, http.StatusNotFound) } -func TestAPIActionsDeleteRun(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() - +func testAPIActionsDeleteRunGeneral(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, user.Name) @@ -118,9 +126,7 @@ func TestAPIActionsDeleteRun(t *testing.T) { testAPIActionsDeleteRun(t, repo, token, http.StatusNotFound) } -func TestAPIActionsDeleteRunRunning(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() - +func testAPIActionsDeleteRunRunning(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) session := loginUser(t, user.Name) @@ -138,22 +144,17 @@ func testAPIActionsDeleteRun(t *testing.T, repo *repo_model.Repository, token st } func testAPIActionsDeleteRunListArtifacts(t *testing.T, repo *repo_model.Repository, token string, artifacts int) { - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/artifacts", repo.FullName())). - AddTokenAuth(token) + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/artifacts", repo.FullName())).AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) - var listResp api.ActionArtifactsResponse - err := json.Unmarshal(resp.Body.Bytes(), &listResp) - assert.NoError(t, err) + listResp := DecodeJSON(t, resp, &api.ActionArtifactsResponse{}) assert.Len(t, listResp.Entries, artifacts) } func testAPIActionsDeleteRunListTasks(t *testing.T, repo *repo_model.Repository, token string, expected bool) { - req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/tasks", repo.FullName())). - AddTokenAuth(token) + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/tasks", repo.FullName())).AddTokenAuth(token) resp := MakeRequest(t, req, http.StatusOK) - var listResp api.ActionTaskResponse - err := json.Unmarshal(resp.Body.Bytes(), &listResp) - assert.NoError(t, err) + listResp := DecodeJSON(t, resp, &api.ActionTaskResponse{}) + findTask1 := false findTask2 := false for _, entry := range listResp.Entries { @@ -170,9 +171,7 @@ func testAPIActionsDeleteRunListTasks(t *testing.T, repo *repo_model.Repository, assert.Equal(t, expected, findTask2) } -func TestAPIActionsRerunWorkflowRun(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() - +func testAPIActionsRerunWorkflowRun(t *testing.T) { t.Run("NotDone", func(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) @@ -192,13 +191,10 @@ func TestAPIActionsRerunWorkflowRun(t *testing.T) { readToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) t.Run("Success", func(t *testing.T) { - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/rerun", repo.FullName())). - AddTokenAuth(writeToken) + req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/rerun", repo.FullName())).AddTokenAuth(writeToken) resp := MakeRequest(t, req, http.StatusCreated) + rerunResp := DecodeJSON(t, resp, &api.ActionWorkflowRun{}) - var rerunResp api.ActionWorkflowRun - err := json.Unmarshal(resp.Body.Bytes(), &rerunResp) - require.NoError(t, err) assert.Equal(t, int64(795), rerunResp.ID) assert.Equal(t, "queued", rerunResp.Status) assert.Equal(t, "c2d72f548424103f01ee1dc02889c1e2bff816b0", rerunResp.HeadSha) @@ -236,9 +232,7 @@ func TestAPIActionsRerunWorkflowRun(t *testing.T) { }) } -func TestAPIActionsRerunWorkflowJob(t *testing.T) { - defer prepareTestEnvActionsArtifacts(t)() - +func testAPIActionsRerunWorkflowJob(t *testing.T) { t.Run("NotDone", func(t *testing.T) { repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) @@ -258,13 +252,10 @@ func TestAPIActionsRerunWorkflowJob(t *testing.T) { readToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) t.Run("Success", func(t *testing.T) { - req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/jobs/199/rerun", repo.FullName())). - AddTokenAuth(writeToken) + req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/actions/runs/795/jobs/199/rerun", repo.FullName())).AddTokenAuth(writeToken) resp := MakeRequest(t, req, http.StatusCreated) + rerunResp := DecodeJSON(t, resp, &api.ActionWorkflowJob{}) - var rerunResp api.ActionWorkflowJob - err := json.Unmarshal(resp.Body.Bytes(), &rerunResp) - require.NoError(t, err) job199Rerun := getLatestAttemptJobByTemplateJobID(t, 795, 199) assert.Equal(t, job199Rerun.ID, rerunResp.ID) assert.Equal(t, "queued", rerunResp.Status) @@ -301,3 +292,59 @@ func TestAPIActionsRerunWorkflowJob(t *testing.T) { MakeRequest(t, req, http.StatusNotFound) }) } + +func testAPIActionsListUserWorkflows(t *testing.T) { + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser) + + t.Run("Runs", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/user/actions/runs").AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + runs := DecodeJSON(t, resp, &api.ActionWorkflowRunsResponse{}) + + assert.Positive(t, runs.TotalCount) + assert.NotEmpty(t, runs.Entries) + + for _, run := range runs.Entries { + assert.NotEmpty(t, run.DisplayTitle, "display_title should be populated") + assert.NotNil(t, run.Repository, "repository should be populated via batch loading") + assert.NotEmpty(t, run.Repository.FullName, "repository full_name should be populated") + assert.NotNil(t, run.TriggerActor, "trigger_actor should be populated via batch loading") + } + }) + + t.Run("Jobs", func(t *testing.T) { + req := NewRequest(t, "GET", "/api/v1/user/actions/jobs").AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + jobs := DecodeJSON(t, resp, &api.ActionWorkflowJobsResponse{}) + + assert.Positive(t, jobs.TotalCount) + assert.NotEmpty(t, jobs.Entries) + + for _, job := range jobs.Entries { + assert.NotEmpty(t, job.Name, "job name should be populated") + assert.NotEmpty(t, job.HTMLURL, "html_url should be populated via batch-loaded repo") + } + }) +} + +func testAPIActionsListRepoWorkflows(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + session := loginUser(t, user.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + + req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/%s/actions/runs", repo.FullName())).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + runs := DecodeJSON(t, resp, &api.ActionWorkflowRunsResponse{}) + + assert.Positive(t, runs.TotalCount) + assert.NotEmpty(t, runs.Entries) + + for _, run := range runs.Entries { + assert.NotNil(t, run.Repository, "repository should be populated from ctx.Repo") + assert.Equal(t, repo.FullName(), run.Repository.FullName, "repository full_name should match") + assert.NotNil(t, run.TriggerActor, "trigger_actor should be populated") + } +}