cli/pkg/cmd/agent-task/list/list_test.go
Kynan Ware 680ebade59
Remove repo-scoped session listing and related code
This commit removes support for listing agent sessions scoped to a specific repository, including the ListSessionsForRepo method and related tests. The list command now always lists the latest sessions for the viewer, simplifying the code and user experience. Test and mock code have been updated accordingly.
2025-09-18 12:00:50 +01:00

334 lines
8.3 KiB
Go

package list
import (
"bytes"
"context"
"io"
"testing"
"time"
"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/browser"
"github.com/cli/cli/v2/pkg/cmd/agent-task/capi"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/google/shlex"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewCmdList(t *testing.T) {
tests := []struct {
name string
args string
wantOpts ListOptions
wantErr string
}{
{
name: "no arguments",
wantOpts: ListOptions{
Limit: defaultLimit,
},
},
{
name: "custom limit",
args: "--limit 15",
wantOpts: ListOptions{
Limit: 15,
},
},
{
name: "invalid limit",
args: "--limit 0",
wantErr: "invalid limit: 0",
},
{
name: "negative limit",
args: "--limit -5",
wantErr: "invalid limit: -5",
},
{
name: "web flag",
args: "--web",
wantOpts: ListOptions{
Limit: defaultLimit,
Web: true,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ios, _, _, _ := iostreams.Test()
f := &cmdutil.Factory{
IOStreams: ios,
}
var gotOpts *ListOptions
cmd := NewCmdList(f, func(opts *ListOptions) error { gotOpts = opts; return nil })
argv, err := shlex.Split(tt.args)
require.NoError(t, err)
cmd.SetArgs(argv)
cmd.SetIn(&bytes.Buffer{})
cmd.SetOut(io.Discard)
cmd.SetErr(io.Discard)
_, err = cmd.ExecuteC()
if tt.wantErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
}
require.NoError(t, err)
assert.Equal(t, tt.wantOpts.Limit, gotOpts.Limit)
assert.Equal(t, tt.wantOpts.Web, gotOpts.Web)
})
}
}
func Test_listRun(t *testing.T) {
sampleDate := time.Now().Add(-6 * time.Hour) // 6h ago
sampleDateString := sampleDate.Format(time.RFC3339)
tests := []struct {
name string
tty bool
capiStubs func(*testing.T, *capi.CapiClientMock)
limit int
web bool
wantOut string
wantErr error
wantStderr string
wantBrowserURL string
}{
{
name: "viewer-scoped no sessions",
tty: true,
capiStubs: func(t *testing.T, m *capi.CapiClientMock) {
m.ListLatestSessionsForViewerFunc = func(ctx context.Context, limit int) ([]*capi.Session, error) {
return nil, nil
}
},
wantErr: cmdutil.NewNoResultsError("no agent tasks found"),
},
{
name: "viewer-scoped respects --limit",
tty: true,
limit: 999,
capiStubs: func(t *testing.T, m *capi.CapiClientMock) {
m.ListLatestSessionsForViewerFunc = func(ctx context.Context, limit int) ([]*capi.Session, error) {
assert.Equal(t, 999, limit)
return nil, nil
}
},
wantErr: cmdutil.NewNoResultsError("no agent tasks found"), // not important
},
{
name: "viewer-scoped single session (tty)",
tty: true,
capiStubs: func(t *testing.T, m *capi.CapiClientMock) {
m.ListLatestSessionsForViewerFunc = func(ctx context.Context, limit int) ([]*capi.Session, error) {
return []*capi.Session{
{
ID: "id1",
Name: "s1",
State: "completed",
CreatedAt: sampleDate,
ResourceType: "pull",
PullRequest: &api.PullRequest{
Number: 101,
Repository: &api.PRRepository{
NameWithOwner: "OWNER/REPO",
},
},
},
}, nil
}
},
wantOut: heredoc.Doc(`
Showing 1 session
SESSION NAME PULL REQUEST REPO SESSION STATE CREATED
s1 #101 OWNER/REPO completed about 6 hours ago
`),
},
{
name: "viewer-scoped single session (nontty)",
tty: false,
capiStubs: func(t *testing.T, m *capi.CapiClientMock) {
m.ListLatestSessionsForViewerFunc = func(ctx context.Context, limit int) ([]*capi.Session, error) {
return []*capi.Session{
{
ID: "id1",
Name: "s1",
State: "completed",
ResourceType: "pull",
CreatedAt: sampleDate,
PullRequest: &api.PullRequest{
Number: 101,
Repository: &api.PRRepository{
NameWithOwner: "OWNER/REPO",
},
},
},
}, nil
}
},
wantOut: "s1\t#101\tOWNER/REPO\tcompleted\t" + sampleDateString + "\n", // header omitted for non-tty
},
{
name: "viewer-scoped many sessions (tty)",
tty: true,
capiStubs: func(t *testing.T, m *capi.CapiClientMock) {
m.ListLatestSessionsForViewerFunc = func(ctx context.Context, limit int) ([]*capi.Session, error) {
return []*capi.Session{
{
ID: "id1",
Name: "s1",
State: "completed",
CreatedAt: sampleDate,
ResourceType: "pull",
PullRequest: &api.PullRequest{
Number: 101,
Repository: &api.PRRepository{
NameWithOwner: "OWNER/REPO",
},
},
},
{
ID: "id2",
Name: "s2",
State: "failed",
CreatedAt: sampleDate,
ResourceType: "pull",
PullRequest: &api.PullRequest{
Number: 102,
Repository: &api.PRRepository{
NameWithOwner: "OWNER/REPO",
},
},
},
{
ID: "id3",
Name: "s3",
State: "in_progress",
CreatedAt: sampleDate,
ResourceType: "pull",
PullRequest: &api.PullRequest{
Number: 103,
Repository: &api.PRRepository{
NameWithOwner: "OWNER/REPO",
},
},
},
{
ID: "id4",
Name: "s4",
State: "queued",
CreatedAt: sampleDate,
ResourceType: "pull",
PullRequest: &api.PullRequest{
Number: 104,
Repository: &api.PRRepository{
NameWithOwner: "OWNER/REPO",
},
},
},
{
ID: "id5",
Name: "s5",
State: "canceled",
CreatedAt: sampleDate,
ResourceType: "pull",
PullRequest: &api.PullRequest{
Number: 105,
Repository: &api.PRRepository{
NameWithOwner: "OWNER/REPO",
},
},
},
{
ID: "id6",
Name: "s6",
State: "mystery",
CreatedAt: sampleDate,
ResourceType: "pull",
PullRequest: &api.PullRequest{
Number: 106,
Repository: &api.PRRepository{
NameWithOwner: "OWNER/REPO",
},
},
},
}, nil
}
},
wantOut: heredoc.Doc(`
Showing 6 sessions
SESSION NAME PULL REQUEST REPO SESSION STATE CREATED
s1 #101 OWNER/REPO completed about 6 hours ago
s2 #102 OWNER/REPO failed about 6 hours ago
s3 #103 OWNER/REPO in_progress about 6 hours ago
s4 #104 OWNER/REPO queued about 6 hours ago
s5 #105 OWNER/REPO canceled about 6 hours ago
s6 #106 OWNER/REPO mystery about 6 hours ago
`),
},
{
name: "web mode",
tty: true,
web: true,
wantOut: "",
wantStderr: "Opening https://github.com/copilot/agents in your browser.\n",
wantBrowserURL: "https://github.com/copilot/agents",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
capiClientMock := &capi.CapiClientMock{}
if tt.capiStubs != nil {
tt.capiStubs(t, capiClientMock)
}
ios, _, stdout, stderr := iostreams.Test()
ios.SetStdoutTTY(tt.tty)
var br *browser.Stub
if tt.web {
br = &browser.Stub{}
}
opts := &ListOptions{
IO: ios,
Limit: tt.limit,
Web: tt.web,
Browser: br,
CapiClient: func() (capi.CapiClient, error) {
if tt.web {
require.FailNow(t, "CapiClient was called with --web")
}
return capiClientMock, nil
},
}
err := listRun(opts)
if tt.wantErr != nil {
assert.Error(t, err)
require.EqualError(t, err, tt.wantErr.Error())
} else {
require.NoError(t, err)
}
got := stdout.String()
require.Equal(t, tt.wantOut, got)
require.Equal(t, tt.wantStderr, stderr.String())
if tt.web {
br.Verify(t, tt.wantBrowserURL)
}
})
}
}