feat(cli): add chat share remove command

This commit is contained in:
Danielle Maywood
2026-05-29 09:35:08 +00:00
parent 8b64661e6d
commit e50517611b
2 changed files with 167 additions and 0 deletions
+95
View File
@@ -42,6 +42,7 @@ func (r *RootCmd) chatShareCommand() *serpent.Command {
},
Children: []*serpent.Command{
r.chatShareAddCommand(),
r.chatShareRemoveCommand(),
},
}
}
@@ -140,6 +141,100 @@ func (r *RootCmd) chatShareAddCommand() *serpent.Command {
}
}
func (r *RootCmd) chatShareRemoveCommand() *serpent.Command {
var users []string
var groups []string
return &serpent.Command{
Use: "remove <chat-id> --user <user> --group <group>",
Short: "Remove shared access for users or groups from a chat.",
Options: serpent.OptionSet{
{
Name: "user",
Description: "A comma separated list of users to remove shared chat access from.",
Flag: "user",
Value: serpent.StringArrayOf(&users),
}, {
Name: "group",
Description: "A comma separated list of groups to remove shared chat access from.",
Flag: "group",
Value: serpent.StringArrayOf(&groups),
},
},
Middleware: serpent.Chain(
serpent.RequireNArgs(1),
),
Handler: func(inv *serpent.Invocation) error {
if len(users) == 0 && len(groups) == 0 {
return xerrors.New("at least one user or group must be provided")
}
chatID, err := parseChatShareID(inv.Args[0])
if err != nil {
return err
}
userRoleStrings := make([][2]string, len(users))
for i, user := range users {
parsed, err := parseChatShareActor(user)
if err != nil {
return xerrors.Errorf("invalid user format %q: %w", user, err)
}
userRoleStrings[i] = parsed
}
groupRoleStrings := make([][2]string, len(groups))
for i, group := range groups {
parsed, err := parseChatShareActor(group)
if err != nil {
return xerrors.Errorf("invalid group format %q: %w", group, err)
}
groupRoleStrings[i] = parsed
}
client, err := r.InitClient(inv)
if err != nil {
return err
}
experimentalClient := codersdk.NewExperimentalClient(client)
chat, err := experimentalClient.GetChat(inv.Context(), chatID)
if err != nil {
return xerrors.Errorf("unable to fetch chat %s: %w", inv.Args[0], err)
}
userRoles, groupRoles, err := fetchChatUsersAndGroups(inv.Context(), chatRoleLookupParams{
Client: client,
OrgID: chat.OrganizationID,
Users: userRoleStrings,
Groups: groupRoleStrings,
DefaultRole: codersdk.ChatRoleDeleted,
})
if err != nil {
return err
}
if err := experimentalClient.UpdateChatACL(inv.Context(), chat.ID, codersdk.UpdateChatACL{
UserRoles: userRoles,
GroupRoles: groupRoles,
}); err != nil {
return err
}
acl, err := experimentalClient.GetChatACL(inv.Context(), chat.ID)
if err != nil {
return xerrors.Errorf("could not fetch current chat ACL after sharing: %w", err)
}
out, err := chatACLToTable(inv.Context(), &acl)
if err != nil {
return err
}
_, err = fmt.Fprintln(inv.Stdout, out)
return err
},
}
}
func (r *RootCmd) chatContextCommand() *serpent.Command {
return &serpent.Command{
Use: "context",
+72
View File
@@ -187,6 +187,78 @@ func TestExpChatShareAdd(t *testing.T) {
})
}
func TestExpChatShareRemove(t *testing.T) {
t.Parallel()
t.Run("RemoveSharedUser", func(t *testing.T) {
t.Parallel()
client, db := coderdtest.NewWithDatabase(t, nil)
firstUser := coderdtest.CreateFirstUser(t, client)
_, sharedUser := coderdtest.CreateAnotherUser(t, client, firstUser.OrganizationID)
modelConfig := dbgen.ChatModelConfig(t, db, database.ChatModelConfig{})
chat := dbgen.Chat(t, db, database.Chat{
OrganizationID: firstUser.OrganizationID,
OwnerID: firstUser.UserID,
LastModelConfigID: modelConfig.ID,
Title: "share remove user",
})
experimentalClient := codersdk.NewExperimentalClient(client)
ctx := testutil.Context(t, testutil.WaitMedium)
err := experimentalClient.UpdateChatACL(ctx, chat.ID, codersdk.UpdateChatACL{
UserRoles: map[string]codersdk.ChatRole{sharedUser.ID.String(): codersdk.ChatRoleRead},
})
require.NoError(t, err)
inv, root := clitest.New(t, "exp", "chat", "share", "remove", chat.ID.String(), "--user", sharedUser.Username)
clitest.SetupConfig(t, client, root)
out := new(bytes.Buffer)
inv.Stdout = out
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
acl, err := experimentalClient.GetChatACL(ctx, chat.ID)
require.NoError(t, err)
for _, user := range acl.Users {
assert.NotEqual(t, sharedUser.ID, user.ID)
}
assert.NotContains(t, out.String(), sharedUser.Username)
})
t.Run("RequiresActor", func(t *testing.T) {
t.Parallel()
chatID := "00000000-0000-0000-0000-000000000001"
inv, _ := clitest.New(t, "exp", "chat", "share", "remove", chatID)
err := inv.Run()
require.Error(t, err)
require.Contains(t, err.Error(), "at least one user or group must be provided")
})
t.Run("RejectsRoleSyntax", func(t *testing.T) {
t.Parallel()
chatID := "00000000-0000-0000-0000-000000000001"
inv, _ := clitest.New(t, "exp", "chat", "share", "remove", chatID, "--user", "alice:read")
err := inv.Run()
require.Error(t, err)
require.Contains(t, err.Error(), "roles are only accepted by chat share add")
})
t.Run("RejectsInvalidChatID", func(t *testing.T) {
t.Parallel()
inv, _ := clitest.New(t, "exp", "chat", "share", "remove", "not-a-uuid", "--user", "alice")
err := inv.Run()
require.Error(t, err)
require.Contains(t, err.Error(), "invalid chat ID")
})
}
func TestExpChatContextAdd(t *testing.T) {
t.Parallel()