Full Mattermost server source with integrated Community Enterprise features. Includes vendor directory for offline/air-gapped builds. Structure: - enterprise-impl/: Enterprise feature implementations - enterprise-community/: Init files that register implementations - enterprise/: Bridge imports (community_imports.go) - vendor/: All dependencies for offline builds Build (online): go build ./cmd/mattermost Build (offline/air-gapped): go build -mod=vendor ./cmd/mattermost 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1066 lines
30 KiB
Go
1066 lines
30 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package commands
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
|
|
"github.com/golang/mock/gomock"
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/printer"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
func (s *MmctlUnitTestSuite) TestCPAFieldListCmd() {
|
|
s.Run("Should list all CPA fields with plain text output format", func() {
|
|
printer.Clean()
|
|
printer.SetFormat(printer.FormatPlain)
|
|
viper.Set("json", false)
|
|
|
|
// Mock property fields from API
|
|
mockFields := []*model.PropertyField{
|
|
{
|
|
ID: "field1",
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
Attrs: model.StringInterface{
|
|
"managed": "admin",
|
|
},
|
|
},
|
|
{
|
|
ID: "field2",
|
|
Name: "Skills",
|
|
Type: model.PropertyFieldTypeMultiselect,
|
|
Attrs: model.StringInterface{
|
|
"managed": "",
|
|
"options": []map[string]any{
|
|
{"id": "opt1", "name": "Go"},
|
|
{"id": "opt2", "name": "React"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return(mockFields, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
err := cpaFieldListCmdF(s.client, &cobra.Command{}, []string{})
|
|
s.Require().NoError(err)
|
|
|
|
lines := printer.GetLines()
|
|
s.Require().NotEmpty(lines)
|
|
})
|
|
|
|
s.Run("Should handle empty fields list scenario", func() {
|
|
printer.Clean()
|
|
printer.SetFormat(printer.FormatPlain)
|
|
viper.Set("json", false)
|
|
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return([]*model.PropertyField{}, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
err := cpaFieldListCmdF(s.client, &cobra.Command{}, []string{})
|
|
s.Require().NoError(err)
|
|
|
|
lines := printer.GetLines()
|
|
s.Require().Empty(lines)
|
|
})
|
|
|
|
s.Run("Should handle API error when ListCPAFields fails", func() {
|
|
printer.Clean()
|
|
|
|
expectedError := errors.New("API error")
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return(nil, &model.Response{}, expectedError).
|
|
Times(1)
|
|
|
|
err := cpaFieldListCmdF(s.client, &cobra.Command{}, []string{})
|
|
s.Require().Error(err)
|
|
s.Require().Contains(err.Error(), "failed to get CPA fields")
|
|
s.Require().Contains(err.Error(), "API error")
|
|
})
|
|
|
|
s.Run("Should handle conversion error when NewCPAFieldFromPropertyField fails", func() {
|
|
printer.Clean()
|
|
|
|
// Create a property field with invalid attrs that will cause conversion to fail
|
|
invalidField := &model.PropertyField{
|
|
ID: "invalid",
|
|
Name: "Invalid Field",
|
|
Type: model.PropertyFieldTypeText,
|
|
Attrs: model.StringInterface{
|
|
"options": "invalid-json-structure", // This should cause JSON unmarshaling to fail
|
|
},
|
|
}
|
|
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return([]*model.PropertyField{invalidField}, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
err := cpaFieldListCmdF(s.client, &cobra.Command{}, []string{})
|
|
s.Require().Error(err)
|
|
s.Require().Contains(err.Error(), "failed to convert field")
|
|
s.Require().Contains(err.Error(), "Invalid Field")
|
|
})
|
|
|
|
s.Run("Should show correct field properties", func() {
|
|
printer.Clean()
|
|
printer.SetFormat(printer.FormatPlain)
|
|
viper.Set("json", false)
|
|
|
|
// Test admin-managed field
|
|
adminField := &model.PropertyField{
|
|
ID: "admin-field",
|
|
Name: "Admin Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
Attrs: model.StringInterface{
|
|
"managed": "admin",
|
|
},
|
|
}
|
|
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return([]*model.PropertyField{adminField}, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
err := cpaFieldListCmdF(s.client, &cobra.Command{}, []string{})
|
|
s.Require().NoError(err)
|
|
|
|
// Verify that exactly one field is printed (since printer.SetSingle(true) is used)
|
|
s.Require().Len(printer.GetLines(), 1)
|
|
|
|
// The output should be a string containing the field information
|
|
output, ok := printer.GetLines()[0].(string)
|
|
s.Require().True(ok, "Expected output to be a string")
|
|
s.Require().Contains(output, "admin-field", "Output should contain field ID")
|
|
s.Require().Contains(output, "Admin Department", "Output should contain field name")
|
|
s.Require().Contains(output, "text", "Output should contain field type")
|
|
s.Require().Contains(output, "admin-managed", "Output should show admin-managed status")
|
|
})
|
|
|
|
s.Run("Should show options for select/multiselect fields", func() {
|
|
printer.Clean()
|
|
printer.SetFormat(printer.FormatPlain)
|
|
viper.Set("json", false)
|
|
|
|
// Create field with options
|
|
selectField := &model.PropertyField{
|
|
ID: "select-field",
|
|
Name: "Level",
|
|
Type: model.PropertyFieldTypeSelect,
|
|
Attrs: model.StringInterface{
|
|
"managed": "",
|
|
"options": json.RawMessage(`[{"id":"opt1","name":"Junior"},{"id":"opt2","name":"Senior"}]`),
|
|
},
|
|
}
|
|
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return([]*model.PropertyField{selectField}, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
err := cpaFieldListCmdF(s.client, &cobra.Command{}, []string{})
|
|
s.Require().NoError(err)
|
|
|
|
// Verify that exactly one field is printed
|
|
s.Require().Len(printer.GetLines(), 1)
|
|
|
|
// The output should be a string containing the field information and options
|
|
output, ok := printer.GetLines()[0].(string)
|
|
s.Require().True(ok, "Expected output to be a string")
|
|
s.Require().Contains(output, "select-field", "Output should contain field ID")
|
|
s.Require().Contains(output, "Level", "Output should contain field name")
|
|
s.Require().Contains(output, "select", "Output should contain field type")
|
|
s.Require().Contains(output, "user-managed", "Output should show user-managed status")
|
|
s.Require().Contains(output, "Junior, Senior", "Output should contain option names")
|
|
})
|
|
}
|
|
|
|
func (s *MmctlUnitTestSuite) TestCPAFieldCreateCmd() {
|
|
s.Run("Should successfully create text field with name and type only", func() {
|
|
printer.Clean()
|
|
printer.SetFormat(printer.FormatPlain)
|
|
viper.Set("json", false)
|
|
|
|
expectedField := &model.PropertyField{
|
|
ID: "created-field-id",
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
Attrs: make(model.StringInterface),
|
|
}
|
|
|
|
s.client.
|
|
EXPECT().
|
|
CreateCPAField(context.TODO(), &model.PropertyField{
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
Attrs: make(model.StringInterface),
|
|
}).
|
|
Return(expectedField, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
err := cpaFieldCreateCmdF(s.client, cmd, []string{"Department", "text"})
|
|
s.Require().NoError(err)
|
|
|
|
lines := printer.GetLines()
|
|
s.Require().Len(lines, 1)
|
|
s.Require().Contains(lines[0], "Field Department correctly created")
|
|
})
|
|
|
|
s.Run("Should successfully create admin-managed field with managed=true flag", func() {
|
|
printer.Clean()
|
|
printer.SetFormat(printer.FormatPlain)
|
|
viper.Set("json", false)
|
|
|
|
expectedField := &model.PropertyField{
|
|
ID: "admin-field-id",
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
Attrs: model.StringInterface{
|
|
"managed": "admin",
|
|
},
|
|
}
|
|
|
|
s.client.
|
|
EXPECT().
|
|
CreateCPAField(context.TODO(), &model.PropertyField{
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
Attrs: model.StringInterface{
|
|
"managed": "admin",
|
|
},
|
|
}).
|
|
Return(expectedField, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("managed", false, "")
|
|
_ = cmd.Flags().Set("managed", "true")
|
|
err := cpaFieldCreateCmdF(s.client, cmd, []string{"Department", "text"})
|
|
s.Require().NoError(err)
|
|
|
|
lines := printer.GetLines()
|
|
s.Require().Len(lines, 1)
|
|
s.Require().Contains(lines[0], "Field Department correctly created")
|
|
})
|
|
|
|
s.Run("Should successfully create select field with multiple option flags", func() {
|
|
printer.Clean()
|
|
printer.SetFormat(printer.FormatPlain)
|
|
viper.Set("json", false)
|
|
|
|
expectedField := &model.PropertyField{
|
|
ID: "select-field-id",
|
|
Name: "Level",
|
|
Type: model.PropertyFieldTypeSelect,
|
|
TargetType: "user",
|
|
Attrs: model.StringInterface{
|
|
"options": []*model.CustomProfileAttributesSelectOption{
|
|
{ID: "opt1", Name: "Junior"},
|
|
{ID: "opt2", Name: "Senior"},
|
|
},
|
|
},
|
|
}
|
|
|
|
// We need to match on a field that has options, but we can't predict the generated IDs
|
|
s.client.
|
|
EXPECT().
|
|
CreateCPAField(context.TODO(), gomock.Any()).
|
|
DoAndReturn(func(ctx context.Context, field *model.PropertyField) (*model.PropertyField, *model.Response, error) {
|
|
// Verify the structure of the field being created
|
|
s.Require().Equal("Level", field.Name)
|
|
s.Require().Equal(model.PropertyFieldTypeSelect, field.Type)
|
|
s.Require().Equal("user", field.TargetType)
|
|
|
|
// Check that options were created with the right names
|
|
options, ok := field.Attrs["options"].([]*model.CustomProfileAttributesSelectOption)
|
|
s.Require().True(ok)
|
|
s.Require().Len(options, 2)
|
|
s.Require().Equal("Junior", options[0].Name)
|
|
s.Require().Equal("Senior", options[1].Name)
|
|
s.Require().NotEmpty(options[0].ID)
|
|
s.Require().NotEmpty(options[1].ID)
|
|
|
|
return expectedField, &model.Response{}, nil
|
|
}).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().StringSlice("option", []string{}, "")
|
|
_ = cmd.Flags().Set("option", "Junior")
|
|
_ = cmd.Flags().Set("option", "Senior")
|
|
err := cpaFieldCreateCmdF(s.client, cmd, []string{"Level", "select"})
|
|
s.Require().NoError(err)
|
|
|
|
lines := printer.GetLines()
|
|
s.Require().Len(lines, 1)
|
|
s.Require().Contains(lines[0], "Field Level correctly created")
|
|
})
|
|
|
|
s.Run("Should successfully create field with attrs JSON string", func() {
|
|
printer.Clean()
|
|
printer.SetFormat(printer.FormatPlain)
|
|
viper.Set("json", false)
|
|
|
|
expectedField := &model.PropertyField{
|
|
ID: "attrs-field-id",
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
Attrs: model.StringInterface{
|
|
"visibility": "always",
|
|
"required": true,
|
|
},
|
|
}
|
|
|
|
s.client.
|
|
EXPECT().
|
|
CreateCPAField(context.TODO(), &model.PropertyField{
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
Attrs: model.StringInterface{
|
|
"visibility": "always",
|
|
"required": true,
|
|
},
|
|
}).
|
|
Return(expectedField, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().String("attrs", "", "")
|
|
_ = cmd.Flags().Set("attrs", `{"visibility":"always","required":true}`)
|
|
err := cpaFieldCreateCmdF(s.client, cmd, []string{"Department", "text"})
|
|
s.Require().NoError(err)
|
|
|
|
lines := printer.GetLines()
|
|
s.Require().Len(lines, 1)
|
|
s.Require().Contains(lines[0], "Field Department correctly created")
|
|
})
|
|
|
|
s.Run("Should have individual flags override attrs JSON precedence", func() {
|
|
printer.Clean()
|
|
printer.SetFormat(printer.FormatPlain)
|
|
viper.Set("json", false)
|
|
|
|
expectedField := &model.PropertyField{
|
|
ID: "override-field-id",
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
Attrs: model.StringInterface{
|
|
"visibility": "always",
|
|
"managed": "admin", // Individual flag should override this
|
|
},
|
|
}
|
|
|
|
s.client.
|
|
EXPECT().
|
|
CreateCPAField(context.TODO(), &model.PropertyField{
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
Attrs: model.StringInterface{
|
|
"visibility": "always",
|
|
"managed": "admin", // Should be overridden by the --managed flag
|
|
},
|
|
}).
|
|
Return(expectedField, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().String("attrs", "", "")
|
|
cmd.Flags().Bool("managed", false, "")
|
|
_ = cmd.Flags().Set("attrs", `{"visibility":"always","managed":""}`)
|
|
_ = cmd.Flags().Set("managed", "true")
|
|
err := cpaFieldCreateCmdF(s.client, cmd, []string{"Department", "text"})
|
|
s.Require().NoError(err)
|
|
|
|
lines := printer.GetLines()
|
|
s.Require().Len(lines, 1)
|
|
s.Require().Contains(lines[0], "Field Department correctly created")
|
|
})
|
|
|
|
s.Run("Should handle error for invalid attrs JSON syntax", func() {
|
|
printer.Clean()
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().String("attrs", "", "")
|
|
_ = cmd.Flags().Set("attrs", `{"invalid": json}`) // Invalid JSON
|
|
err := cpaFieldCreateCmdF(s.client, cmd, []string{"Department", "text"})
|
|
s.Require().Error(err)
|
|
s.Require().Contains(err.Error(), "failed to parse attrs JSON")
|
|
})
|
|
|
|
s.Run("Should handle API error when CreateCPAField client call fails", func() {
|
|
printer.Clean()
|
|
|
|
expectedError := errors.New("API error")
|
|
s.client.
|
|
EXPECT().
|
|
CreateCPAField(context.TODO(), gomock.Any()).
|
|
Return(nil, &model.Response{}, expectedError).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
err := cpaFieldCreateCmdF(s.client, cmd, []string{"Department", "text"})
|
|
s.Require().Error(err)
|
|
s.Require().Contains(err.Error(), "failed to create CPA field")
|
|
s.Require().Contains(err.Error(), "API error")
|
|
})
|
|
}
|
|
|
|
func (s *MmctlUnitTestSuite) TestCPAFieldEditCmd() {
|
|
s.Run("Should successfully update field name with --name flag", func() {
|
|
printer.Clean()
|
|
printer.SetFormat(printer.FormatPlain)
|
|
viper.Set("json", false)
|
|
|
|
fieldID := model.NewId()
|
|
mockFields := []*model.PropertyField{
|
|
{
|
|
ID: fieldID,
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
},
|
|
}
|
|
|
|
expectedField := &model.PropertyField{
|
|
ID: fieldID,
|
|
Name: "New Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
Attrs: make(model.StringInterface),
|
|
}
|
|
|
|
newName := "New Department"
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return(mockFields, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
s.client.
|
|
EXPECT().
|
|
PatchCPAField(context.TODO(), fieldID, &model.PropertyFieldPatch{
|
|
Name: &newName,
|
|
}).
|
|
Return(expectedField, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().String("name", "", "")
|
|
_ = cmd.Flags().Set("name", "New Department")
|
|
err := cpaFieldEditCmdF(s.client, cmd, []string{fieldID})
|
|
s.Require().NoError(err)
|
|
|
|
lines := printer.GetLines()
|
|
s.Require().Len(lines, 1)
|
|
s.Require().Contains(lines[0], "Field New Department successfully updated")
|
|
})
|
|
|
|
s.Run("Should successfully update managed flag to true", func() {
|
|
printer.Clean()
|
|
printer.SetFormat(printer.FormatPlain)
|
|
viper.Set("json", false)
|
|
|
|
fieldID := model.NewId()
|
|
expectedField := &model.PropertyField{
|
|
ID: fieldID,
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
Attrs: model.StringInterface{
|
|
"managed": "admin",
|
|
},
|
|
}
|
|
|
|
expectedAttrs := model.StringInterface{
|
|
"managed": "admin",
|
|
}
|
|
|
|
mockFields := []*model.PropertyField{expectedField}
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return(mockFields, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
s.client.
|
|
EXPECT().
|
|
PatchCPAField(context.TODO(), fieldID, &model.PropertyFieldPatch{
|
|
Attrs: &expectedAttrs,
|
|
}).
|
|
Return(expectedField, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("managed", false, "")
|
|
cmd.Flags().String("attrs", "", "")
|
|
cmd.Flags().StringSlice("option", []string{}, "")
|
|
_ = cmd.Flags().Set("managed", "true")
|
|
err := cpaFieldEditCmdF(s.client, cmd, []string{fieldID})
|
|
s.Require().NoError(err)
|
|
|
|
lines := printer.GetLines()
|
|
s.Require().Len(lines, 1)
|
|
s.Require().Contains(lines[0], "Field Department successfully updated")
|
|
})
|
|
|
|
s.Run("Should successfully update managed flag to false", func() {
|
|
printer.Clean()
|
|
printer.SetFormat(printer.FormatPlain)
|
|
viper.Set("json", false)
|
|
|
|
fieldID := model.NewId()
|
|
expectedField := &model.PropertyField{
|
|
ID: fieldID,
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
Attrs: model.StringInterface{
|
|
"managed": "",
|
|
},
|
|
}
|
|
|
|
expectedAttrs := model.StringInterface{
|
|
"managed": "",
|
|
}
|
|
|
|
mockFields := []*model.PropertyField{expectedField}
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return(mockFields, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
s.client.
|
|
EXPECT().
|
|
PatchCPAField(context.TODO(), fieldID, &model.PropertyFieldPatch{
|
|
Attrs: &expectedAttrs,
|
|
}).
|
|
Return(expectedField, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("managed", false, "")
|
|
cmd.Flags().String("attrs", "", "")
|
|
cmd.Flags().StringSlice("option", []string{}, "")
|
|
_ = cmd.Flags().Set("managed", "false")
|
|
err := cpaFieldEditCmdF(s.client, cmd, []string{fieldID})
|
|
s.Require().NoError(err)
|
|
|
|
lines := printer.GetLines()
|
|
s.Require().Len(lines, 1)
|
|
s.Require().Contains(lines[0], "Field Department successfully updated")
|
|
})
|
|
|
|
s.Run("Should successfully update with attrs JSON string", func() {
|
|
printer.Clean()
|
|
printer.SetFormat(printer.FormatPlain)
|
|
viper.Set("json", false)
|
|
|
|
fieldID := model.NewId()
|
|
expectedField := &model.PropertyField{
|
|
ID: fieldID,
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
Attrs: model.StringInterface{
|
|
"visibility": "always",
|
|
"required": true,
|
|
},
|
|
}
|
|
|
|
expectedAttrs := model.StringInterface{
|
|
"visibility": "always",
|
|
"required": true,
|
|
}
|
|
|
|
mockFields := []*model.PropertyField{expectedField}
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return(mockFields, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
s.client.
|
|
EXPECT().
|
|
PatchCPAField(context.TODO(), fieldID, &model.PropertyFieldPatch{
|
|
Attrs: &expectedAttrs,
|
|
}).
|
|
Return(expectedField, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("managed", false, "")
|
|
cmd.Flags().String("attrs", "", "")
|
|
cmd.Flags().StringSlice("option", []string{}, "")
|
|
_ = cmd.Flags().Set("attrs", `{"visibility":"always","required":true}`)
|
|
err := cpaFieldEditCmdF(s.client, cmd, []string{fieldID})
|
|
s.Require().NoError(err)
|
|
|
|
lines := printer.GetLines()
|
|
s.Require().Len(lines, 1)
|
|
s.Require().Contains(lines[0], "Field Department successfully updated")
|
|
})
|
|
|
|
s.Run("Should successfully update with multiple option flags", func() {
|
|
printer.Clean()
|
|
printer.SetFormat(printer.FormatPlain)
|
|
viper.Set("json", false)
|
|
|
|
fieldID := model.NewId()
|
|
expectedField := &model.PropertyField{
|
|
ID: fieldID,
|
|
Name: "Skills",
|
|
Type: model.PropertyFieldTypeMultiselect,
|
|
TargetType: "user",
|
|
Attrs: model.StringInterface{
|
|
"options": []*model.CustomProfileAttributesSelectOption{
|
|
{ID: "opt1", Name: "Go"},
|
|
{ID: "opt2", Name: "React"},
|
|
{ID: "opt3", Name: "Python"},
|
|
},
|
|
},
|
|
}
|
|
|
|
mockFields := []*model.PropertyField{expectedField}
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return(mockFields, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
s.client.
|
|
EXPECT().
|
|
PatchCPAField(context.TODO(), fieldID, gomock.Any()).
|
|
DoAndReturn(func(ctx context.Context, receivedFieldID string, patch *model.PropertyFieldPatch) (*model.PropertyField, *model.Response, error) {
|
|
s.Require().Equal(fieldID, receivedFieldID)
|
|
s.Require().NotNil(patch.Attrs)
|
|
|
|
options, ok := (*patch.Attrs)["options"].([]*model.CustomProfileAttributesSelectOption)
|
|
s.Require().True(ok)
|
|
s.Require().Len(options, 3)
|
|
s.Require().Equal("Go", options[0].Name)
|
|
s.Require().Equal("React", options[1].Name)
|
|
s.Require().Equal("Python", options[2].Name)
|
|
s.Require().NotEmpty(options[0].ID)
|
|
s.Require().NotEmpty(options[1].ID)
|
|
s.Require().NotEmpty(options[2].ID)
|
|
|
|
return expectedField, &model.Response{}, nil
|
|
}).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("managed", false, "")
|
|
cmd.Flags().String("attrs", "", "")
|
|
cmd.Flags().StringSlice("option", []string{}, "")
|
|
_ = cmd.Flags().Set("option", "Go")
|
|
_ = cmd.Flags().Set("option", "React")
|
|
_ = cmd.Flags().Set("option", "Python")
|
|
err := cpaFieldEditCmdF(s.client, cmd, []string{fieldID})
|
|
s.Require().NoError(err)
|
|
|
|
lines := printer.GetLines()
|
|
s.Require().Len(lines, 1)
|
|
s.Require().Contains(lines[0], "Field Skills successfully updated")
|
|
})
|
|
|
|
s.Run("Should have individual flags override attrs JSON", func() {
|
|
printer.Clean()
|
|
printer.SetFormat(printer.FormatPlain)
|
|
viper.Set("json", false)
|
|
|
|
fieldID := model.NewId()
|
|
expectedField := &model.PropertyField{
|
|
ID: fieldID,
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
Attrs: model.StringInterface{
|
|
"visibility": "always",
|
|
"managed": "admin", // individual flag should override attrs
|
|
},
|
|
}
|
|
|
|
mockFields := []*model.PropertyField{expectedField}
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return(mockFields, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
s.client.
|
|
EXPECT().
|
|
PatchCPAField(context.TODO(), fieldID, gomock.Any()).
|
|
DoAndReturn(func(ctx context.Context, receivedFieldID string, patch *model.PropertyFieldPatch) (*model.PropertyField, *model.Response, error) {
|
|
s.Require().Equal(fieldID, receivedFieldID)
|
|
s.Require().NotNil(patch.Attrs)
|
|
|
|
// individual flags should take precedence over attrs
|
|
s.Require().Equal("admin", (*patch.Attrs)["managed"])
|
|
s.Require().Equal("always", (*patch.Attrs)["visibility"])
|
|
|
|
return expectedField, &model.Response{}, nil
|
|
}).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("managed", false, "")
|
|
cmd.Flags().String("attrs", "", "")
|
|
cmd.Flags().StringSlice("option", []string{}, "")
|
|
_ = cmd.Flags().Set("managed", "true")
|
|
_ = cmd.Flags().Set("attrs", `{"visibility":"always","managed":""}`)
|
|
err := cpaFieldEditCmdF(s.client, cmd, []string{fieldID})
|
|
s.Require().NoError(err)
|
|
|
|
lines := printer.GetLines()
|
|
s.Require().Len(lines, 1)
|
|
s.Require().Contains(lines[0], "Field Department successfully updated")
|
|
})
|
|
|
|
s.Run("Should skip attrs when no changes provided", func() {
|
|
printer.Clean()
|
|
printer.SetFormat(printer.FormatPlain)
|
|
viper.Set("json", false)
|
|
|
|
newName := "New Name"
|
|
fieldID := model.NewId()
|
|
expectedField := &model.PropertyField{
|
|
ID: fieldID,
|
|
Name: "New Name",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
Attrs: make(model.StringInterface),
|
|
}
|
|
|
|
mockFields := []*model.PropertyField{expectedField}
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return(mockFields, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
// Should only pass name, no attrs
|
|
s.client.
|
|
EXPECT().
|
|
PatchCPAField(context.TODO(), fieldID, &model.PropertyFieldPatch{
|
|
Name: &newName,
|
|
}).
|
|
Return(expectedField, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().String("name", "", "")
|
|
_ = cmd.Flags().Set("name", "New Name")
|
|
err := cpaFieldEditCmdF(s.client, cmd, []string{fieldID})
|
|
s.Require().NoError(err)
|
|
|
|
lines := printer.GetLines()
|
|
s.Require().Len(lines, 1)
|
|
s.Require().Contains(lines[0], "Field New Name successfully updated")
|
|
})
|
|
|
|
s.Run("Should handle error for invalid attrs JSON syntax", func() {
|
|
printer.Clean()
|
|
|
|
fieldID := model.NewId()
|
|
mockField := &model.PropertyField{
|
|
ID: fieldID,
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
}
|
|
mockFields := []*model.PropertyField{mockField}
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return(mockFields, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("managed", false, "")
|
|
cmd.Flags().String("attrs", "", "")
|
|
cmd.Flags().StringSlice("option", []string{}, "")
|
|
_ = cmd.Flags().Set("attrs", `{"invalid": json}`) // Invalid JSON
|
|
err := cpaFieldEditCmdF(s.client, cmd, []string{fieldID})
|
|
s.Require().Error(err)
|
|
s.Require().Contains(err.Error(), "failed to parse attrs JSON")
|
|
})
|
|
|
|
s.Run("Should handle API error when PatchCPAField client call fails", func() {
|
|
printer.Clean()
|
|
|
|
fieldID := model.NewId()
|
|
mockField := &model.PropertyField{
|
|
ID: fieldID,
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
}
|
|
mockFields := []*model.PropertyField{mockField}
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return(mockFields, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
expectedError := errors.New("API error")
|
|
s.client.
|
|
EXPECT().
|
|
PatchCPAField(context.TODO(), fieldID, gomock.Any()).
|
|
Return(nil, &model.Response{}, expectedError).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().String("name", "", "")
|
|
_ = cmd.Flags().Set("name", "New Name")
|
|
err := cpaFieldEditCmdF(s.client, cmd, []string{fieldID})
|
|
s.Require().Error(err)
|
|
s.Require().Contains(err.Error(), "failed to update CPA field")
|
|
s.Require().Contains(err.Error(), "API error")
|
|
})
|
|
|
|
s.Run("Should successfully edit field by name", func() {
|
|
printer.Clean()
|
|
printer.SetFormat(printer.FormatPlain)
|
|
viper.Set("json", false)
|
|
|
|
fieldID := model.NewId()
|
|
mockFields := []*model.PropertyField{
|
|
{
|
|
ID: fieldID,
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
},
|
|
}
|
|
|
|
expectedField := &model.PropertyField{
|
|
ID: fieldID,
|
|
Name: "Team",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
Attrs: model.StringInterface{
|
|
"managed": "admin",
|
|
},
|
|
}
|
|
|
|
newName := "Team"
|
|
expectedAttrs := model.StringInterface{
|
|
"managed": "admin",
|
|
}
|
|
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return(mockFields, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
s.client.
|
|
EXPECT().
|
|
PatchCPAField(context.TODO(), fieldID, &model.PropertyFieldPatch{
|
|
Name: &newName,
|
|
Attrs: &expectedAttrs,
|
|
}).
|
|
Return(expectedField, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().String("name", "", "")
|
|
cmd.Flags().Bool("managed", false, "")
|
|
cmd.Flags().String("attrs", "", "")
|
|
cmd.Flags().StringSlice("option", []string{}, "")
|
|
_ = cmd.Flags().Set("name", "Team")
|
|
_ = cmd.Flags().Set("managed", "true")
|
|
err := cpaFieldEditCmdF(s.client, cmd, []string{"Department"})
|
|
s.Require().NoError(err)
|
|
|
|
lines := printer.GetLines()
|
|
s.Require().Len(lines, 1)
|
|
s.Require().Contains(lines[0], "Field Team successfully updated")
|
|
})
|
|
}
|
|
|
|
func (s *MmctlUnitTestSuite) TestCPAFieldDeleteCmd() {
|
|
s.Run("Should successfully delete field with --confirm flag", func() {
|
|
printer.Clean()
|
|
|
|
fieldID := model.NewId()
|
|
mockFields := []*model.PropertyField{
|
|
{
|
|
ID: fieldID,
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
},
|
|
}
|
|
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return(mockFields, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
s.client.
|
|
EXPECT().
|
|
DeleteCPAField(context.TODO(), fieldID).
|
|
Return(&model.Response{}, nil).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("confirm", false, "")
|
|
_ = cmd.Flags().Set("confirm", "true")
|
|
err := cpaFieldDeleteCmdF(s.client, cmd, []string{fieldID})
|
|
s.Require().NoError(err)
|
|
|
|
lines := printer.GetLines()
|
|
s.Require().Len(lines, 1)
|
|
s.Require().Contains(lines[0], "Successfully deleted CPA field: "+fieldID)
|
|
})
|
|
|
|
s.Run("Should successfully delete field by name with --confirm flag", func() {
|
|
printer.Clean()
|
|
|
|
fieldID := model.NewId()
|
|
mockFields := []*model.PropertyField{
|
|
{
|
|
ID: fieldID,
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
},
|
|
}
|
|
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return(mockFields, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
s.client.
|
|
EXPECT().
|
|
DeleteCPAField(context.TODO(), fieldID).
|
|
Return(&model.Response{}, nil).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("confirm", false, "")
|
|
_ = cmd.Flags().Set("confirm", "true")
|
|
err := cpaFieldDeleteCmdF(s.client, cmd, []string{"Department"})
|
|
s.Require().NoError(err)
|
|
|
|
lines := printer.GetLines()
|
|
s.Require().Len(lines, 1)
|
|
s.Require().Contains(lines[0], "Successfully deleted CPA field: Department")
|
|
})
|
|
|
|
s.Run("Should handle getFieldFromArg error when field not found", func() {
|
|
printer.Clean()
|
|
|
|
fieldID := model.NewId()
|
|
mockFields := []*model.PropertyField{
|
|
{
|
|
ID: fieldID,
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
},
|
|
}
|
|
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return(mockFields, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("confirm", false, "")
|
|
_ = cmd.Flags().Set("confirm", "true")
|
|
err := cpaFieldDeleteCmdF(s.client, cmd, []string{"NonexistentField"})
|
|
s.Require().Error(err)
|
|
s.Require().Contains(err.Error(), `failed to get field for "NonexistentField"`)
|
|
})
|
|
|
|
s.Run("Should handle ListCPAFields API error in getFieldFromArg", func() {
|
|
printer.Clean()
|
|
|
|
expectedError := errors.New("API error")
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return(nil, &model.Response{}, expectedError).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("confirm", false, "")
|
|
_ = cmd.Flags().Set("confirm", "true")
|
|
err := cpaFieldDeleteCmdF(s.client, cmd, []string{"field-name"})
|
|
s.Require().Error(err)
|
|
s.Require().Contains(err.Error(), "failed to get CPA fields")
|
|
s.Require().Contains(err.Error(), "API error")
|
|
})
|
|
|
|
s.Run("Should error when --confirm flag is not provided in non-interactive shell", func() {
|
|
printer.Clean()
|
|
|
|
// No client call expected since confirmation fails in non-interactive shell
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("confirm", false, "")
|
|
err := cpaFieldDeleteCmdF(s.client, cmd, []string{"field-id"})
|
|
s.Require().Error(err)
|
|
s.Require().Contains(err.Error(), "could not proceed, either enable --confirm flag or use an interactive shell to complete operation: this is not an interactive shell")
|
|
})
|
|
|
|
s.Run("Should handle API error when DeleteCPAField client call fails", func() {
|
|
printer.Clean()
|
|
|
|
fieldID := model.NewId()
|
|
mockFields := []*model.PropertyField{
|
|
{
|
|
ID: fieldID,
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
},
|
|
}
|
|
|
|
s.client.
|
|
EXPECT().
|
|
ListCPAFields(context.TODO()).
|
|
Return(mockFields, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
expectedError := errors.New("API error")
|
|
s.client.
|
|
EXPECT().
|
|
DeleteCPAField(context.TODO(), fieldID).
|
|
Return(&model.Response{}, expectedError).
|
|
Times(1)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("confirm", false, "")
|
|
_ = cmd.Flags().Set("confirm", "true")
|
|
err := cpaFieldDeleteCmdF(s.client, cmd, []string{fieldID})
|
|
s.Require().Error(err)
|
|
s.Require().Contains(err.Error(), "failed to delete CPA field")
|
|
s.Require().Contains(err.Error(), "API error")
|
|
})
|
|
}
|