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