Compare commits

...

1 Commits

Author SHA1 Message Date
Spike Curtis
fb08b94694 chore: mock codersdk in show command 2025-11-21 09:25:34 +00:00
4 changed files with 113 additions and 18 deletions

View File

@@ -78,6 +78,14 @@ func NewWithCommand(
return i, configDir
}
func NewWithSDKOverride(t testing.TB, override any, args ...string) (*serpent.Invocation, config.Root) {
var root cli.RootCmd
cmd, err := root.Command(root.AGPL())
root.TestOverrideSDKClient = override
require.NoError(t, err)
return NewWithCommand(t, cmd, args...)
}
// SetupConfig applies the URL and SessionToken of the client to the config.
func SetupConfig(t *testing.T, client *codersdk.Client, root config.Root) {
err := root.Session().Write(client.SessionToken())

View File

@@ -538,6 +538,23 @@ type RootCmd struct {
noVersionCheck bool
noFeatureWarning bool
useKeyring bool
// TestOverrideSDKClient is returned by GetClient if set. This is used in unit testing to mock or fake the
// *codersdk.Client
TestOverrideSDKClient any
}
// GetClient gets the SDK client of the given type. In tests, this can be used with TestOverrideSDKClient to mock or
// fake the client. In production, this returns a *codersdk.Client.
func GetClient[T any](r *RootCmd, inv *serpent.Invocation) (T, error) {
if r.TestOverrideSDKClient != nil {
// nolint: forcetypeassert
return r.TestOverrideSDKClient.(T), nil
}
var a any
a, err := r.InitClient(inv)
// nolint: forcetypeassert
return a.(T), err
}
// InitClient creates and configures a new client with authentication, telemetry,
@@ -893,10 +910,14 @@ func splitNamedWorkspace(identifier string) (owner string, workspaceName string,
return owner, workspaceName, nil
}
type workspaceGetterByName interface {
WorkspaceByOwnerAndName(context.Context, string, string, codersdk.WorkspaceOptions) (codersdk.Workspace, error)
}
// namedWorkspace fetches and returns a workspace by an identifier, which may be either
// a bare name (for a workspace owned by the current user) or a "user/workspace" combination,
// where user is either a username or UUID.
func namedWorkspace(ctx context.Context, client *codersdk.Client, identifier string) (codersdk.Workspace, error) {
func namedWorkspace(ctx context.Context, client workspaceGetterByName, identifier string) (codersdk.Workspace, error) {
owner, name, err := splitNamedWorkspace(identifier)
if err != nil {
return codersdk.Workspace{}, err

View File

@@ -1,6 +1,7 @@
package cli
import (
"context"
"sort"
"sync"
@@ -14,6 +15,17 @@ import (
"github.com/coder/serpent"
)
// showSDKClient is the subset of codersdk.Client that the show command uses.
type showSDKClient interface {
workspaceGetterByName
BuildInfo(context.Context) (codersdk.BuildInfoResponse, error)
WorkspaceAgentListeningPorts(context.Context, uuid.UUID) (codersdk.WorkspaceAgentListeningPortsResponse, error)
WorkspaceAgentListContainers(context.Context, uuid.UUID, map[string]string) (codersdk.WorkspaceAgentListContainersResponse, error)
}
// ensure methods track the SDK
var _ showSDKClient = &codersdk.Client{}
func (r *RootCmd) show() *serpent.Command {
var details bool
return &serpent.Command{
@@ -31,7 +43,7 @@ func (r *RootCmd) show() *serpent.Command {
serpent.RequireNArgs(1),
),
Handler: func(inv *serpent.Invocation) error {
client, err := r.InitClient(inv)
client, err := GetClient[showSDKClient](r, inv)
if err != nil {
return err
}
@@ -62,7 +74,7 @@ func (r *RootCmd) show() *serpent.Command {
}
}
func fetchRuntimeResources(inv *serpent.Invocation, client *codersdk.Client, resources ...codersdk.WorkspaceResource) (map[uuid.UUID]codersdk.WorkspaceAgentListeningPortsResponse, map[uuid.UUID]codersdk.WorkspaceAgentListContainersResponse) {
func fetchRuntimeResources(inv *serpent.Invocation, client showSDKClient, resources ...codersdk.WorkspaceResource) (map[uuid.UUID]codersdk.WorkspaceAgentListeningPortsResponse, map[uuid.UUID]codersdk.WorkspaceAgentListContainersResponse) {
ports := make(map[uuid.UUID]codersdk.WorkspaceAgentListeningPortsResponse)
devcontainers := make(map[uuid.UUID]codersdk.WorkspaceAgentListContainersResponse)
var wg sync.WaitGroup

View File

@@ -2,6 +2,7 @@ package cli_test
import (
"bytes"
"context"
"testing"
"time"
@@ -12,7 +13,6 @@ import (
"github.com/coder/coder/v2/agent/agentcontainers"
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/pty/ptytest"
)
@@ -21,21 +21,36 @@ func TestShow(t *testing.T) {
t.Parallel()
t.Run("Exists", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
owner := coderdtest.CreateFirstUser(t, client)
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, completeWithAgent())
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, member, template.ID)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
args := []string{
"show",
workspace.Name,
agentID := uuid.UUID{1}
workspaceID := uuid.UUID{2}
workspace := codersdk.Workspace{
Name: "test",
ID: workspaceID,
LatestBuild: codersdk.WorkspaceBuild{
Status: codersdk.WorkspaceStatusRunning,
Resources: []codersdk.WorkspaceResource{
{
Name: "main",
Type: "compute",
Agents: []codersdk.WorkspaceAgent{
{Name: "smith", ID: agentID, Architecture: "i386", OperatingSystem: "linux"},
},
},
},
},
}
inv, root := clitest.New(t, args...)
clitest.SetupConfig(t, member, root)
fSDK := &fakeShowSDK{
t: t,
expectedOwner: codersdk.Me,
returnedWorkspace: workspace,
expectedAgentID: agentID,
expectedContainerLabels: map[string]string{
agentcontainers.DevcontainerConfigFileLabel: "",
agentcontainers.DevcontainerLocalFolderLabel: "",
},
}
inv, _ := clitest.NewWithSDKOverride(t, fSDK, "show", workspace.Name)
doneChan := make(chan struct{})
pty := ptytest.New(t).Attach(inv)
go func() {
@@ -61,6 +76,45 @@ func TestShow(t *testing.T) {
})
}
type fakeShowSDK struct {
t *testing.T
expectedOwner string
returnedWorkspace codersdk.Workspace
expectedAgentID uuid.UUID
expectedContainerLabels map[string]string
}
func (f *fakeShowSDK) WorkspaceByOwnerAndName(_ context.Context, owner string, name string, _ codersdk.WorkspaceOptions) (codersdk.Workspace, error) {
assert.Equal(f.t, f.expectedOwner, owner)
assert.Equal(f.t, f.returnedWorkspace.Name, name)
return f.returnedWorkspace, nil
}
func (*fakeShowSDK) BuildInfo(_ context.Context) (codersdk.BuildInfoResponse, error) {
return codersdk.BuildInfoResponse{
Version: "test-version",
}, nil
}
func (f *fakeShowSDK) WorkspaceAgentListeningPorts(_ context.Context, u uuid.UUID) (codersdk.WorkspaceAgentListeningPortsResponse, error) {
assert.Equal(f.t, f.expectedAgentID, u)
return codersdk.WorkspaceAgentListeningPortsResponse{
Ports: []codersdk.WorkspaceAgentListeningPort{
{
ProcessName: "postgres",
Port: 5432,
Network: "tcp",
},
},
}, nil
}
func (f *fakeShowSDK) WorkspaceAgentListContainers(_ context.Context, u uuid.UUID, m map[string]string) (codersdk.WorkspaceAgentListContainersResponse, error) {
assert.Equal(f.t, f.expectedAgentID, u)
assert.Equal(f.t, f.expectedContainerLabels, m)
return codersdk.WorkspaceAgentListContainersResponse{}, nil
}
func TestShowDevcontainers_Golden(t *testing.T) {
t.Parallel()