github.com/turgay/mattermost-server@v5.3.2-0.20181002173352-2945e8a2b0ce+incompatible/utils/config_test.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package utils
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  
    18  	"github.com/mattermost/mattermost-server/model"
    19  )
    20  
    21  func TestConfig(t *testing.T) {
    22  	TranslationsPreInit()
    23  	_, _, _, err := LoadConfig("config.json")
    24  	require.Nil(t, err)
    25  }
    26  
    27  func TestReadConfig(t *testing.T) {
    28  	TranslationsPreInit()
    29  
    30  	_, _, err := ReadConfig(bytes.NewReader([]byte(``)), false)
    31  	require.EqualError(t, err, "parsing error at line 1, character 1: unexpected end of JSON input")
    32  
    33  	_, _, err = ReadConfig(bytes.NewReader([]byte(`
    34  		{
    35  			malformed
    36  	`)), false)
    37  	require.EqualError(t, err, "parsing error at line 3, character 5: invalid character 'm' looking for beginning of object key string")
    38  }
    39  
    40  func TestReadConfig_PluginSettings(t *testing.T) {
    41  	TranslationsPreInit()
    42  
    43  	config, _, err := ReadConfig(bytes.NewReader([]byte(`{
    44  		"PluginSettings": {
    45  			"Directory": "/temp/mattermost-plugins",
    46  			"Plugins": {
    47  				"com.example.plugin": {
    48  					"number": 1,
    49  					"string": "abc",
    50  					"boolean": false,
    51  					"abc.def.ghi": {
    52  						"abc": 123,
    53  						"def": "456"
    54  					}
    55  				},
    56  				"jira": {
    57  					"number": 2,
    58  					"string": "123",
    59  					"boolean": true,
    60  					"abc.def.ghi": {
    61  						"abc": 456,
    62  						"def": "123"
    63  					}
    64   				}
    65  			},
    66  			"PluginStates": {
    67  				"com.example.plugin": {
    68  					"enable": true
    69  				},
    70  				"jira": {
    71  					"enable": false
    72   				}
    73  			}
    74  		}
    75  	}`)), false)
    76  	require.Nil(t, err)
    77  
    78  	assert.Equal(t, "/temp/mattermost-plugins", *config.PluginSettings.Directory)
    79  
    80  	if assert.Contains(t, config.PluginSettings.Plugins, "com.example.plugin") {
    81  		assert.Equal(t, map[string]interface{}{
    82  			"number":  float64(1),
    83  			"string":  "abc",
    84  			"boolean": false,
    85  			"abc.def.ghi": map[string]interface{}{
    86  				"abc": float64(123),
    87  				"def": "456",
    88  			},
    89  		}, config.PluginSettings.Plugins["com.example.plugin"])
    90  	}
    91  	if assert.Contains(t, config.PluginSettings.PluginStates, "com.example.plugin") {
    92  		assert.Equal(t, model.PluginState{
    93  			Enable: true,
    94  		}, *config.PluginSettings.PluginStates["com.example.plugin"])
    95  	}
    96  
    97  	if assert.Contains(t, config.PluginSettings.Plugins, "jira") {
    98  		assert.Equal(t, map[string]interface{}{
    99  			"number":  float64(2),
   100  			"string":  "123",
   101  			"boolean": true,
   102  			"abc.def.ghi": map[string]interface{}{
   103  				"abc": float64(456),
   104  				"def": "123",
   105  			},
   106  		}, config.PluginSettings.Plugins["jira"])
   107  	}
   108  	if assert.Contains(t, config.PluginSettings.PluginStates, "jira") {
   109  		assert.Equal(t, model.PluginState{
   110  			Enable: false,
   111  		}, *config.PluginSettings.PluginStates["jira"])
   112  	}
   113  }
   114  
   115  func TestTimezoneConfig(t *testing.T) {
   116  	TranslationsPreInit()
   117  	supportedTimezones := LoadTimezones("timezones.json")
   118  	assert.Equal(t, len(supportedTimezones) > 0, true)
   119  
   120  	supportedTimezones2 := LoadTimezones("timezones_file_does_not_exists.json")
   121  	assert.Equal(t, len(supportedTimezones2) > 0, true)
   122  }
   123  
   124  func TestFindConfigFile(t *testing.T) {
   125  	t.Run("config.json in current working directory, not inside config/", func(t *testing.T) {
   126  		// Force a unique working directory
   127  		cwd, err := ioutil.TempDir("", "")
   128  		require.NoError(t, err)
   129  		defer os.RemoveAll(cwd)
   130  
   131  		prevDir, err := os.Getwd()
   132  		require.NoError(t, err)
   133  		defer os.Chdir(prevDir)
   134  		os.Chdir(cwd)
   135  
   136  		configJson, err := filepath.Abs("config.json")
   137  		require.NoError(t, err)
   138  		require.NoError(t, ioutil.WriteFile(configJson, []byte("{}"), 0600))
   139  
   140  		// Relative paths end up getting symlinks fully resolved.
   141  		configJsonResolved, err := filepath.EvalSymlinks(configJson)
   142  		require.NoError(t, err)
   143  
   144  		assert.Equal(t, configJsonResolved, FindConfigFile("config.json"))
   145  	})
   146  
   147  	t.Run("config/config.json from various paths", func(t *testing.T) {
   148  		// Create the following directory structure:
   149  		// tmpDir1/
   150  		//   config/
   151  		//     config.json
   152  		//   tmpDir2/
   153  		//     tmpDir3/
   154  		//       tmpDir4/
   155  		//         tmpDir5/
   156  		tmpDir1, err := ioutil.TempDir("", "")
   157  		require.NoError(t, err)
   158  		defer os.RemoveAll(tmpDir1)
   159  
   160  		err = os.Mkdir(filepath.Join(tmpDir1, "config"), 0700)
   161  		require.NoError(t, err)
   162  
   163  		tmpDir2, err := ioutil.TempDir(tmpDir1, "")
   164  		require.NoError(t, err)
   165  
   166  		tmpDir3, err := ioutil.TempDir(tmpDir2, "")
   167  		require.NoError(t, err)
   168  
   169  		tmpDir4, err := ioutil.TempDir(tmpDir3, "")
   170  		require.NoError(t, err)
   171  
   172  		tmpDir5, err := ioutil.TempDir(tmpDir4, "")
   173  		require.NoError(t, err)
   174  
   175  		configJson := filepath.Join(tmpDir1, "config", "config.json")
   176  		require.NoError(t, ioutil.WriteFile(configJson, []byte("{}"), 0600))
   177  
   178  		// Relative paths end up getting symlinks fully resolved, so use this below as necessary.
   179  		configJsonResolved, err := filepath.EvalSymlinks(configJson)
   180  		require.NoError(t, err)
   181  
   182  		testCases := []struct {
   183  			Description string
   184  			Cwd         *string
   185  			FileName    string
   186  			Expected    string
   187  		}{
   188  			{
   189  				"absolute path to config.json",
   190  				nil,
   191  				configJson,
   192  				configJson,
   193  			},
   194  			{
   195  				"absolute path to config.json from directory containing config.json",
   196  				&tmpDir1,
   197  				configJson,
   198  				configJson,
   199  			},
   200  			{
   201  				"relative path to config.json from directory containing config.json",
   202  				&tmpDir1,
   203  				"config.json",
   204  				configJsonResolved,
   205  			},
   206  			{
   207  				"subdirectory of directory containing config.json",
   208  				&tmpDir2,
   209  				"config.json",
   210  				configJsonResolved,
   211  			},
   212  			{
   213  				"twice-nested subdirectory of directory containing config.json",
   214  				&tmpDir3,
   215  				"config.json",
   216  				configJsonResolved,
   217  			},
   218  			{
   219  				"thrice-nested subdirectory of directory containing config.json",
   220  				&tmpDir4,
   221  				"config.json",
   222  				configJsonResolved,
   223  			},
   224  			{
   225  				"can't find from four nesting levels deep",
   226  				&tmpDir5,
   227  				"config.json",
   228  				"",
   229  			},
   230  		}
   231  
   232  		for _, testCase := range testCases {
   233  			t.Run(testCase.Description, func(t *testing.T) {
   234  				if testCase.Cwd != nil {
   235  					prevDir, err := os.Getwd()
   236  					require.NoError(t, err)
   237  					defer os.Chdir(prevDir)
   238  					os.Chdir(*testCase.Cwd)
   239  				}
   240  
   241  				assert.Equal(t, testCase.Expected, FindConfigFile(testCase.FileName))
   242  			})
   243  		}
   244  	})
   245  
   246  	t.Run("config/config.json relative to executable", func(t *testing.T) {
   247  		osExecutable, err := os.Executable()
   248  		require.NoError(t, err)
   249  		osExecutableDir := filepath.Dir(osExecutable)
   250  
   251  		// Force a working directory different than the executable.
   252  		cwd, err := ioutil.TempDir("", "")
   253  		require.NoError(t, err)
   254  		defer os.RemoveAll(cwd)
   255  
   256  		prevDir, err := os.Getwd()
   257  		require.NoError(t, err)
   258  		defer os.Chdir(prevDir)
   259  		os.Chdir(cwd)
   260  
   261  		testCases := []struct {
   262  			Description  string
   263  			RelativePath string
   264  		}{
   265  			{
   266  				"config/config.json",
   267  				".",
   268  			},
   269  			{
   270  				"../config/config.json",
   271  				"../",
   272  			},
   273  		}
   274  
   275  		for _, testCase := range testCases {
   276  			t.Run(testCase.Description, func(t *testing.T) {
   277  				// Install the config in config/config.json relative to the executable
   278  				configJson := filepath.Join(osExecutableDir, testCase.RelativePath, "config", "config.json")
   279  				require.NoError(t, os.Mkdir(filepath.Dir(configJson), 0700))
   280  				require.NoError(t, ioutil.WriteFile(configJson, []byte("{}"), 0600))
   281  				defer os.RemoveAll(filepath.Dir(configJson))
   282  
   283  				// Relative paths end up getting symlinks fully resolved.
   284  				configJsonResolved, err := filepath.EvalSymlinks(configJson)
   285  				require.NoError(t, err)
   286  
   287  				assert.Equal(t, configJsonResolved, FindConfigFile("config.json"))
   288  			})
   289  		}
   290  	})
   291  }
   292  
   293  func TestFindFile(t *testing.T) {
   294  	t.Run("files from various paths", func(t *testing.T) {
   295  		// Create the following directory structure:
   296  		// tmpDir1/
   297  		//   file1.json
   298  		//   file2.xml
   299  		//   other.txt
   300  		//   tmpDir2/
   301  		//     other.txt/ [directory]
   302  		//     tmpDir3/
   303  		//       tmpDir4/
   304  		//         tmpDir5/
   305  		tmpDir1, err := ioutil.TempDir("", "")
   306  		require.NoError(t, err)
   307  		defer os.RemoveAll(tmpDir1)
   308  
   309  		tmpDir2, err := ioutil.TempDir(tmpDir1, "")
   310  		require.NoError(t, err)
   311  
   312  		err = os.Mkdir(filepath.Join(tmpDir2, "other.txt"), 0700)
   313  		require.NoError(t, err)
   314  
   315  		tmpDir3, err := ioutil.TempDir(tmpDir2, "")
   316  		require.NoError(t, err)
   317  
   318  		tmpDir4, err := ioutil.TempDir(tmpDir3, "")
   319  		require.NoError(t, err)
   320  
   321  		tmpDir5, err := ioutil.TempDir(tmpDir4, "")
   322  		require.NoError(t, err)
   323  
   324  		type testCase struct {
   325  			Description string
   326  			Cwd         *string
   327  			FileName    string
   328  			Expected    string
   329  		}
   330  
   331  		testCases := []testCase{}
   332  
   333  		for _, fileName := range []string{"file1.json", "file2.xml", "other.txt"} {
   334  			filePath := filepath.Join(tmpDir1, fileName)
   335  			require.NoError(t, ioutil.WriteFile(filePath, []byte("{}"), 0600))
   336  
   337  			// Relative paths end up getting symlinks fully resolved, so use this below as necessary.
   338  			filePathResolved, err := filepath.EvalSymlinks(filePath)
   339  			require.NoError(t, err)
   340  
   341  			testCases = append(testCases, []testCase{
   342  				{
   343  					fmt.Sprintf("absolute path to %s", fileName),
   344  					nil,
   345  					filePath,
   346  					filePath,
   347  				},
   348  				{
   349  					fmt.Sprintf("absolute path to %s from containing directory", fileName),
   350  					&tmpDir1,
   351  					filePath,
   352  					filePath,
   353  				},
   354  				{
   355  					fmt.Sprintf("relative path to %s from containing directory", fileName),
   356  					&tmpDir1,
   357  					fileName,
   358  					filePathResolved,
   359  				},
   360  				{
   361  					fmt.Sprintf("%s: subdirectory of containing directory", fileName),
   362  					&tmpDir2,
   363  					fileName,
   364  					filePathResolved,
   365  				},
   366  				{
   367  					fmt.Sprintf("%s: twice-nested subdirectory of containing directory", fileName),
   368  					&tmpDir3,
   369  					fileName,
   370  					filePathResolved,
   371  				},
   372  				{
   373  					fmt.Sprintf("%s: thrice-nested subdirectory of containing directory", fileName),
   374  					&tmpDir4,
   375  					fileName,
   376  					filePathResolved,
   377  				},
   378  				{
   379  					fmt.Sprintf("%s: can't find from four nesting levels deep", fileName),
   380  					&tmpDir5,
   381  					fileName,
   382  					"",
   383  				},
   384  			}...)
   385  		}
   386  
   387  		for _, testCase := range testCases {
   388  			t.Run(testCase.Description, func(t *testing.T) {
   389  				if testCase.Cwd != nil {
   390  					prevDir, err := os.Getwd()
   391  					require.NoError(t, err)
   392  					defer os.Chdir(prevDir)
   393  					os.Chdir(*testCase.Cwd)
   394  				}
   395  
   396  				assert.Equal(t, testCase.Expected, FindFile(testCase.FileName))
   397  			})
   398  		}
   399  	})
   400  }
   401  
   402  func TestConfigFromEnviroVars(t *testing.T) {
   403  	TranslationsPreInit()
   404  
   405  	config := `{
   406  		"ServiceSettings": {
   407  			"EnableCommands": true,
   408  			"ReadTimeout": 100
   409  		},
   410  		"TeamSettings": {
   411  			"SiteName": "Mattermost",
   412  			"CustomBrandText": ""
   413  		},
   414  		"SupportSettings": {
   415  			"TermsOfServiceLink": "https://about.mattermost.com/default-terms/"
   416  		},
   417  		"PluginSettings": {
   418  			"Enable": true,
   419  			"Plugins": {
   420  				"jira": {
   421  					"enabled": "true",
   422  					"secret": "config-secret"
   423  				}
   424  			},
   425  			"PluginStates": {
   426  				"jira": {
   427  					"Enable": true
   428  				}
   429  			}
   430  		}
   431  	}`
   432  
   433  	t.Run("string settings", func(t *testing.T) {
   434  		os.Setenv("MM_TEAMSETTINGS_SITENAME", "From Environment")
   435  		os.Setenv("MM_TEAMSETTINGS_CUSTOMBRANDTEXT", "Custom Brand")
   436  
   437  		cfg, envCfg, err := ReadConfig(strings.NewReader(config), true)
   438  		require.Nil(t, err)
   439  
   440  		if cfg.TeamSettings.SiteName != "From Environment" {
   441  			t.Fatal("Couldn't read config from environment var")
   442  		}
   443  
   444  		if *cfg.TeamSettings.CustomBrandText != "Custom Brand" {
   445  			t.Fatal("Couldn't read config from environment var")
   446  		}
   447  
   448  		if teamSettings, ok := envCfg["TeamSettings"]; !ok {
   449  			t.Fatal("TeamSettings is missing from envConfig")
   450  		} else if teamSettingsAsMap, ok := teamSettings.(map[string]interface{}); !ok {
   451  			t.Fatal("TeamSettings is not a map in envConfig")
   452  		} else {
   453  			if siteNameInEnv, ok := teamSettingsAsMap["SiteName"].(bool); !ok || !siteNameInEnv {
   454  				t.Fatal("SiteName should be in envConfig")
   455  			}
   456  
   457  			if customBrandTextInEnv, ok := teamSettingsAsMap["CustomBrandText"].(bool); !ok || !customBrandTextInEnv {
   458  				t.Fatal("SiteName should be in envConfig")
   459  			}
   460  		}
   461  
   462  		os.Unsetenv("MM_TEAMSETTINGS_SITENAME")
   463  		os.Unsetenv("MM_TEAMSETTINGS_CUSTOMBRANDTEXT")
   464  
   465  		cfg, envCfg, err = ReadConfig(strings.NewReader(config), true)
   466  		require.Nil(t, err)
   467  
   468  		if cfg.TeamSettings.SiteName != "Mattermost" {
   469  			t.Fatal("should have been reset")
   470  		}
   471  
   472  		if _, ok := envCfg["TeamSettings"]; ok {
   473  			t.Fatal("TeamSettings should be missing from envConfig")
   474  		}
   475  	})
   476  
   477  	t.Run("boolean setting", func(t *testing.T) {
   478  		os.Setenv("MM_SERVICESETTINGS_ENABLECOMMANDS", "false")
   479  		defer os.Unsetenv("MM_SERVICESETTINGS_ENABLECOMMANDS")
   480  
   481  		cfg, envCfg, err := ReadConfig(strings.NewReader(config), true)
   482  		require.Nil(t, err)
   483  
   484  		if *cfg.ServiceSettings.EnableCommands {
   485  			t.Fatal("Couldn't read config from environment var")
   486  		}
   487  
   488  		if serviceSettings, ok := envCfg["ServiceSettings"]; !ok {
   489  			t.Fatal("ServiceSettings is missing from envConfig")
   490  		} else if serviceSettingsAsMap, ok := serviceSettings.(map[string]interface{}); !ok {
   491  			t.Fatal("ServiceSettings is not a map in envConfig")
   492  		} else {
   493  			if enableCommandsInEnv, ok := serviceSettingsAsMap["EnableCommands"].(bool); !ok || !enableCommandsInEnv {
   494  				t.Fatal("EnableCommands should be in envConfig")
   495  			}
   496  		}
   497  	})
   498  
   499  	t.Run("integer setting", func(t *testing.T) {
   500  		os.Setenv("MM_SERVICESETTINGS_READTIMEOUT", "400")
   501  		defer os.Unsetenv("MM_SERVICESETTINGS_READTIMEOUT")
   502  
   503  		cfg, envCfg, err := ReadConfig(strings.NewReader(config), true)
   504  		require.Nil(t, err)
   505  
   506  		if *cfg.ServiceSettings.ReadTimeout != 400 {
   507  			t.Fatal("Couldn't read config from environment var")
   508  		}
   509  
   510  		if serviceSettings, ok := envCfg["ServiceSettings"]; !ok {
   511  			t.Fatal("ServiceSettings is missing from envConfig")
   512  		} else if serviceSettingsAsMap, ok := serviceSettings.(map[string]interface{}); !ok {
   513  			t.Fatal("ServiceSettings is not a map in envConfig")
   514  		} else {
   515  			if readTimeoutInEnv, ok := serviceSettingsAsMap["ReadTimeout"].(bool); !ok || !readTimeoutInEnv {
   516  				t.Fatal("ReadTimeout should be in envConfig")
   517  			}
   518  		}
   519  	})
   520  
   521  	t.Run("setting missing from config.json", func(t *testing.T) {
   522  		os.Setenv("MM_SERVICESETTINGS_SITEURL", "https://example.com")
   523  		defer os.Unsetenv("MM_SERVICESETTINGS_SITEURL")
   524  
   525  		cfg, envCfg, err := ReadConfig(strings.NewReader(config), true)
   526  		require.Nil(t, err)
   527  
   528  		if *cfg.ServiceSettings.SiteURL != "https://example.com" {
   529  			t.Fatal("Couldn't read config from environment var")
   530  		}
   531  
   532  		if serviceSettings, ok := envCfg["ServiceSettings"]; !ok {
   533  			t.Fatal("ServiceSettings is missing from envConfig")
   534  		} else if serviceSettingsAsMap, ok := serviceSettings.(map[string]interface{}); !ok {
   535  			t.Fatal("ServiceSettings is not a map in envConfig")
   536  		} else {
   537  			if siteURLInEnv, ok := serviceSettingsAsMap["SiteURL"].(bool); !ok || !siteURLInEnv {
   538  				t.Fatal("SiteURL should be in envConfig")
   539  			}
   540  		}
   541  	})
   542  
   543  	t.Run("empty string setting", func(t *testing.T) {
   544  		os.Setenv("MM_SUPPORTSETTINGS_TERMSOFSERVICELINK", "")
   545  		defer os.Unsetenv("MM_SUPPORTSETTINGS_TERMSOFSERVICELINK")
   546  
   547  		cfg, envCfg, err := ReadConfig(strings.NewReader(config), true)
   548  		require.Nil(t, err)
   549  
   550  		if *cfg.SupportSettings.TermsOfServiceLink != "" {
   551  			t.Fatal("Couldn't read empty TermsOfServiceLink from environment var")
   552  		}
   553  
   554  		if supportSettings, ok := envCfg["SupportSettings"]; !ok {
   555  			t.Fatal("SupportSettings is missing from envConfig")
   556  		} else if supportSettingsAsMap, ok := supportSettings.(map[string]interface{}); !ok {
   557  			t.Fatal("SupportSettings is not a map in envConfig")
   558  		} else {
   559  			if termsOfServiceLinkInEnv, ok := supportSettingsAsMap["TermsOfServiceLink"].(bool); !ok || !termsOfServiceLinkInEnv {
   560  				t.Fatal("TermsOfServiceLink should be in envConfig")
   561  			}
   562  		}
   563  	})
   564  
   565  	t.Run("plugin directory settings", func(t *testing.T) {
   566  		os.Setenv("MM_PLUGINSETTINGS_ENABLE", "false")
   567  		os.Setenv("MM_PLUGINSETTINGS_DIRECTORY", "/temp/plugins")
   568  		os.Setenv("MM_PLUGINSETTINGS_CLIENTDIRECTORY", "/temp/clientplugins")
   569  		defer os.Unsetenv("MM_PLUGINSETTINGS_ENABLE")
   570  		defer os.Unsetenv("MM_PLUGINSETTINGS_DIRECTORY")
   571  		defer os.Unsetenv("MM_PLUGINSETTINGS_CLIENTDIRECTORY")
   572  
   573  		cfg, envCfg, err := ReadConfig(strings.NewReader(config), true)
   574  		require.Nil(t, err)
   575  
   576  		assert.Equal(t, false, *cfg.PluginSettings.Enable)
   577  		assert.Equal(t, "/temp/plugins", *cfg.PluginSettings.Directory)
   578  		assert.Equal(t, "/temp/clientplugins", *cfg.PluginSettings.ClientDirectory)
   579  
   580  		if pluginSettings, ok := envCfg["PluginSettings"]; !ok {
   581  			t.Fatal("PluginSettings is missing from envConfig")
   582  		} else if pluginSettingsAsMap, ok := pluginSettings.(map[string]interface{}); !ok {
   583  			t.Fatal("PluginSettings is not a map in envConfig")
   584  		} else {
   585  			if directory, ok := pluginSettingsAsMap["Directory"].(bool); !ok || !directory {
   586  				t.Fatal("Directory should be in envConfig")
   587  			}
   588  			if clientDirectory, ok := pluginSettingsAsMap["ClientDirectory"].(bool); !ok || !clientDirectory {
   589  				t.Fatal("ClientDirectory should be in envConfig")
   590  			}
   591  		}
   592  	})
   593  
   594  	t.Run("plugin specific settings cannot be overridden via environment", func(t *testing.T) {
   595  		os.Setenv("MM_PLUGINSETTINGS_PLUGINS_JIRA_ENABLED", "false")
   596  		os.Setenv("MM_PLUGINSETTINGS_PLUGINS_JIRA_SECRET", "env-secret")
   597  		os.Setenv("MM_PLUGINSETTINGS_PLUGINSTATES_JIRA_ENABLE", "false")
   598  		defer os.Unsetenv("MM_PLUGINSETTINGS_PLUGINS_JIRA_ENABLED")
   599  		defer os.Unsetenv("MM_PLUGINSETTINGS_PLUGINS_JIRA_SECRET")
   600  		defer os.Unsetenv("MM_PLUGINSETTINGS_PLUGINSTATES_JIRA_ENABLE")
   601  
   602  		cfg, envCfg, err := ReadConfig(strings.NewReader(config), true)
   603  		require.Nil(t, err)
   604  
   605  		if pluginsJira, ok := cfg.PluginSettings.Plugins["jira"]; !ok {
   606  			t.Fatal("PluginSettings.Plugins.jira is missing from config")
   607  		} else {
   608  			if enabled, ok := pluginsJira["enabled"]; !ok {
   609  				t.Fatal("PluginSettings.Plugins.jira.enabled is missing from config")
   610  			} else {
   611  				assert.Equal(t, "true", enabled)
   612  			}
   613  
   614  			if secret, ok := pluginsJira["secret"]; !ok {
   615  				t.Fatal("PluginSettings.Plugins.jira.secret is missing from config")
   616  			} else {
   617  				assert.Equal(t, "config-secret", secret)
   618  			}
   619  		}
   620  
   621  		if pluginStatesJira, ok := cfg.PluginSettings.PluginStates["jira"]; !ok {
   622  			t.Fatal("PluginSettings.PluginStates.jira is missing from config")
   623  		} else {
   624  			require.Equal(t, true, pluginStatesJira.Enable)
   625  		}
   626  
   627  		if pluginSettings, ok := envCfg["PluginSettings"]; !ok {
   628  			t.Fatal("PluginSettings is missing from envConfig")
   629  		} else if pluginSettingsAsMap, ok := pluginSettings.(map[string]interface{}); !ok {
   630  			t.Fatal("PluginSettings is not a map in envConfig")
   631  		} else {
   632  			if plugins, ok := pluginSettingsAsMap["Plugins"].(map[string]interface{}); !ok {
   633  				t.Fatal("PluginSettings.Plugins is not a map in envConfig")
   634  			} else if _, ok := plugins["jira"].(map[string]interface{}); ok {
   635  				t.Fatal("PluginSettings.Plugins.jira should not be a map in envConfig")
   636  			}
   637  
   638  			if pluginStates, ok := pluginSettingsAsMap["PluginStates"].(map[string]interface{}); !ok {
   639  				t.Fatal("PluginSettings.PluginStates is missing from envConfig")
   640  			} else if _, ok := pluginStates["jira"].(map[string]interface{}); ok {
   641  				t.Fatal("PluginSettings.PluginStates.jira should not be a map in envConfig")
   642  			}
   643  		}
   644  	})
   645  }
   646  
   647  func TestValidateLocales(t *testing.T) {
   648  	TranslationsPreInit()
   649  	cfg, _, _, err := LoadConfig("config.json")
   650  	require.Nil(t, err)
   651  
   652  	*cfg.LocalizationSettings.DefaultServerLocale = "en"
   653  	*cfg.LocalizationSettings.DefaultClientLocale = "en"
   654  	*cfg.LocalizationSettings.AvailableLocales = ""
   655  
   656  	// t.Logf("*cfg.LocalizationSettings.DefaultClientLocale: %+v", *cfg.LocalizationSettings.DefaultClientLocale)
   657  	if err := ValidateLocales(cfg); err != nil {
   658  		t.Fatal("Should have not returned an error")
   659  	}
   660  
   661  	// validate DefaultServerLocale
   662  	*cfg.LocalizationSettings.DefaultServerLocale = "junk"
   663  	if err := ValidateLocales(cfg); err != nil {
   664  		if *cfg.LocalizationSettings.DefaultServerLocale != "en" {
   665  			t.Fatal("DefaultServerLocale should have assigned to en as a default value")
   666  		}
   667  	} else {
   668  		t.Fatal("Should have returned an error validating DefaultServerLocale")
   669  	}
   670  
   671  	*cfg.LocalizationSettings.DefaultServerLocale = ""
   672  	if err := ValidateLocales(cfg); err != nil {
   673  		if *cfg.LocalizationSettings.DefaultServerLocale != "en" {
   674  			t.Fatal("DefaultServerLocale should have assigned to en as a default value")
   675  		}
   676  	} else {
   677  		t.Fatal("Should have returned an error validating DefaultServerLocale")
   678  	}
   679  
   680  	*cfg.LocalizationSettings.AvailableLocales = "en"
   681  	*cfg.LocalizationSettings.DefaultServerLocale = "de"
   682  	if err := ValidateLocales(cfg); err != nil {
   683  		if strings.Contains(*cfg.LocalizationSettings.AvailableLocales, *cfg.LocalizationSettings.DefaultServerLocale) {
   684  			t.Fatal("DefaultServerLocale should not be added to AvailableLocales")
   685  		}
   686  		t.Fatal("Should have not returned an error validating DefaultServerLocale")
   687  	}
   688  
   689  	// validate DefaultClientLocale
   690  	*cfg.LocalizationSettings.AvailableLocales = ""
   691  	*cfg.LocalizationSettings.DefaultClientLocale = "junk"
   692  	if err := ValidateLocales(cfg); err != nil {
   693  		if *cfg.LocalizationSettings.DefaultClientLocale != "en" {
   694  			t.Fatal("DefaultClientLocale should have assigned to en as a default value")
   695  		}
   696  	} else {
   697  
   698  		t.Fatal("Should have returned an error validating DefaultClientLocale")
   699  	}
   700  
   701  	*cfg.LocalizationSettings.DefaultClientLocale = ""
   702  	if err := ValidateLocales(cfg); err != nil {
   703  		if *cfg.LocalizationSettings.DefaultClientLocale != "en" {
   704  			t.Fatal("DefaultClientLocale should have assigned to en as a default value")
   705  		}
   706  	} else {
   707  		t.Fatal("Should have returned an error validating DefaultClientLocale")
   708  	}
   709  
   710  	*cfg.LocalizationSettings.AvailableLocales = "en"
   711  	*cfg.LocalizationSettings.DefaultClientLocale = "de"
   712  	if err := ValidateLocales(cfg); err != nil {
   713  		if !strings.Contains(*cfg.LocalizationSettings.AvailableLocales, *cfg.LocalizationSettings.DefaultClientLocale) {
   714  			t.Fatal("DefaultClientLocale should have added to AvailableLocales")
   715  		}
   716  	} else {
   717  		t.Fatal("Should have returned an error validating DefaultClientLocale")
   718  	}
   719  
   720  	// validate AvailableLocales
   721  	*cfg.LocalizationSettings.DefaultServerLocale = "en"
   722  	*cfg.LocalizationSettings.DefaultClientLocale = "en"
   723  	*cfg.LocalizationSettings.AvailableLocales = "junk"
   724  	if err := ValidateLocales(cfg); err != nil {
   725  		if *cfg.LocalizationSettings.AvailableLocales != "" {
   726  			t.Fatal("AvailableLocales should have assigned to empty string as a default value")
   727  		}
   728  	} else {
   729  		t.Fatal("Should have returned an error validating AvailableLocales")
   730  	}
   731  
   732  	*cfg.LocalizationSettings.AvailableLocales = "en,de,junk"
   733  	if err := ValidateLocales(cfg); err != nil {
   734  		if *cfg.LocalizationSettings.AvailableLocales != "" {
   735  			t.Fatal("AvailableLocales should have assigned to empty string as a default value")
   736  		}
   737  	} else {
   738  		t.Fatal("Should have returned an error validating AvailableLocales")
   739  	}
   740  
   741  	*cfg.LocalizationSettings.DefaultServerLocale = "fr"
   742  	*cfg.LocalizationSettings.DefaultClientLocale = "de"
   743  	*cfg.LocalizationSettings.AvailableLocales = "en"
   744  	if err := ValidateLocales(cfg); err != nil {
   745  		if strings.Contains(*cfg.LocalizationSettings.AvailableLocales, *cfg.LocalizationSettings.DefaultServerLocale) {
   746  			t.Fatal("DefaultServerLocale should not be added to AvailableLocales")
   747  		}
   748  		if !strings.Contains(*cfg.LocalizationSettings.AvailableLocales, *cfg.LocalizationSettings.DefaultClientLocale) {
   749  			t.Fatal("DefaultClientLocale should have added to AvailableLocales")
   750  		}
   751  	} else {
   752  		t.Fatal("Should have returned an error validating AvailableLocales")
   753  	}
   754  }
   755  
   756  func TestGetClientConfig(t *testing.T) {
   757  	t.Parallel()
   758  	testCases := []struct {
   759  		description    string
   760  		config         *model.Config
   761  		diagnosticId   string
   762  		license        *model.License
   763  		expectedFields map[string]string
   764  	}{
   765  		{
   766  			"unlicensed",
   767  			&model.Config{
   768  				EmailSettings: model.EmailSettings{
   769  					EmailNotificationContentsType: sToP(model.EMAIL_NOTIFICATION_CONTENTS_FULL),
   770  				},
   771  				ThemeSettings: model.ThemeSettings{
   772  					// Ignored, since not licensed.
   773  					AllowCustomThemes: bToP(false),
   774  				},
   775  			},
   776  			"",
   777  			nil,
   778  			map[string]string{
   779  				"DiagnosticId":                  "",
   780  				"EmailNotificationContentsType": "full",
   781  				"AllowCustomThemes":             "true",
   782  			},
   783  		},
   784  		{
   785  			"licensed, but not for theme management",
   786  			&model.Config{
   787  				EmailSettings: model.EmailSettings{
   788  					EmailNotificationContentsType: sToP(model.EMAIL_NOTIFICATION_CONTENTS_FULL),
   789  				},
   790  				ThemeSettings: model.ThemeSettings{
   791  					// Ignored, since not licensed.
   792  					AllowCustomThemes: bToP(false),
   793  				},
   794  			},
   795  			"tag1",
   796  			&model.License{
   797  				Features: &model.Features{
   798  					ThemeManagement: bToP(false),
   799  				},
   800  			},
   801  			map[string]string{
   802  				"DiagnosticId":                  "tag1",
   803  				"EmailNotificationContentsType": "full",
   804  				"AllowCustomThemes":             "true",
   805  			},
   806  		},
   807  		{
   808  			"licensed for theme management",
   809  			&model.Config{
   810  				EmailSettings: model.EmailSettings{
   811  					EmailNotificationContentsType: sToP(model.EMAIL_NOTIFICATION_CONTENTS_FULL),
   812  				},
   813  				ThemeSettings: model.ThemeSettings{
   814  					AllowCustomThemes: bToP(false),
   815  				},
   816  			},
   817  			"tag2",
   818  			&model.License{
   819  				Features: &model.Features{
   820  					ThemeManagement: bToP(true),
   821  				},
   822  			},
   823  			map[string]string{
   824  				"DiagnosticId":                  "tag2",
   825  				"EmailNotificationContentsType": "full",
   826  				"AllowCustomThemes":             "false",
   827  			},
   828  		},
   829  	}
   830  
   831  	for _, testCase := range testCases {
   832  		testCase := testCase
   833  		t.Run(testCase.description, func(t *testing.T) {
   834  			t.Parallel()
   835  
   836  			testCase.config.SetDefaults()
   837  			if testCase.license != nil {
   838  				testCase.license.Features.SetDefaults()
   839  			}
   840  
   841  			configMap := GenerateClientConfig(testCase.config, testCase.diagnosticId, testCase.license)
   842  			for expectedField, expectedValue := range testCase.expectedFields {
   843  				actualValue, ok := configMap[expectedField]
   844  				assert.True(t, ok, fmt.Sprintf("config does not contain %v", expectedField))
   845  				assert.Equal(t, expectedValue, actualValue)
   846  			}
   847  		})
   848  	}
   849  }
   850  
   851  func sToP(s string) *string {
   852  	return &s
   853  }
   854  
   855  func bToP(b bool) *bool {
   856  	return &b
   857  }
   858  
   859  func TestGetDefaultsFromStruct(t *testing.T) {
   860  	s := struct {
   861  		TestSettings struct {
   862  			IntValue    int
   863  			BoolValue   bool
   864  			StringValue string
   865  		}
   866  		PointerToTestSettings *struct {
   867  			Value int
   868  		}
   869  	}{}
   870  
   871  	defaults := getDefaultsFromStruct(s)
   872  
   873  	assert.Equal(t, defaults["TestSettings.IntValue"], 0)
   874  	assert.Equal(t, defaults["TestSettings.BoolValue"], false)
   875  	assert.Equal(t, defaults["TestSettings.StringValue"], "")
   876  	assert.Equal(t, defaults["PointerToTestSettings.Value"], 0)
   877  	assert.NotContains(t, defaults, "PointerToTestSettings")
   878  	assert.Len(t, defaults, 4)
   879  }