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>
468 lines
14 KiB
Go
468 lines
14 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package commands
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func (s *MmctlUnitTestSuite) TestHasAttrsChanges() {
|
|
testCases := []struct {
|
|
Name string
|
|
FlagChanges map[string]string // map of flag name -> value to set
|
|
Expected bool
|
|
}{
|
|
{
|
|
Name: "Should return true when managed flag is changed",
|
|
FlagChanges: map[string]string{"managed": "true"},
|
|
Expected: true,
|
|
},
|
|
{
|
|
Name: "Should return true when attrs flag is changed",
|
|
FlagChanges: map[string]string{"attrs": `{"visibility":"always"}`},
|
|
Expected: true,
|
|
},
|
|
{
|
|
Name: "Should return true when option flag is changed",
|
|
FlagChanges: map[string]string{"option": "Go"},
|
|
Expected: true,
|
|
},
|
|
{
|
|
Name: "Should return true when multiple relevant flags are changed",
|
|
FlagChanges: map[string]string{
|
|
"managed": "true",
|
|
"attrs": `{"visibility":"always"}`,
|
|
"option": "Go",
|
|
},
|
|
Expected: true,
|
|
},
|
|
{
|
|
Name: "Should return false when no relevant flags are changed",
|
|
FlagChanges: map[string]string{}, // No flags set
|
|
Expected: false,
|
|
},
|
|
{
|
|
Name: "Should return false for other unrelated flag changes like name",
|
|
FlagChanges: map[string]string{"name": "New Name"},
|
|
Expected: false,
|
|
},
|
|
{
|
|
Name: "Should return true when managed flag is changed along with unrelated flags",
|
|
FlagChanges: map[string]string{
|
|
"managed": "true",
|
|
"name": "New Name",
|
|
},
|
|
Expected: true,
|
|
},
|
|
{
|
|
Name: "Should return true when attrs flag is changed along with unrelated flags",
|
|
FlagChanges: map[string]string{
|
|
"attrs": `{"visibility":"always"}`,
|
|
"name": "New Name",
|
|
},
|
|
Expected: true,
|
|
},
|
|
{
|
|
Name: "Should return true when option flag is changed along with unrelated flags",
|
|
FlagChanges: map[string]string{
|
|
"option": "Go",
|
|
"name": "New Name",
|
|
},
|
|
Expected: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
s.Run(tc.Name, func() {
|
|
cmd := &cobra.Command{}
|
|
|
|
// Set up all the flags that might be used
|
|
cmd.Flags().Bool("managed", false, "")
|
|
cmd.Flags().String("attrs", "", "")
|
|
cmd.Flags().StringSlice("option", []string{}, "")
|
|
cmd.Flags().String("name", "", "")
|
|
|
|
// Apply the flag changes for this test case
|
|
for flagName, flagValue := range tc.FlagChanges {
|
|
err := cmd.Flags().Set(flagName, flagValue)
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
result := hasAttrsChanges(cmd)
|
|
s.Require().Equal(tc.Expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func (s *MmctlUnitTestSuite) TestBuildFieldAttrs() {
|
|
testCases := []struct {
|
|
Name string
|
|
FlagChanges map[string]any // map of flag name -> value or []string for options
|
|
Expected model.StringInterface
|
|
ShouldError bool
|
|
ErrorText string
|
|
}{
|
|
{
|
|
Name: "Should return empty attrs when no flags are set",
|
|
FlagChanges: map[string]any{},
|
|
Expected: model.StringInterface{},
|
|
ShouldError: false,
|
|
},
|
|
{
|
|
Name: "Should create attrs with managed=admin when managed=true",
|
|
FlagChanges: map[string]any{"managed": "true"},
|
|
Expected: model.StringInterface{"managed": "admin"},
|
|
ShouldError: false,
|
|
},
|
|
{
|
|
Name: "Should create attrs with managed='' when managed=false",
|
|
FlagChanges: map[string]any{"managed": "false"},
|
|
Expected: model.StringInterface{"managed": ""},
|
|
ShouldError: false,
|
|
},
|
|
{
|
|
Name: "Should parse attrs JSON string and apply to StringInterface",
|
|
FlagChanges: map[string]any{"attrs": `{"visibility":"always","required":true}`},
|
|
Expected: model.StringInterface{"visibility": "always", "required": true},
|
|
ShouldError: false,
|
|
},
|
|
{
|
|
Name: "Should create CustomProfileAttributesSelectOption array with generated IDs for option flags",
|
|
FlagChanges: map[string]any{"option": []string{"Go"}},
|
|
Expected: model.StringInterface{},
|
|
ShouldError: false,
|
|
},
|
|
{
|
|
Name: "Should have individual flags override attrs JSON values",
|
|
FlagChanges: map[string]any{
|
|
"attrs": `{"visibility":"always","managed":""}`,
|
|
"managed": "true", // Should override the managed="" from attrs
|
|
},
|
|
Expected: model.StringInterface{
|
|
"visibility": "always",
|
|
"managed": "admin", // Individual flag should override
|
|
},
|
|
ShouldError: false,
|
|
},
|
|
{
|
|
Name: "Should handle error for invalid attrs JSON syntax",
|
|
FlagChanges: map[string]any{"attrs": `{"invalid": json}`},
|
|
Expected: nil,
|
|
ShouldError: true,
|
|
ErrorText: "failed to parse attrs JSON",
|
|
},
|
|
{
|
|
Name: "Should combine managed and option flags correctly",
|
|
FlagChanges: map[string]any{
|
|
"managed": "true",
|
|
"option": []string{"Go"},
|
|
},
|
|
Expected: model.StringInterface{"managed": "admin"},
|
|
ShouldError: false,
|
|
},
|
|
{
|
|
Name: "Should handle multiple option flags",
|
|
FlagChanges: map[string]any{
|
|
"option": []string{"Go", "React", "Python"},
|
|
},
|
|
Expected: model.StringInterface{},
|
|
ShouldError: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
s.Run(tc.Name, func() {
|
|
cmd := &cobra.Command{}
|
|
|
|
// Set up all the flags that might be used
|
|
cmd.Flags().Bool("managed", false, "")
|
|
cmd.Flags().String("attrs", "", "")
|
|
cmd.Flags().StringSlice("option", []string{}, "")
|
|
|
|
// Apply the flag changes for this test case
|
|
for flagName, flagValue := range tc.FlagChanges {
|
|
if flagName == "option" {
|
|
// Handle option flag with list of values
|
|
if options, ok := flagValue.([]string); ok {
|
|
for _, optionName := range options {
|
|
err := cmd.Flags().Set("option", optionName)
|
|
s.Require().NoError(err)
|
|
}
|
|
}
|
|
} else {
|
|
// Handle other flags as strings
|
|
if stringValue, ok := flagValue.(string); ok {
|
|
err := cmd.Flags().Set(flagName, stringValue)
|
|
s.Require().NoError(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
result, err := buildFieldAttrs(cmd, nil)
|
|
|
|
if tc.ShouldError {
|
|
s.Require().Error(err)
|
|
s.Require().Contains(err.Error(), tc.ErrorText)
|
|
s.Require().Nil(result)
|
|
} else {
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(result)
|
|
|
|
// Check if we expect options based on FlagChanges
|
|
var expectedOptions []string
|
|
if optionValue, exists := tc.FlagChanges["option"]; exists {
|
|
if options, ok := optionValue.([]string); ok {
|
|
expectedOptions = options
|
|
}
|
|
}
|
|
|
|
// Validate options if specified
|
|
if len(expectedOptions) > 0 {
|
|
s.Require().Contains(result, "options")
|
|
options, ok := result["options"].([]*model.CustomProfileAttributesSelectOption)
|
|
s.Require().True(ok, "Options should be []*model.CustomProfileAttributesSelectOption")
|
|
|
|
optionNames := make([]string, len(options))
|
|
for i, opt := range options {
|
|
optionNames[i] = opt.Name
|
|
s.Require().NotEmpty(opt.ID)
|
|
}
|
|
s.Require().ElementsMatch(expectedOptions, optionNames)
|
|
}
|
|
|
|
// Standard validation for expected fields
|
|
for key, expectedValue := range tc.Expected {
|
|
s.Require().Contains(result, key)
|
|
s.Require().Equal(expectedValue, result[key])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test cases with existing attributes (for edit scenarios)
|
|
s.Run("WithExistingAttrs", func() {
|
|
existingAttrsTestCases := []struct {
|
|
Name string
|
|
ExistingAttrs model.StringInterface
|
|
FlagChanges map[string]any
|
|
ExpectedAttrs model.StringInterface
|
|
ShouldError bool
|
|
ErrorText string
|
|
}{
|
|
{
|
|
Name: "Should preserve existing attrs when no flags changed",
|
|
ExistingAttrs: model.StringInterface{
|
|
"visibility": "always",
|
|
"required": true,
|
|
"managed": "admin",
|
|
},
|
|
FlagChanges: map[string]any{},
|
|
ExpectedAttrs: model.StringInterface{
|
|
"visibility": "always",
|
|
"required": true,
|
|
"managed": "admin",
|
|
},
|
|
ShouldError: false,
|
|
},
|
|
{
|
|
Name: "Should preserve existing attrs and only update managed flag",
|
|
ExistingAttrs: model.StringInterface{
|
|
"visibility": "always",
|
|
"required": true,
|
|
"managed": "admin",
|
|
"options": []*model.CustomProfileAttributesSelectOption{
|
|
{ID: "existing1", Name: "Option1"},
|
|
{ID: "existing2", Name: "Option2"},
|
|
},
|
|
},
|
|
FlagChanges: map[string]any{"managed": "false"},
|
|
ExpectedAttrs: model.StringInterface{
|
|
"visibility": "always",
|
|
"required": true,
|
|
"managed": "",
|
|
"options": []*model.CustomProfileAttributesSelectOption{
|
|
{ID: "existing1", Name: "Option1"},
|
|
{ID: "existing2", Name: "Option2"},
|
|
},
|
|
},
|
|
ShouldError: false,
|
|
},
|
|
{
|
|
Name: "Should preserve existing option IDs when re-specifying same options",
|
|
ExistingAttrs: model.StringInterface{
|
|
"managed": "admin",
|
|
"options": []*model.CustomProfileAttributesSelectOption{
|
|
{ID: "existing1", Name: "Option1"},
|
|
{ID: "existing2", Name: "Option2"},
|
|
},
|
|
},
|
|
FlagChanges: map[string]any{"option": []string{"Option1", "Option2"}},
|
|
ExpectedAttrs: model.StringInterface{
|
|
"managed": "admin",
|
|
"options": []*model.CustomProfileAttributesSelectOption{
|
|
{ID: "existing1", Name: "Option1"},
|
|
{ID: "existing2", Name: "Option2"},
|
|
},
|
|
},
|
|
ShouldError: false,
|
|
},
|
|
{
|
|
Name: "Should preserve existing option IDs and add new options",
|
|
ExistingAttrs: model.StringInterface{
|
|
"visibility": "always",
|
|
"options": []*model.CustomProfileAttributesSelectOption{
|
|
{ID: "existing1", Name: "Option1"},
|
|
{ID: "existing2", Name: "Option2"},
|
|
},
|
|
},
|
|
FlagChanges: map[string]any{"option": []string{"Option1", "Option2", "Option3"}},
|
|
ExpectedAttrs: model.StringInterface{
|
|
"visibility": "always",
|
|
"options": []*model.CustomProfileAttributesSelectOption{
|
|
{ID: "existing1", Name: "Option1"}, // Preserve existing ID
|
|
{ID: "existing2", Name: "Option2"}, // Preserve existing ID
|
|
{ID: "any", Name: "Option3"}, // New option, ID will be generated
|
|
},
|
|
},
|
|
ShouldError: false,
|
|
},
|
|
{
|
|
Name: "Should remove options not specified in new option list",
|
|
ExistingAttrs: model.StringInterface{
|
|
"managed": "",
|
|
"options": []*model.CustomProfileAttributesSelectOption{
|
|
{ID: "existing1", Name: "Option1"},
|
|
{ID: "existing2", Name: "Option2"},
|
|
{ID: "existing3", Name: "Option3"},
|
|
},
|
|
},
|
|
FlagChanges: map[string]any{"option": []string{"Option2", "Option4"}},
|
|
ExpectedAttrs: model.StringInterface{
|
|
"managed": "",
|
|
"options": []*model.CustomProfileAttributesSelectOption{
|
|
{ID: "existing2", Name: "Option2"}, // Preserve existing ID
|
|
{ID: "any", Name: "Option4"}, // New option, ID will be generated
|
|
},
|
|
},
|
|
ShouldError: false,
|
|
},
|
|
{
|
|
Name: "Should handle attrs JSON merge with existing attrs",
|
|
ExistingAttrs: model.StringInterface{
|
|
"visibility": "always",
|
|
"required": true,
|
|
"managed": "admin",
|
|
},
|
|
FlagChanges: map[string]any{"attrs": `{"required":false,"newfield":"newvalue"}`},
|
|
ExpectedAttrs: model.StringInterface{
|
|
"visibility": "always",
|
|
"required": false,
|
|
"managed": "admin",
|
|
"newfield": "newvalue",
|
|
},
|
|
ShouldError: false,
|
|
},
|
|
{
|
|
Name: "Should handle managed flag override after attrs JSON",
|
|
ExistingAttrs: model.StringInterface{
|
|
"visibility": "always",
|
|
"managed": "admin",
|
|
},
|
|
FlagChanges: map[string]any{
|
|
"attrs": `{"managed":"user","newfield":"value"}`,
|
|
"managed": "true",
|
|
},
|
|
ExpectedAttrs: model.StringInterface{
|
|
"visibility": "always",
|
|
"managed": "admin", // managed flag should override attrs
|
|
"newfield": "value",
|
|
},
|
|
ShouldError: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range existingAttrsTestCases {
|
|
s.Run(tc.Name, func() {
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("managed", false, "")
|
|
cmd.Flags().String("attrs", "", "")
|
|
cmd.Flags().StringSlice("option", []string{}, "")
|
|
|
|
// Set flags based on test case
|
|
for flagName, flagValue := range tc.FlagChanges {
|
|
switch flagName {
|
|
case "option":
|
|
if options, ok := flagValue.([]string); ok {
|
|
for _, opt := range options {
|
|
err := cmd.Flags().Set(flagName, opt)
|
|
s.Require().NoError(err)
|
|
}
|
|
}
|
|
default:
|
|
err := cmd.Flags().Set(flagName, fmt.Sprintf("%v", flagValue))
|
|
s.Require().NoError(err)
|
|
}
|
|
}
|
|
|
|
result, err := buildFieldAttrs(cmd, tc.ExistingAttrs)
|
|
|
|
if tc.ShouldError {
|
|
s.Require().Error(err)
|
|
s.Require().Contains(err.Error(), tc.ErrorText)
|
|
s.Require().Nil(result)
|
|
} else {
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(result)
|
|
|
|
// Validate all attributes (including options)
|
|
for key, expectedValue := range tc.ExpectedAttrs {
|
|
s.Require().Contains(result, key)
|
|
|
|
if key == "options" {
|
|
// Special validation for options to verify IDs and structure
|
|
expectedOptions, ok := expectedValue.([]*model.CustomProfileAttributesSelectOption)
|
|
s.Require().True(ok, "Expected options should be []*model.CustomProfileAttributesSelectOption")
|
|
|
|
resultOptions, ok := result[key].([]*model.CustomProfileAttributesSelectOption)
|
|
s.Require().True(ok, "Result options should be []*model.CustomProfileAttributesSelectOption")
|
|
|
|
s.Require().Len(resultOptions, len(expectedOptions), "Options count should match")
|
|
|
|
// Create maps for easier comparison
|
|
expectedMap := make(map[string]string) // name -> id
|
|
for _, opt := range expectedOptions {
|
|
expectedMap[opt.Name] = opt.ID
|
|
}
|
|
|
|
resultMap := make(map[string]string) // name -> id
|
|
for _, opt := range resultOptions {
|
|
resultMap[opt.Name] = opt.ID
|
|
s.Require().NotEmpty(opt.ID, "Option ID should not be empty")
|
|
}
|
|
|
|
// Verify all expected options exist with correct IDs
|
|
for name, expectedID := range expectedMap {
|
|
resultID, exists := resultMap[name]
|
|
s.Require().True(exists, "Option %s should exist in result", name)
|
|
|
|
// Only check ID if it's not a placeholder ("any")
|
|
if expectedID != "any" {
|
|
s.Require().Equal(expectedID, resultID,
|
|
"Option %s should preserve existing ID %s, got %s", name, expectedID, resultID)
|
|
}
|
|
}
|
|
} else {
|
|
// Standard validation for non-option attributes
|
|
s.Require().Equal(expectedValue, result[key])
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|