Files
coder/cli/sharing_test.go
T
Brett Kolodny 909acbc833 feat: add sharing add command to the CLI (#19576)
Adds a `sharing add` command for sharing Workspaces with other users and
groups.

The command allows sharing with multiple users, and groups within one
command as well as specifying the role (`use`, or `admin`) defaulting to
`use` if none is specified.

In the current implementation when the command completes we show the
user the current state of the workspace ACL.

```
$ coder sharing add apricot-catfish-86 --user=member:admin --group=contractors:use
USER    GROUP        ROLE
member  -            admin
member  contractors  use
```

If a user is a part of multiple groups, or the workspace has been
individually shared with them they will show up multiple times. Although
this is a bit confusing at first glance it's important to be able to
tell what the maximum role a user may have, and via what ACL they have
it.

---

One piece of UX to consider is that in order to be able to share a
Workspace with a user they must have a role that can read that user. In
the tests we give the user the `ScopedRoleOrgAuditor` role.

Closes
[coder/internal#859](https://github.com/coder/internal/issues/859)
2025-09-04 17:37:16 -04:00

171 lines
5.9 KiB
Go

package cli_test
import (
"bytes"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbfake"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/testutil"
)
func TestSharingShare(t *testing.T) {
t.Parallel()
dv := coderdtest.DeploymentValues(t)
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
t.Run("ShareWithUsers_Simple", func(t *testing.T) {
t.Parallel()
var (
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: dv,
})
orgOwner = coderdtest.CreateFirstUser(t, client)
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID, rbac.ScopedRoleOrgAuditor(orgOwner.OrganizationID))
workspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
OwnerID: workspaceOwner.ID,
OrganizationID: orgOwner.OrganizationID,
}).Do().Workspace
_, toShareWithUser = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
)
ctx := testutil.Context(t, testutil.WaitMedium)
inv, root := clitest.New(t, "sharing", "add", workspace.Name, "--org", orgOwner.OrganizationID.String(), "--user", toShareWithUser.Username)
clitest.SetupConfig(t, workspaceOwnerClient, root)
out := bytes.NewBuffer(nil)
inv.Stdout = out
err := inv.WithContext(ctx).Run()
require.NoError(t, err)
acl, err := workspaceOwnerClient.WorkspaceACL(inv.Context(), workspace.ID)
require.NoError(t, err)
assert.Contains(t, acl.Users, codersdk.WorkspaceUser{
MinimalUser: codersdk.MinimalUser{
ID: toShareWithUser.ID,
Username: toShareWithUser.Username,
AvatarURL: toShareWithUser.AvatarURL,
},
Role: codersdk.WorkspaceRole("use"),
})
assert.Contains(t, out.String(), toShareWithUser.Username)
assert.Contains(t, out.String(), codersdk.WorkspaceRoleUse)
})
t.Run("ShareWithUsers_Multiple", func(t *testing.T) {
t.Parallel()
var (
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: dv,
})
orgOwner = coderdtest.CreateFirstUser(t, client)
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID, rbac.ScopedRoleOrgAuditor(orgOwner.OrganizationID))
workspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
OwnerID: workspaceOwner.ID,
OrganizationID: orgOwner.OrganizationID,
}).Do().Workspace
_, toShareWithUser1 = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
_, toShareWithUser2 = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
)
ctx := testutil.Context(t, testutil.WaitMedium)
inv, root := clitest.New(t,
"sharing",
"add", workspace.Name, "--org", orgOwner.OrganizationID.String(),
fmt.Sprintf("--user=%s,%s", toShareWithUser1.Username, toShareWithUser2.Username),
)
clitest.SetupConfig(t, workspaceOwnerClient, root)
out := bytes.NewBuffer(nil)
inv.Stdout = out
err := inv.WithContext(ctx).Run()
require.NoError(t, err)
acl, err := workspaceOwnerClient.WorkspaceACL(inv.Context(), workspace.ID)
require.NoError(t, err)
assert.Contains(t, acl.Users, codersdk.WorkspaceUser{
MinimalUser: codersdk.MinimalUser{
ID: toShareWithUser1.ID,
Username: toShareWithUser1.Username,
AvatarURL: toShareWithUser1.AvatarURL,
},
Role: codersdk.WorkspaceRoleUse,
})
assert.Contains(t, acl.Users, codersdk.WorkspaceUser{
MinimalUser: codersdk.MinimalUser{
ID: toShareWithUser2.ID,
Username: toShareWithUser2.Username,
AvatarURL: toShareWithUser2.AvatarURL,
},
Role: codersdk.WorkspaceRoleUse,
})
assert.Contains(t, out.String(), toShareWithUser1.Username)
assert.Contains(t, out.String(), toShareWithUser2.Username)
})
t.Run("ShareWithUsers_Roles", func(t *testing.T) {
t.Parallel()
var (
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
DeploymentValues: dv,
})
orgOwner = coderdtest.CreateFirstUser(t, client)
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID, rbac.ScopedRoleOrgAuditor(orgOwner.OrganizationID))
workspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
OwnerID: workspaceOwner.ID,
OrganizationID: orgOwner.OrganizationID,
}).Do().Workspace
_, toShareWithUser = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
)
ctx := testutil.Context(t, testutil.WaitMedium)
inv, root := clitest.New(t, "sharing", "add", workspace.Name,
"--org", orgOwner.OrganizationID.String(),
"--user", fmt.Sprintf("%s:admin", toShareWithUser.Username),
)
clitest.SetupConfig(t, workspaceOwnerClient, root)
out := bytes.NewBuffer(nil)
inv.Stdout = out
err := inv.WithContext(ctx).Run()
require.NoError(t, err)
acl, err := workspaceOwnerClient.WorkspaceACL(inv.Context(), workspace.ID)
require.NoError(t, err)
assert.Contains(t, acl.Users, codersdk.WorkspaceUser{
MinimalUser: codersdk.MinimalUser{
ID: toShareWithUser.ID,
Username: toShareWithUser.Username,
AvatarURL: toShareWithUser.AvatarURL,
},
Role: codersdk.WorkspaceRoleAdmin,
})
found := false
for _, line := range strings.Split(out.String(), "\n") {
if strings.Contains(line, toShareWithUser.Username) && strings.Contains(line, string(codersdk.WorkspaceRoleAdmin)) {
found = true
break
}
}
assert.True(t, found, fmt.Sprintf("expected to find the username %s and role %s in the command: %s", toShareWithUser.Username, codersdk.WorkspaceRoleAdmin, out.String()))
})
}