github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/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 5 6 import ( 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/masterhung0112/hk_server/v5/config" 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 := 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. 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.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. 102 func 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. 110 func assertDatabaseNotEqualsConfig(t *testing.T, expectedCfg *model.Config) { 111 t.Helper() 112 113 _, actualCfg := getActualDatabaseConfig(t) 114 assert.NotEqual(t, expectedCfg, actualCfg) 115 } 116 117 func 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 132 func 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 231 func 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 248 func 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.TEAM_SETTINGS_DEFAULT_MAX_USERS_PER_TEAM, *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 408 func 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.FAKE_SETTING) 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 645 func 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.TEAM_SETTINGS_DEFAULT_MAX_USERS_PER_TEAM, *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 newCfg := minimalConfig.Clone() 878 dbStore, ok := ds.backingStore.(*DatabaseStore) 879 require.True(t, ok) 880 err = dbStore.persist(newCfg) 881 require.NoError(t, err) 882 883 err = ds.Load() 884 require.NoError(t, err) 885 886 require.True(t, wasCalled(called, 5*time.Second), "callback should have been called when config changed on load") 887 }) 888 } 889 890 func TestDatabaseGetFile(t *testing.T) { 891 _, tearDown := setupConfigDatabase(t, minimalConfig, map[string][]byte{ 892 "empty-file": {}, 893 "test-file": []byte("test"), 894 }) 895 defer tearDown() 896 897 ds, err := newTestDatabaseStore(nil) 898 require.NoError(t, err) 899 defer ds.Close() 900 901 t.Run("get empty filename", func(t *testing.T) { 902 _, err := ds.GetFile("") 903 require.Error(t, err) 904 }) 905 906 t.Run("get non-existent file", func(t *testing.T) { 907 _, err := ds.GetFile("unknown") 908 require.Error(t, err) 909 }) 910 911 t.Run("get empty file", func(t *testing.T) { 912 data, err := ds.GetFile("empty-file") 913 require.NoError(t, err) 914 require.Empty(t, data) 915 }) 916 917 t.Run("get non-empty file", func(t *testing.T) { 918 data, err := ds.GetFile("test-file") 919 require.NoError(t, err) 920 require.Equal(t, []byte("test"), data) 921 }) 922 } 923 924 func TestDatabaseSetFile(t *testing.T) { 925 _, tearDown := setupConfigDatabase(t, minimalConfig, nil) 926 defer tearDown() 927 928 ds, err := newTestDatabaseStore(nil) 929 require.NoError(t, err) 930 defer ds.Close() 931 932 t.Run("set new file", func(t *testing.T) { 933 err := ds.SetFile("new", []byte("new file")) 934 require.NoError(t, err) 935 936 data, err := ds.GetFile("new") 937 require.NoError(t, err) 938 require.Equal(t, []byte("new file"), data) 939 }) 940 941 t.Run("overwrite existing file", func(t *testing.T) { 942 err := ds.SetFile("existing", []byte("existing file")) 943 require.NoError(t, err) 944 945 err = ds.SetFile("existing", []byte("overwritten file")) 946 require.NoError(t, err) 947 948 data, err := ds.GetFile("existing") 949 require.NoError(t, err) 950 require.Equal(t, []byte("overwritten file"), data) 951 }) 952 953 t.Run("max length", func(t *testing.T) { 954 if *mainHelper.Settings.DriverName == "postgres" { 955 t.Skip("No limit for postgres") 956 } 957 longFile := bytes.Repeat([]byte("a"), MaxWriteLength) 958 959 err := ds.SetFile("toolong", longFile) 960 require.NoError(t, err) 961 }) 962 963 t.Run("too long", func(t *testing.T) { 964 if *mainHelper.Settings.DriverName == "postgres" { 965 t.Skip("No limit for postgres") 966 } 967 longFile := bytes.Repeat([]byte("a"), MaxWriteLength+1) 968 969 err := ds.SetFile("toolong", longFile) 970 if assert.Error(t, err) { 971 assert.True(t, strings.HasPrefix(err.Error(), "file data failed length check: value is too long")) 972 } 973 }) 974 } 975 976 func TestDatabaseHasFile(t *testing.T) { 977 t.Run("has non-existent", func(t *testing.T) { 978 _, tearDown := setupConfigDatabase(t, minimalConfig, nil) 979 defer tearDown() 980 981 ds, err := newTestDatabaseStore(nil) 982 require.NoError(t, err) 983 defer ds.Close() 984 985 has, err := ds.HasFile("non-existent") 986 require.NoError(t, err) 987 require.False(t, has) 988 }) 989 990 t.Run("has existing", func(t *testing.T) { 991 _, tearDown := setupConfigDatabase(t, minimalConfig, nil) 992 defer tearDown() 993 994 ds, err := newTestDatabaseStore(nil) 995 require.NoError(t, err) 996 defer ds.Close() 997 998 err = ds.SetFile("existing", []byte("existing file")) 999 require.NoError(t, err) 1000 1001 has, err := ds.HasFile("existing") 1002 require.NoError(t, err) 1003 require.True(t, has) 1004 }) 1005 1006 t.Run("has manually created file", func(t *testing.T) { 1007 _, tearDown := setupConfigDatabase(t, minimalConfig, map[string][]byte{ 1008 "manual": []byte("manual file"), 1009 }) 1010 defer tearDown() 1011 1012 ds, err := newTestDatabaseStore(nil) 1013 require.NoError(t, err) 1014 defer ds.Close() 1015 1016 has, err := ds.HasFile("manual") 1017 require.NoError(t, err) 1018 require.True(t, has) 1019 }) 1020 1021 t.Run("has non-existent empty string", func(t *testing.T) { 1022 _, tearDown := setupConfigDatabase(t, minimalConfig, nil) 1023 defer tearDown() 1024 1025 ds, err := newTestDatabaseStore(nil) 1026 require.NoError(t, err) 1027 defer ds.Close() 1028 1029 has, err := ds.HasFile("") 1030 require.NoError(t, err) 1031 require.False(t, has) 1032 }) 1033 } 1034 1035 func TestDatabaseRemoveFile(t *testing.T) { 1036 t.Run("remove non-existent", func(t *testing.T) { 1037 _, tearDown := setupConfigDatabase(t, minimalConfig, nil) 1038 defer tearDown() 1039 1040 ds, err := newTestDatabaseStore(nil) 1041 require.NoError(t, err) 1042 defer ds.Close() 1043 1044 err = ds.RemoveFile("non-existent") 1045 require.NoError(t, err) 1046 }) 1047 1048 t.Run("remove existing", func(t *testing.T) { 1049 _, tearDown := setupConfigDatabase(t, minimalConfig, nil) 1050 defer tearDown() 1051 1052 ds, err := newTestDatabaseStore(nil) 1053 require.NoError(t, err) 1054 defer ds.Close() 1055 1056 err = ds.SetFile("existing", []byte("existing file")) 1057 require.NoError(t, err) 1058 1059 err = ds.RemoveFile("existing") 1060 require.NoError(t, err) 1061 1062 has, err := ds.HasFile("existing") 1063 require.NoError(t, err) 1064 require.False(t, has) 1065 1066 _, err = ds.GetFile("existing") 1067 require.Error(t, err) 1068 }) 1069 1070 t.Run("remove manually created file", func(t *testing.T) { 1071 _, tearDown := setupConfigDatabase(t, minimalConfig, map[string][]byte{ 1072 "manual": []byte("manual file"), 1073 }) 1074 defer tearDown() 1075 1076 ds, err := newTestDatabaseStore(nil) 1077 require.NoError(t, err) 1078 defer ds.Close() 1079 1080 err = ds.RemoveFile("manual") 1081 require.NoError(t, err) 1082 1083 has, err := ds.HasFile("manual") 1084 require.NoError(t, err) 1085 require.False(t, has) 1086 1087 _, err = ds.GetFile("manual") 1088 require.Error(t, err) 1089 }) 1090 } 1091 1092 func TestDatabaseStoreString(t *testing.T) { 1093 if testing.Short() { 1094 t.SkipNow() 1095 } 1096 _, tearDown := setupConfigDatabase(t, emptyConfig, nil) 1097 defer tearDown() 1098 1099 ds, err := newTestDatabaseStore(nil) 1100 require.NoError(t, err) 1101 require.NotNil(t, ds) 1102 defer ds.Close() 1103 1104 if *mainHelper.GetSQLSettings().DriverName == "postgres" { 1105 maskedDSN := ds.String() 1106 assert.True(t, strings.HasPrefix(maskedDSN, "postgres://")) 1107 assert.True(t, strings.Contains(maskedDSN, "hkuser")) 1108 assert.False(t, strings.Contains(maskedDSN, "mostest")) 1109 } else { 1110 maskedDSN := ds.String() 1111 assert.True(t, strings.HasPrefix(maskedDSN, "mysql://")) 1112 assert.True(t, strings.Contains(maskedDSN, "hkuser")) 1113 assert.False(t, strings.Contains(maskedDSN, "mostest")) 1114 } 1115 }