github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/cmd/mattermost/commands/config_test.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package commands 5 6 import ( 7 "encoding/json" 8 "io/ioutil" 9 "os" 10 "reflect" 11 "sort" 12 "strings" 13 "testing" 14 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 18 "github.com/mattermost/mattermost-server/v5/config" 19 "github.com/mattermost/mattermost-server/v5/model" 20 ) 21 22 type TestConfig struct { 23 TestServiceSettings TestServiceSettings 24 TestTeamSettings TestTeamSettings 25 TestClientRequirements TestClientRequirements 26 TestMessageExportSettings TestMessageExportSettings 27 } 28 29 type TestMessageExportSettings struct { 30 Enableexport bool 31 Exportformat string 32 TestGlobalRelaySettings TestGlobalRelaySettings 33 } 34 35 type TestGlobalRelaySettings struct { 36 Customertype string 37 Smtpusername string 38 Smtppassword string 39 } 40 41 type TestServiceSettings struct { 42 Siteurl string 43 Websocketurl string 44 Licensedfieldlocation string 45 } 46 47 type TestTeamSettings struct { 48 Sitename string 49 Maxuserperteam int 50 } 51 52 type TestClientRequirements struct { 53 Androidlatestversion string 54 Androidminversion string 55 Desktoplatestversion string 56 } 57 58 type TestNewConfig struct { 59 TestNewServiceSettings TestNewServiceSettings 60 TestNewTeamSettings TestNewTeamSettings 61 } 62 63 type TestNewServiceSettings struct { 64 SiteUrl *string 65 UseLetsEncrypt *bool 66 TLSStrictTransportMaxAge *int64 67 AllowedThemes []string 68 } 69 70 type TestNewTeamSettings struct { 71 SiteName *string 72 MaxUserPerTeam *int 73 } 74 75 type TestPluginSettings struct { 76 Enable *bool 77 Directory *string `restricted:"true"` 78 Plugins map[string]map[string]interface{} 79 PluginStates map[string]*model.PluginState 80 SignaturePublicKeyFiles []string 81 } 82 83 func getDsn(driver string, source string) string { 84 if driver == model.DATABASE_DRIVER_MYSQL { 85 return driver + "://" + source 86 } 87 return source 88 } 89 90 func TestConfigValidate(t *testing.T) { 91 th := Setup(t) 92 defer th.TearDown() 93 94 tempFile, err := ioutil.TempFile("", "TestConfigValidate") 95 require.NoError(t, err) 96 defer os.Remove(tempFile.Name()) 97 98 assert.Error(t, th.RunCommand(t, "--config", tempFile.Name(), "config", "validate")) 99 th.CheckCommand(t, "config", "validate") 100 } 101 102 func TestConfigGet(t *testing.T) { 103 th := Setup(t) 104 defer th.TearDown() 105 106 t.Run("Error when no arguments are given", func(t *testing.T) { 107 assert.Error(t, th.RunCommand(t, "config", "get")) 108 }) 109 110 t.Run("Error when more than one config settings are given", func(t *testing.T) { 111 assert.Error(t, th.RunCommand(t, "config", "get", "abc", "def")) 112 }) 113 114 t.Run("Error when a config setting which is not in the config.json is given", func(t *testing.T) { 115 assert.Error(t, th.RunCommand(t, "config", "get", "abc")) 116 }) 117 118 t.Run("No Error when a config setting which is in the config.json is given", func(t *testing.T) { 119 th.CheckCommand(t, "config", "get", "MessageExportSettings") 120 th.CheckCommand(t, "config", "get", "MessageExportSettings.GlobalRelaySettings") 121 th.CheckCommand(t, "config", "get", "MessageExportSettings.GlobalRelaySettings.CustomerType") 122 }) 123 124 t.Run("check output", func(t *testing.T) { 125 output := th.CheckCommand(t, "config", "get", "MessageExportSettings") 126 127 assert.Contains(t, output, "EnableExport") 128 assert.Contains(t, output, "ExportFormat") 129 assert.Contains(t, output, "DailyRunTime") 130 assert.Contains(t, output, "ExportFromTimestamp") 131 }) 132 } 133 134 func TestConfigSet(t *testing.T) { 135 th := Setup(t) 136 defer th.TearDown() 137 138 t.Run("Error when no arguments are given", func(t *testing.T) { 139 assert.Error(t, th.RunCommand(t, "config", "set")) 140 }) 141 142 t.Run("Error when only one argument is given", func(t *testing.T) { 143 assert.Error(t, th.RunCommand(t, "config", "set", "test")) 144 }) 145 146 t.Run("Error when the wrong key is set", func(t *testing.T) { 147 assert.Error(t, th.RunCommand(t, "config", "set", "invalid-key", "value")) 148 assert.Error(t, th.RunCommand(t, "config", "get", "invalid-key")) 149 }) 150 151 t.Run("Error when the wrong value is set", func(t *testing.T) { 152 assert.Error(t, th.RunCommand(t, "config", "set", "EmailSettings.ConnectionSecurity", "invalid-key")) 153 output := th.CheckCommand(t, "config", "get", "EmailSettings.ConnectionSecurity") 154 assert.NotContains(t, output, "invalid-key") 155 }) 156 157 t.Run("Error when the parameter of an unknown plugin is set", func(t *testing.T) { 158 output, err := th.RunCommandWithOutput(t, "config", "set", "PluginSettings.Plugins.someplugin", "true") 159 assert.Error(t, err) 160 assert.NotContains(t, output, "panic") 161 }) 162 163 t.Run("Error when the wrong locale is set", func(t *testing.T) { 164 th.CheckCommand(t, "config", "set", "LocalizationSettings.DefaultServerLocale", "es") 165 assert.Error(t, th.RunCommand(t, "config", "set", "LocalizationSettings.DefaultServerLocale", "invalid-key")) 166 output := th.CheckCommand(t, "config", "get", "LocalizationSettings.DefaultServerLocale") 167 assert.NotContains(t, output, "invalid-key") 168 assert.NotContains(t, output, "\"en\"") 169 }) 170 171 t.Run("Success when a valid value is set", func(t *testing.T) { 172 assert.NoError(t, th.RunCommand(t, "config", "set", "EmailSettings.ConnectionSecurity", "TLS")) 173 output := th.CheckCommand(t, "config", "get", "EmailSettings.ConnectionSecurity") 174 assert.Contains(t, output, "TLS") 175 }) 176 177 t.Run("Success when a valid locale is set", func(t *testing.T) { 178 assert.NoError(t, th.RunCommand(t, "config", "set", "LocalizationSettings.DefaultServerLocale", "es")) 179 output := th.CheckCommand(t, "config", "get", "LocalizationSettings.DefaultServerLocale") 180 assert.Contains(t, output, "\"es\"") 181 }) 182 } 183 184 func TestConfigReset(t *testing.T) { 185 th := Setup(t) 186 defer th.TearDown() 187 188 t.Run("No Error when no arguments are given (reset all the configurations)", func(t *testing.T) { 189 assert.NoError(t, th.RunCommand(t, "config", "reset")) 190 }) 191 192 t.Run("No Error when a configuration section is given", func(t *testing.T) { 193 assert.NoError(t, th.RunCommand(t, "config", "reset", "JobSettings")) 194 }) 195 196 t.Run("No Error when a configuration setting is given", func(t *testing.T) { 197 assert.NoError(t, th.RunCommand(t, "config", "reset", "JobSettings.RunJobs")) 198 }) 199 200 t.Run("Error when the wrong configuration section is given", func(t *testing.T) { 201 assert.Error(t, th.RunCommand(t, "config", "reset", "InvalidSettings")) 202 }) 203 204 t.Run("Error when the wrong configuration setting is given", func(t *testing.T) { 205 assert.Error(t, th.RunCommand(t, "config", "reset", "JobSettings.InvalidConfiguration")) 206 }) 207 208 t.Run("Success when the confirm boolean flag is given", func(t *testing.T) { 209 assert.NoError(t, th.RunCommand(t, "config", "set", "JobSettings.RunJobs", "false")) 210 assert.NoError(t, th.RunCommand(t, "config", "set", "PrivacySettings.ShowFullName", "false")) 211 assert.NoError(t, th.RunCommand(t, "config", "reset", "--confirm")) 212 output1 := th.CheckCommand(t, "config", "get", "JobSettings.RunJobs") 213 output2 := th.CheckCommand(t, "config", "get", "PrivacySettings.ShowFullName") 214 assert.Contains(t, output1, "true") 215 assert.Contains(t, output2, "true") 216 }) 217 218 t.Run("Success when a configuration section is given", func(t *testing.T) { 219 assert.NoError(t, th.RunCommand(t, "config", "set", "JobSettings.RunJobs", "false")) 220 assert.NoError(t, th.RunCommand(t, "config", "set", "JobSettings.RunScheduler", "false")) 221 assert.NoError(t, th.RunCommand(t, "config", "set", "PrivacySettings.ShowFullName", "false")) 222 assert.NoError(t, th.RunCommand(t, "config", "reset", "JobSettings")) 223 output1 := th.CheckCommand(t, "config", "get", "JobSettings.RunJobs") 224 output2 := th.CheckCommand(t, "config", "get", "JobSettings.RunScheduler") 225 output3 := th.CheckCommand(t, "config", "get", "PrivacySettings.ShowFullName") 226 assert.Contains(t, output1, "true") 227 assert.Contains(t, output2, "true") 228 assert.Contains(t, output3, "false") 229 }) 230 231 t.Run("Success when a configuration setting is given", func(t *testing.T) { 232 assert.NoError(t, th.RunCommand(t, "config", "set", "JobSettings.RunJobs", "false")) 233 assert.NoError(t, th.RunCommand(t, "config", "set", "JobSettings.RunScheduler", "false")) 234 assert.NoError(t, th.RunCommand(t, "config", "reset", "JobSettings.RunJobs")) 235 output1 := th.CheckCommand(t, "config", "get", "JobSettings.RunJobs") 236 output2 := th.CheckCommand(t, "config", "get", "JobSettings.RunScheduler") 237 assert.Contains(t, output1, "true") 238 assert.Contains(t, output2, "false") 239 }) 240 } 241 242 func TestConfigToMap(t *testing.T) { 243 // This test is almost the same as TestStructToMap, but I have it here for the sake of completions 244 cases := []struct { 245 Name string 246 Input interface{} 247 Expected map[string]interface{} 248 }{ 249 { 250 Name: "Struct with one string field", 251 Input: struct { 252 Test string 253 }{ 254 Test: "test", 255 }, 256 Expected: map[string]interface{}{ 257 "Test": "test", 258 }, 259 }, 260 { 261 Name: "String with multiple fields of different ", 262 Input: struct { 263 Test1 string 264 Test2 int 265 Test3 string 266 Test4 bool 267 }{ 268 Test1: "test1", 269 Test2: 21, 270 Test3: "test2", 271 Test4: false, 272 }, 273 Expected: map[string]interface{}{ 274 "Test1": "test1", 275 "Test2": 21, 276 "Test3": "test2", 277 "Test4": false, 278 }, 279 }, 280 { 281 Name: "Nested fields", 282 Input: TestConfig{ 283 TestServiceSettings{"abc", "def", "ghi"}, 284 TestTeamSettings{"abc", 1}, 285 TestClientRequirements{"abc", "def", "ghi"}, 286 TestMessageExportSettings{true, "abc", TestGlobalRelaySettings{"abc", "def", "ghi"}}, 287 }, 288 Expected: map[string]interface{}{ 289 "TestServiceSettings": map[string]interface{}{ 290 "Siteurl": "abc", 291 "Websocketurl": "def", 292 "Licensedfieldlocation": "ghi", 293 }, 294 "TestTeamSettings": map[string]interface{}{ 295 "Sitename": "abc", 296 "Maxuserperteam": 1, 297 }, 298 "TestClientRequirements": map[string]interface{}{ 299 "Androidlatestversion": "abc", 300 "Androidminversion": "def", 301 "Desktoplatestversion": "ghi", 302 }, 303 "TestMessageExportSettings": map[string]interface{}{ 304 "Enableexport": true, 305 "Exportformat": "abc", 306 "TestGlobalRelaySettings": map[string]interface{}{ 307 "Customertype": "abc", 308 "Smtpusername": "def", 309 "Smtppassword": "ghi", 310 }, 311 }, 312 }, 313 }, 314 } 315 316 for _, test := range cases { 317 t.Run(test.Name, func(t *testing.T) { 318 res := configToMap(test.Input) 319 320 if !reflect.DeepEqual(res, test.Expected) { 321 t.Errorf("got %v want %v ", res, test.Expected) 322 } 323 }) 324 } 325 } 326 327 func TestPrintConfigValues(t *testing.T) { 328 outputs := []string{ 329 "Siteurl: \"abc\"\nWebsocketurl: \"def\"\nLicensedfieldlocation: \"ghi\"\n", 330 "Sitename: \"abc\"\nMaxuserperteam: \"1\"\n", 331 "Androidlatestversion: \"abc\"\nAndroidminversion: \"def\"\nDesktoplatestversion: \"ghi\"\n", 332 "Enableexport: \"true\"\nExportformat: \"abc\"\nTestGlobalRelaySettings:\n\tCustomertype: \"abc\"\n\tSmtpusername: \"def\"\n\tSmtppassword: \"ghi\"\n", 333 "Customertype: \"abc\"\nSmtpusername: \"def\"\nSmtppassword: \"ghi\"\n", 334 } 335 336 commands := []string{ 337 "TestServiceSettings", 338 "TestTeamSettings", 339 "TestClientRequirements", 340 "TestMessageExportSettings", 341 "TestMessageExportSettings.TestGlobalRelaySettings", 342 } 343 344 input := TestConfig{ 345 TestServiceSettings{"abc", "def", "ghi"}, 346 TestTeamSettings{"abc", 1}, 347 TestClientRequirements{"abc", "def", "ghi"}, 348 TestMessageExportSettings{true, "abc", TestGlobalRelaySettings{"abc", "def", "ghi"}}, 349 } 350 351 configMap := structToMap(input) 352 353 cases := []struct { 354 Name string 355 Command string 356 Expected string 357 }{ 358 { 359 Name: "First test", 360 Command: commands[0], 361 Expected: outputs[0], 362 }, 363 { 364 Name: "Second test", 365 Command: commands[1], 366 Expected: outputs[1], 367 }, 368 { 369 Name: "third test", 370 Command: commands[2], 371 Expected: outputs[2], 372 }, 373 { 374 Name: "fourth test", 375 Command: commands[3], 376 Expected: outputs[3], 377 }, 378 { 379 Name: "fifth test", 380 Command: commands[4], 381 Expected: outputs[4], 382 }, 383 } 384 385 for _, test := range cases { 386 t.Run(test.Name, func(t *testing.T) { 387 res, _ := printConfigValues(configMap, strings.Split(test.Command, "."), test.Command) 388 389 // create two slice of string formed by splitting our strings on \n 390 slice1 := strings.Split(res, "\n") 391 slice2 := strings.Split(test.Expected, "\n") 392 393 sort.Strings(slice1) 394 sort.Strings(slice2) 395 396 if !reflect.DeepEqual(slice1, slice2) { 397 t.Errorf("got '%#v' want '%#v", slice1, slice2) 398 } 399 }) 400 } 401 } 402 403 func TestConfigShow(t *testing.T) { 404 th := Setup(t) 405 defer th.TearDown() 406 407 t.Run("error with unknown subcommand", func(t *testing.T) { 408 assert.Error(t, th.RunCommand(t, "config", "show", "abc")) 409 }) 410 411 t.Run("successfully dumping config", func(t *testing.T) { 412 output := th.CheckCommand(t, "config", "show") 413 assert.Contains(t, output, "SqlSettings") 414 assert.Contains(t, output, "MessageExportSettings") 415 assert.Contains(t, output, "AnnouncementSettings") 416 }) 417 418 t.Run("successfully dumping config as json", func(t *testing.T) { 419 output, err := th.RunCommandWithOutput(t, "config", "show", "--json") 420 require.Nil(t, err) 421 422 // Filter out the test headers 423 var filteredOutput []string 424 for _, line := range strings.Split(output, "\n") { 425 if strings.HasPrefix(line, "---") || strings.HasPrefix(line, "===") || strings.HasPrefix(line, "PASS") || strings.HasPrefix(line, "coverage:") { 426 continue 427 } 428 429 filteredOutput = append(filteredOutput, line) 430 } 431 432 output = strings.Join(filteredOutput, "") 433 434 var config model.Config 435 err = json.Unmarshal([]byte(output), &config) 436 require.Nil(t, err) 437 }) 438 } 439 440 func TestSetConfig(t *testing.T) { 441 th := Setup(t) 442 defer th.TearDown() 443 444 // Error when no argument is given 445 assert.Error(t, th.RunCommand(t, "config", "set")) 446 447 // No Error when more than one argument is given 448 th.CheckCommand(t, "config", "set", "ThemeSettings.AllowedThemes", "hello", "World") 449 450 // No Error when two arguments are given 451 th.CheckCommand(t, "config", "set", "ThemeSettings.AllowedThemes", "hello") 452 453 // Error when only one argument is given 454 assert.Error(t, th.RunCommand(t, "config", "set", "ThemeSettings.AllowedThemes")) 455 456 // Error when config settings not in the config file are given 457 assert.Error(t, th.RunCommand(t, "config", "set", "Abc")) 458 } 459 460 func TestUpdateMap(t *testing.T) { 461 // create a config to make changes 462 config := TestNewConfig{ 463 TestNewServiceSettings{ 464 SiteUrl: model.NewString("abc.def"), 465 UseLetsEncrypt: model.NewBool(false), 466 TLSStrictTransportMaxAge: model.NewInt64(36), 467 AllowedThemes: []string{"Hello", "World"}, 468 }, 469 TestNewTeamSettings{ 470 SiteName: model.NewString("def.ghi"), 471 MaxUserPerTeam: model.NewInt(12), 472 }, 473 } 474 475 // create a map of type map[string]interface 476 configMap := configToMap(config) 477 478 cases := []struct { 479 Name string 480 configSettings []string 481 newVal []string 482 expected interface{} 483 }{ 484 { 485 Name: "check for Map and string", 486 configSettings: []string{"TestNewServiceSettings", "SiteUrl"}, 487 newVal: []string{"siteurl"}, 488 expected: "siteurl", 489 }, 490 { 491 Name: "check for Map and bool", 492 configSettings: []string{"TestNewServiceSettings", "UseLetsEncrypt"}, 493 newVal: []string{"true"}, 494 expected: true, 495 }, 496 { 497 Name: "check for Map and int64", 498 configSettings: []string{"TestNewServiceSettings", "TLSStrictTransportMaxAge"}, 499 newVal: []string{"56"}, 500 expected: int64(56), 501 }, 502 { 503 Name: "check for Map and string Slice", 504 configSettings: []string{"TestNewServiceSettings", "AllowedThemes"}, 505 newVal: []string{"hello1", "world1"}, 506 expected: []string{"hello1", "world1"}, 507 }, 508 { 509 Name: "Map and string", 510 configSettings: []string{"TestNewTeamSettings", "SiteName"}, 511 newVal: []string{"jkl.mno"}, 512 expected: "jkl.mno", 513 }, 514 { 515 Name: "Map and int", 516 configSettings: []string{"TestNewTeamSettings", "MaxUserPerTeam"}, 517 newVal: []string{"18"}, 518 expected: 18, 519 }, 520 } 521 522 for _, test := range cases { 523 524 t.Run(test.Name, func(t *testing.T) { 525 err := UpdateMap(configMap, test.configSettings, test.newVal) 526 527 require.Nil(t, err, "Wasn't expecting an error") 528 529 if !contains(configMap, test.expected, test.configSettings) { 530 t.Error("update didn't happen") 531 } 532 533 }) 534 } 535 } 536 537 func TestConfigMigrate(t *testing.T) { 538 th := Setup(t) 539 defer th.TearDown() 540 541 sqlSettings := mainHelper.GetSQLSettings() 542 sqlDSN := getDsn(*sqlSettings.DriverName, *sqlSettings.DataSource) 543 fileDSN := "config.json" 544 545 ds, err := config.NewStore(sqlDSN, false) 546 require.NoError(t, err) 547 fs, err := config.NewStore(fileDSN, false) 548 require.NoError(t, err) 549 550 defer ds.Close() 551 defer fs.Close() 552 553 t.Run("Should error with too few parameters", func(t *testing.T) { 554 assert.Error(t, th.RunCommand(t, "config", "migrate", fileDSN)) 555 }) 556 557 t.Run("Should error with too many parameters", func(t *testing.T) { 558 assert.Error(t, th.RunCommand(t, "config", "migrate", fileDSN, sqlDSN, "reallyfast")) 559 }) 560 561 t.Run("Should work passing two parameters", func(t *testing.T) { 562 assert.NoError(t, th.RunCommand(t, "config", "migrate", fileDSN, sqlDSN)) 563 }) 564 565 t.Run("Should fail passing an invalid target", func(t *testing.T) { 566 assert.Error(t, th.RunCommand(t, "config", "migrate", fileDSN, "mysql://asd")) 567 }) 568 569 t.Run("Should fail passing an invalid source", func(t *testing.T) { 570 assert.Error(t, th.RunCommand(t, "config", "migrate", "invalid/path", sqlDSN)) 571 }) 572 } 573 574 func contains(configMap map[string]interface{}, v interface{}, configSettings []string) bool { 575 res := configMap[configSettings[0]] 576 577 value := reflect.ValueOf(res) 578 579 switch value.Kind() { 580 case reflect.Map: 581 return contains(res.(map[string]interface{}), v, configSettings[1:]) 582 case reflect.Slice: 583 return reflect.DeepEqual(value.Interface(), v) 584 case reflect.Int64: 585 return value.Interface() == v.(int64) 586 default: 587 return value.Interface() == v 588 } 589 } 590 591 func TestPluginConfigs(t *testing.T) { 592 pluginConfig := TestPluginSettings{ 593 Enable: model.NewBool(true), 594 Directory: model.NewString("dir"), 595 Plugins: map[string]map[string]interface{}{ 596 "antivirus": { 597 "clamavhostport": "localhost:3310", 598 "scantimeoutseconds": 12, 599 }, 600 "com.mattermost.demo-plugin": { 601 "channelname": "demo_plugin", 602 "customsetting": "7", 603 "enablementionuser": false, 604 "lastname": "Plugin User", 605 "mentionuser": "demo_plugin", 606 "randomsecret": "random secret", 607 "secretmessage": "Changed value.", 608 "textstyle": "", 609 "username": "demo_plugin", 610 }, 611 "com.mattermost.webex": { 612 "sitehost": "praptishrestha.my.webex.com", 613 }, 614 "jira": { 615 "enablejiraui": true, 616 "groupsallowedtoeditjirasubscriptions": "", 617 "rolesallowedtoeditjirasubscriptions": "system_admin", 618 "secret": "some secret", 619 }, 620 "mattermost-autolink": { 621 "enableadmincommand": false, 622 }, 623 }, 624 PluginStates: map[string]*model.PluginState{ 625 "antivirus": { 626 Enable: false, 627 }, 628 "com.github.manland.mattermost-plugin-gitlab": { 629 Enable: true, 630 }, 631 }, 632 SignaturePublicKeyFiles: []string{"Hello", "World"}, 633 } 634 635 configMap := configToMap(pluginConfig) 636 err := UpdateMap(configMap, []string{"Enable"}, []string{"false"}) 637 require.Nil(t, err, "Wasn't expecting an error") 638 assert.Equal(t, false, configMap["Enable"].(bool)) 639 640 err = UpdateMap(configMap, []string{"Plugins", "antivirus", "clamavhostport"}, []string{"some text"}) 641 require.Nil(t, err, "Wasn't expecting an error") 642 assert.Equal(t, "some text", configMap["Plugins"].(map[string]map[string]interface{})["antivirus"]["clamavhostport"].(string)) 643 644 err = UpdateMap(configMap, []string{"Plugins", "mattermost-autolink", "enableadmincommand"}, []string{"true"}) 645 require.Nil(t, err, "Wasn't expecting an error") 646 assert.Equal(t, true, configMap["Plugins"].(map[string]map[string]interface{})["mattermost-autolink"]["enableadmincommand"].(bool)) 647 648 err = UpdateMap(configMap, []string{"PluginStates", "antivirus", "Enable"}, []string{"true"}) 649 require.Nil(t, err, "Wasn't expecting an error") 650 assert.Equal(t, true, configMap["PluginStates"].(map[string]*model.PluginState)["antivirus"].Enable) 651 }