mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 19:11:25 +01:00
run list prints a table with a status icon, the workflow name, title, branch, event, elapsed time and age, and the total from the server; runs carry workflow, workflow_path and run_number in JSON. run view shows the run's state and trigger, its jobs with their steps and durations, the next steps and the run's URL; --json includes the jobs, --log prints their logs and --web opens the run. Run commands accept #N and run URLs, and ask for a recent run in a terminal when the argument is left out. run watch redraws the run on the alternate screen, then prints it with a summary; an expired --timeout names the run and suspects a missing runner when no job started. run cancel no longer asks for confirmation, rerun refuses unfinished runs up front, reruns a single job with --job and reports the new attempt, and delete names the run in its prompt. run logs follows several jobs with --follow, prefixing lines with the job's name, selects jobs by name or with --failed, and downloads the archive with --output or --dir. run download extracts the artifacts of a run by name, and artifact list shows sizes and expiry. Signed-off-by: bircni <bircni@icloud.com>
398 lines
19 KiB
Go
398 lines
19 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.dev/tea/internal/clierr"
|
|
"gitea.dev/tea/internal/terminal"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// actionsNow is the clock of the Actions fixtures.
|
|
var actionsNow = time.Date(2028, 1, 1, 12, 0, 0, 0, time.UTC)
|
|
|
|
const (
|
|
workflowsFixture = `{"total_count":2,"workflows":[
|
|
{"id":"ci.yml","name":"CI","path":".gitea/workflows/ci.yml","state":"active","html_url":"https://gitea.test/alice/project/src/branch/main/.gitea/workflows/ci.yml"},
|
|
{"id":"nightly.yaml","name":"Nightly","path":".gitea/workflows/nightly.yaml","state":"disabled_manually"}]}`
|
|
queuedRunFixture = `{"id":47,"run_number":10,"display_title":"Add workflows","path":"ci.yml@refs/heads/main","event":"workflow_dispatch",
|
|
"head_branch":"main","head_sha":"f0c151dde0c0ef6c","status":"queued","run_attempt":1,"actor":{"login":"alice"},
|
|
"created_at":"2028-01-01T11:58:00Z","started_at":"1970-01-01T00:00:00Z","completed_at":"1970-01-01T00:00:00Z",
|
|
"html_url":"https://gitea.test/alice/project/actions/runs/47"}`
|
|
failedRunFixture = `{"id":29,"run_number":9,"display_title":"Refactor the deployment pipeline","path":"nightly.yaml@refs/heads/feature/x",
|
|
"event":"push","head_branch":"feature/x","head_sha":"209d545ac554","status":"completed","conclusion":"failure","run_attempt":2,
|
|
"actor":{"login":"bob"},"created_at":"2028-01-01T11:00:00Z","started_at":"2028-01-01T11:00:05Z","completed_at":"2028-01-01T11:01:17Z",
|
|
"html_url":"https://gitea.test/alice/project/actions/runs/29"}`
|
|
queuedJobsFixture = `{"total_count":2,"jobs":[
|
|
{"id":48,"name":"build","status":"queued","labels":["ubuntu-latest"],"html_url":"https://gitea.test/alice/project/actions/runs/47/jobs/48","started_at":"1970-01-01T00:00:00Z","completed_at":"1970-01-01T00:00:00Z"},
|
|
{"id":49,"name":"test","status":"waiting","labels":["ubuntu-latest"],"started_at":"1970-01-01T00:00:00Z","completed_at":"1970-01-01T00:00:00Z"}]}`
|
|
failedJobsFixture = `{"total_count":2,"jobs":[
|
|
{"id":30,"name":"build","status":"completed","conclusion":"success","started_at":"2028-01-01T11:00:05Z","completed_at":"2028-01-01T11:00:50Z",
|
|
"steps":[{"number":1,"name":"Set up job","status":"completed","conclusion":"success","started_at":"2028-01-01T11:00:05Z","completed_at":"2028-01-01T11:00:07Z"}]},
|
|
{"id":31,"name":"test","status":"completed","conclusion":"failure","started_at":"2028-01-01T11:00:50Z","completed_at":"2028-01-01T11:01:17Z",
|
|
"steps":[{"number":1,"name":"Run tests","status":"completed","conclusion":"failure","started_at":"2028-01-01T11:00:51Z","completed_at":"2028-01-01T11:01:16Z"}]}]}`
|
|
)
|
|
|
|
// actionsAPI serves canned responses for alice/project, keyed by method and path below the
|
|
// repository ("GET actions/runs"). A response that starts with a status code and a space, such as
|
|
// "404 {...}", is sent with that status. It records the keys of all requests, followed by their
|
|
// bodies, and fails the test on requests without a response.
|
|
func actionsAPI(t *testing.T, responses map[string]string) (*httptest.Server, *[]string) {
|
|
t.Helper()
|
|
var requests []string
|
|
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
key := r.Method + " " + strings.TrimPrefix(strings.TrimPrefix(r.URL.Path, "/api/v1/repos/alice/project"), "/")
|
|
if payload, _ := io.ReadAll(r.Body); len(payload) > 0 {
|
|
requests = append(requests, key+" "+string(payload))
|
|
} else {
|
|
requests = append(requests, key)
|
|
}
|
|
body, ok := responses[key]
|
|
if !ok {
|
|
t.Errorf("unexpected request %s", key)
|
|
w.WriteHeader(http.StatusTeapot)
|
|
return
|
|
}
|
|
if status, rest, found := strings.Cut(body, " "); found && len(status) == 3 {
|
|
if code, err := strconv.Atoi(status); err == nil {
|
|
w.WriteHeader(code)
|
|
body = rest
|
|
}
|
|
}
|
|
_, _ = w.Write([]byte(body))
|
|
}))
|
|
t.Cleanup(server.Close)
|
|
return server, &requests
|
|
}
|
|
|
|
// actionsDeps returns dependencies for an actionsAPI server at the fixtures' clock, on a terminal
|
|
// without colors or links when tty is set.
|
|
func actionsDeps(t *testing.T, serverURL string, tty bool) (Dependencies, *bytes.Buffer, *bytes.Buffer) {
|
|
t.Helper()
|
|
deps, out, diagnostics := serverDeps(t, serverURL)
|
|
deps.Getenv = serverEnv(serverURL, map[string]string{"TERM": "dumb"})
|
|
deps.Now = func() time.Time { return actionsNow }
|
|
deps.Streams.OutTTY = tty
|
|
return deps, out, diagnostics
|
|
}
|
|
|
|
func TestRunTimeBoundsUseOneClockReading(t *testing.T) {
|
|
deps, _, _ := testDeps(t)
|
|
reads := 0
|
|
deps.Now = func() time.Time { reads++; return actionsNow }
|
|
err := Execute(context.Background(), deps, []string{"run", "list", "--since=1h", "--until=2h"})
|
|
require.ErrorContains(t, err, "--since must not be later than --until")
|
|
require.Equal(t, 1, reads)
|
|
}
|
|
|
|
func TestRunListShowsStatusWorkflowAndTiming(t *testing.T) {
|
|
server, _ := actionsAPI(t, map[string]string{
|
|
"GET actions/runs": `{"total_count":2,"workflow_runs":[` + queuedRunFixture + `,` + failedRunFixture + `]}`,
|
|
"GET actions/workflows": workflowsFixture,
|
|
})
|
|
for _, tc := range []struct {
|
|
name string
|
|
tty bool
|
|
args []string
|
|
want string
|
|
}{
|
|
{"terminal", true, nil, `Showing 2 of 2 runs in alice/project
|
|
|
|
STATUS ID WORKFLOW TITLE BRANCH EVENT ELAPSED AGE
|
|
* 47 CI Add workflows main workflow_dispatch about 2 minutes ago
|
|
X 29 Nightly Refactor the deployment pipeline feature/x push 1m 12s about 1 hour ago
|
|
`},
|
|
{"piped", false, nil, "queued\t\t47\tCI\tAdd workflows\tmain\tworkflow_dispatch\t\t2028-01-01T11:58:00Z\thttps://gitea.test/alice/project/actions/runs/47\n" +
|
|
"completed\tfailure\t29\tNightly\tRefactor the deployment pipeline\tfeature/x\tpush\t1m 12s\t2028-01-01T11:00:00Z\thttps://gitea.test/alice/project/actions/runs/29\n"},
|
|
{"json", false, []string{"--json", "id,workflow,workflow_path,run_number,started_at"}, `[{"id":47,"workflow":"CI","workflow_path":"ci.yml","run_number":10,"started_at":null},` +
|
|
`{"id":29,"workflow":"Nightly","workflow_path":"nightly.yaml","run_number":9,"started_at":"2028-01-01T11:00:05Z"}]` + "\n"},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
deps, out, diagnostics := actionsDeps(t, server.URL, tc.tty)
|
|
require.NoError(t, Execute(context.Background(), deps, append([]string{"run", "list", "-R", "alice/project"}, tc.args...)))
|
|
if strings.HasPrefix(tc.want, "[") {
|
|
require.JSONEq(t, tc.want, out.String())
|
|
} else {
|
|
require.Equal(t, tc.want, out.String())
|
|
}
|
|
require.Empty(t, diagnostics.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRunListResolvesWorkflowNamesAndReportsEmptyFilters(t *testing.T) {
|
|
server, requests := actionsAPI(t, map[string]string{
|
|
"GET actions/workflows": workflowsFixture,
|
|
"GET actions/workflows/ci.yml/runs": `{"total_count":0,"workflow_runs":[]}`,
|
|
})
|
|
deps, out, diagnostics := actionsDeps(t, server.URL, true)
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"run", "list", "-R", "alice/project", "-w", "ci", "-s", "FAILURE"}))
|
|
require.Empty(t, out.String())
|
|
require.Equal(t, "no runs match your filters in alice/project\n", diagnostics.String())
|
|
require.Contains(t, *requests, "GET actions/workflows/ci.yml/runs")
|
|
requireUsageBeforeRequests(t, "--status must be queued, in_progress, waiting", "run", "list", "--status", "nope")
|
|
}
|
|
|
|
func TestRunListExplainsTheAccountFlag(t *testing.T) {
|
|
server, _ := actionsAPI(t, map[string]string{"GET actions/runs": `{"workflow_runs":[]}`})
|
|
deps, _, diagnostics := actionsDeps(t, server.URL, true)
|
|
deps.Streams.ErrTTY = true
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"run", "list", "-R", "alice/project", "--user", "alice"}))
|
|
require.Equal(t, "note: --user selects the account tea acts as (alice); to list the runs someone triggered, use --actor\nno runs in alice/project\n", diagnostics.String())
|
|
}
|
|
|
|
func TestRunViewShowsJobsStepsAndNextSteps(t *testing.T) {
|
|
server, _ := actionsAPI(t, map[string]string{
|
|
"GET actions/workflows": workflowsFixture,
|
|
"GET actions/runs/47": queuedRunFixture,
|
|
"GET actions/runs/47/jobs": queuedJobsFixture,
|
|
"GET actions/runs/29": failedRunFixture,
|
|
"GET actions/runs/29/jobs": failedJobsFixture,
|
|
})
|
|
for _, tc := range []struct {
|
|
name string
|
|
tty bool
|
|
args []string
|
|
want string
|
|
}{
|
|
{"queued", true, []string{"47"}, `* Add workflows CI #10 · run 47
|
|
queued • main • f0c151d • alice triggered it via workflow_dispatch about 2 minutes ago
|
|
|
|
JOBS
|
|
* build queued (ID 48)
|
|
* test waiting (ID 49)
|
|
|
|
Waiting for a runner for "ubuntu-latest"
|
|
|
|
Watch it: tea run watch 47 -R alice/project
|
|
View this run on Gitea: https://gitea.test/alice/project/actions/runs/47
|
|
`},
|
|
{"completed from its URL", true, []string{"{server}/alice/project/actions/runs/29"}, `X Refactor the deployment pipeline Nightly #9 · run 29
|
|
failure • feature/x • 209d545 • bob triggered it via push about 1 hour ago • took 1m 12s • attempt 2
|
|
|
|
JOBS
|
|
✓ build success 45s (ID 30)
|
|
✓ Set up job 2s
|
|
X test failure 27s (ID 31)
|
|
X Run tests 25s
|
|
|
|
Logs of a job: tea run logs 29 -R alice/project --job <id>
|
|
View this run on Gitea: https://gitea.test/alice/project/actions/runs/29
|
|
`},
|
|
{"piped", false, []string{"47"}, "title:\tAdd workflows\nid:\t47\nworkflow:\tCI\nworkflow_path:\tci.yml\nrun_number:\t10\nstatus:\tqueued\nconclusion:\t\n" +
|
|
"branch:\tmain\ncommit:\tf0c151dde0c0ef6c\nevent:\tworkflow_dispatch\nactor:\talice\nattempt:\t1\ncreated_at:\t2028-01-01T11:58:00Z\nstarted_at:\t\n" +
|
|
"completed_at:\t\nelapsed:\t\nurl:\thttps://gitea.test/alice/project/actions/runs/47\n--\n" +
|
|
"queued\t\t48\tbuild\t\thttps://gitea.test/alice/project/actions/runs/47/jobs/48\nwaiting\t\t49\ttest\t\t\n"},
|
|
{"json with jobs", false, []string{"29", "--json", "id,jobs", "--jq", "[.id, [.jobs[] | .name, .conclusion, .steps[0].name]]"}, `[29,["build","success","Set up job","test","failure","Run tests"]]` + "\n"},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
deps, out, _ := actionsDeps(t, server.URL, tc.tty)
|
|
args := append([]string{"run", "view", strings.ReplaceAll(tc.args[0], "{server}", server.URL)}, tc.args[1:]...)
|
|
if !strings.Contains(tc.args[0], "{server}") {
|
|
args = append(args, "-R", "alice/project")
|
|
}
|
|
require.NoError(t, Execute(context.Background(), deps, args))
|
|
require.Equal(t, tc.want, out.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
// choosingPrompter answers selections with the choice at index choose.
|
|
type choosingPrompter struct {
|
|
scriptedPrompter
|
|
choose int
|
|
choices []terminal.Choice
|
|
titles []string
|
|
}
|
|
|
|
func (p *choosingPrompter) Choose(_ context.Context, title string, choices []terminal.Choice, _ string) (string, error) {
|
|
p.titles = append(p.titles, title)
|
|
p.choices = choices
|
|
if p.choose >= len(choices) {
|
|
return "", errors.New("no such choice")
|
|
}
|
|
return choices[p.choose].Value, nil
|
|
}
|
|
|
|
func TestRunCommandsAskForARunWithoutAnArgument(t *testing.T) {
|
|
server, requests := actionsAPI(t, map[string]string{
|
|
"GET actions/runs": `{"workflow_runs":[` + failedRunFixture + `,` + queuedRunFixture + `]}`,
|
|
"GET actions/workflows": workflowsFixture,
|
|
"GET actions/runs/29/jobs": failedJobsFixture,
|
|
})
|
|
deps, out, _ := actionsDeps(t, server.URL, false)
|
|
prompter := &choosingPrompter{}
|
|
deps.Prompter = prompter
|
|
deps.Streams.InTTY, deps.Streams.ErrTTY = true, true
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"run", "jobs", "-R", "alice/project", "--json", "id", "--interactive"}))
|
|
require.JSONEq(t, `[{"id":30},{"id":31}]`, out.String())
|
|
require.Equal(t, []string{"Select a run"}, prompter.titles)
|
|
require.Equal(t, []terminal.Choice{
|
|
{Label: "X Refactor the deployment pipeline · Nightly · feature/x (run 29, about 1 hour ago)", Value: "29"},
|
|
{Label: "* Add workflows · CI · main (run 47, about 2 minutes ago)", Value: "47"},
|
|
}, prompter.choices)
|
|
|
|
*requests = nil
|
|
prompter.choose = 1
|
|
require.ErrorContains(t, Execute(context.Background(), deps, []string{"run", "watch", "-R", "alice/project"}), "no such choice")
|
|
require.Equal(t, []string{"GET actions/runs", "GET actions/workflows"}, *requests)
|
|
require.Equal(t, []terminal.Choice{{Label: "* Add workflows · CI · main (run 47, about 2 minutes ago)", Value: "47"}}, prompter.choices, "watch offers runs in progress")
|
|
|
|
deps.Streams.InTTY = false
|
|
err := Execute(context.Background(), deps, []string{"run", "view", "-R", "alice/project"})
|
|
require.Equal(t, clierr.Usage, clierr.ExitCode(err))
|
|
require.ErrorContains(t, err, "missing run ID\nUsage: tea run view [<run-id>] [flags]")
|
|
}
|
|
|
|
func TestRunWatchExitStatusAndTimeout(t *testing.T) {
|
|
succeeded := `{"id":7,"path":"ci.yml@refs/heads/main","status":"completed","conclusion":"success","created_at":"2028-01-01T11:59:00Z",
|
|
"started_at":"2028-01-01T11:59:00Z","completed_at":"2028-01-01T11:59:30Z"}`
|
|
for _, tc := range []struct {
|
|
name, run, jobs, want, err, progress string
|
|
code int
|
|
}{
|
|
{"success", succeeded, `{"jobs":[]}`, `{"id":7,"conclusion":"success","workflow":"CI"}`, "", "run 7: success\n", 0},
|
|
{"failure", strings.Replace(succeeded, `"success"`, `"failure"`, 1), `{"jobs":[]}`, `{"id":7,"conclusion":"failure","workflow":"CI"}`, "run 7 (CI) completed with failure", "run 7: failure\n", clierr.Failure},
|
|
{
|
|
"timeout", strings.Replace(queuedRunFixture, `"id":47`, `"id":7`, 1), queuedJobsFixture, "",
|
|
`timed out after 100ms waiting for run 7 (still queued; is a runner registered for "ubuntu-latest"?)`, "run 7: queued (build: queued, test: waiting)\n", clierr.Canceled,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
server, _ := actionsAPI(t, map[string]string{"GET actions/workflows": workflowsFixture, "GET actions/runs/7": tc.run, "GET actions/runs/7/jobs": tc.jobs})
|
|
deps, out, diagnostics := actionsDeps(t, server.URL, false)
|
|
err := Execute(context.Background(), deps, []string{"run", "watch", "7", "-R", "alice/project", "--json=id,conclusion,workflow", "--interval=1ms", "--timeout=100ms"})
|
|
require.Equal(t, tc.code, clierr.ExitCode(err), "%v", err)
|
|
if tc.err == "" {
|
|
require.NoError(t, err)
|
|
} else {
|
|
require.EqualError(t, err, tc.err)
|
|
}
|
|
if tc.want == "" {
|
|
require.Empty(t, out.String())
|
|
} else {
|
|
require.JSONEq(t, tc.want, out.String())
|
|
}
|
|
require.Equal(t, tc.progress, diagnostics.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRunWatchRedrawsOnATerminalAndSummarizes(t *testing.T) {
|
|
server, _ := actionsAPI(t, map[string]string{
|
|
"GET actions/workflows": workflowsFixture,
|
|
"GET actions/runs/7": `{"id":7,"display_title":"Ship it","path":"ci.yml@refs/heads/main","status":"completed","conclusion":"success",
|
|
"created_at":"2028-01-01T11:59:00Z","started_at":"2028-01-01T11:59:00Z","completed_at":"2028-01-01T11:59:30Z"}`,
|
|
"GET actions/runs/7/jobs": `{"jobs":[]}`,
|
|
})
|
|
deps, out, diagnostics := actionsDeps(t, server.URL, true)
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"run", "watch", "7", "-R", "alice/project", "--interval=2s"}))
|
|
frame := "✓ Ship it CI · run 7\nsuccess • triggered about 1 minute ago • took 30s\n"
|
|
require.Equal(t, "\x1b[?1049h\x1b[H\x1b[JRefreshing run status every 2s. Press Ctrl+C to quit.\n\n"+frame+"\x1b[?1049l"+frame, out.String())
|
|
require.Equal(t, "✓ Run 7 (CI) completed with success in 30s\n", diagnostics.String())
|
|
}
|
|
|
|
func TestRunMutationsReportWhatHappened(t *testing.T) {
|
|
completedRun := failedRunFixture
|
|
for _, tc := range []struct {
|
|
name string
|
|
args []string
|
|
responses map[string]string
|
|
requests []string
|
|
out, err string
|
|
prompts []string
|
|
}{
|
|
{
|
|
"cancel without confirmation",
|
|
[]string{"run", "cancel", "47"},
|
|
map[string]string{"POST actions/runs/47/cancel": queuedRunFixture},
|
|
[]string{"POST actions/runs/47/cancel"},
|
|
"", "✓ Requested cancellation of run 47\n", nil,
|
|
},
|
|
{
|
|
"cancel returns the run",
|
|
[]string{"run", "cancel", "47", "--json", "id,status"},
|
|
map[string]string{"POST actions/runs/47/cancel": queuedRunFixture},
|
|
[]string{"POST actions/runs/47/cancel"},
|
|
`{"id":47,"status":"queued"}`, "", nil,
|
|
},
|
|
{
|
|
"rerun",
|
|
[]string{"run", "rerun", "29"},
|
|
map[string]string{"GET actions/runs/29": completedRun, "POST actions/runs/29/rerun": completedRun},
|
|
[]string{"GET actions/runs/29", "POST actions/runs/29/rerun", "GET actions/runs/29"},
|
|
"", "✓ Requested rerun of run 29 (attempt 2); watch it with: tea run watch 29 -R alice/project\n", nil,
|
|
},
|
|
{
|
|
"rerun a job by name",
|
|
[]string{"run", "rerun", "29", "--job", "TEST"},
|
|
map[string]string{"GET actions/runs/29": completedRun, "GET actions/runs/29/jobs": failedJobsFixture, "POST actions/runs/29/jobs/31/rerun": `{"id":31}`},
|
|
[]string{"GET actions/runs/29", "GET actions/runs/29/jobs", "POST actions/runs/29/jobs/31/rerun", "GET actions/runs/29"},
|
|
"", "✓ Requested rerun of run 29 (attempt 2); watch it with: tea run watch 29 -R alice/project\n", nil,
|
|
},
|
|
{
|
|
"rerun refuses an unfinished run",
|
|
[]string{"run", "rerun", "47"},
|
|
map[string]string{"GET actions/runs/47": queuedRunFixture},
|
|
[]string{"GET actions/runs/47"},
|
|
"", "", nil,
|
|
},
|
|
{
|
|
"delete after confirmation",
|
|
[]string{"run", "delete", "29"},
|
|
map[string]string{"GET actions/workflows": workflowsFixture, "GET actions/runs/29": completedRun, "GET actions/runs/29/jobs": failedJobsFixture, "DELETE actions/runs/29": "204 "},
|
|
[]string{"GET actions/workflows", "GET actions/runs/29", "GET actions/runs/29/jobs", "DELETE actions/runs/29"},
|
|
"", "✓ Deleted run 29 from alice/project\n",
|
|
[]string{"Delete run 29 (Nightly · Refactor the deployment pipeline) from alice/project?"},
|
|
},
|
|
{
|
|
"delete refuses an unfinished run before asking",
|
|
[]string{"run", "delete", "47"},
|
|
map[string]string{"GET actions/workflows": workflowsFixture, "GET actions/runs/47": queuedRunFixture, "GET actions/runs/47/jobs": queuedJobsFixture},
|
|
[]string{"GET actions/workflows", "GET actions/runs/47", "GET actions/runs/47/jobs"},
|
|
"", "", nil,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
server, requests := actionsAPI(t, tc.responses)
|
|
deps, out, diagnostics := actionsDeps(t, server.URL, true)
|
|
prompter := &scriptedPrompter{accept: true}
|
|
deps.Prompter = prompter
|
|
deps.Streams.InTTY, deps.Streams.ErrTTY = true, true
|
|
err := Execute(context.Background(), deps, append(tc.args, "-R", "alice/project"))
|
|
if strings.Contains(tc.name, "unfinished") {
|
|
require.EqualError(t, err, "run 47 is still queued; cancel it first: tea run cancel 47 -R alice/project")
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
require.Equal(t, tc.requests, *requests)
|
|
if strings.HasPrefix(tc.out, "{") {
|
|
require.JSONEq(t, tc.out, out.String())
|
|
} else {
|
|
require.Equal(t, tc.out, out.String())
|
|
}
|
|
require.Equal(t, tc.err, diagnostics.String())
|
|
require.Equal(t, tc.prompts, prompter.confirmations)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRunJobsListsStatusAndElapsedTime(t *testing.T) {
|
|
server, _ := actionsAPI(t, map[string]string{"GET actions/runs/29/jobs": failedJobsFixture})
|
|
deps, out, _ := actionsDeps(t, server.URL, true)
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"run", "jobs", "#29", "-R", "alice/project"}))
|
|
require.Equal(t, "Showing 2 of 2 jobs in run 29\n\nSTATUS ID NAME ELAPSED\n✓ 30 build 45s\nX 31 test 27s\n", out.String())
|
|
}
|