1// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2// See LICENSE.txt for license information.
3
4package config
5
6import (
7	"bytes"
8	"encoding/json"
9	"fmt"
10	"os"
11	"strings"
12	"testing"
13	"time"
14
15	"github.com/jmoiron/sqlx"
16	"github.com/stretchr/testify/assert"
17	"github.com/stretchr/testify/require"
18
19	"github.com/mattermost/mattermost-server/v6/model"
20)
21
22func getDsn(driver string, source string) string {
23	if driver == model.DatabaseDriverMysql {
24		return driver + "://" + source
25	}
26	return source
27}
28
29func setupConfigDatabase(t *testing.T, cfg *model.Config, files map[string][]byte) (string, func()) {
30	if testing.Short() {
31		t.SkipNow()
32	}
33	t.Helper()
34	os.Clearenv()
35	truncateTables(t)
36
37	cfgData, err := marshalConfig(cfg)
38	require.NoError(t, err)
39
40	db := sqlx.NewDb(mainHelper.GetSQLStore().GetMaster().Db, *mainHelper.GetSQLSettings().DriverName)
41	err = initializeConfigurationsTable(db)
42	require.NoError(t, err)
43
44	id := model.NewId()
45	_, err = db.NamedExec("INSERT INTO Configurations (Id, Value, CreateAt, Active) VALUES(:Id, :Value, :CreateAt, TRUE)", map[string]interface{}{
46		"Id":       id,
47		"Value":    cfgData,
48		"CreateAt": model.GetMillis(),
49	})
50	require.NoError(t, err)
51
52	for name, data := range files {
53		params := map[string]interface{}{
54			"name":      name,
55			"data":      data,
56			"create_at": model.GetMillis(),
57			"update_at": model.GetMillis(),
58		}
59
60		_, err = db.NamedExec("INSERT INTO ConfigurationFiles (Name, Data, CreateAt, UpdateAt) VALUES (:name, :data, :create_at, :update_at)", params)
61		require.NoError(t, err)
62	}
63
64	return id, func() {
65		truncateTables(t)
66	}
67}
68
69// getActualDatabaseConfig returns the active configuration in the database without relying on a config store.
70func getActualDatabaseConfig(t *testing.T) (string, *model.Config) {
71	t.Helper()
72
73	if *mainHelper.GetSQLSettings().DriverName == "postgres" {
74		var actual struct {
75			ID    string `db:"id"`
76			Value []byte `db:"value"`
77		}
78		db := sqlx.NewDb(mainHelper.GetSQLStore().GetMaster().Db, *mainHelper.GetSQLSettings().DriverName)
79		err := db.Get(&actual, "SELECT Id, Value FROM Configurations WHERE Active")
80		require.NoError(t, err)
81
82		var actualCfg *model.Config
83		err = json.Unmarshal(actual.Value, &actualCfg)
84		require.NoError(t, err)
85		return actual.ID, actualCfg
86	}
87	var actual struct {
88		ID    string `db:"Id"`
89		Value []byte `db:"Value"`
90	}
91	db := sqlx.NewDb(mainHelper.GetSQLStore().GetMaster().Db, *mainHelper.GetSQLSettings().DriverName)
92	err := db.Get(&actual, "SELECT Id, Value FROM Configurations WHERE Active")
93	require.NoError(t, err)
94
95	var actualCfg *model.Config
96	err = json.Unmarshal(actual.Value, &actualCfg)
97	require.NoError(t, err)
98	return actual.ID, actualCfg
99}
100
101// assertDatabaseEqualsConfig verifies the active in-database configuration equals the given config.
102func assertDatabaseEqualsConfig(t *testing.T, expectedCfg *model.Config) {
103	t.Helper()
104
105	_, actualCfg := getActualDatabaseConfig(t)
106	assert.Equal(t, expectedCfg, actualCfg)
107}
108
109// assertDatabaseNotEqualsConfig verifies the in-database configuration does not equal the given config.
110func assertDatabaseNotEqualsConfig(t *testing.T, expectedCfg *model.Config) {
111	t.Helper()
112
113	_, actualCfg := getActualDatabaseConfig(t)
114	assert.NotEqual(t, expectedCfg, actualCfg)
115}
116
117func newTestDatabaseStore(customDefaults *model.Config) (*Store, error) {
118	sqlSettings := mainHelper.GetSQLSettings()
119	dss, err := NewDatabaseStore(getDsn(*sqlSettings.DriverName, *sqlSettings.DataSource))
120	if err != nil {
121		return nil, err
122	}
123
124	cStore, err := NewStoreFromBacking(dss, customDefaults, false)
125	if err != nil {
126		return nil, err
127	}
128
129	return cStore, nil
130}
131
132func TestDatabaseStoreNew(t *testing.T) {
133	if testing.Short() {
134		t.SkipNow()
135	}
136	sqlSettings := mainHelper.GetSQLSettings()
137
138	t.Run("no existing configuration - initialization required", func(t *testing.T) {
139		ds, err := newTestDatabaseStore(nil)
140		require.NoError(t, err)
141		defer ds.Close()
142
143		assert.Equal(t, "", *ds.Get().ServiceSettings.SiteURL)
144	})
145
146	t.Run("no existing configuration with custom defaults", func(t *testing.T) {
147		truncateTables(t)
148		ds, err := newTestDatabaseStore(customConfigDefaults)
149		require.NoError(t, err)
150		defer ds.Close()
151
152		assert.Equal(t, *customConfigDefaults.ServiceSettings.SiteURL, *ds.Get().ServiceSettings.SiteURL)
153		assert.Equal(t, *customConfigDefaults.DisplaySettings.ExperimentalTimezone, *ds.Get().DisplaySettings.ExperimentalTimezone)
154	})
155
156	t.Run("existing config, initialization required", func(t *testing.T) {
157		_, tearDown := setupConfigDatabase(t, testConfig, nil)
158		defer tearDown()
159
160		ds, err := newTestDatabaseStore(nil)
161		require.NoError(t, err)
162		defer ds.Close()
163
164		assert.Equal(t, "http://TestStoreNew", *ds.Get().ServiceSettings.SiteURL)
165		assertDatabaseNotEqualsConfig(t, testConfig)
166	})
167
168	t.Run("existing config with custom defaults, initialization required", func(t *testing.T) {
169		_, tearDown := setupConfigDatabase(t, testConfig, nil)
170		defer tearDown()
171
172		ds, err := newTestDatabaseStore(customConfigDefaults)
173		require.NoError(t, err)
174		defer ds.Close()
175
176		// already existing value should not be overwritten by the
177		// custom default value
178		assert.Equal(t, "http://TestStoreNew", *ds.Get().ServiceSettings.SiteURL)
179		// not existing value should be overwritten by the custom
180		// default value
181		assert.Equal(t, *customConfigDefaults.DisplaySettings.ExperimentalTimezone, *ds.Get().DisplaySettings.ExperimentalTimezone)
182		assertDatabaseNotEqualsConfig(t, testConfig)
183	})
184
185	t.Run("already minimally configured", func(t *testing.T) {
186		_, tearDown := setupConfigDatabase(t, minimalConfigNoFF, nil)
187		defer tearDown()
188
189		ds, err := newTestDatabaseStore(nil)
190		require.NoError(t, err)
191		defer ds.Close()
192
193		assert.Equal(t, "http://minimal", *ds.Get().ServiceSettings.SiteURL)
194		assertDatabaseEqualsConfig(t, minimalConfigNoFF)
195	})
196
197	t.Run("already minimally configured with custom defaults", func(t *testing.T) {
198		_, tearDown := setupConfigDatabase(t, minimalConfigNoFF, nil)
199		defer tearDown()
200
201		ds, err := newTestDatabaseStore(customConfigDefaults)
202		require.NoError(t, err)
203		defer ds.Close()
204
205		// as the whole config has default values already, custom
206		// defaults should have no effect
207		assert.Equal(t, "http://minimal", *ds.Get().ServiceSettings.SiteURL)
208		assert.NotEqual(t, *customConfigDefaults.DisplaySettings.ExperimentalTimezone, *ds.Get().DisplaySettings.ExperimentalTimezone)
209		assertDatabaseEqualsConfig(t, minimalConfigNoFF)
210	})
211
212	t.Run("invalid url", func(t *testing.T) {
213		_, err := NewDatabaseStore("")
214		require.Error(t, err)
215
216		_, err = NewDatabaseStore("mysql")
217		require.Error(t, err)
218	})
219
220	t.Run("unsupported scheme", func(t *testing.T) {
221		_, err := NewDatabaseStore("invalid")
222		require.Error(t, err)
223	})
224
225	t.Run("unsupported scheme with valid data source", func(t *testing.T) {
226		_, err := NewDatabaseStore(fmt.Sprintf("invalid://%s", *sqlSettings.DataSource))
227		require.Error(t, err)
228	})
229}
230
231func TestDatabaseStoreGet(t *testing.T) {
232	_, tearDown := setupConfigDatabase(t, testConfig, nil)
233	defer tearDown()
234
235	ds, err := newTestDatabaseStore(nil)
236	require.NoError(t, err)
237	defer ds.Close()
238
239	cfg := ds.Get()
240	assert.Equal(t, "http://TestStoreNew", *cfg.ServiceSettings.SiteURL)
241
242	cfg2 := ds.Get()
243	assert.Equal(t, "http://TestStoreNew", *cfg.ServiceSettings.SiteURL)
244
245	assert.True(t, cfg == cfg2, "Get() returned different configuration instances")
246}
247
248func TestDatabaseStoreGetEnivironmentOverrides(t *testing.T) {
249	t.Run("get override for a string variable", func(t *testing.T) {
250		_, tearDown := setupConfigDatabase(t, testConfig, nil)
251		defer tearDown()
252
253		ds, err := newTestDatabaseStore(nil)
254		require.NoError(t, err)
255		defer ds.Close()
256
257		assert.Equal(t, "http://TestStoreNew", *ds.Get().ServiceSettings.SiteURL)
258		assert.Empty(t, ds.GetEnvironmentOverrides())
259
260		os.Setenv("MM_SERVICESETTINGS_SITEURL", "http://override")
261		defer os.Unsetenv("MM_SERVICESETTINGS_SITEURL")
262
263		ds, err = newTestDatabaseStore(nil)
264		require.NoError(t, err)
265		defer ds.Close()
266
267		assert.Equal(t, "http://override", *ds.Get().ServiceSettings.SiteURL)
268		assert.Equal(t, map[string]interface{}{"ServiceSettings": map[string]interface{}{"SiteURL": true}}, ds.GetEnvironmentOverrides())
269	})
270
271	t.Run("get override for a string variable with a custom default value", func(t *testing.T) {
272		_, tearDown := setupConfigDatabase(t, testConfig, nil)
273		defer tearDown()
274
275		ds, err := newTestDatabaseStore(customConfigDefaults)
276		require.NoError(t, err)
277		defer ds.Close()
278
279		assert.Equal(t, "http://TestStoreNew", *ds.Get().ServiceSettings.SiteURL)
280		assert.Empty(t, ds.GetEnvironmentOverrides())
281
282		os.Setenv("MM_SERVICESETTINGS_SITEURL", "http://override")
283		defer os.Unsetenv("MM_SERVICESETTINGS_SITEURL")
284
285		ds, err = newTestDatabaseStore(customConfigDefaults)
286		require.NoError(t, err)
287		defer ds.Close()
288
289		// environment override should take priority over the custom default value
290		assert.Equal(t, "http://override", *ds.Get().ServiceSettings.SiteURL)
291		assert.Equal(t, map[string]interface{}{"ServiceSettings": map[string]interface{}{"SiteURL": true}}, ds.GetEnvironmentOverrides())
292	})
293
294	t.Run("get override for a bool variable", func(t *testing.T) {
295		_, tearDown := setupConfigDatabase(t, testConfig, nil)
296		defer tearDown()
297
298		ds, err := newTestDatabaseStore(nil)
299		require.NoError(t, err)
300		defer ds.Close()
301
302		assert.Equal(t, false, *ds.Get().PluginSettings.EnableUploads)
303		assert.Empty(t, ds.GetEnvironmentOverrides())
304
305		os.Setenv("MM_PLUGINSETTINGS_ENABLEUPLOADS", "true")
306		defer os.Unsetenv("MM_PLUGINSETTINGS_ENABLEUPLOADS")
307
308		ds, err = newTestDatabaseStore(nil)
309		require.NoError(t, err)
310		defer ds.Close()
311
312		assert.Equal(t, true, *ds.Get().PluginSettings.EnableUploads)
313		assert.Equal(t, map[string]interface{}{"PluginSettings": map[string]interface{}{"EnableUploads": true}}, ds.GetEnvironmentOverrides())
314	})
315
316	t.Run("get override for an int variable", func(t *testing.T) {
317		_, tearDown := setupConfigDatabase(t, testConfig, nil)
318		defer tearDown()
319
320		ds, err := newTestDatabaseStore(nil)
321		require.NoError(t, err)
322		defer ds.Close()
323
324		assert.Equal(t, model.TeamSettingsDefaultMaxUsersPerTeam, *ds.Get().TeamSettings.MaxUsersPerTeam)
325		assert.Empty(t, ds.GetEnvironmentOverrides())
326
327		os.Setenv("MM_TEAMSETTINGS_MAXUSERSPERTEAM", "3000")
328		defer os.Unsetenv("MM_TEAMSETTINGS_MAXUSERSPERTEAM")
329
330		ds, err = newTestDatabaseStore(nil)
331		require.NoError(t, err)
332		defer ds.Close()
333
334		assert.Equal(t, 3000, *ds.Get().TeamSettings.MaxUsersPerTeam)
335		assert.Equal(t, map[string]interface{}{"TeamSettings": map[string]interface{}{"MaxUsersPerTeam": true}}, ds.GetEnvironmentOverrides())
336	})
337
338	t.Run("get override for an int64 variable", func(t *testing.T) {
339		_, tearDown := setupConfigDatabase(t, testConfig, nil)
340		defer tearDown()
341
342		ds, err := newTestDatabaseStore(nil)
343		require.NoError(t, err)
344		defer ds.Close()
345
346		assert.Equal(t, int64(63072000), *ds.Get().ServiceSettings.TLSStrictTransportMaxAge)
347		assert.Empty(t, ds.GetEnvironmentOverrides())
348
349		os.Setenv("MM_SERVICESETTINGS_TLSSTRICTTRANSPORTMAXAGE", "123456")
350		defer os.Unsetenv("MM_SERVICESETTINGS_TLSSTRICTTRANSPORTMAXAGE")
351
352		ds, err = newTestDatabaseStore(nil)
353		require.NoError(t, err)
354		defer ds.Close()
355
356		assert.Equal(t, int64(123456), *ds.Get().ServiceSettings.TLSStrictTransportMaxAge)
357		assert.Equal(t, map[string]interface{}{"ServiceSettings": map[string]interface{}{"TLSStrictTransportMaxAge": true}}, ds.GetEnvironmentOverrides())
358	})
359
360	t.Run("get override for a slice variable - one value", func(t *testing.T) {
361		_, tearDown := setupConfigDatabase(t, testConfig, nil)
362		defer tearDown()
363
364		ds, err := newTestDatabaseStore(nil)
365		require.NoError(t, err)
366		defer ds.Close()
367
368		assert.Equal(t, []string{}, ds.Get().SqlSettings.DataSourceReplicas)
369		assert.Empty(t, ds.GetEnvironmentOverrides())
370
371		os.Setenv("MM_SQLSETTINGS_DATASOURCEREPLICAS", "user:pwd@db:5432/test-db")
372		defer os.Unsetenv("MM_SQLSETTINGS_DATASOURCEREPLICAS")
373
374		ds, err = newTestDatabaseStore(nil)
375		require.NoError(t, err)
376		defer ds.Close()
377
378		assert.Equal(t, []string{"user:pwd@db:5432/test-db"}, ds.Get().SqlSettings.DataSourceReplicas)
379		assert.Equal(t, map[string]interface{}{"SqlSettings": map[string]interface{}{"DataSourceReplicas": true}}, ds.GetEnvironmentOverrides())
380	})
381
382	t.Run("get override for a slice variable - three values", func(t *testing.T) {
383		// This should work, but Viper (or we) don't parse environment variables to turn strings with spaces into slices.
384		t.Skip("not implemented yet")
385
386		_, tearDown := setupConfigDatabase(t, testConfig, nil)
387		defer tearDown()
388
389		ds, err := newTestDatabaseStore(nil)
390		require.NoError(t, err)
391		defer ds.Close()
392
393		assert.Equal(t, []string{}, ds.Get().SqlSettings.DataSourceReplicas)
394		assert.Empty(t, ds.GetEnvironmentOverrides())
395
396		os.Setenv("MM_SQLSETTINGS_DATASOURCEREPLICAS", "user:pwd@db:5432/test-db user:pwd@db2:5433/test-db2 user:pwd@db3:5434/test-db3")
397		defer os.Unsetenv("MM_SQLSETTINGS_DATASOURCEREPLICAS")
398
399		ds, err = newTestDatabaseStore(nil)
400		require.NoError(t, err)
401		defer ds.Close()
402
403		assert.Equal(t, []string{"user:pwd@db:5432/test-db", "user:pwd@db2:5433/test-db2", "user:pwd@db3:5434/test-db3"}, ds.Get().SqlSettings.DataSourceReplicas)
404		assert.Equal(t, map[string]interface{}{"SqlSettings": map[string]interface{}{"DataSourceReplicas": true}}, ds.GetEnvironmentOverrides())
405	})
406}
407
408func TestDatabaseStoreSet(t *testing.T) {
409	if testing.Short() {
410		t.SkipNow()
411	}
412
413	t.Run("set same pointer value", func(t *testing.T) {
414		t.Skip("not yet implemented")
415
416		_, tearDown := setupConfigDatabase(t, emptyConfig, nil)
417		defer tearDown()
418
419		ds, err := newTestDatabaseStore(nil)
420		require.NoError(t, err)
421		defer ds.Close()
422
423		_, _, err = ds.Set(ds.Get())
424		if assert.Error(t, err) {
425			assert.EqualError(t, err, "old configuration modified instead of cloning")
426		}
427	})
428
429	t.Run("defaults required", func(t *testing.T) {
430		_, tearDown := setupConfigDatabase(t, minimalConfig, nil)
431		defer tearDown()
432
433		ds, err := newTestDatabaseStore(nil)
434		require.NoError(t, err)
435		defer ds.Close()
436
437		newCfg := &model.Config{}
438
439		_, _, err = ds.Set(newCfg)
440		require.NoError(t, err)
441
442		assert.Equal(t, "", *ds.Get().ServiceSettings.SiteURL)
443	})
444
445	t.Run("desanitization required", func(t *testing.T) {
446		_, tearDown := setupConfigDatabase(t, ldapConfig, nil)
447		defer tearDown()
448
449		ds, err := newTestDatabaseStore(nil)
450		require.NoError(t, err)
451		defer ds.Close()
452
453		newCfg := &model.Config{}
454		newCfg.LdapSettings.BindPassword = model.NewString(model.FakeSetting)
455
456		_, _, err = ds.Set(newCfg)
457		require.NoError(t, err)
458
459		assert.Equal(t, "password", *ds.Get().LdapSettings.BindPassword)
460	})
461
462	t.Run("invalid", func(t *testing.T) {
463		_, tearDown := setupConfigDatabase(t, emptyConfig, nil)
464		defer tearDown()
465
466		ds, err := newTestDatabaseStore(nil)
467		require.NoError(t, err)
468		defer ds.Close()
469
470		newCfg := &model.Config{}
471		newCfg.ServiceSettings.SiteURL = model.NewString("invalid")
472
473		_, _, err = ds.Set(newCfg)
474		if assert.Error(t, err) {
475			assert.EqualError(t, err, "new configuration is invalid: Config.IsValid: model.config.is_valid.site_url.app_error, ")
476		}
477
478		assert.Equal(t, "", *ds.Get().ServiceSettings.SiteURL)
479	})
480
481	t.Run("duplicate ignored", func(t *testing.T) {
482		_, tearDown := setupConfigDatabase(t, minimalConfig, nil)
483		defer tearDown()
484
485		ds, err := newTestDatabaseStore(nil)
486		require.NoError(t, err)
487		defer ds.Close()
488
489		_, _, err = ds.Set(ds.Get())
490		require.NoError(t, err)
491
492		beforeID, _ := getActualDatabaseConfig(t)
493		_, _, err = ds.Set(ds.Get())
494		require.NoError(t, err)
495
496		afterID, _ := getActualDatabaseConfig(t)
497		assert.Equal(t, beforeID, afterID, "new record should not have been written")
498	})
499
500	t.Run("read-only ignored", func(t *testing.T) {
501		_, tearDown := setupConfigDatabase(t, readOnlyConfig, nil)
502		defer tearDown()
503
504		ds, err := newTestDatabaseStore(nil)
505		require.NoError(t, err)
506		defer ds.Close()
507
508		newCfg := &model.Config{
509			ServiceSettings: model.ServiceSettings{
510				SiteURL: model.NewString("http://new"),
511			},
512		}
513
514		_, _, err = ds.Set(newCfg)
515		require.NoError(t, err)
516
517		assert.Equal(t, "http://new", *ds.Get().ServiceSettings.SiteURL)
518	})
519
520	t.Run("set with automatic save", func(t *testing.T) {
521		_, tearDown := setupConfigDatabase(t, minimalConfig, nil)
522		defer tearDown()
523
524		ds, err := newTestDatabaseStore(nil)
525		require.NoError(t, err)
526		defer ds.Close()
527
528		newCfg := &model.Config{
529			ServiceSettings: model.ServiceSettings{
530				SiteURL: model.NewString("http://new"),
531			},
532		}
533
534		_, _, err = ds.Set(newCfg)
535		require.NoError(t, err)
536
537		err = ds.Load()
538		require.NoError(t, err)
539
540		assert.Equal(t, "http://new", *ds.Get().ServiceSettings.SiteURL)
541	})
542
543	t.Run("persist failed", func(t *testing.T) {
544		_, tearDown := setupConfigDatabase(t, emptyConfig, nil)
545		defer tearDown()
546
547		ds, err := newTestDatabaseStore(nil)
548		require.NoError(t, err)
549		defer ds.Close()
550
551		sqlSettings := mainHelper.GetSQLSettings()
552		db := sqlx.NewDb(mainHelper.GetSQLStore().GetMaster().Db, *sqlSettings.DriverName)
553		_, err = db.Exec("DROP TABLE Configurations")
554		require.NoError(t, err)
555
556		newCfg := minimalConfig
557
558		_, _, err = ds.Set(newCfg)
559		require.Error(t, err)
560		assert.True(t, strings.HasPrefix(err.Error(), "failed to persist: failed to query active configuration"), "unexpected error: "+err.Error())
561
562		assert.Equal(t, "", *ds.Get().ServiceSettings.SiteURL)
563	})
564
565	t.Run("persist failed: too long", func(t *testing.T) {
566		if *mainHelper.Settings.DriverName == "postgres" {
567			t.Skip("No limit for postgres")
568		}
569		_, tearDown := setupConfigDatabase(t, emptyConfig, nil)
570		defer tearDown()
571
572		ds, err := newTestDatabaseStore(nil)
573		require.NoError(t, err)
574		defer ds.Close()
575
576		longSiteURL := fmt.Sprintf("http://%s", strings.Repeat("a", MaxWriteLength))
577		newCfg := emptyConfig.Clone()
578		newCfg.ServiceSettings.SiteURL = model.NewString(longSiteURL)
579
580		_, _, err = ds.Set(newCfg)
581		require.Error(t, err)
582		assert.True(t, strings.HasPrefix(err.Error(), "failed to persist: marshalled configuration failed length check: value is too long"), "unexpected error: "+err.Error())
583	})
584
585	t.Run("listeners notified", func(t *testing.T) {
586		activeID, tearDown := setupConfigDatabase(t, emptyConfig, nil)
587		defer tearDown()
588
589		ds, err := newTestDatabaseStore(nil)
590		require.NoError(t, err)
591		defer ds.Close()
592
593		called := make(chan bool, 1)
594		callback := func(oldfg, newCfg *model.Config) {
595			called <- true
596		}
597		ds.AddListener(callback)
598
599		newCfg := minimalConfig
600
601		_, _, err = ds.Set(newCfg)
602		require.NoError(t, err)
603
604		id, _ := getActualDatabaseConfig(t)
605		assert.NotEqual(t, activeID, id, "new record should have been written")
606
607		require.True(t, wasCalled(called, 5*time.Second), "callback should have been called when config written")
608	})
609
610	t.Run("setting config without persistent feature flag", func(t *testing.T) {
611		_, tearDown := setupConfigDatabase(t, minimalConfig, nil)
612		defer tearDown()
613
614		ds, err := newTestDatabaseStore(nil)
615		require.NoError(t, err)
616		defer ds.Close()
617
618		ds.SetReadOnlyFF(true)
619		_, _, err = ds.Set(minimalConfig)
620		require.NoError(t, err)
621
622		assert.Equal(t, "http://minimal", *ds.Get().ServiceSettings.SiteURL)
623		assertDatabaseEqualsConfig(t, minimalConfigNoFF)
624	})
625
626	t.Run("setting config with persistent feature flags", func(t *testing.T) {
627		_, tearDown := setupConfigDatabase(t, minimalConfig, nil)
628		defer tearDown()
629
630		ds, err := newTestDatabaseStore(nil)
631		require.NoError(t, err)
632		defer ds.Close()
633
634		ds.SetReadOnlyFF(false)
635
636		_, _, err = ds.Set(minimalConfig)
637		require.NoError(t, err)
638
639		assert.Equal(t, "http://minimal", *ds.Get().ServiceSettings.SiteURL)
640		assertDatabaseEqualsConfig(t, minimalConfig)
641	})
642
643}
644
645func TestDatabaseStoreLoad(t *testing.T) {
646	if testing.Short() {
647		t.SkipNow()
648	}
649
650	t.Run("active configuration no longer exists", func(t *testing.T) {
651		_, tearDown := setupConfigDatabase(t, emptyConfig, nil)
652		defer tearDown()
653
654		ds, err := newTestDatabaseStore(nil)
655		require.NoError(t, err)
656		defer ds.Close()
657
658		truncateTables(t)
659
660		err = ds.Load()
661		require.NoError(t, err)
662		assertDatabaseNotEqualsConfig(t, emptyConfig)
663	})
664
665	t.Run("honour environment", func(t *testing.T) {
666		_, tearDown := setupConfigDatabase(t, minimalConfig, nil)
667		defer tearDown()
668
669		ds, err := newTestDatabaseStore(nil)
670		require.NoError(t, err)
671		defer ds.Close()
672
673		assert.Equal(t, "http://minimal", *ds.Get().ServiceSettings.SiteURL)
674
675		os.Setenv("MM_SERVICESETTINGS_SITEURL", "http://override")
676		defer os.Unsetenv("MM_SERVICESETTINGS_SITEURL")
677
678		err = ds.Load()
679		require.NoError(t, err)
680		assert.Equal(t, "http://override", *ds.Get().ServiceSettings.SiteURL)
681		assert.Equal(t, map[string]interface{}{"ServiceSettings": map[string]interface{}{"SiteURL": true}}, ds.GetEnvironmentOverrides())
682	})
683
684	t.Run("do not persist environment variables - string", func(t *testing.T) {
685		_, tearDown := setupConfigDatabase(t, minimalConfig, nil)
686		defer tearDown()
687
688		os.Setenv("MM_SERVICESETTINGS_SITEURL", "http://overridePersistEnvVariables")
689		defer os.Unsetenv("MM_SERVICESETTINGS_SITEURL")
690
691		ds, err := newTestDatabaseStore(nil)
692		require.NoError(t, err)
693		defer ds.Close()
694
695		_, _, err = ds.Set(ds.Get())
696		require.NoError(t, err)
697
698		assert.Equal(t, "http://overridePersistEnvVariables", *ds.Get().ServiceSettings.SiteURL)
699		assert.Equal(t, map[string]interface{}{"ServiceSettings": map[string]interface{}{"SiteURL": true}}, ds.GetEnvironmentOverrides())
700		// check that in DB config does not include overwritten variable
701		_, actualConfig := getActualDatabaseConfig(t)
702		assert.Equal(t, "http://minimal", *actualConfig.ServiceSettings.SiteURL)
703	})
704
705	t.Run("do not persist environment variables - boolean", func(t *testing.T) {
706		_, tearDown := setupConfigDatabase(t, minimalConfig, nil)
707		defer tearDown()
708
709		os.Setenv("MM_PLUGINSETTINGS_ENABLEUPLOADS", "true")
710		defer os.Unsetenv("MM_PLUGINSETTINGS_ENABLEUPLOADS")
711
712		ds, err := newTestDatabaseStore(nil)
713		require.NoError(t, err)
714		defer ds.Close()
715
716		assert.Equal(t, true, *ds.Get().PluginSettings.EnableUploads)
717
718		_, _, err = ds.Set(ds.Get())
719		require.NoError(t, err)
720
721		assert.Equal(t, true, *ds.Get().PluginSettings.EnableUploads)
722		assert.Equal(t, map[string]interface{}{"PluginSettings": map[string]interface{}{"EnableUploads": true}}, ds.GetEnvironmentOverrides())
723		// check that in DB config does not include overwritten variable
724		_, actualConfig := getActualDatabaseConfig(t)
725		assert.Equal(t, false, *actualConfig.PluginSettings.EnableUploads)
726	})
727
728	t.Run("do not persist environment variables - int", func(t *testing.T) {
729		_, tearDown := setupConfigDatabase(t, minimalConfig, nil)
730		defer tearDown()
731
732		os.Setenv("MM_TEAMSETTINGS_MAXUSERSPERTEAM", "3000")
733		defer os.Unsetenv("MM_TEAMSETTINGS_MAXUSERSPERTEAM")
734
735		ds, err := newTestDatabaseStore(nil)
736		require.NoError(t, err)
737		defer ds.Close()
738
739		assert.Equal(t, 3000, *ds.Get().TeamSettings.MaxUsersPerTeam)
740
741		_, _, err = ds.Set(ds.Get())
742		require.NoError(t, err)
743
744		assert.Equal(t, 3000, *ds.Get().TeamSettings.MaxUsersPerTeam)
745		assert.Equal(t, map[string]interface{}{"TeamSettings": map[string]interface{}{"MaxUsersPerTeam": true}}, ds.GetEnvironmentOverrides())
746		// check that in DB config does not include overwritten variable
747		_, actualConfig := getActualDatabaseConfig(t)
748		assert.Equal(t, model.TeamSettingsDefaultMaxUsersPerTeam, *actualConfig.TeamSettings.MaxUsersPerTeam)
749	})
750
751	t.Run("do not persist environment variables - int64", func(t *testing.T) {
752		_, tearDown := setupConfigDatabase(t, minimalConfig, nil)
753		defer tearDown()
754
755		os.Setenv("MM_SERVICESETTINGS_TLSSTRICTTRANSPORTMAXAGE", "123456")
756		defer os.Unsetenv("MM_SERVICESETTINGS_TLSSTRICTTRANSPORTMAXAGE")
757
758		ds, err := newTestDatabaseStore(nil)
759		require.NoError(t, err)
760		defer ds.Close()
761
762		assert.Equal(t, int64(123456), *ds.Get().ServiceSettings.TLSStrictTransportMaxAge)
763
764		_, _, err = ds.Set(ds.Get())
765		require.NoError(t, err)
766
767		assert.Equal(t, int64(123456), *ds.Get().ServiceSettings.TLSStrictTransportMaxAge)
768		assert.Equal(t, map[string]interface{}{"ServiceSettings": map[string]interface{}{"TLSStrictTransportMaxAge": true}}, ds.GetEnvironmentOverrides())
769		// check that in DB config does not include overwritten variable
770		_, actualConfig := getActualDatabaseConfig(t)
771		assert.Equal(t, int64(63072000), *actualConfig.ServiceSettings.TLSStrictTransportMaxAge)
772	})
773
774	t.Run("do not persist environment variables - string slice beginning with default", func(t *testing.T) {
775		_, tearDown := setupConfigDatabase(t, minimalConfig, nil)
776		defer tearDown()
777
778		os.Setenv("MM_SQLSETTINGS_DATASOURCEREPLICAS", "user:pwd@db:5432/test-db")
779		defer os.Unsetenv("MM_SQLSETTINGS_DATASOURCEREPLICAS")
780
781		ds, err := newTestDatabaseStore(nil)
782		require.NoError(t, err)
783		defer ds.Close()
784
785		assert.Equal(t, []string{"user:pwd@db:5432/test-db"}, ds.Get().SqlSettings.DataSourceReplicas)
786
787		_, _, err = ds.Set(ds.Get())
788		require.NoError(t, err)
789
790		assert.Equal(t, []string{"user:pwd@db:5432/test-db"}, ds.Get().SqlSettings.DataSourceReplicas)
791		assert.Equal(t, map[string]interface{}{"SqlSettings": map[string]interface{}{"DataSourceReplicas": true}}, ds.GetEnvironmentOverrides())
792		// check that in DB config does not include overwritten variable
793		_, actualConfig := getActualDatabaseConfig(t)
794		assert.Equal(t, []string{}, actualConfig.SqlSettings.DataSourceReplicas)
795	})
796
797	t.Run("do not persist environment variables - string slice beginning with slice of three", func(t *testing.T) {
798		modifiedMinimalConfig := minimalConfig.Clone()
799		modifiedMinimalConfig.SqlSettings.DataSourceReplicas = []string{"user:pwd@db:5432/test-db", "user:pwd@db2:5433/test-db2", "user:pwd@db3:5434/test-db3"}
800		_, tearDown := setupConfigDatabase(t, modifiedMinimalConfig, nil)
801		defer tearDown()
802
803		os.Setenv("MM_SQLSETTINGS_DATASOURCEREPLICAS", "user:pwd@db:5432/test-db")
804		defer os.Unsetenv("MM_SQLSETTINGS_DATASOURCEREPLICAS")
805
806		ds, err := newTestDatabaseStore(nil)
807		require.NoError(t, err)
808		defer ds.Close()
809
810		assert.Equal(t, []string{"user:pwd@db:5432/test-db"}, ds.Get().SqlSettings.DataSourceReplicas)
811
812		_, _, err = ds.Set(ds.Get())
813		require.NoError(t, err)
814
815		assert.Equal(t, []string{"user:pwd@db:5432/test-db"}, ds.Get().SqlSettings.DataSourceReplicas)
816		assert.Equal(t, map[string]interface{}{"SqlSettings": map[string]interface{}{"DataSourceReplicas": true}}, ds.GetEnvironmentOverrides())
817		// check that in DB config does not include overwritten variable
818		_, actualConfig := getActualDatabaseConfig(t)
819		assert.Equal(t, []string{"user:pwd@db:5432/test-db", "user:pwd@db2:5433/test-db2", "user:pwd@db3:5434/test-db3"}, actualConfig.SqlSettings.DataSourceReplicas)
820	})
821
822	t.Run("invalid", func(t *testing.T) {
823		_, tearDown := setupConfigDatabase(t, emptyConfig, nil)
824		defer tearDown()
825
826		ds, err := newTestDatabaseStore(nil)
827		require.NoError(t, err)
828		defer ds.Close()
829
830		cfgData, err := marshalConfig(invalidConfig)
831		require.NoError(t, err)
832
833		sqlSettings := mainHelper.GetSQLSettings()
834		db := sqlx.NewDb(mainHelper.GetSQLStore().GetMaster().Db, *sqlSettings.DriverName)
835		truncateTables(t)
836		id := model.NewId()
837		_, err = db.NamedExec("INSERT INTO Configurations (Id, Value, CreateAt, Active) VALUES(:Id, :Value, :CreateAt, TRUE)", map[string]interface{}{
838			"Id":       id,
839			"Value":    cfgData,
840			"CreateAt": model.GetMillis(),
841		})
842		require.NoError(t, err)
843
844		err = ds.Load()
845		if assert.Error(t, err) {
846			assert.EqualError(t, err, "invalid config: Config.IsValid: model.config.is_valid.site_url.app_error, ")
847		}
848	})
849
850	t.Run("fixes required", func(t *testing.T) {
851		_, tearDown := setupConfigDatabase(t, fixesRequiredConfig, nil)
852		defer tearDown()
853
854		ds, err := newTestDatabaseStore(nil)
855		require.NoError(t, err)
856		defer ds.Close()
857
858		err = ds.Load()
859		require.NoError(t, err)
860		assertDatabaseNotEqualsConfig(t, fixesRequiredConfig)
861		assert.Equal(t, "http://trailingslash", *ds.Get().ServiceSettings.SiteURL)
862	})
863
864	t.Run("listeners notifed on change", func(t *testing.T) {
865		_, tearDown := setupConfigDatabase(t, emptyConfig, nil)
866		defer tearDown()
867
868		ds, err := newTestDatabaseStore(nil)
869		require.NoError(t, err)
870		defer ds.Close()
871
872		called := make(chan bool, 1)
873		callback := func(oldfg, newCfg *model.Config) {
874			called <- true
875		}
876		ds.AddListener(callback)
877
878		newCfg := minimalConfig.Clone()
879		dbStore, ok := ds.backingStore.(*DatabaseStore)
880		require.True(t, ok)
881		err = dbStore.persist(newCfg)
882		require.NoError(t, err)
883
884		err = ds.Load()
885		require.NoError(t, err)
886
887		require.True(t, wasCalled(called, 5*time.Second), "callback should have been called when config changed on load")
888	})
889}
890
891func TestDatabaseGetFile(t *testing.T) {
892	_, tearDown := setupConfigDatabase(t, minimalConfig, map[string][]byte{
893		"empty-file": {},
894		"test-file":  []byte("test"),
895	})
896	defer tearDown()
897
898	ds, err := newTestDatabaseStore(nil)
899	require.NoError(t, err)
900	defer ds.Close()
901
902	t.Run("get empty filename", func(t *testing.T) {
903		_, err := ds.GetFile("")
904		require.Error(t, err)
905	})
906
907	t.Run("get non-existent file", func(t *testing.T) {
908		_, err := ds.GetFile("unknown")
909		require.Error(t, err)
910	})
911
912	t.Run("get empty file", func(t *testing.T) {
913		data, err := ds.GetFile("empty-file")
914		require.NoError(t, err)
915		require.Empty(t, data)
916	})
917
918	t.Run("get non-empty file", func(t *testing.T) {
919		data, err := ds.GetFile("test-file")
920		require.NoError(t, err)
921		require.Equal(t, []byte("test"), data)
922	})
923}
924
925func TestDatabaseSetFile(t *testing.T) {
926	_, tearDown := setupConfigDatabase(t, minimalConfig, nil)
927	defer tearDown()
928
929	ds, err := newTestDatabaseStore(nil)
930	require.NoError(t, err)
931	defer ds.Close()
932
933	t.Run("set new file", func(t *testing.T) {
934		err := ds.SetFile("new", []byte("new file"))
935		require.NoError(t, err)
936
937		data, err := ds.GetFile("new")
938		require.NoError(t, err)
939		require.Equal(t, []byte("new file"), data)
940	})
941
942	t.Run("overwrite existing file", func(t *testing.T) {
943		err := ds.SetFile("existing", []byte("existing file"))
944		require.NoError(t, err)
945
946		err = ds.SetFile("existing", []byte("overwritten file"))
947		require.NoError(t, err)
948
949		data, err := ds.GetFile("existing")
950		require.NoError(t, err)
951		require.Equal(t, []byte("overwritten file"), data)
952	})
953
954	t.Run("max length", func(t *testing.T) {
955		if *mainHelper.Settings.DriverName == "postgres" {
956			t.Skip("No limit for postgres")
957		}
958		longFile := bytes.Repeat([]byte("a"), MaxWriteLength)
959
960		err := ds.SetFile("toolong", longFile)
961		require.NoError(t, err)
962	})
963
964	t.Run("too long", func(t *testing.T) {
965		if *mainHelper.Settings.DriverName == "postgres" {
966			t.Skip("No limit for postgres")
967		}
968		longFile := bytes.Repeat([]byte("a"), MaxWriteLength+1)
969
970		err := ds.SetFile("toolong", longFile)
971		if assert.Error(t, err) {
972			assert.True(t, strings.HasPrefix(err.Error(), "file data failed length check: value is too long"))
973		}
974	})
975}
976
977func TestDatabaseHasFile(t *testing.T) {
978	t.Run("has non-existent", func(t *testing.T) {
979		_, tearDown := setupConfigDatabase(t, minimalConfig, nil)
980		defer tearDown()
981
982		ds, err := newTestDatabaseStore(nil)
983		require.NoError(t, err)
984		defer ds.Close()
985
986		has, err := ds.HasFile("non-existent")
987		require.NoError(t, err)
988		require.False(t, has)
989	})
990
991	t.Run("has existing", func(t *testing.T) {
992		_, tearDown := setupConfigDatabase(t, minimalConfig, nil)
993		defer tearDown()
994
995		ds, err := newTestDatabaseStore(nil)
996		require.NoError(t, err)
997		defer ds.Close()
998
999		err = ds.SetFile("existing", []byte("existing file"))
1000		require.NoError(t, err)
1001
1002		has, err := ds.HasFile("existing")
1003		require.NoError(t, err)
1004		require.True(t, has)
1005	})
1006
1007	t.Run("has manually created file", func(t *testing.T) {
1008		_, tearDown := setupConfigDatabase(t, minimalConfig, map[string][]byte{
1009			"manual": []byte("manual file"),
1010		})
1011		defer tearDown()
1012
1013		ds, err := newTestDatabaseStore(nil)
1014		require.NoError(t, err)
1015		defer ds.Close()
1016
1017		has, err := ds.HasFile("manual")
1018		require.NoError(t, err)
1019		require.True(t, has)
1020	})
1021
1022	t.Run("has non-existent empty string", func(t *testing.T) {
1023		_, tearDown := setupConfigDatabase(t, minimalConfig, nil)
1024		defer tearDown()
1025
1026		ds, err := newTestDatabaseStore(nil)
1027		require.NoError(t, err)
1028		defer ds.Close()
1029
1030		has, err := ds.HasFile("")
1031		require.NoError(t, err)
1032		require.False(t, has)
1033	})
1034}
1035
1036func TestDatabaseRemoveFile(t *testing.T) {
1037	t.Run("remove non-existent", func(t *testing.T) {
1038		_, tearDown := setupConfigDatabase(t, minimalConfig, nil)
1039		defer tearDown()
1040
1041		ds, err := newTestDatabaseStore(nil)
1042		require.NoError(t, err)
1043		defer ds.Close()
1044
1045		err = ds.RemoveFile("non-existent")
1046		require.NoError(t, err)
1047	})
1048
1049	t.Run("remove existing", func(t *testing.T) {
1050		_, tearDown := setupConfigDatabase(t, minimalConfig, nil)
1051		defer tearDown()
1052
1053		ds, err := newTestDatabaseStore(nil)
1054		require.NoError(t, err)
1055		defer ds.Close()
1056
1057		err = ds.SetFile("existing", []byte("existing file"))
1058		require.NoError(t, err)
1059
1060		err = ds.RemoveFile("existing")
1061		require.NoError(t, err)
1062
1063		has, err := ds.HasFile("existing")
1064		require.NoError(t, err)
1065		require.False(t, has)
1066
1067		_, err = ds.GetFile("existing")
1068		require.Error(t, err)
1069	})
1070
1071	t.Run("remove manually created file", func(t *testing.T) {
1072		_, tearDown := setupConfigDatabase(t, minimalConfig, map[string][]byte{
1073			"manual": []byte("manual file"),
1074		})
1075		defer tearDown()
1076
1077		ds, err := newTestDatabaseStore(nil)
1078		require.NoError(t, err)
1079		defer ds.Close()
1080
1081		err = ds.RemoveFile("manual")
1082		require.NoError(t, err)
1083
1084		has, err := ds.HasFile("manual")
1085		require.NoError(t, err)
1086		require.False(t, has)
1087
1088		_, err = ds.GetFile("manual")
1089		require.Error(t, err)
1090	})
1091}
1092
1093func TestDatabaseStoreString(t *testing.T) {
1094	if testing.Short() {
1095		t.SkipNow()
1096	}
1097	_, tearDown := setupConfigDatabase(t, emptyConfig, nil)
1098	defer tearDown()
1099
1100	ds, err := newTestDatabaseStore(nil)
1101	require.NoError(t, err)
1102	require.NotNil(t, ds)
1103	defer ds.Close()
1104
1105	if *mainHelper.GetSQLSettings().DriverName == "postgres" {
1106		maskedDSN := ds.String()
1107		assert.True(t, strings.HasPrefix(maskedDSN, "postgres://"))
1108		assert.True(t, strings.Contains(maskedDSN, "mmuser"))
1109		assert.False(t, strings.Contains(maskedDSN, "mostest"))
1110	} else {
1111		maskedDSN := ds.String()
1112		assert.True(t, strings.HasPrefix(maskedDSN, "mysql://"))
1113		assert.True(t, strings.Contains(maskedDSN, "mmuser"))
1114		assert.False(t, strings.Contains(maskedDSN, "mostest"))
1115	}
1116}
1117