cli/pkg/cmd/run/shared/shared_test.go
Andy Feller 86b4bb9956 Include startedAt, completedAt in run steps data
This commit expands the `Step` structure used with GitHub Actions workflow runs to include fields indicating when steps start and complete.

This information is already provided by the GitHub API, so this only involves expanding the structure, fields exported, and the associated tests.

In the future, I could see `gh` including the duration calculation which is used when viewing workflow or workflow run.
2024-10-16 19:38:10 -04:00

222 lines
6.7 KiB
Go

package shared
import (
"bytes"
"encoding/json"
"net/http"
"strings"
"testing"
"time"
"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/pkg/httpmock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPreciseAgo(t *testing.T) {
const form = "2006-Jan-02 15:04:05"
now, _ := time.Parse(form, "2021-Apr-12 14:00:00")
cases := map[string]string{
"2021-Apr-12 14:00:00": "0s ago",
"2021-Apr-12 13:59:30": "30s ago",
"2021-Apr-12 13:59:00": "1m0s ago",
"2021-Apr-12 13:30:15": "29m45s ago",
"2021-Apr-12 13:00:00": "1h0m0s ago",
"2021-Apr-12 02:30:45": "11h29m15s ago",
"2021-Apr-11 14:00:00": "24h0m0s ago",
"2021-Apr-01 14:00:00": "264h0m0s ago",
"2021-Mar-12 14:00:00": "Mar 12, 2021",
}
for createdAt, expected := range cases {
d, _ := time.Parse(form, createdAt)
got := preciseAgo(now, d)
if got != expected {
t.Errorf("expected %s but got %s for %s", expected, got, createdAt)
}
}
}
func TestGetAnnotations404(t *testing.T) {
reg := &httpmock.Registry{}
defer reg.Verify(t)
reg.Register(
httpmock.REST("GET", "repos/OWNER/REPO/check-runs/123456/annotations"),
httpmock.StatusStringResponse(404, "not found"))
httpClient := &http.Client{Transport: reg}
apiClient := api.NewClientFromHTTP(httpClient)
repo := ghrepo.New("OWNER", "REPO")
result, err := GetAnnotations(apiClient, repo, Job{ID: 123456, Name: "a job"})
assert.NoError(t, err)
assert.Equal(t, result, []Annotation{})
}
func TestRun_Duration(t *testing.T) {
now, _ := time.Parse(time.RFC3339, "2022-07-20T11:22:58Z")
tests := []struct {
name string
json string
wants string
}{
{
name: "no run_started_at",
json: heredoc.Doc(`
{
"created_at": "2022-07-20T11:20:13Z",
"updated_at": "2022-07-20T11:21:16Z",
"status": "completed"
}`),
wants: "1m3s",
},
{
name: "with run_started_at",
json: heredoc.Doc(`
{
"created_at": "2022-07-20T11:20:13Z",
"run_started_at": "2022-07-20T11:20:55Z",
"updated_at": "2022-07-20T11:21:16Z",
"status": "completed"
}`),
wants: "21s",
},
{
name: "in_progress",
json: heredoc.Doc(`
{
"created_at": "2022-07-20T11:20:13Z",
"run_started_at": "2022-07-20T11:20:55Z",
"updated_at": "2022-07-20T11:21:16Z",
"status": "in_progress"
}`),
wants: "2m3s",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var r Run
assert.NoError(t, json.Unmarshal([]byte(tt.json), &r))
assert.Equal(t, tt.wants, r.Duration(now).String())
})
}
}
func TestRunExportData(t *testing.T) {
oldestStartedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:20:13Z")
oldestStepStartedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:20:15Z")
oldestStepCompletedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:21:10Z")
oldestCompletedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:21:16Z")
newestStartedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:20:55Z")
newestStepStartedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:21:01Z")
newestStepCompletedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:23:10Z")
newestCompletedAt, _ := time.Parse(time.RFC3339, "2022-07-20T11:23:16Z")
tests := []struct {
name string
fields []string
run Run
output string
}{
{
name: "exports workflow run's single job",
fields: []string{"jobs"},
run: Run{
Jobs: []Job{
{
ID: 123456,
Status: "completed",
Conclusion: "success",
Name: "macos",
Steps: []Step{
{
Name: "Checkout",
Status: "completed",
Conclusion: "success",
Number: 1,
StartedAt: oldestStepStartedAt,
CompletedAt: oldestStepCompletedAt,
},
},
StartedAt: oldestStartedAt,
CompletedAt: oldestCompletedAt,
URL: "https://example.com/OWNER/REPO/actions/runs/123456",
},
},
},
output: `{"jobs":[{"completedAt":"2022-07-20T11:21:16Z","conclusion":"success","databaseId":123456,"name":"macos","startedAt":"2022-07-20T11:20:13Z","status":"completed","steps":[{"completedAt":"2022-07-20T11:21:10Z","conclusion":"success","name":"Checkout","number":1,"startedAt":"2022-07-20T11:20:15Z","status":"completed"}],"url":"https://example.com/OWNER/REPO/actions/runs/123456"}]}`,
},
{
name: "exports workflow run's multiple jobs",
fields: []string{"jobs"},
run: Run{
Jobs: []Job{
{
ID: 123456,
Status: "completed",
Conclusion: "success",
Name: "macos",
Steps: []Step{
{
Name: "Checkout",
Status: "completed",
Conclusion: "success",
Number: 1,
StartedAt: oldestStepStartedAt,
CompletedAt: oldestStepCompletedAt,
},
},
StartedAt: oldestStartedAt,
CompletedAt: oldestCompletedAt,
URL: "https://example.com/OWNER/REPO/actions/runs/123456",
},
{
ID: 234567,
Status: "completed",
Conclusion: "error",
Name: "windows",
Steps: []Step{
{
Name: "Checkout",
Status: "completed",
Conclusion: "error",
Number: 2,
StartedAt: newestStepStartedAt,
CompletedAt: newestStepCompletedAt,
},
},
StartedAt: newestStartedAt,
CompletedAt: newestCompletedAt,
URL: "https://example.com/OWNER/REPO/actions/runs/234567",
},
},
},
output: `{"jobs":[{"completedAt":"2022-07-20T11:21:16Z","conclusion":"success","databaseId":123456,"name":"macos","startedAt":"2022-07-20T11:20:13Z","status":"completed","steps":[{"completedAt":"2022-07-20T11:21:10Z","conclusion":"success","name":"Checkout","number":1,"startedAt":"2022-07-20T11:20:15Z","status":"completed"}],"url":"https://example.com/OWNER/REPO/actions/runs/123456"},{"completedAt":"2022-07-20T11:23:16Z","conclusion":"error","databaseId":234567,"name":"windows","startedAt":"2022-07-20T11:20:55Z","status":"completed","steps":[{"completedAt":"2022-07-20T11:23:10Z","conclusion":"error","name":"Checkout","number":2,"startedAt":"2022-07-20T11:21:01Z","status":"completed"}],"url":"https://example.com/OWNER/REPO/actions/runs/234567"}]}`,
},
{
name: "exports workflow run with attempt count",
fields: []string{"attempt"},
run: Run{
Attempt: 1,
Jobs: []Job{},
},
output: `{"attempt":1}`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
exported := tt.run.ExportData(tt.fields)
buf := bytes.Buffer{}
enc := json.NewEncoder(&buf)
require.NoError(t, enc.Encode(exported))
assert.Equal(t, tt.output, strings.TrimSpace(buf.String()))
})
}
}