mattermost/server/cmd/mmctl/commands/config_e2e_test.go
Jesse Hallam d4fc0ecb1c
Some checks are pending
API / build (push) Waiting to run
Server CI / Compute Go Version (push) Waiting to run
Server CI / Check mocks (push) Blocked by required conditions
Server CI / Check go mod tidy (push) Blocked by required conditions
Server CI / Check go fix (push) Blocked by required conditions
Server CI / check-style (push) Blocked by required conditions
Server CI / Check serialization methods for hot structs (push) Blocked by required conditions
Server CI / Vet API (push) Blocked by required conditions
Server CI / Check migration files (push) Blocked by required conditions
Server CI / Generate email templates (push) Blocked by required conditions
Server CI / Check store layers (push) Blocked by required conditions
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres (shard 0) (push) Blocked by required conditions
Server CI / Postgres (shard 1) (push) Blocked by required conditions
Server CI / Postgres (shard 2) (push) Blocked by required conditions
Server CI / Postgres (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres Test Results (push) Blocked by required conditions
Server CI / Elasticsearch v8 Compatibility (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 0) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 1) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 2) (push) Blocked by required conditions
Server CI / Postgres FIPS (shard 3) (push) Blocked by required conditions
Server CI / Merge Postgres FIPS Test Results (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Tools CI / check-style (mattermost-govet) (push) Waiting to run
Tools CI / Test (mattermost-govet) (push) Waiting to run
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-external-links (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
YAML Lint / yamllint (push) Waiting to run
MM-68150: Upgrade golangci-lint to v2.12.2 (#36554)
* Simplify invite_people email parsing

Replace backwards in-place mutation loop with a straightforward forward
filter into a new slice. Extract into parseEmailList so the logic can be
unit tested directly.

* MM-68150: Upgrade golangci-lint to v2.12.2

Remove //go:fix inline from NewPointer, which is a generic function not
yet supported by the inline analyzer, and fix 11 slicesbackward
modernize issues flagged by the new version.

* MM-68150: Enable all linters by default; disable those with >20 existing issues

Switch from opt-in (default: none) to opt-out (default: all) so new
linters added to golangci-lint are evaluated automatically. Explicitly
disable every linter that has more than 20 pre-existing violations,
deferring those for later cleanup. Also disable a handful of linters
whose violations are intentional patterns in this codebase (nilerr,
dogsled, sqlclosecheck, iotamixing, predeclared, containedctx, iface,
gocheckcompilerdirectives, promlinter, goprintffuncname, gomoddirectives).

* MM-68150: Fix mirror linter issues

Replace Write([]byte(s)) with WriteString(s), and FindIndex([]byte(s))
with FindStringIndex(s), to avoid unnecessary allocations.

* MM-68150: Fix nosprintfhostport linter issue

Use net.JoinHostPort to construct host:port strings instead of
fmt.Sprintf with a manually formatted pattern.

* MM-68150: Fix rowserrcheck and sqlclosecheck linter issues

Check rows.Err() after iteration loops in schema_dump.go. In the
sqlx_wrapper test, defer rows.Close() rather than closing inline.

* MM-68150: Fix nilnesserr linter issues — wrong variable in error handlers

In 11 places, a stale variable (often the outer err from a prior
assignment) was used instead of the freshly-checked error variable
(appErr, rowErr, jsonErr, writeErr, esErr). Each produces a typed-nil
wrapped in a non-nil interface, silently discarding the real error.

* MM-68150: Add i18n string for app.compile_csv_chunks.write_error

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
2026-05-14 17:29:37 -04:00

432 lines
14 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package commands
import (
"os"
"github.com/mattermost/mattermost/server/public/model"
"github.com/spf13/cobra"
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/client"
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/printer"
)
func (s *MmctlE2ETestSuite) TestConfigResetCmdE2E() {
s.SetupTestHelper().InitBasic(s.T())
s.RunForSystemAdminAndLocal("System admin and local reset", func(c client.Client) {
printer.Clean()
s.th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PrivacySettings.ShowEmailAddress = false })
resetCmd := &cobra.Command{}
resetCmd.Flags().Bool("confirm", true, "")
err := configResetCmdF(c, resetCmd, []string{"PrivacySettings"})
s.Require().Nil(err)
s.Require().Len(printer.GetLines(), 1)
s.Require().Len(printer.GetErrorLines(), 0)
config := s.th.App.Config()
s.Require().True(*config.PrivacySettings.ShowEmailAddress)
})
s.Run("Reset for user without permission", func() {
printer.Clean()
resetCmd := &cobra.Command{}
args := []string{"PrivacySettings"}
resetCmd.Flags().Bool("confirm", true, "")
err := configResetCmdF(s.th.Client, resetCmd, args)
s.Require().NotNil(err)
s.Assert().Errorf(err, "You do not have the appropriate permissions.")
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 0)
})
}
func (s *MmctlE2ETestSuite) TestConfigPatchCmd() {
s.SetupTestHelper().InitBasic(s.T())
tmpFile, err := os.CreateTemp(os.TempDir(), "config_*.json")
s.Require().Nil(err)
invalidFile, err := os.CreateTemp(os.TempDir(), "invalid_config_*.json")
s.Require().Nil(err)
_, err = tmpFile.WriteString(configFilePayload)
s.Require().Nil(err)
defer func() {
os.Remove(tmpFile.Name())
os.Remove(invalidFile.Name())
}()
s.RunForSystemAdminAndLocal("MM-T4051 - System admin and local patch", func(c client.Client) {
printer.Clean()
err := configPatchCmdF(c, &cobra.Command{}, []string{tmpFile.Name()})
s.Require().Nil(err)
s.Require().Len(printer.GetLines(), 1)
s.Require().Len(printer.GetErrorLines(), 0)
})
s.RunForSystemAdminAndLocal("MM-T4052 - System admin and local patch with invalid file", func(c client.Client) {
printer.Clean()
err := configPatchCmdF(c, &cobra.Command{}, []string{invalidFile.Name()})
s.Require().NotNil(err)
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 0)
})
s.Run("MM-T4053 - Patch config for user without permission", func() {
printer.Clean()
err := configPatchCmdF(s.th.Client, &cobra.Command{}, []string{tmpFile.Name()})
s.Require().NotNil(err)
s.Assert().Errorf(err, "You do not have the appropriate permissions.")
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 0)
})
}
func (s *MmctlE2ETestSuite) TestConfigGetCmdF() {
s.SetupTestHelper().InitBasic(s.T())
var driver string
if d := s.th.App.Config().SqlSettings.DriverName; d != nil {
driver = *d
}
s.RunForSystemAdminAndLocal("Get config value for a given key", func(c client.Client) {
printer.Clean()
args := []string{"SqlSettings.DriverName"}
err := configGetCmdF(c, &cobra.Command{}, args)
s.Require().Nil(err)
s.Require().Len(printer.GetLines(), 1)
s.Require().Equal(driver, *(printer.GetLines()[0].(*string)))
s.Require().Len(printer.GetErrorLines(), 0)
})
s.RunForSystemAdminAndLocal("Expect error when using a nonexistent key", func(c client.Client) {
printer.Clean()
args := []string{"NonExistent.Key"}
err := configGetCmdF(c, &cobra.Command{}, args)
s.Require().NotNil(err)
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 0)
})
s.Run("Get config value for a given key without permissions", func() {
printer.Clean()
args := []string{"SqlSettings.DriverName"}
err := configGetCmdF(s.th.Client, &cobra.Command{}, args)
s.Require().NotNil(err)
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 0)
})
}
func (s *MmctlE2ETestSuite) TestConfigSetCmd() {
s.SetupTestHelper().InitBasic(s.T())
s.RunForSystemAdminAndLocal("Set config value for a given key", func(c client.Client) {
printer.Clean()
args := []string{"SqlSettings.DriverName", "postgres"}
err := configSetCmdF(c, &cobra.Command{}, args)
s.Require().Nil(err)
s.Require().Len(printer.GetErrorLines(), 0)
s.Require().Len(printer.GetLines(), 1)
config, ok := printer.GetLines()[0].(*model.Config)
s.Require().True(ok)
s.Require().Equal("postgres", *(config.SqlSettings.DriverName))
})
s.RunForSystemAdminAndLocal("Get error if the key doesn't exist", func(c client.Client) {
printer.Clean()
args := []string{"SqlSettings.WrongKey", "postgres"}
err := configSetCmdF(c, &cobra.Command{}, args)
s.Require().NotNil(err)
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 0)
})
s.Run("Set config value for a given key without permissions", func() {
printer.Clean()
args := []string{"SqlSettings.DriverName", "postgres"}
err := configSetCmdF(s.th.Client, &cobra.Command{}, args)
s.Require().NotNil(err)
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 0)
})
s.Run("ImportSettings.Directory cannot be set via API but can via local mode", func() {
printer.Clean()
originalDir := *s.th.App.Config().ImportSettings.Directory
args := []string{"ImportSettings.Directory", "./api-blocked-import"}
err := configSetCmdF(s.th.SystemAdminClient, &cobra.Command{}, args)
s.Require().NotNil(err)
s.Require().Contains(err.Error(), "not allowed due to security reasons")
s.Require().Len(printer.GetLines(), 0)
// Verify value didn't change
s.Require().Equal(originalDir, *s.th.App.Config().ImportSettings.Directory)
printer.Clean()
args = []string{"ImportSettings.Directory", "./local-allowed-import"}
err = configSetCmdF(s.th.LocalClient, &cobra.Command{}, args)
s.Require().Nil(err)
s.Require().Len(printer.GetLines(), 1)
config, ok := printer.GetLines()[0].(*model.Config)
s.Require().True(ok)
s.Require().Equal("./local-allowed-import", *config.ImportSettings.Directory)
// Reset to original
s.th.App.UpdateConfig(func(cfg *model.Config) {
cfg.ImportSettings.Directory = &originalDir
})
})
}
func (s *MmctlE2ETestSuite) TestConfigEditCmd() {
s.SetupTestHelper().InitBasic(s.T())
s.RunForSystemAdminAndLocal("Edit a key in config", func(c client.Client) {
printer.Clean()
// ensure the value before editing
s.th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableSVGs = false })
// create a shell script to edit config
content := `#!/bin/bash
sed -i'old' 's/\"EnableSVGs\": false/\"EnableSVGs\": true/' $1
rm $1'old'`
file, err := os.CreateTemp(os.TempDir(), "config_edit_*.sh")
s.Require().Nil(err)
defer func() {
os.Remove(file.Name())
}()
_, err = file.WriteString(content)
s.Require().Nil(err)
s.Require().Nil(file.Close())
s.Require().Nil(os.Chmod(file.Name(), 0700))
s.T().Setenv("EDITOR", file.Name())
// check the value after editing
err = configEditCmdF(c, nil, nil)
s.Require().Nil(err)
s.Require().Len(printer.GetErrorLines(), 0)
s.Require().Len(printer.GetLines(), 1)
config := s.th.App.Config()
s.Require().True(*config.ServiceSettings.EnableSVGs)
})
s.Run("Edit config value without permissions", func() {
printer.Clean()
err := configEditCmdF(s.th.Client, nil, nil)
s.Require().NotNil(err)
s.Require().Error(err, "You do not have the appropriate permissions.")
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 0)
})
}
func (s *MmctlE2ETestSuite) TestConfigShowCmdF() {
s.SetupTestHelper().InitBasic(s.T())
s.RunForSystemAdminAndLocal("Show server configs", func(c client.Client) {
printer.Clean()
err := configShowCmdF(c, nil, nil)
s.Require().Nil(err)
s.Require().Len(printer.GetLines(), 1)
s.Require().Len(printer.GetErrorLines(), 0)
})
s.Run("Show server configs without permissions", func() {
printer.Clean()
err := configShowCmdF(s.th.Client, nil, nil)
s.Require().NotNil(err)
s.Require().Error(err, "You do not have the appropriate permissions")
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 0)
})
}
func (s *MmctlE2ETestSuite) TestConfigReloadCmdF() {
s.SetupTestHelper().InitBasic(s.T())
s.RunForSystemAdminAndLocal("Reload server config", func(c client.Client) {
printer.Clean()
err := configReloadCmdF(c, nil, nil)
s.Require().NoError(err)
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 0)
})
s.Run("Reload server config without permissions", func() {
printer.Clean()
err := configReloadCmdF(s.th.Client, nil, nil)
s.Require().NotNil(err)
s.Require().Error(err, "You do not have the appropriate permissions")
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 0)
})
}
func (s *MmctlE2ETestSuite) TestConfigExportCmdF() {
s.SetupTestHelper().InitBasic(s.T())
s.RunForSystemAdminAndLocal("Get config normally", func(c client.Client) {
printer.Clean()
err := configExportCmdF(c, &cobra.Command{}, nil)
s.Require().Nil(err)
s.Require().Len(printer.GetLines(), 1)
s.Require().Len(printer.GetErrorLines(), 0)
m, ok := printer.GetLines()[0].(map[string]any)
s.Require().True(ok)
if c == s.th.LocalClient {
// filter config is used to convert the config to a map[string]any
// local client has unrestricted access to the config
expectedConfig, err2 := model.FilterConfig(s.th.App.Config(), model.ConfigFilterOptions{GetConfigOptions: model.GetConfigOptions{}})
s.Require().NoError(err2)
s.Require().Equal(expectedConfig, m)
} else {
// filter config is used to convert the config to a map[string]any
// system admin client has restricted access to the config
expectedConfig, err2 := model.FilterConfig(s.th.App.GetSanitizedConfig(), model.ConfigFilterOptions{GetConfigOptions: model.GetConfigOptions{}})
s.Require().NoError(err2)
s.Require().Equal(expectedConfig, m)
}
})
s.Run("Should remove masked values for system admin client", func() {
printer.Clean()
exportCmd := &cobra.Command{}
exportCmd.Flags().Bool("remove-masked", true, "")
err := configExportCmdF(s.th.SystemAdminClient, exportCmd, nil)
s.Require().Nil(err)
s.Require().Len(printer.GetLines(), 1)
m, ok := printer.GetLines()[0].(map[string]any)
s.Require().True(ok)
ss, ok := m["SqlSettings"].(map[string]any)
s.Require().True(ok)
_, ok = ss["DataSource"]
s.Require().False(ok)
s.Require().Len(printer.GetErrorLines(), 0)
})
s.Run("Should retrieve configuration as-is with local client", func() {
printer.Clean()
exportCmd := &cobra.Command{}
err := configExportCmdF(s.th.LocalClient, exportCmd, nil)
s.Require().Nil(err)
s.Require().Len(printer.GetLines(), 1)
m, ok := printer.GetLines()[0].(map[string]any)
s.Require().True(ok)
ss, ok := m["SqlSettings"].(map[string]any)
s.Require().True(ok)
ds, ok := ss["DataSource"]
s.Require().True(ok)
cfg := s.th.App.Config()
s.Require().Equal(*cfg.SqlSettings.DataSource, ds)
s.Require().Len(printer.GetErrorLines(), 0)
})
s.RunForSystemAdminAndLocal("Should remove default values", func(c client.Client) {
printer.Clean()
exportCmd := &cobra.Command{}
exportCmd.Flags().Bool("remove-defaults", true, "")
err := configExportCmdF(c, exportCmd, nil)
s.Require().Nil(err)
s.Require().Len(printer.GetLines(), 1)
m, ok := printer.GetLines()[0].(map[string]any)
s.Require().True(ok)
ss, ok := m["TeamSettings"].(map[string]any)
s.Require().True(ok)
_, ok = ss["MaxUsersPerTeam"] // it's not being changed by the test suite
s.Require().False(ok)
s.Require().Len(printer.GetErrorLines(), 0)
})
s.Run("Get config value for a given key without permissions", func() {
printer.Clean()
err := configExportCmdF(s.th.Client, &cobra.Command{}, nil)
s.Require().NotNil(err)
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 0)
})
}
func (s *MmctlE2ETestSuite) TestConfigMigrateCmdF() {
s.SetupTestHelper().InitBasic(s.T())
s.Run("Should fail without the --local flag", func() {
printer.Clean()
args := []string{"config.json", "output.json"}
err := configMigrateCmdF(s.th.Client, &cobra.Command{}, args)
s.Require().Error(err)
s.Require().Equal("this command is only available in local mode. Please set the --local flag", err.Error())
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 0)
})
s.Run("Should be able to migrate config to file", func() {
printer.Clean()
args := []string{"config.json", "output.json"}
cmd := &cobra.Command{}
cmd.Flags().Bool("local", true, "")
err := configMigrateCmdF(s.th.LocalClient, cmd, args)
s.Require().NoError(err)
s.Require().Len(printer.GetErrorLines(), 0)
})
s.Run("Should be able to migrate config to database", func() {
printer.Clean()
// Get the current database DSN from the test configuration
currentDSN := *s.th.App.Config().SqlSettings.DataSource
args := []string{"config.json", currentDSN}
cmd := &cobra.Command{}
cmd.Flags().Bool("local", true, "")
err := configMigrateCmdF(s.th.LocalClient, cmd, args)
s.Require().NoError(err)
s.Require().Len(printer.GetErrorLines(), 0)
})
s.Run("Should fail on error when migrating config", func() {
printer.Clean()
args := []string{"from", "to"}
cmd := &cobra.Command{}
cmd.Flags().Bool("local", true, "")
err := configMigrateCmdF(s.th.LocalClient, cmd, args)
s.Require().Error(err)
s.Require().Equal("Failed to migrate config store.", err.Error())
s.Require().Len(printer.GetLines(), 0)
s.Require().Len(printer.GetErrorLines(), 0)
})
}