Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions cmd/sandbox/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,15 @@ func printSandboxes(cmd *cobra.Command, clients *shared.ClientFactory, token str
clients.IO.PrintInfo(ctx, false, " %s", style.Secondary(fmt.Sprintf("URL: https://%s.slack.com", s.Domain)))
}

if s.IsPartner {
clients.IO.PrintInfo(ctx, false, " %s", style.Secondary("Type: Partner"))
switch s.Type {
case "basic":
clients.IO.PrintInfo(ctx, false, " %s", style.Secondary("Type: basic"))
case "partner":
clients.IO.PrintInfo(ctx, false, " %s", style.Secondary("Type: partner"))
}

if s.Status != "" {
status := style.Secondary(fmt.Sprintf("Status: %s", strings.ToTitle(s.Status)))
status := style.Secondary(fmt.Sprintf("Status: %s", strings.ToLower(s.Status)))

@vegeris vegeris Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking this could be lowercase so it doesn't feel like we're shouting

  my cool sandbox (E0C6ZL333CM)
    URL: https://sandbox-pwureipq.slack.com
    Type: Partner vs. partner
    Status: ACTIVE vs. active
    Created: 2026-10-06
    Expires: 2026-12-06

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol

if strings.EqualFold(s.Status, "archived") {
clients.IO.PrintInfo(ctx, false, " %s", style.Red(status))
} else {
Expand Down
97 changes: 19 additions & 78 deletions cmd/sandbox/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"github.com/slackapi/slack-cli/internal/shared/types"
"github.com/slackapi/slack-cli/test/testutil"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

Expand All @@ -47,112 +46,54 @@ func TestListCommand(t *testing.T) {
cm.API.AssertCalled(t, "ListSandboxes", mock.Anything, "xoxb-test-token", "")
},
},
"with active sandboxes": {

@vegeris vegeris Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It didn't seem worth having separate tests to verify that if the sandbox status was active vs archived, it would be reflected in the printed output. Merged this into a single test for 'printed output displays sandbox attributes as expected'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good choice!

"with multiple sandboxes": {
CmdArgs: []string{"--token", "xoxb-test-token"},
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
testToken := "xoxb-test-token"
cm.Auth.On("AuthWithToken", mock.Anything, testToken).Return(types.SlackAuth{Token: testToken}, nil)
cm.Auth.On("ResolveAPIHost", mock.Anything, mock.Anything, mock.Anything).Return("https://api.slack.com")
cm.Auth.On("ResolveLogstashHost", mock.Anything, mock.Anything).Return("https://slackb.com/events/cli")
sandboxes := []types.Sandbox{
{
TeamID: "T123",
Name: "my-sandbox",
Domain: "my-sandbox",
Status: "active",
DateCreated: 1700000000,
DateArchived: 0,
},
}
cm.API.On("ListSandboxes", mock.Anything, testToken, "").Return(sandboxes, nil)
cm.API.On("UsersInfo", mock.Anything, mock.Anything, mock.Anything).Return(&types.UserInfo{Profile: types.UserProfile{}}, nil)

cm.AddDefaultMocks()
},
ExpectedStdoutOutputs: []string{"my-sandbox", "T123", "https://my-sandbox.slack.com", "Status: ACTIVE"},
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
cm.API.AssertCalled(t, "ListSandboxes", mock.Anything, "xoxb-test-token", "")
assert.NotContains(t, cm.GetStdoutOutput(), "Type:")
},
},
"with archived sandbox": {
CmdArgs: []string{"--token", "xoxb-test-token"},
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
testToken := "xoxb-test-token"
cm.Auth.On("AuthWithToken", mock.Anything, testToken).Return(types.SlackAuth{Token: testToken}, nil)
cm.Auth.On("ResolveAPIHost", mock.Anything, mock.Anything, mock.Anything).Return("https://api.slack.com")
cm.Auth.On("ResolveLogstashHost", mock.Anything, mock.Anything).Return("https://slackb.com/events/cli")
sandboxes := []types.Sandbox{
{
TeamID: "T456",
Name: "old-sandbox",
Domain: "old-sandbox",
Status: "archived",
DateCreated: 1700000000,
DateArchived: 1710000000,
},
}
cm.API.On("ListSandboxes", mock.Anything, testToken, "").Return(sandboxes, nil)
cm.API.On("UsersInfo", mock.Anything, mock.Anything, mock.Anything).Return(&types.UserInfo{Profile: types.UserProfile{}}, nil)

cm.AddDefaultMocks()
},
ExpectedStdoutOutputs: []string{"old-sandbox", "T456", "Status: ARCHIVED"},
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
cm.API.AssertCalled(t, "ListSandboxes", mock.Anything, "xoxb-test-token", "")
},
},
"with partner sandbox shows type for all sandboxes": {
CmdArgs: []string{"--experiment=sandboxes", "--token", "xoxb-test-token"},
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
testToken := "xoxb-test-token"
cm.Auth.On("AuthWithToken", mock.Anything, testToken).Return(types.SlackAuth{Token: testToken}, nil)
cm.Auth.On("ResolveAPIHost", mock.Anything, mock.Anything, mock.Anything).Return("https://api.slack.com")
cm.Auth.On("ResolveLogstashHost", mock.Anything, mock.Anything, mock.Anything).Return("https://slackb.com/events/cli")
sandboxes := []types.Sandbox{
{
TeamID: "T123",
Name: "regular-sandbox",
Domain: "regular-sandbox",
Status: "active",
DateCreated: 1700000000,
DateCreated: 1700001000,
Type: "regular",
},
{
TeamID: "T456",
Name: "basic-sandbox",
Domain: "basic-sandbox",
Status: "archived",
DateCreated: 1700002000,
Type: "basic",
},
{
TeamID: "T789",
Name: "partner-sandbox",
Domain: "partner-sandbox",
Status: "active",
DateCreated: 1700000000,
IsPartner: true,
DateCreated: 1700003000,
Type: "partner",
},
}
cm.API.On("ListSandboxes", mock.Anything, testToken, "").Return(sandboxes, nil)
cm.API.On("UsersInfo", mock.Anything, mock.Anything, mock.Anything).Return(&types.UserInfo{Profile: types.UserProfile{}}, nil)

cm.AddDefaultMocks()
},
ExpectedStdoutOutputs: []string{"regular-sandbox", "partner-sandbox", "Type: Partner"},
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
cm.API.AssertCalled(t, "ListSandboxes", mock.Anything, "xoxb-test-token", "")
},
},
"with status": {
CmdArgs: []string{"--token", "xoxb-test-token", "--status", "active"},
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
testToken := "xoxb-test-token"
cm.Auth.On("AuthWithToken", mock.Anything, testToken).Return(types.SlackAuth{Token: testToken}, nil)
cm.Auth.On("ResolveAPIHost", mock.Anything, mock.Anything, mock.Anything).Return("https://api.slack.com")
cm.Auth.On("ResolveLogstashHost", mock.Anything, mock.Anything).Return("https://slackb.com/events/cli")
cm.API.On("ListSandboxes", mock.Anything, testToken, "active").Return([]types.Sandbox{}, nil)
cm.API.On("UsersInfo", mock.Anything, mock.Anything, mock.Anything).Return(&types.UserInfo{Profile: types.UserProfile{}}, nil)

cm.AddDefaultMocks()
ExpectedStdoutOutputs: []string{
"regular-sandbox (T123)\n URL: https://regular-sandbox.slack.com\n Status: active",
"basic-sandbox (T456)\n URL: https://basic-sandbox.slack.com\n Type: basic\n Status: archived",
"partner-sandbox (T789)\n URL: https://partner-sandbox.slack.com\n Type: partner\n Status: active",
},
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
cm.API.AssertCalled(t, "ListSandboxes", mock.Anything, "xoxb-test-token", "active")
cm.API.AssertCalled(t, "ListSandboxes", mock.Anything, "xoxb-test-token", "")
},
},
"list error": {
"with an error from the list API": {
CmdArgs: []string{"--token", "xoxb-test-token"},
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
testToken := "xoxb-test-token"
Expand Down
4 changes: 2 additions & 2 deletions internal/api/api_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,8 @@ func (m *APIMock) DeleteSandbox(ctx context.Context, token, sandboxID string) er
return args.Error(0)
}

func (m *APIMock) ListSandboxes(ctx context.Context, token string, filter string) ([]types.Sandbox, error) {
args := m.Called(ctx, token, filter)
func (m *APIMock) ListSandboxes(ctx context.Context, token string, status string) ([]types.Sandbox, error) {
args := m.Called(ctx, token, status)
return args.Get(0).([]types.Sandbox), args.Error(1)
}

Expand Down
2 changes: 1 addition & 1 deletion internal/api/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const (
type SandboxClient interface {
CreateSandbox(ctx context.Context, token, name, domain, password, locale, owningOrgID string, templateID int, eventCode string, archiveDate int64, isPartner bool) (teamID, sandboxURL string, err error)
DeleteSandbox(ctx context.Context, token, sandboxID string) error
ListSandboxes(ctx context.Context, token string, filter string) ([]types.Sandbox, error)
ListSandboxes(ctx context.Context, token string, status string) ([]types.Sandbox, error)
}

type createSandboxResponse struct {
Expand Down
2 changes: 1 addition & 1 deletion internal/shared/types/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ type Sandbox struct {
Name string `json:"sandbox_name"`
TeamID string `json:"sandbox_team_id"`
Status string `json:"status"`
IsPartner bool `json:"is_partner"`
Type string `json:"type"`
}
Loading