mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
refactor(cli): split chat share remove command files
This commit is contained in:
-374
@@ -1,18 +1,14 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/agent/agentcontextconfig"
|
||||
"github.com/coder/coder/v2/cli/cliui"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
||||
"github.com/coder/serpent"
|
||||
)
|
||||
@@ -32,209 +28,6 @@ func (r *RootCmd) chatCommand() *serpent.Command {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RootCmd) chatShareCommand() *serpent.Command {
|
||||
return &serpent.Command{
|
||||
Use: "share",
|
||||
Short: "Manage chat sharing",
|
||||
Long: "Share chats with users and groups.",
|
||||
Handler: func(i *serpent.Invocation) error {
|
||||
return i.Command.HelpHandler(i)
|
||||
},
|
||||
Children: []*serpent.Command{
|
||||
r.chatShareAddCommand(),
|
||||
r.chatShareRemoveCommand(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RootCmd) chatShareAddCommand() *serpent.Command {
|
||||
var users []string
|
||||
var groups []string
|
||||
|
||||
return &serpent.Command{
|
||||
Use: "add <chat-id> --user <user>:<role> --group <group>:<role>",
|
||||
Short: "Share a chat with a user or group.",
|
||||
Options: serpent.OptionSet{
|
||||
{
|
||||
Name: "user",
|
||||
Description: "A comma separated list of users to share the chat with.",
|
||||
Flag: "user",
|
||||
Value: serpent.StringArrayOf(&users),
|
||||
}, {
|
||||
Name: "group",
|
||||
Description: "A comma separated list of groups to share the chat with.",
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
userRoleStrings := make([][2]string, len(users))
|
||||
for i, user := range users {
|
||||
parsed, err := parseChatShareActorRole(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 := parseChatShareActorRole(group)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("invalid group format %q: %w", group, err)
|
||||
}
|
||||
groupRoleStrings[i] = parsed
|
||||
}
|
||||
|
||||
userRoles, groupRoles, err := fetchChatUsersAndGroups(inv.Context(), chatRoleLookupParams{
|
||||
Client: client,
|
||||
OrgID: chat.OrganizationID,
|
||||
Users: userRoleStrings,
|
||||
Groups: groupRoleStrings,
|
||||
DefaultRole: codersdk.ChatRoleRead,
|
||||
})
|
||||
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) 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",
|
||||
@@ -387,173 +180,6 @@ func (*RootCmd) chatContextClearCommand() *serpent.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
const chatShareDefaultGroupDisplay = "-"
|
||||
|
||||
type chatRoleLookupParams struct {
|
||||
Client *codersdk.Client
|
||||
OrgID uuid.UUID
|
||||
OrgName string
|
||||
Users [][2]string
|
||||
Groups [][2]string
|
||||
DefaultRole codersdk.ChatRole
|
||||
}
|
||||
|
||||
func parseChatShareID(raw string) (uuid.UUID, error) {
|
||||
parsed, err := uuid.Parse(raw)
|
||||
if err != nil {
|
||||
return uuid.Nil, xerrors.Errorf("invalid chat ID %q: %w", raw, err)
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func parseChatShareActorRole(raw string) ([2]string, error) {
|
||||
if strings.Count(raw, ":") > 1 {
|
||||
return [2]string{}, xerrors.New("must match pattern 'name:role'")
|
||||
}
|
||||
parts := strings.SplitN(raw, ":", 2)
|
||||
name := parts[0]
|
||||
if name == "" || !codersdk.UsernameValidRegex.MatchString(name) {
|
||||
return [2]string{}, xerrors.New("invalid name")
|
||||
}
|
||||
if len(parts) == 1 {
|
||||
return [2]string{name, ""}, nil
|
||||
}
|
||||
if parts[1] == "" {
|
||||
return [2]string{}, xerrors.New("role cannot be empty")
|
||||
}
|
||||
return [2]string{name, parts[1]}, nil
|
||||
}
|
||||
|
||||
func parseChatShareActor(raw string) ([2]string, error) {
|
||||
if strings.Contains(raw, ":") {
|
||||
return [2]string{}, xerrors.New("roles are only accepted by chat share add")
|
||||
}
|
||||
if raw == "" || !codersdk.UsernameValidRegex.MatchString(raw) {
|
||||
return [2]string{}, xerrors.New("invalid name")
|
||||
}
|
||||
return [2]string{raw, ""}, nil
|
||||
}
|
||||
|
||||
func stringToChatRole(role string) (codersdk.ChatRole, error) {
|
||||
switch role {
|
||||
case string(codersdk.ChatRoleRead):
|
||||
return codersdk.ChatRoleRead, nil
|
||||
case string(codersdk.ChatRoleDeleted):
|
||||
return codersdk.ChatRoleDeleted, nil
|
||||
default:
|
||||
return "", xerrors.Errorf("invalid role %q: expected %q", role, codersdk.ChatRoleRead)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchChatUsersAndGroups(ctx context.Context, params chatRoleLookupParams) (map[string]codersdk.ChatRole, map[string]codersdk.ChatRole, error) {
|
||||
userRoles := make(map[string]codersdk.ChatRole, len(params.Users))
|
||||
if len(params.Users) > 0 {
|
||||
orgMembers, err := params.Client.OrganizationMembers(ctx, params.OrgID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, user := range params.Users {
|
||||
username := user[0]
|
||||
role := user[1]
|
||||
if role == "" {
|
||||
role = string(params.DefaultRole)
|
||||
}
|
||||
|
||||
userID := ""
|
||||
for _, member := range orgMembers {
|
||||
if member.Username == username {
|
||||
userID = member.UserID.String()
|
||||
break
|
||||
}
|
||||
}
|
||||
if userID == "" {
|
||||
return nil, nil, xerrors.Errorf("could not find user %s in the organization %s", username, params.OrgName)
|
||||
}
|
||||
|
||||
chatRole, err := stringToChatRole(role)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
userRoles[userID] = chatRole
|
||||
}
|
||||
}
|
||||
|
||||
groupRoles := make(map[string]codersdk.ChatRole, len(params.Groups))
|
||||
if len(params.Groups) > 0 {
|
||||
orgGroups, err := params.Client.Groups(ctx, codersdk.GroupArguments{
|
||||
Organization: params.OrgID.String(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, group := range params.Groups {
|
||||
groupName := group[0]
|
||||
role := group[1]
|
||||
if role == "" {
|
||||
role = string(params.DefaultRole)
|
||||
}
|
||||
|
||||
var orgGroup *codersdk.Group
|
||||
for _, candidate := range orgGroups {
|
||||
if candidate.Name == groupName {
|
||||
orgGroup = &candidate
|
||||
break
|
||||
}
|
||||
}
|
||||
if orgGroup == nil {
|
||||
return nil, nil, xerrors.Errorf("could not find group named %s belonging to the organization %s", groupName, params.OrgName)
|
||||
}
|
||||
|
||||
chatRole, err := stringToChatRole(role)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
groupRoles[orgGroup.ID.String()] = chatRole
|
||||
}
|
||||
}
|
||||
|
||||
return userRoles, groupRoles, nil
|
||||
}
|
||||
|
||||
func chatACLToTable(ctx context.Context, acl *codersdk.ChatACL) (string, error) {
|
||||
type chatShareRow struct {
|
||||
User string `table:"user"`
|
||||
Group string `table:"group,default_sort"`
|
||||
Role codersdk.ChatRole `table:"role"`
|
||||
}
|
||||
|
||||
formatter := cliui.NewOutputFormatter(
|
||||
cliui.TableFormat(
|
||||
[]chatShareRow{}, []string{"User", "Group", "Role"}),
|
||||
cliui.JSONFormat())
|
||||
|
||||
outputRows := make([]chatShareRow, 0, len(acl.Users)+len(acl.Groups))
|
||||
for _, user := range acl.Users {
|
||||
if user.Role == codersdk.ChatRoleDeleted {
|
||||
continue
|
||||
}
|
||||
outputRows = append(outputRows, chatShareRow{
|
||||
User: user.Username,
|
||||
Group: chatShareDefaultGroupDisplay,
|
||||
Role: user.Role,
|
||||
})
|
||||
}
|
||||
for _, group := range acl.Groups {
|
||||
if group.Role == codersdk.ChatRoleDeleted {
|
||||
continue
|
||||
}
|
||||
outputRows = append(outputRows, chatShareRow{
|
||||
User: "",
|
||||
Group: group.Name,
|
||||
Role: group.Role,
|
||||
})
|
||||
}
|
||||
|
||||
return formatter.Format(ctx, outputRows)
|
||||
}
|
||||
|
||||
// parseChatID returns the chat UUID from the flag value (which
|
||||
// serpent already populates from --chat or CODER_CHAT_ID). Returns
|
||||
// uuid.Nil if empty (the server will auto-detect).
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/cli/cliui"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/serpent"
|
||||
)
|
||||
|
||||
func (r *RootCmd) chatShareCommand() *serpent.Command {
|
||||
return &serpent.Command{
|
||||
Use: "share",
|
||||
Short: "Manage chat sharing",
|
||||
Long: "Share chats with users and groups.",
|
||||
Handler: func(i *serpent.Invocation) error {
|
||||
return i.Command.HelpHandler(i)
|
||||
},
|
||||
Children: []*serpent.Command{
|
||||
r.chatShareAddCommand(),
|
||||
r.chatShareRemoveCommand(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const chatShareDefaultGroupDisplay = "-"
|
||||
|
||||
type chatRoleLookupParams struct {
|
||||
Client *codersdk.Client
|
||||
OrgID uuid.UUID
|
||||
OrgName string
|
||||
Users [][2]string
|
||||
Groups [][2]string
|
||||
DefaultRole codersdk.ChatRole
|
||||
}
|
||||
|
||||
func parseChatShareID(raw string) (uuid.UUID, error) {
|
||||
parsed, err := uuid.Parse(raw)
|
||||
if err != nil {
|
||||
return uuid.Nil, xerrors.Errorf("invalid chat ID %q: %w", raw, err)
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func parseChatShareActorRole(raw string) ([2]string, error) {
|
||||
if strings.Count(raw, ":") > 1 {
|
||||
return [2]string{}, xerrors.New("must match pattern 'name:role'")
|
||||
}
|
||||
parts := strings.SplitN(raw, ":", 2)
|
||||
name := parts[0]
|
||||
if name == "" || !codersdk.UsernameValidRegex.MatchString(name) {
|
||||
return [2]string{}, xerrors.New("invalid name")
|
||||
}
|
||||
if len(parts) == 1 {
|
||||
return [2]string{name, ""}, nil
|
||||
}
|
||||
if parts[1] == "" {
|
||||
return [2]string{}, xerrors.New("role cannot be empty")
|
||||
}
|
||||
return [2]string{name, parts[1]}, nil
|
||||
}
|
||||
|
||||
func parseChatShareActor(raw string) ([2]string, error) {
|
||||
if strings.Contains(raw, ":") {
|
||||
return [2]string{}, xerrors.New("roles are only accepted by chat share add")
|
||||
}
|
||||
if raw == "" || !codersdk.UsernameValidRegex.MatchString(raw) {
|
||||
return [2]string{}, xerrors.New("invalid name")
|
||||
}
|
||||
return [2]string{raw, ""}, nil
|
||||
}
|
||||
|
||||
func stringToChatRole(role string) (codersdk.ChatRole, error) {
|
||||
switch role {
|
||||
case string(codersdk.ChatRoleRead):
|
||||
return codersdk.ChatRoleRead, nil
|
||||
case string(codersdk.ChatRoleDeleted):
|
||||
return codersdk.ChatRoleDeleted, nil
|
||||
default:
|
||||
return "", xerrors.Errorf("invalid role %q: expected %q", role, codersdk.ChatRoleRead)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchChatUsersAndGroups(ctx context.Context, params chatRoleLookupParams) (map[string]codersdk.ChatRole, map[string]codersdk.ChatRole, error) {
|
||||
userRoles := make(map[string]codersdk.ChatRole, len(params.Users))
|
||||
if len(params.Users) > 0 {
|
||||
orgMembers, err := params.Client.OrganizationMembers(ctx, params.OrgID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, user := range params.Users {
|
||||
username := user[0]
|
||||
role := user[1]
|
||||
if role == "" {
|
||||
role = string(params.DefaultRole)
|
||||
}
|
||||
|
||||
userID := ""
|
||||
for _, member := range orgMembers {
|
||||
if member.Username == username {
|
||||
userID = member.UserID.String()
|
||||
break
|
||||
}
|
||||
}
|
||||
if userID == "" {
|
||||
return nil, nil, xerrors.Errorf("could not find user %s in the organization %s", username, params.OrgName)
|
||||
}
|
||||
|
||||
chatRole, err := stringToChatRole(role)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
userRoles[userID] = chatRole
|
||||
}
|
||||
}
|
||||
|
||||
groupRoles := make(map[string]codersdk.ChatRole, len(params.Groups))
|
||||
if len(params.Groups) > 0 {
|
||||
orgGroups, err := params.Client.Groups(ctx, codersdk.GroupArguments{
|
||||
Organization: params.OrgID.String(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, group := range params.Groups {
|
||||
groupName := group[0]
|
||||
role := group[1]
|
||||
if role == "" {
|
||||
role = string(params.DefaultRole)
|
||||
}
|
||||
|
||||
var orgGroup *codersdk.Group
|
||||
for _, candidate := range orgGroups {
|
||||
if candidate.Name == groupName {
|
||||
orgGroup = &candidate
|
||||
break
|
||||
}
|
||||
}
|
||||
if orgGroup == nil {
|
||||
return nil, nil, xerrors.Errorf("could not find group named %s belonging to the organization %s", groupName, params.OrgName)
|
||||
}
|
||||
|
||||
chatRole, err := stringToChatRole(role)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
groupRoles[orgGroup.ID.String()] = chatRole
|
||||
}
|
||||
}
|
||||
|
||||
return userRoles, groupRoles, nil
|
||||
}
|
||||
|
||||
func chatACLToTable(ctx context.Context, acl *codersdk.ChatACL) (string, error) {
|
||||
type chatShareRow struct {
|
||||
User string `table:"user"`
|
||||
Group string `table:"group,default_sort"`
|
||||
Role codersdk.ChatRole `table:"role"`
|
||||
}
|
||||
|
||||
formatter := cliui.NewOutputFormatter(
|
||||
cliui.TableFormat(
|
||||
[]chatShareRow{}, []string{"User", "Group", "Role"}),
|
||||
cliui.JSONFormat())
|
||||
|
||||
outputRows := make([]chatShareRow, 0, len(acl.Users)+len(acl.Groups))
|
||||
for _, user := range acl.Users {
|
||||
if user.Role == codersdk.ChatRoleDeleted {
|
||||
continue
|
||||
}
|
||||
outputRows = append(outputRows, chatShareRow{
|
||||
User: user.Username,
|
||||
Group: chatShareDefaultGroupDisplay,
|
||||
Role: user.Role,
|
||||
})
|
||||
}
|
||||
for _, group := range acl.Groups {
|
||||
if group.Role == codersdk.ChatRoleDeleted {
|
||||
continue
|
||||
}
|
||||
outputRows = append(outputRows, chatShareRow{
|
||||
User: "",
|
||||
Group: group.Name,
|
||||
Role: group.Role,
|
||||
})
|
||||
}
|
||||
|
||||
return formatter.Format(ctx, outputRows)
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/serpent"
|
||||
)
|
||||
|
||||
func (r *RootCmd) chatShareAddCommand() *serpent.Command {
|
||||
var users []string
|
||||
var groups []string
|
||||
|
||||
return &serpent.Command{
|
||||
Use: "add <chat-id> --user <user>:<role> --group <group>:<role>",
|
||||
Short: "Share a chat with a user or group.",
|
||||
Options: serpent.OptionSet{
|
||||
{
|
||||
Name: "user",
|
||||
Description: "A comma separated list of users to share the chat with.",
|
||||
Flag: "user",
|
||||
Value: serpent.StringArrayOf(&users),
|
||||
}, {
|
||||
Name: "group",
|
||||
Description: "A comma separated list of groups to share the chat with.",
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
userRoleStrings := make([][2]string, len(users))
|
||||
for i, user := range users {
|
||||
parsed, err := parseChatShareActorRole(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 := parseChatShareActorRole(group)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("invalid group format %q: %w", group, err)
|
||||
}
|
||||
groupRoleStrings[i] = parsed
|
||||
}
|
||||
|
||||
userRoles, groupRoles, err := fetchChatUsersAndGroups(inv.Context(), chatRoleLookupParams{
|
||||
Client: client,
|
||||
OrgID: chat.OrganizationID,
|
||||
Users: userRoleStrings,
|
||||
Groups: groupRoleStrings,
|
||||
DefaultRole: codersdk.ChatRoleRead,
|
||||
})
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"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/dbgen"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
func TestExpChatShareAdd(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("ShareWithUserExplicitReadRole", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, db := coderdtest.NewWithDatabase(t, nil)
|
||||
firstUser := coderdtest.CreateFirstUser(t, client)
|
||||
_, toShareWithUser := 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 add user",
|
||||
})
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
inv, root := clitest.New(t, "exp", "chat", "share", "add", chat.ID.String(), "--user", toShareWithUser.Username+":read")
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
out := new(bytes.Buffer)
|
||||
inv.Stdout = out
|
||||
err := inv.WithContext(ctx).Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
acl, err := codersdk.NewExperimentalClient(client).GetChatACL(ctx, chat.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, acl.Users, codersdk.ChatUser{
|
||||
MinimalUser: codersdk.MinimalUser{
|
||||
ID: toShareWithUser.ID,
|
||||
Username: toShareWithUser.Username,
|
||||
Name: toShareWithUser.Name,
|
||||
AvatarURL: toShareWithUser.AvatarURL,
|
||||
},
|
||||
Role: codersdk.ChatRoleRead,
|
||||
})
|
||||
assert.Contains(t, out.String(), toShareWithUser.Username)
|
||||
assert.Contains(t, out.String(), string(codersdk.ChatRoleRead))
|
||||
})
|
||||
|
||||
t.Run("ShareWithUserDefaultReadRole", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, db := coderdtest.NewWithDatabase(t, nil)
|
||||
firstUser := coderdtest.CreateFirstUser(t, client)
|
||||
_, toShareWithUser := 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 add user default role",
|
||||
})
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
inv, root := clitest.New(t, "exp", "chat", "share", "add", chat.ID.String(), "--user", toShareWithUser.Username)
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
out := new(bytes.Buffer)
|
||||
inv.Stdout = out
|
||||
err := inv.WithContext(ctx).Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
acl, err := codersdk.NewExperimentalClient(client).GetChatACL(ctx, chat.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, acl.Users, codersdk.ChatUser{
|
||||
MinimalUser: codersdk.MinimalUser{
|
||||
ID: toShareWithUser.ID,
|
||||
Username: toShareWithUser.Username,
|
||||
Name: toShareWithUser.Name,
|
||||
AvatarURL: toShareWithUser.AvatarURL,
|
||||
},
|
||||
Role: codersdk.ChatRoleRead,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("ShareWithMultipleUsers", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, db := coderdtest.NewWithDatabase(t, nil)
|
||||
firstUser := coderdtest.CreateFirstUser(t, client)
|
||||
_, toShareWithUser1 := coderdtest.CreateAnotherUser(t, client, firstUser.OrganizationID)
|
||||
_, toShareWithUser2 := 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 add multiple users",
|
||||
})
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
inv, root := clitest.New(t,
|
||||
"exp", "chat", "share", "add", chat.ID.String(),
|
||||
fmt.Sprintf("--user=%s:read,%s:read", toShareWithUser1.Username, toShareWithUser2.Username),
|
||||
)
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
out := new(bytes.Buffer)
|
||||
inv.Stdout = out
|
||||
err := inv.WithContext(ctx).Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
acl, err := codersdk.NewExperimentalClient(client).GetChatACL(ctx, chat.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, acl.Users, codersdk.ChatUser{
|
||||
MinimalUser: codersdk.MinimalUser{
|
||||
ID: toShareWithUser1.ID,
|
||||
Username: toShareWithUser1.Username,
|
||||
Name: toShareWithUser1.Name,
|
||||
AvatarURL: toShareWithUser1.AvatarURL,
|
||||
},
|
||||
Role: codersdk.ChatRoleRead,
|
||||
})
|
||||
assert.Contains(t, acl.Users, codersdk.ChatUser{
|
||||
MinimalUser: codersdk.MinimalUser{
|
||||
ID: toShareWithUser2.ID,
|
||||
Username: toShareWithUser2.Username,
|
||||
Name: toShareWithUser2.Name,
|
||||
AvatarURL: toShareWithUser2.AvatarURL,
|
||||
},
|
||||
Role: codersdk.ChatRoleRead,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("RejectsUnknownRole", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, db := coderdtest.NewWithDatabase(t, nil)
|
||||
firstUser := coderdtest.CreateFirstUser(t, client)
|
||||
_, toShareWithUser := 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 add invalid role",
|
||||
})
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
inv, root := clitest.New(t, "exp", "chat", "share", "add", chat.ID.String(), "--user", toShareWithUser.Username+":write")
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
err := inv.WithContext(ctx).Run()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "invalid role \"write\"")
|
||||
})
|
||||
|
||||
t.Run("RequiresActor", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
chatID := "00000000-0000-0000-0000-000000000001"
|
||||
inv, _ := clitest.New(t, "exp", "chat", "share", "add", chatID)
|
||||
|
||||
err := inv.Run()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "at least one user or group must be provided")
|
||||
})
|
||||
|
||||
t.Run("RejectsInvalidChatID", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
inv, _ := clitest.New(t, "exp", "chat", "share", "add", "not-a-uuid", "--user", "alice:read")
|
||||
|
||||
err := inv.Run()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "invalid chat ID")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/serpent"
|
||||
)
|
||||
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"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/dbgen"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
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")
|
||||
})
|
||||
}
|
||||
@@ -1,264 +1,13 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"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/dbgen"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
func TestExpChatShareAdd(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("ShareWithUserExplicitReadRole", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, db := coderdtest.NewWithDatabase(t, nil)
|
||||
firstUser := coderdtest.CreateFirstUser(t, client)
|
||||
_, toShareWithUser := 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 add user",
|
||||
})
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
inv, root := clitest.New(t, "exp", "chat", "share", "add", chat.ID.String(), "--user", toShareWithUser.Username+":read")
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
out := new(bytes.Buffer)
|
||||
inv.Stdout = out
|
||||
err := inv.WithContext(ctx).Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
acl, err := codersdk.NewExperimentalClient(client).GetChatACL(ctx, chat.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, acl.Users, codersdk.ChatUser{
|
||||
MinimalUser: codersdk.MinimalUser{
|
||||
ID: toShareWithUser.ID,
|
||||
Username: toShareWithUser.Username,
|
||||
Name: toShareWithUser.Name,
|
||||
AvatarURL: toShareWithUser.AvatarURL,
|
||||
},
|
||||
Role: codersdk.ChatRoleRead,
|
||||
})
|
||||
assert.Contains(t, out.String(), toShareWithUser.Username)
|
||||
assert.Contains(t, out.String(), string(codersdk.ChatRoleRead))
|
||||
})
|
||||
|
||||
t.Run("ShareWithUserDefaultReadRole", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, db := coderdtest.NewWithDatabase(t, nil)
|
||||
firstUser := coderdtest.CreateFirstUser(t, client)
|
||||
_, toShareWithUser := 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 add user default role",
|
||||
})
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
inv, root := clitest.New(t, "exp", "chat", "share", "add", chat.ID.String(), "--user", toShareWithUser.Username)
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
out := new(bytes.Buffer)
|
||||
inv.Stdout = out
|
||||
err := inv.WithContext(ctx).Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
acl, err := codersdk.NewExperimentalClient(client).GetChatACL(ctx, chat.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, acl.Users, codersdk.ChatUser{
|
||||
MinimalUser: codersdk.MinimalUser{
|
||||
ID: toShareWithUser.ID,
|
||||
Username: toShareWithUser.Username,
|
||||
Name: toShareWithUser.Name,
|
||||
AvatarURL: toShareWithUser.AvatarURL,
|
||||
},
|
||||
Role: codersdk.ChatRoleRead,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("ShareWithMultipleUsers", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, db := coderdtest.NewWithDatabase(t, nil)
|
||||
firstUser := coderdtest.CreateFirstUser(t, client)
|
||||
_, toShareWithUser1 := coderdtest.CreateAnotherUser(t, client, firstUser.OrganizationID)
|
||||
_, toShareWithUser2 := 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 add multiple users",
|
||||
})
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
inv, root := clitest.New(t,
|
||||
"exp", "chat", "share", "add", chat.ID.String(),
|
||||
fmt.Sprintf("--user=%s:read,%s:read", toShareWithUser1.Username, toShareWithUser2.Username),
|
||||
)
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
out := new(bytes.Buffer)
|
||||
inv.Stdout = out
|
||||
err := inv.WithContext(ctx).Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
acl, err := codersdk.NewExperimentalClient(client).GetChatACL(ctx, chat.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, acl.Users, codersdk.ChatUser{
|
||||
MinimalUser: codersdk.MinimalUser{
|
||||
ID: toShareWithUser1.ID,
|
||||
Username: toShareWithUser1.Username,
|
||||
Name: toShareWithUser1.Name,
|
||||
AvatarURL: toShareWithUser1.AvatarURL,
|
||||
},
|
||||
Role: codersdk.ChatRoleRead,
|
||||
})
|
||||
assert.Contains(t, acl.Users, codersdk.ChatUser{
|
||||
MinimalUser: codersdk.MinimalUser{
|
||||
ID: toShareWithUser2.ID,
|
||||
Username: toShareWithUser2.Username,
|
||||
Name: toShareWithUser2.Name,
|
||||
AvatarURL: toShareWithUser2.AvatarURL,
|
||||
},
|
||||
Role: codersdk.ChatRoleRead,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("RejectsUnknownRole", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, db := coderdtest.NewWithDatabase(t, nil)
|
||||
firstUser := coderdtest.CreateFirstUser(t, client)
|
||||
_, toShareWithUser := 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 add invalid role",
|
||||
})
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
inv, root := clitest.New(t, "exp", "chat", "share", "add", chat.ID.String(), "--user", toShareWithUser.Username+":write")
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
err := inv.WithContext(ctx).Run()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "invalid role \"write\"")
|
||||
})
|
||||
|
||||
t.Run("RequiresActor", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
chatID := "00000000-0000-0000-0000-000000000001"
|
||||
inv, _ := clitest.New(t, "exp", "chat", "share", "add", chatID)
|
||||
|
||||
err := inv.Run()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "at least one user or group must be provided")
|
||||
})
|
||||
|
||||
t.Run("RejectsInvalidChatID", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
inv, _ := clitest.New(t, "exp", "chat", "share", "add", "not-a-uuid", "--user", "alice:read")
|
||||
|
||||
err := inv.Run()
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "invalid chat ID")
|
||||
})
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user