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