Files
coder/enterprise/cli/sharing_test.go
T
Sushant P 37a8e61ea2 chore: move Shared Workspaces from experiments to beta (#22206)
* Removed the shared-workspaces experiment and cleaned up related
middleware
* Added beta tagging to the UI for shared workspaces
2026-02-23 08:30:32 -08:00

388 lines
13 KiB
Go

package cli_test
import (
"bytes"
"context"
"fmt"
"slices"
"strings"
"testing"
"github.com/google/uuid"
"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/enterprise/coderd/coderdenttest"
"github.com/coder/coder/v2/enterprise/coderd/license"
"github.com/coder/coder/v2/testutil"
)
func TestSharingShare(t *testing.T) {
t.Parallel()
t.Run("ShareWithGroups_Simple", func(t *testing.T) {
t.Parallel()
var (
client, db, orgOwner = coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureTemplateRBAC: 1,
},
},
})
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
_, orgMember = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
)
ctx := testutil.Context(t, testutil.WaitMedium)
group, err := createGroupWithMembers(ctx, client, orgOwner.OrganizationID, "new-group", []uuid.UUID{orgMember.ID})
require.NoError(t, err)
inv, root := clitest.New(t, "sharing", "share", workspace.Name, "--group", group.Name)
clitest.SetupConfig(t, workspaceOwnerClient, root)
out := new(bytes.Buffer)
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.Len(t, acl.Groups, 1)
assert.Equal(t, acl.Groups[0].Group.ID, group.ID)
assert.Equal(t, acl.Groups[0].Role, codersdk.WorkspaceRoleUse)
found := false
for _, line := range strings.Split(out.String(), "\n") {
found = strings.Contains(line, group.Name) && strings.Contains(line, string(codersdk.WorkspaceRoleUse))
if found {
break
}
}
assert.True(t, found, "Expected to find group name %s and role %s in output: %s", group.Name, codersdk.WorkspaceRoleUse, out.String())
})
t.Run("ShareWithGroups_Multiple", func(t *testing.T) {
t.Parallel()
var (
client, db, orgOwner = coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureTemplateRBAC: 1,
},
},
})
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
_, wibbleMember = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
_, wobbleMember = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
)
ctx := testutil.Context(t, testutil.WaitMedium)
wibbleGroup, err := createGroupWithMembers(ctx, client, orgOwner.OrganizationID, "wibble", []uuid.UUID{wibbleMember.ID})
require.NoError(t, err)
wobbleGroup, err := createGroupWithMembers(ctx, client, orgOwner.OrganizationID, "wobble", []uuid.UUID{wobbleMember.ID})
require.NoError(t, err)
inv, root := clitest.New(t, "sharing", "share", workspace.Name,
fmt.Sprintf("--group=%s,%s", wibbleGroup.Name, wobbleGroup.Name))
clitest.SetupConfig(t, workspaceOwnerClient, root)
out := new(bytes.Buffer)
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.Len(t, acl.Groups, 2)
type workspaceGroup []codersdk.WorkspaceGroup
assert.NotEqual(t, -1, slices.IndexFunc(workspaceGroup(acl.Groups), func(g codersdk.WorkspaceGroup) bool {
return g.Group.ID == wibbleGroup.ID
}))
assert.NotEqual(t, -1, slices.IndexFunc(workspaceGroup(acl.Groups), func(g codersdk.WorkspaceGroup) bool {
return g.Group.ID == wobbleGroup.ID
}))
t.Run("ShareWithGroups_Role", func(t *testing.T) {
t.Parallel()
var (
client, db, orgOwner = coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureTemplateRBAC: 1,
},
},
})
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
_, orgMember = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
)
ctx := testutil.Context(t, testutil.WaitMedium)
group, err := createGroupWithMembers(ctx, client, orgOwner.OrganizationID, "new-group", []uuid.UUID{orgMember.ID})
require.NoError(t, err)
inv, root := clitest.New(t, "sharing", "share", workspace.Name, "--group", fmt.Sprintf("%s:admin", group.Name))
clitest.SetupConfig(t, workspaceOwnerClient, root)
out := new(bytes.Buffer)
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.Len(t, acl.Groups, 1)
assert.Equal(t, acl.Groups[0].Group.ID, group.ID)
assert.Equal(t, acl.Groups[0].Role, codersdk.WorkspaceRoleAdmin)
found := false
for _, line := range strings.Split(out.String(), "\n") {
found = strings.Contains(line, group.Name) && strings.Contains(line, string(codersdk.WorkspaceRoleAdmin))
if found {
break
}
}
assert.True(t, found, "Expected to find group name %s and role %s in output: %s", group.Name, codersdk.WorkspaceRoleAdmin, out.String())
})
})
}
func TestSharingStatus(t *testing.T) {
t.Parallel()
t.Run("ListSharedUsers", func(t *testing.T) {
t.Parallel()
var (
client, db, orgOwner = coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureTemplateRBAC: 1,
},
},
})
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
_, orgMember = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
ctx = testutil.Context(t, testutil.WaitMedium)
)
group, err := createGroupWithMembers(ctx, client, orgOwner.OrganizationID, "new-group", []uuid.UUID{orgMember.ID})
require.NoError(t, err)
err = client.UpdateWorkspaceACL(ctx, workspace.ID, codersdk.UpdateWorkspaceACL{
GroupRoles: map[string]codersdk.WorkspaceRole{
group.ID.String(): codersdk.WorkspaceRoleUse,
},
})
require.NoError(t, err)
inv, root := clitest.New(t, "sharing", "status", workspace.Name)
clitest.SetupConfig(t, workspaceOwnerClient, root)
out := new(bytes.Buffer)
inv.Stdout = out
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
found := false
for _, line := range strings.Split(out.String(), "\n") {
if strings.Contains(line, orgMember.Username) && strings.Contains(line, string(codersdk.WorkspaceRoleUse)) && strings.Contains(line, group.Name) {
found = true
break
}
}
assert.True(t, found, "expected to find username %s with role %s in the output: %s", orgMember.Username, codersdk.WorkspaceRoleUse, out.String())
})
}
func TestSharingRemove(t *testing.T) {
t.Parallel()
t.Run("RemoveSharedGroup_Single", func(t *testing.T) {
t.Parallel()
var (
client, db, orgOwner = coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureTemplateRBAC: 1,
},
},
})
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
_, groupUser1 = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
_, groupUser2 = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
)
ctx := testutil.Context(t, testutil.WaitMedium)
group1, err := createGroupWithMembers(ctx, client, orgOwner.OrganizationID, "group-1", []uuid.UUID{groupUser1.ID, groupUser2.ID})
require.NoError(t, err)
group2, err := createGroupWithMembers(ctx, client, orgOwner.OrganizationID, "group-2", []uuid.UUID{groupUser1.ID, groupUser2.ID})
require.NoError(t, err)
// Share the workspace with a user to later remove
err = client.UpdateWorkspaceACL(ctx, workspace.ID, codersdk.UpdateWorkspaceACL{
GroupRoles: map[string]codersdk.WorkspaceRole{
group1.ID.String(): codersdk.WorkspaceRoleUse,
group2.ID.String(): codersdk.WorkspaceRoleUse,
},
})
require.NoError(t, err)
inv, root := clitest.New(t,
"sharing",
"remove",
workspace.Name,
"--group", group1.Name,
)
clitest.SetupConfig(t, workspaceOwnerClient, root)
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
acl, err := workspaceOwnerClient.WorkspaceACL(inv.Context(), workspace.ID)
require.NoError(t, err)
removedGroup1 := true
removedGroup2 := true
for _, group := range acl.Groups {
if group.ID == group1.ID {
removedGroup1 = false
continue
}
if group.ID == group2.ID {
removedGroup2 = false
continue
}
}
assert.True(t, removedGroup1)
assert.False(t, removedGroup2)
})
t.Run("RemoveSharedGroup_Multiple", func(t *testing.T) {
t.Parallel()
var (
client, db, orgOwner = coderdenttest.NewWithDatabase(t, &coderdenttest.Options{
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureTemplateRBAC: 1,
},
},
})
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
_, groupUser1 = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
_, groupUser2 = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
)
ctx := testutil.Context(t, testutil.WaitMedium)
group1, err := createGroupWithMembers(ctx, client, orgOwner.OrganizationID, "group-1", []uuid.UUID{groupUser1.ID, groupUser2.ID})
require.NoError(t, err)
group2, err := createGroupWithMembers(ctx, client, orgOwner.OrganizationID, "group-2", []uuid.UUID{groupUser1.ID, groupUser2.ID})
require.NoError(t, err)
// Share the workspace with a user to later remove
err = client.UpdateWorkspaceACL(ctx, workspace.ID, codersdk.UpdateWorkspaceACL{
GroupRoles: map[string]codersdk.WorkspaceRole{
group1.ID.String(): codersdk.WorkspaceRoleUse,
group2.ID.String(): codersdk.WorkspaceRoleUse,
},
})
require.NoError(t, err)
inv, root := clitest.New(t,
"sharing",
"remove",
workspace.Name,
fmt.Sprintf("--group=%s,%s", group1.Name, group2.Name),
)
clitest.SetupConfig(t, workspaceOwnerClient, root)
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
acl, err := workspaceOwnerClient.WorkspaceACL(inv.Context(), workspace.ID)
require.NoError(t, err)
removedGroup1 := true
removedGroup2 := true
for _, group := range acl.Groups {
if group.ID == group1.ID {
removedGroup1 = false
continue
}
if group.ID == group2.ID {
removedGroup2 = false
continue
}
}
assert.True(t, removedGroup1)
assert.True(t, removedGroup2)
})
}
func createGroupWithMembers(ctx context.Context, client *codersdk.Client, orgID uuid.UUID, name string, memberIDs []uuid.UUID) (codersdk.Group, error) {
group, err := client.CreateGroup(ctx, orgID, codersdk.CreateGroupRequest{
Name: name,
DisplayName: name,
})
if err != nil {
return codersdk.Group{}, err
}
ids := make([]string, len(memberIDs))
for i, id := range memberIDs {
ids[i] = id.String()
}
return client.PatchGroup(ctx, group.ID, codersdk.PatchGroupRequest{
AddUsers: ids,
})
}