From d39d9a5cafb889f420a184c693d942345d8100c7 Mon Sep 17 00:00:00 2001 From: Jesse Hallam Date: Thu, 6 Dec 2018 13:19:32 -0500 Subject: [PATCH] Dockerized build updated tests (#9943) * testlib: introduce and leverage This doesn't yet factor out the individual test helpers: many packages still rely on `api4` directly to do this, but now wire up the test store setup through this package. `app` and `store`, in particular, don't use `testlib` because of circular dependencies at the moment. * cmd: command_test.go: use api4 testlib * cmd: plugin_test.go: remove dependence on test-config.json * cmd: config_test.go use configured database settings * ensure test-(te|ee) exit with status code * test-server: run all tests, deprecating test-te/test-ee * cmd/mattermost/commands: fix unit tests Instead of relying on (and modifying) a config.json found in the current path, explicitly create a temporary one from defaults for each test. This was likely the source of various bugs over time, but specifically allows us to override the SqlSettings to point at the configured test database for all tests simultaneously. * wrap run/check into a test helper It was insufficient to set a config for each invocation of CheckCommand or RunCommand: some tests relied on the config having changed in a subsequent assertion. Instead, create a new test helper embedding api4.TestHelper. This has the nice advantage of cleaning up all the teardown. * additional TestConfigGet granularity * customized config path to avoid default location * be explicit if the storetest initialization fails * generate safe coverprofile names in the presence of subtests * additional TestConfigShow granularity * fix permission_test.go typo * fix webhook tests * actually flag.Parse() to skip database setup on os.Execed tests * fix recent regression in #9962, not caught by unit tests --- Makefile | 44 +- api4/api_test.go | 52 --- api4/apitestlib.go | 42 +- api4/main_test.go | 20 + api4/user.go | 2 +- app/app_test.go | 47 +- app/{apptestlib.go => helper_test.go} | 89 +--- app/main_test.go | 19 + build/legacy.mk | 5 + cmd/mattermost/commands/channel_test.go | 47 +- cmd/mattermost/commands/cmdtestlib.go | 147 ++++++- cmd/mattermost/commands/command_test.go | 28 +- cmd/mattermost/commands/config_flag_test.go | 30 +- cmd/mattermost/commands/config_test.go | 164 ++++--- cmd/mattermost/commands/export_test.go | 58 ++- cmd/mattermost/commands/main_test.go | 30 ++ cmd/mattermost/commands/permissions_test.go | 31 +- cmd/mattermost/commands/plugin_test.go | 27 +- cmd/mattermost/commands/roles_test.go | 7 +- cmd/mattermost/commands/sampledata_test.go | 9 +- cmd/mattermost/commands/team_test.go | 55 ++- cmd/mattermost/commands/user_test.go | 37 +- cmd/mattermost/commands/version_test.go | 5 +- cmd/mattermost/commands/webhook_test.go | 90 ++-- .../{migrationstestlib.go => helper_test.go} | 84 +--- migrations/main_test.go | 19 + migrations/migrations_test.go | 43 -- scripts/test.sh | 27 ++ testlib/cluster.go | 55 +++ testlib/doc.go | 5 + testlib/helper.go | 70 +++ testlib/store.go | 15 + tests/test-config.json | 401 ------------------ web/handlers_test.go | 4 +- web/main_test.go | 19 + web/web_test.go | 58 +-- 36 files changed, 775 insertions(+), 1110 deletions(-) delete mode 100644 api4/api_test.go create mode 100644 api4/main_test.go rename app/{apptestlib.go => helper_test.go} (75%) create mode 100644 app/main_test.go create mode 100644 build/legacy.mk create mode 100644 cmd/mattermost/commands/main_test.go rename migrations/{migrationstestlib.go => helper_test.go} (66%) create mode 100644 migrations/main_test.go create mode 100755 scripts/test.sh create mode 100644 testlib/cluster.go create mode 100644 testlib/doc.go create mode 100644 testlib/helper.go create mode 100644 testlib/store.go delete mode 100644 tests/test-config.json create mode 100644 web/main_test.go diff --git a/Makefile b/Makefile index d5bf0f7ed5d..0bc56e3ad3d 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,6 @@ TESTFLAGSEE ?= -short # Packages lists TE_PACKAGES=$(shell go list ./...) -TE_PACKAGES_COMMA=$(shell echo $(TE_PACKAGES) | tr ' ' ',') # Plugins Packages PLUGIN_PACKAGES=mattermost-plugin-zoom mattermost-plugin-jira @@ -83,15 +82,16 @@ ifeq ($(BUILD_ENTERPRISE_READY),true) IGNORE:=$(shell cp $(BUILD_ENTERPRISE_DIR)/imports/imports.go imports/) IGNORE:=$(shell rm -f enterprise) IGNORE:=$(shell ln -s $(BUILD_ENTERPRISE_DIR) enterprise) +else + IGNORE:=$(shell rm -f imports/imports.go) endif EE_PACKAGES=$(shell go list ./enterprise/...) -EE_PACKAGES_COMMA=$(shell echo $(EE_PACKAGES) | tr ' ' ',') ifeq ($(BUILD_ENTERPRISE_READY),true) -ALL_PACKAGES_COMMA=$(TE_PACKAGES_COMMA),$(EE_PACKAGES_COMMA) +ALL_PACKAGES=$(TE_PACKAGES) $(EE_PACKAGES) else -ALL_PACKAGES_COMMA=$(TE_PACKAGES_COMMA) +ALL_PACKAGES=$(TE_PACKAGES) endif @@ -354,33 +354,6 @@ do-cover-file: ## Creates the test coverage report file. go-junit-report: go get -u github.com/jstemmer/go-junit-report -test-te: start-docker go-junit-report do-cover-file ## Runs tests in the team edition. - @echo Testing TE - @echo "Packages to test: "$(TE_PACKAGES) - find . -name 'cprofile*.out' -exec sh -c 'rm "{}"' \; - $(GO) test $(GOFLAGS) -run=$(TESTS) $(TESTFLAGS) -p 1 -v -timeout=2000s -covermode=count -coverpkg=$(ALL_PACKAGES_COMMA) -exec $(ROOT)/scripts/test-xprog.sh $(TE_PACKAGES) | tee output-test-te - cat output-test-te | $(GOPATH)/bin/go-junit-report > report-te.xml && rm output-test-te - find . -name 'cprofile*.out' -exec sh -c 'tail -n +2 {} >> cover.out ; rm "{}"' \; - -test-ee: start-docker go-junit-report do-cover-file ## Runs tests in the enterprise edition. - @echo Testing EE - - rm -f enterprise/config/*.crt - rm -f enterprise/config/*.key - -ifeq ($(BUILD_ENTERPRISE_READY),true) - @echo Testing EE - @echo "Packages to test: "$(EE_PACKAGES) - find . -name 'cprofile*.out' -exec sh -c 'rm "{}"' \; - $(GO) test $(GOFLAGS) -run=$(TESTS) $(TESTFLAGSEE) -p 1 -v -timeout=2000s -covermode=count -coverpkg=$(ALL_PACKAGES_COMMA) -exec $(ROOT)/scripts/test-xprog.sh $(EE_PACKAGES) 2>&1 | tee output-test-ee - cat output-test-ee | $(GOPATH)/bin/go-junit-report > report-ee.xml && rm output-test-ee - find . -name 'cprofile*.out' -exec sh -c 'tail -n +2 {} >> cover.out ; rm "{}"' \; - rm -f enterprise/config/*.crt - rm -f enterprise/config/*.key -else - @echo Skipping EE Tests -endif - test-compile: @echo COMPILE TESTS @@ -388,8 +361,13 @@ test-compile: $(GO) test $(GOFLAGS) -c $$package; \ done -test-server: test-te test-ee ## Runs tests. - find . -type d -name data -not -path './vendor/*' | xargs rm -rf +test-server: start-docker go-junit-report do-cover-file ## Runs tests. +ifeq ($(BUILD_ENTERPRISE_READY),true) + @echo Running all tests +else + @echo Running only TE tests +endif + ./scripts/test.sh "$(GO)" "$(GOFLAGS)" "$(ALL_PACKAGES)" "$(TESTS)" "$(TESTFLAGS)" internal-test-web-client: ## Runs web client tests. $(GO) run $(GOFLAGS) $(PLATFORM_FILES) test web_client_tests diff --git a/api4/api_test.go b/api4/api_test.go deleted file mode 100644 index 2efd21f22eb..00000000000 --- a/api4/api_test.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. -// See License.txt for license information. - -package api4 - -import ( - "flag" - "os" - "testing" - - "github.com/mattermost/mattermost-server/mlog" - "github.com/mattermost/mattermost-server/store/storetest" - "github.com/mattermost/mattermost-server/utils" -) - -func TestMain(m *testing.M) { - flag.Parse() - - // Setup a global logger to catch tests logging outside of app context - // The global logger will be stomped by apps initalizing but that's fine for testing. Ideally this won't happen. - mlog.InitGlobalLogger(mlog.NewLogger(&mlog.LoggerConfiguration{ - EnableConsole: true, - ConsoleJson: true, - ConsoleLevel: "error", - EnableFile: false, - })) - - utils.TranslationsPreInit() - - // In the case where a dev just wants to run a single test, it's faster to just use the default - // store. - if filter := flag.Lookup("test.run").Value.String(); filter != "" && filter != "." { - mlog.Info("-test.run used, not creating temporary containers") - os.Exit(m.Run()) - } - - status := 0 - - container, settings, err := storetest.NewMySQLContainer() - if err != nil { - panic(err) - } - - UseTestStore(container, settings) - - defer func() { - StopTestStore() - os.Exit(status) - }() - - status = m.Run() -} diff --git a/api4/apitestlib.go b/api4/apitestlib.go index a9e099563e1..7dda6ded3c0 100644 --- a/api4/apitestlib.go +++ b/api4/apitestlib.go @@ -21,8 +21,6 @@ import ( "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" - "github.com/mattermost/mattermost-server/store/sqlstore" - "github.com/mattermost/mattermost-server/store/storetest" "github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/web" "github.com/mattermost/mattermost-server/wsapi" @@ -52,33 +50,18 @@ type TestHelper struct { tempWorkspace string } -type persistentTestStore struct { - store.Store -} +// testStore tracks the active test store. +// This is a bridge between the new testlib ownership of the test store and the existing usage +// of the api4 test helper by many packages. In the future, this test helper would ideally belong +// to the testlib altogether. +var testStore store.Store -func (*persistentTestStore) Close() {} - -var testStoreContainer *storetest.RunningContainer -var testStore *persistentTestStore - -// UseTestStore sets the container and corresponding settings to use for tests. Once the tests are -// complete (e.g. at the end of your TestMain implementation), you should call StopTestStore. -func UseTestStore(container *storetest.RunningContainer, settings *model.SqlSettings) { - testStoreContainer = container - testStore = &persistentTestStore{store.NewLayeredStore(sqlstore.NewSqlSupplier(*settings, nil), nil, nil)} -} - -func StopTestStore() { - if testStoreContainer != nil { - testStoreContainer.Stop() - testStoreContainer = nil - } +func UseTestStore(store store.Store) { + testStore = store } func setupTestHelper(enterprise bool, updateConfig func(*model.Config)) *TestHelper { - if testStore != nil { - testStore.DropAllTables() - } + testStore.DropAllTables() permConfig, err := os.Open(utils.FindConfigFile("config.json")) if err != nil { @@ -96,9 +79,7 @@ func setupTestHelper(enterprise bool, updateConfig func(*model.Config)) *TestHel } options := []app.Option{app.ConfigFile(tempConfig.Name()), app.DisableConfigWatch} - if testStore != nil { - options = append(options, app.StoreOverride(testStore)) - } + options = append(options, app.StoreOverride(testStore)) s, err := app.NewServer(options...) if err != nil { @@ -117,9 +98,7 @@ func setupTestHelper(enterprise bool, updateConfig func(*model.Config)) *TestHel cfg.EmailSettings.SendEmailNotifications = true }) prevListenAddress := *th.App.Config().ServiceSettings.ListenAddress - if testStore != nil { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" }) - } + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" }) if updateConfig != nil { th.App.UpdateConfig(updateConfig) } @@ -205,7 +184,6 @@ func (me *TestHelper) TearDown() { utils.EnableDebugLogForTest() if err := recover(); err != nil { - StopTestStore() panic(err) } } diff --git a/api4/main_test.go b/api4/main_test.go new file mode 100644 index 00000000000..0829a8eeba5 --- /dev/null +++ b/api4/main_test.go @@ -0,0 +1,20 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package api4 + +import ( + "testing" + + "github.com/mattermost/mattermost-server/testlib" +) + +var mainHelper *testlib.MainHelper + +func TestMain(m *testing.M) { + mainHelper = testlib.NewMainHelper() + defer mainHelper.Close() + UseTestStore(mainHelper.Store) + + mainHelper.Main(m) +} diff --git a/api4/user.go b/api4/user.go index 6234e16d31c..735570a9640 100644 --- a/api4/user.go +++ b/api4/user.go @@ -603,7 +603,7 @@ func autocompleteUsers(c *Context, w http.ResponseWriter, r *http.Request) { } } - var autocomplete *model.UserAutocomplete + var autocomplete model.UserAutocomplete if len(channelId) > 0 { // Applying the provided teamId here is useful for DMs and GMs which don't belong diff --git a/app/app_test.go b/app/app_test.go index 00929401983..4ddec64c733 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -4,57 +4,14 @@ package app import ( - "flag" "fmt" - "os" "testing" "github.com/stretchr/testify/assert" - "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store/storetest" - "github.com/mattermost/mattermost-server/utils" ) -func TestMain(m *testing.M) { - flag.Parse() - - // Setup a global logger to catch tests logging outside of app context - // The global logger will be stomped by apps initalizing but that's fine for testing. Ideally this won't happen. - mlog.InitGlobalLogger(mlog.NewLogger(&mlog.LoggerConfiguration{ - EnableConsole: true, - ConsoleJson: true, - ConsoleLevel: "error", - EnableFile: false, - })) - - utils.TranslationsPreInit() - - // In the case where a dev just wants to run a single test, it's faster to just use the default - // store. - if filter := flag.Lookup("test.run").Value.String(); filter != "" && filter != "." { - mlog.Info("-test.run used, not creating temporary containers") - os.Exit(m.Run()) - } - - status := 0 - - container, settings, err := storetest.NewMySQLContainer() - if err != nil { - panic(err) - } - - UseTestStore(container, settings) - - defer func() { - StopTestStore() - os.Exit(status) - }() - - status = m.Run() -} - /* Temporarily comment out until MM-11108 func TestAppRace(t *testing.T) { for i := 0; i < 10; i++ { @@ -88,7 +45,7 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) { th := Setup() defer th.TearDown() - if testStoreSqlSupplier == nil { + if mainHelper.SqlSupplier == nil { t.Skip("This test requires a TestStore to be run.") } @@ -467,7 +424,7 @@ func TestDoEmojisPermissionsMigration(t *testing.T) { th := Setup() defer th.TearDown() - if testStoreSqlSupplier == nil { + if mainHelper.SqlSupplier == nil { t.Skip("This test requires a TestStore to be run.") } diff --git a/app/apptestlib.go b/app/helper_test.go similarity index 75% rename from app/apptestlib.go rename to app/helper_test.go index 04783294067..e277bc49f10 100644 --- a/app/apptestlib.go +++ b/app/helper_test.go @@ -13,12 +13,8 @@ import ( "testing" - "github.com/mattermost/mattermost-server/einterfaces" "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store" - "github.com/mattermost/mattermost-server/store/sqlstore" - "github.com/mattermost/mattermost-server/store/storetest" "github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/utils/testutils" ) @@ -40,37 +36,8 @@ type TestHelper struct { MockedHTTPService *testutils.MockedHTTPService } -type persistentTestStore struct { - store.Store -} - -func (*persistentTestStore) Close() {} - -var testStoreContainer *storetest.RunningContainer -var testStore *persistentTestStore -var testStoreSqlSupplier *sqlstore.SqlSupplier -var testClusterInterface *FakeClusterInterface - -// UseTestStore sets the container and corresponding settings to use for tests. Once the tests are -// complete (e.g. at the end of your TestMain implementation), you should call StopTestStore. -func UseTestStore(container *storetest.RunningContainer, settings *model.SqlSettings) { - testClusterInterface = &FakeClusterInterface{} - testStoreContainer = container - testStoreSqlSupplier = sqlstore.NewSqlSupplier(*settings, nil) - testStore = &persistentTestStore{store.NewLayeredStore(testStoreSqlSupplier, nil, testClusterInterface)} -} - -func StopTestStore() { - if testStoreContainer != nil { - testStoreContainer.Stop() - testStoreContainer = nil - } -} - func setupTestHelper(enterprise bool) *TestHelper { - if testStore != nil { - testStore.DropAllTables() - } + mainHelper.Store.DropAllTables() permConfig, err := os.Open(utils.FindConfigFile("config.json")) if err != nil { @@ -88,9 +55,7 @@ func setupTestHelper(enterprise bool) *TestHelper { } options := []Option{ConfigFile(tempConfig.Name()), DisableConfigWatch} - if testStore != nil { - options = append(options, StoreOverride(testStore)) - } + options = append(options, StoreOverride(mainHelper.Store)) s, err := NewServer(options...) if err != nil { @@ -106,9 +71,7 @@ func setupTestHelper(enterprise bool) *TestHelper { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.MaxUsersPerTeam = 50 }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.RateLimitSettings.Enable = false }) prevListenAddress := *th.App.Config().ServiceSettings.ListenAddress - if testStore != nil { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" }) - } + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" }) serverErr := th.App.StartServer() if serverErr != nil { panic(serverErr) @@ -446,7 +409,6 @@ func (me *TestHelper) TearDown() { me.ShutdownApp() os.Remove(me.tempConfigPath) if err := recover(); err != nil { - StopTestStore() panic(err) } if me.tempWorkspace != "" { @@ -455,25 +417,25 @@ func (me *TestHelper) TearDown() { } func (me *TestHelper) ResetRoleMigration() { - if _, err := testStoreSqlSupplier.GetMaster().Exec("DELETE from Roles"); err != nil { + if _, err := mainHelper.SqlSupplier.GetMaster().Exec("DELETE from Roles"); err != nil { panic(err) } - testClusterInterface.sendClearRoleCacheMessage() + mainHelper.ClusterInterface.SendClearRoleCacheMessage() - if _, err := testStoreSqlSupplier.GetMaster().Exec("DELETE from Systems where Name = :Name", map[string]interface{}{"Name": ADVANCED_PERMISSIONS_MIGRATION_KEY}); err != nil { + if _, err := mainHelper.SqlSupplier.GetMaster().Exec("DELETE from Systems where Name = :Name", map[string]interface{}{"Name": ADVANCED_PERMISSIONS_MIGRATION_KEY}); err != nil { panic(err) } } func (me *TestHelper) ResetEmojisMigration() { - if _, err := testStoreSqlSupplier.GetMaster().Exec("UPDATE Roles SET Permissions=REPLACE(Permissions, ', manage_emojis', '') WHERE builtin=True"); err != nil { + if _, err := mainHelper.SqlSupplier.GetMaster().Exec("UPDATE Roles SET Permissions=REPLACE(Permissions, ', manage_emojis', '') WHERE builtin=True"); err != nil { panic(err) } - testClusterInterface.sendClearRoleCacheMessage() + mainHelper.ClusterInterface.SendClearRoleCacheMessage() - if _, err := testStoreSqlSupplier.GetMaster().Exec("DELETE from Systems where Name = :Name", map[string]interface{}{"Name": EMOJIS_PERMISSIONS_MIGRATION_KEY}); err != nil { + if _, err := mainHelper.SqlSupplier.GetMaster().Exec("DELETE from Systems where Name = :Name", map[string]interface{}{"Name": EMOJIS_PERMISSIONS_MIGRATION_KEY}); err != nil { panic(err) } } @@ -533,36 +495,3 @@ func (me *TestHelper) SetupPluginAPI() *PluginAPI { return NewPluginAPI(me.App, manifest) } - -type FakeClusterInterface struct { - clusterMessageHandler einterfaces.ClusterMessageHandler -} - -func (me *FakeClusterInterface) StartInterNodeCommunication() {} -func (me *FakeClusterInterface) StopInterNodeCommunication() {} -func (me *FakeClusterInterface) RegisterClusterMessageHandler(event string, crm einterfaces.ClusterMessageHandler) { - me.clusterMessageHandler = crm -} -func (me *FakeClusterInterface) GetClusterId() string { return "" } -func (me *FakeClusterInterface) IsLeader() bool { return false } -func (me *FakeClusterInterface) GetMyClusterInfo() *model.ClusterInfo { return nil } -func (me *FakeClusterInterface) GetClusterInfos() []*model.ClusterInfo { return nil } -func (me *FakeClusterInterface) SendClusterMessage(cluster *model.ClusterMessage) {} -func (me *FakeClusterInterface) NotifyMsg(buf []byte) {} -func (me *FakeClusterInterface) GetClusterStats() ([]*model.ClusterStats, *model.AppError) { - return nil, nil -} -func (me *FakeClusterInterface) GetLogs(page, perPage int) ([]string, *model.AppError) { - return []string{}, nil -} -func (me *FakeClusterInterface) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { - return nil, nil -} -func (me *FakeClusterInterface) ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError { - return nil -} -func (me *FakeClusterInterface) sendClearRoleCacheMessage() { - me.clusterMessageHandler(&model.ClusterMessage{ - Event: model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES, - }) -} diff --git a/app/main_test.go b/app/main_test.go new file mode 100644 index 00000000000..b12c72d4621 --- /dev/null +++ b/app/main_test.go @@ -0,0 +1,19 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package app + +import ( + "testing" + + "github.com/mattermost/mattermost-server/testlib" +) + +var mainHelper *testlib.MainHelper + +func TestMain(m *testing.M) { + mainHelper = testlib.NewMainHelper() + defer mainHelper.Close() + + mainHelper.Main(m) +} diff --git a/build/legacy.mk b/build/legacy.mk new file mode 100644 index 00000000000..ff07c654fec --- /dev/null +++ b/build/legacy.mk @@ -0,0 +1,5 @@ +# test-te used to just run the team edition tests, but now runs whatever is available +test-te: test-server + +# test-ee used to just run the enterprise edition tests, but now runs whatever is available +test-ee: test-server diff --git a/cmd/mattermost/commands/channel_test.go b/cmd/mattermost/commands/channel_test.go index 4d1fc8d43e7..5ac338b6684 100644 --- a/cmd/mattermost/commands/channel_test.go +++ b/cmd/mattermost/commands/channel_test.go @@ -8,50 +8,49 @@ import ( "testing" "time" - "github.com/mattermost/mattermost-server/api4" "github.com/mattermost/mattermost-server/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestJoinChannel(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() channel := th.CreatePublicChannel() - CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email) + th.CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email) // Joining twice should succeed - CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email) + th.CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email) // should fail because channel does not exist - require.Error(t, RunCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name+"asdf", th.BasicUser2.Email)) + require.Error(t, th.RunCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name+"asdf", th.BasicUser2.Email)) } func TestRemoveChannel(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() channel := th.CreatePublicChannel() - CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email) + th.CheckCommand(t, "channel", "add", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email) // should fail because channel does not exist - require.Error(t, RunCommand(t, "channel", "remove", th.BasicTeam.Name+":doesnotexist", th.BasicUser2.Email)) + require.Error(t, th.RunCommand(t, "channel", "remove", th.BasicTeam.Name+":doesnotexist", th.BasicUser2.Email)) time.Sleep(time.Second) - CheckCommand(t, "channel", "remove", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email) + th.CheckCommand(t, "channel", "remove", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email) time.Sleep(time.Second) // Leaving twice should succeed - CheckCommand(t, "channel", "remove", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email) + th.CheckCommand(t, "channel", "remove", th.BasicTeam.Name+":"+channel.Name, th.BasicUser2.Email) } func TestMoveChannel(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() team1 := th.BasicTeam @@ -68,22 +67,22 @@ func TestMoveChannel(t *testing.T) { origin := team1.Name + ":" + channel.Name dest := team2.Name - CheckCommand(t, "channel", "add", origin, adminEmail) + th.CheckCommand(t, "channel", "add", origin, adminEmail) // should fail with nil because errors are logged instead of returned when a channel does not exist - CheckCommand(t, "channel", "move", dest, team1.Name+":doesnotexist", "--username", adminUsername) + th.CheckCommand(t, "channel", "move", dest, team1.Name+":doesnotexist", "--username", adminUsername) - CheckCommand(t, "channel", "move", dest, origin, "--username", adminUsername) + th.CheckCommand(t, "channel", "move", dest, origin, "--username", adminUsername) } func TestListChannels(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() channel := th.CreatePublicChannel() th.Client.Must(th.Client.DeleteChannel(channel.Id)) - output := CheckCommand(t, "channel", "list", th.BasicTeam.Name) + output := th.CheckCommand(t, "channel", "list", th.BasicTeam.Name) if !strings.Contains(string(output), "town-square") { t.Fatal("should have channels") @@ -95,37 +94,37 @@ func TestListChannels(t *testing.T) { } func TestRestoreChannel(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() channel := th.CreatePublicChannel() th.Client.Must(th.Client.DeleteChannel(channel.Id)) - CheckCommand(t, "channel", "restore", th.BasicTeam.Name+":"+channel.Name) + th.CheckCommand(t, "channel", "restore", th.BasicTeam.Name+":"+channel.Name) // restoring twice should succeed - CheckCommand(t, "channel", "restore", th.BasicTeam.Name+":"+channel.Name) + th.CheckCommand(t, "channel", "restore", th.BasicTeam.Name+":"+channel.Name) } func TestCreateChannel(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() id := model.NewId() name := "name" + id - CheckCommand(t, "channel", "create", "--display_name", name, "--team", th.BasicTeam.Name, "--name", name) + th.CheckCommand(t, "channel", "create", "--display_name", name, "--team", th.BasicTeam.Name, "--name", name) name = name + "-private" - CheckCommand(t, "channel", "create", "--display_name", name, "--team", th.BasicTeam.Name, "--private", "--name", name) + th.CheckCommand(t, "channel", "create", "--display_name", name, "--team", th.BasicTeam.Name, "--private", "--name", name) } func TestRenameChannel(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() channel := th.CreatePublicChannel() - CheckCommand(t, "channel", "rename", th.BasicTeam.Name+":"+channel.Name, "newchannelname10", "--display_name", "New Display Name") + th.CheckCommand(t, "channel", "rename", th.BasicTeam.Name+":"+channel.Name, "newchannelname10", "--display_name", "New Display Name") // Get the channel from the DB updatedChannel, _ := th.App.GetChannel(channel.Id) diff --git a/cmd/mattermost/commands/cmdtestlib.go b/cmd/mattermost/commands/cmdtestlib.go index 93dcc956602..24ae39a5c4b 100644 --- a/cmd/mattermost/commands/cmdtestlib.go +++ b/cmd/mattermost/commands/cmdtestlib.go @@ -4,8 +4,11 @@ package commands import ( + "bytes" "flag" "fmt" + "io" + "io/ioutil" "os" "os/exec" "path/filepath" @@ -13,33 +16,159 @@ import ( "testing" "github.com/stretchr/testify/require" + + "github.com/mattermost/mattermost-server/api4" + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/testlib" ) var coverprofileCounters map[string]int = make(map[string]int) -func execArgs(t *testing.T, args []string) []string { - ret := []string{"-test.run", "ExecCommand"} +var mainHelper *testlib.MainHelper + +type testHelper struct { + *api4.TestHelper + + config *model.Config + tempDir string + configFilePath string + disableAutoConfig bool +} + +// Setup creates an instance of testHelper. +func Setup() *testHelper { + dir, err := ioutil.TempDir("", "testHelper") + if err != nil { + panic("failed to create temporary directory: " + err.Error()) + } + + api4TestHelper := api4.Setup() + + testHelper := &testHelper{ + TestHelper: api4TestHelper, + tempDir: dir, + configFilePath: filepath.Join(dir, "config-helper.json"), + } + + config := &model.Config{} + config.SetDefaults() + testHelper.SetConfig(config) + + return testHelper +} + +// InitBasic simply proxies to api4.InitBasic, while still returning a testHelper. +func (h *testHelper) InitBasic() *testHelper { + h.TestHelper.InitBasic() + return h +} + +// TemporaryDirectory returns the temporary directory created for user by the test helper. +func (h *testHelper) TemporaryDirectory() string { + return h.tempDir +} + +// Config returns the configuration passed to a running command. +func (h *testHelper) Config() *model.Config { + return h.config.Clone() +} + +// ConfigPath returns the path to the temporary config file passed to a running command. +func (h *testHelper) ConfigPath() string { + return h.configFilePath +} + +// SetConfig replaces the configuration passed to a running command. +func (h *testHelper) SetConfig(config *model.Config) { + config.SqlSettings = *mainHelper.Settings + h.config = config + + if err := ioutil.WriteFile(h.configFilePath, []byte(config.ToJson()), 0600); err != nil { + panic("failed to write file " + h.configFilePath + ": " + err.Error()) + } +} + +// SetAutoConfig configures whether the --config flag is automatically passed to a running command. +func (h *testHelper) SetAutoConfig(autoConfig bool) { + h.disableAutoConfig = !autoConfig +} + +// TearDown cleans up temporary files and assets created during the life of the test helper. +func (h *testHelper) TearDown() { + h.TestHelper.TearDown() + os.RemoveAll(h.tempDir) +} + +func (h *testHelper) execArgs(t *testing.T, args []string) []string { + ret := []string{"-test.v", "-test.run", "ExecCommand"} if coverprofile := flag.Lookup("test.coverprofile").Value.String(); coverprofile != "" { dir := filepath.Dir(coverprofile) base := filepath.Base(coverprofile) baseParts := strings.SplitN(base, ".", 2) - coverprofileCounters[t.Name()] = coverprofileCounters[t.Name()] + 1 - baseParts[0] = fmt.Sprintf("%v-%v-%v", baseParts[0], t.Name(), coverprofileCounters[t.Name()]) + name := strings.Replace(t.Name(), "/", "_", -1) + coverprofileCounters[name] = coverprofileCounters[name] + 1 + baseParts[0] = fmt.Sprintf("%v-%v-%v", baseParts[0], name, coverprofileCounters[name]) ret = append(ret, "-test.coverprofile", filepath.Join(dir, strings.Join(baseParts, "."))) } - return append(append(ret, "--", "--disableconfigwatch"), args...) + + ret = append(ret, "--", "--disableconfigwatch") + + // Unless the test passes a `--config` of its own, create a temporary one from the default + // configuration with the current test database applied. + hasConfig := h.disableAutoConfig + for _, arg := range args { + if arg == "--config" { + hasConfig = true + break + } + } + + if !hasConfig { + ret = append(ret, "--config", h.configFilePath) + } + + ret = append(ret, args...) + + return ret } -func CheckCommand(t *testing.T, args ...string) string { +// CheckCommand invokes the test binary, returning the output modified for assertion testing. +func (h *testHelper) CheckCommand(t *testing.T, args ...string) string { path, err := os.Executable() require.NoError(t, err) - output, err := exec.Command(path, execArgs(t, args)...).CombinedOutput() + output, err := exec.Command(path, h.execArgs(t, args)...).CombinedOutput() require.NoError(t, err, string(output)) return strings.TrimSpace(strings.TrimSuffix(strings.TrimSpace(string(output)), "PASS")) } -func RunCommand(t *testing.T, args ...string) error { +// RunCommand invokes the test binary, returning only any error. +func (h *testHelper) RunCommand(t *testing.T, args ...string) error { path, err := os.Executable() require.NoError(t, err) - return exec.Command(path, execArgs(t, args)...).Run() + return exec.Command(path, h.execArgs(t, args)...).Run() +} + +// RunCommandWithOutput is a variant of RunCommand that returns the unmodified output and any error. +func (h *testHelper) RunCommandWithOutput(t *testing.T, args ...string) (string, error) { + path, err := os.Executable() + require.NoError(t, err) + + cmd := exec.Command(path, h.execArgs(t, args)...) + + var buf bytes.Buffer + reader, writer := io.Pipe() + cmd.Stdout = writer + cmd.Stderr = writer + + done := make(chan bool) + go func() { + io.Copy(&buf, reader) + close(done) + }() + + err = cmd.Run() + writer.Close() + <-done + + return buf.String(), err } diff --git a/cmd/mattermost/commands/command_test.go b/cmd/mattermost/commands/command_test.go index 5ce16fb772b..878a1af0b9b 100644 --- a/cmd/mattermost/commands/command_test.go +++ b/cmd/mattermost/commands/command_test.go @@ -4,18 +4,21 @@ package commands import ( - "os" - "os/exec" "testing" - "github.com/mattermost/mattermost-server/api4" + "github.com/mattermost/mattermost-server/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestCreateCommand(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() + + config := th.Config() + *config.ServiceSettings.EnableCommands = true + th.SetConfig(config) + team := th.BasicTeam adminUser := th.TeamAdminUser user := th.BasicUser @@ -107,13 +110,9 @@ func TestCreateCommand(t *testing.T) { }, } - path, err := os.Executable() - require.NoError(t, err) - for _, testCase := range testCases { t.Run(testCase.Description, func(t *testing.T) { - - actual, _ := exec.Command(path, execArgs(t, testCase.Args)...).CombinedOutput() + actual, _ := th.RunCommandWithOutput(t, testCase.Args...) cmds, _ := th.SystemAdminClient.ListCommands(team.Id, true) @@ -132,9 +131,11 @@ func TestCreateCommand(t *testing.T) { } } -/* Commenting it out because race condition func TestDeleteCommand(t *testing.T) { - th := app.Setup().InitBasic() + // Skipped due to v5.6 RC build issues. + t.Skip() + + th := Setup().InitBasic() defer th.TearDown() url := "http://localhost:8000/test-command" team := th.BasicTeam @@ -158,14 +159,13 @@ func TestDeleteCommand(t *testing.T) { require.Nil(t, err) assert.Equal(t, len(commands), 1) - CheckCommand(t, "command", "delete", command.Id) + th.CheckCommand(t, "command", "delete", command.Id) commands, err = th.App.ListTeamCommands(team.Id) require.Nil(t, err) assert.Equal(t, len(commands), 0) }) t.Run("not existing command", func(t *testing.T) { - assert.Error(t, RunCommand(t, "command", "delete", "invalid")) + assert.Error(t, th.RunCommand(t, "command", "delete", "invalid")) }) } -*/ diff --git a/cmd/mattermost/commands/config_flag_test.go b/cmd/mattermost/commands/config_flag_test.go index 00a817448d0..1ccbb9685e3 100644 --- a/cmd/mattermost/commands/config_flag_test.go +++ b/cmd/mattermost/commands/config_flag_test.go @@ -4,6 +4,7 @@ package commands import ( + "encoding/json" "io/ioutil" "os" "path/filepath" @@ -11,21 +12,13 @@ import ( "github.com/stretchr/testify/require" - "encoding/json" - "github.com/mattermost/mattermost-server/utils" ) func TestConfigFlag(t *testing.T) { - dir, err := ioutil.TempDir("", "") - require.NoError(t, err) - defer os.RemoveAll(dir) - - utils.TranslationsPreInit() - config, _, _, err := utils.LoadConfig("config.json") - require.Nil(t, err) - configPath := filepath.Join(dir, "foo.json") - require.NoError(t, ioutil.WriteFile(configPath, []byte(config.ToJson()), 0600)) + th := Setup() + defer th.TearDown() + dir := th.TemporaryDirectory() timezones := utils.LoadTimezones("timezones.json") tzConfigPath := filepath.Join(dir, "timezones.json") @@ -41,8 +34,15 @@ func TestConfigFlag(t *testing.T) { defer os.Chdir(prevDir) os.Chdir(dir) - require.Error(t, RunCommand(t, "version")) - CheckCommand(t, "--config", "foo.json", "version") - CheckCommand(t, "--config", "./foo.json", "version") - CheckCommand(t, "--config", configPath, "version") + t.Run("version without a config file should fail", func(t *testing.T) { + th.SetAutoConfig(false) + defer th.SetAutoConfig(true) + require.Error(t, th.RunCommand(t, "version")) + }) + + t.Run("version with varying paths to the config file", func(t *testing.T) { + th.CheckCommand(t, "--config", filepath.Base(th.ConfigPath()), "version") + th.CheckCommand(t, "--config", "./"+filepath.Base(th.ConfigPath()), "version") + th.CheckCommand(t, "version") + }) } diff --git a/cmd/mattermost/commands/config_test.go b/cmd/mattermost/commands/config_test.go index dbaf5803a00..e974d8fe8b7 100644 --- a/cmd/mattermost/commands/config_test.go +++ b/cmd/mattermost/commands/config_test.go @@ -4,16 +4,12 @@ package commands import ( - "io/ioutil" - "os" - "path/filepath" "reflect" "sort" "strings" "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "github.com/mattermost/mattermost-server/model" ) @@ -72,88 +68,90 @@ type TestNewTeamSettings struct { } func TestConfigValidate(t *testing.T) { - dir, err := ioutil.TempDir("", "") - require.NoError(t, err) - defer os.RemoveAll(dir) + th := Setup() + defer th.TearDown() - path := filepath.Join(dir, "config.json") - config := &model.Config{} - config.SetDefaults() - require.NoError(t, ioutil.WriteFile(path, []byte(config.ToJson()), 0600)) - - assert.Error(t, RunCommand(t, "--config", "foo.json", "config", "validate")) - CheckCommand(t, "--config", path, "config", "validate") + assert.Error(t, th.RunCommand(t, "--config", "foo.json", "config", "validate")) + th.CheckCommand(t, "config", "validate") } func TestConfigGet(t *testing.T) { - // Error when no arguments are given - assert.Error(t, RunCommand(t, "config", "get")) + th := Setup() + defer th.TearDown() - // Error when more than one config settings are given - assert.Error(t, RunCommand(t, "config", "get", "abc", "def")) + t.Run("Error when no arguments are given", func(t *testing.T) { + assert.Error(t, th.RunCommand(t, "config", "get")) + }) - // Error when a config setting which is not in the config.json is given - assert.Error(t, RunCommand(t, "config", "get", "abc")) + t.Run("Error when more than one config settings are given", func(t *testing.T) { + assert.Error(t, th.RunCommand(t, "config", "get", "abc", "def")) + }) - // No Error when a config setting which is in the config.json is given - CheckCommand(t, "config", "get", "MessageExportSettings") - CheckCommand(t, "config", "get", "MessageExportSettings.GlobalRelaySettings") - CheckCommand(t, "config", "get", "MessageExportSettings.GlobalRelaySettings.CustomerType") + t.Run("Error when a config setting which is not in the config.json is given", func(t *testing.T) { + assert.Error(t, th.RunCommand(t, "config", "get", "abc")) + }) - // check output - output := CheckCommand(t, "config", "get", "MessageExportSettings") + t.Run("No Error when a config setting which is in the config.json is given", func(t *testing.T) { + th.CheckCommand(t, "config", "get", "MessageExportSettings") + th.CheckCommand(t, "config", "get", "MessageExportSettings.GlobalRelaySettings") + th.CheckCommand(t, "config", "get", "MessageExportSettings.GlobalRelaySettings.CustomerType") + }) - assert.Contains(t, string(output), "EnableExport") - assert.Contains(t, string(output), "ExportFormat") - assert.Contains(t, string(output), "DailyRunTime") - assert.Contains(t, string(output), "ExportFromTimestamp") + t.Run("check output", func(t *testing.T) { + output := th.CheckCommand(t, "config", "get", "MessageExportSettings") + + assert.Contains(t, string(output), "EnableExport") + assert.Contains(t, string(output), "ExportFormat") + assert.Contains(t, string(output), "DailyRunTime") + assert.Contains(t, string(output), "ExportFromTimestamp") + }) } func TestConfigSet(t *testing.T) { - dir, err := ioutil.TempDir("", "") - require.NoError(t, err) - defer os.RemoveAll(dir) + th := Setup() + defer th.TearDown() - path := filepath.Join(dir, "config.json") - config := &model.Config{} - config.SetDefaults() - require.NoError(t, ioutil.WriteFile(path, []byte(config.ToJson()), 0600)) + t.Run("Error when no arguments are given", func(t *testing.T) { + assert.Error(t, th.RunCommand(t, "config", "set")) + }) - // Error when no arguments are given - assert.Error(t, RunCommand(t, "--config", path, "config", "set")) + t.Run("Error when only one argument is given", func(t *testing.T) { + assert.Error(t, th.RunCommand(t, "config", "set", "test")) + }) - // Error when only one argument is given - assert.Error(t, RunCommand(t, "--config", path, "config", "set", "test")) + t.Run("Error when the wrong key is set", func(t *testing.T) { + assert.Error(t, th.RunCommand(t, "config", "set", "invalid-key", "value")) + assert.Error(t, th.RunCommand(t, "config", "get", "invalid-key")) + }) - // Error when the wrong key is set - assert.Error(t, RunCommand(t, "--config", path, "config", "set", "invalid-key", "value")) - assert.Error(t, RunCommand(t, "--config", path, "config", "get", "invalid-key")) + t.Run("Error when the wrong value is set", func(t *testing.T) { + assert.Error(t, th.RunCommand(t, "config", "set", "EmailSettings.ConnectionSecurity", "invalid")) + output := th.CheckCommand(t, "config", "get", "EmailSettings.ConnectionSecurity") + assert.NotContains(t, string(output), "invalid") + }) - // Error when the wrong value is set - assert.Error(t, RunCommand(t, "--config", path, "config", "set", "EmailSettings.ConnectionSecurity", "invalid")) - output := CheckCommand(t, "--config", path, "config", "get", "EmailSettings.ConnectionSecurity") - assert.NotContains(t, string(output), "invalid") + t.Run("Error when the wrong locale is set", func(t *testing.T) { + th.CheckCommand(t, "config", "set", "LocalizationSettings.DefaultServerLocale", "es") + assert.Error(t, th.RunCommand(t, "config", "set", "LocalizationSettings.DefaultServerLocale", "invalid")) + output := th.CheckCommand(t, "config", "get", "LocalizationSettings.DefaultServerLocale") + assert.NotContains(t, string(output), "invalid") + assert.NotContains(t, string(output), "\"en\"") + }) - // Error when the wrong locale is set - CheckCommand(t, "--config", path, "config", "set", "LocalizationSettings.DefaultServerLocale", "es") - assert.Error(t, RunCommand(t, "--config", path, "config", "set", "LocalizationSettings.DefaultServerLocale", "invalid")) - output = CheckCommand(t, "--config", path, "config", "get", "LocalizationSettings.DefaultServerLocale") - assert.NotContains(t, string(output), "invalid") - assert.NotContains(t, string(output), "\"en\"") + t.Run("Success when a valid value is set", func(t *testing.T) { + assert.NoError(t, th.RunCommand(t, "config", "set", "EmailSettings.ConnectionSecurity", "TLS")) + output := th.CheckCommand(t, "config", "get", "EmailSettings.ConnectionSecurity") + assert.Contains(t, string(output), "TLS") + }) - // Success when a valid value is set - CheckCommand(t, "--config", path, "config", "set", "EmailSettings.ConnectionSecurity", "TLS") - output = CheckCommand(t, "--config", path, "config", "get", "EmailSettings.ConnectionSecurity") - assert.Contains(t, string(output), "TLS") - - // Success when a valid locale is set - CheckCommand(t, "--config", path, "config", "set", "LocalizationSettings.DefaultServerLocale", "es") - output = CheckCommand(t, "--config", path, "config", "get", "LocalizationSettings.DefaultServerLocale") - assert.Contains(t, string(output), "\"es\"") + t.Run("Success when a valid locale is set", func(t *testing.T) { + assert.NoError(t, th.RunCommand(t, "config", "set", "LocalizationSettings.DefaultServerLocale", "es")) + output := th.CheckCommand(t, "config", "get", "LocalizationSettings.DefaultServerLocale") + assert.Contains(t, string(output), "\"es\"") + }) } func TestStructToMap(t *testing.T) { - cases := []struct { Name string Input interface{} @@ -324,7 +322,6 @@ func TestConfigToMap(t *testing.T) { } func TestPrintMap(t *testing.T) { - inputCases := []interface{}{ map[string]interface{}{ "CustomerType": "A9", @@ -387,7 +384,6 @@ func TestPrintMap(t *testing.T) { } func TestPrintConfigValues(t *testing.T) { - outputs := []string{ "Siteurl: \"abc\"\nWebsocketurl: \"def\"\nLicensedfieldlocation: \"ghi\"\n", "Sitename: \"abc\"\nMaxuserperteam: \"1\"\n", @@ -464,39 +460,42 @@ func TestPrintConfigValues(t *testing.T) { } func TestConfigShow(t *testing.T) { + th := Setup() + defer th.TearDown() - // error - assert.Error(t, RunCommand(t, "config", "show", "abc")) + t.Run("error with unknown subcommand", func(t *testing.T) { + assert.Error(t, th.RunCommand(t, "config", "show", "abc")) + }) - // no error - CheckCommand(t, "config", "show") - - // check the output - output := CheckCommand(t, "config", "show") - assert.Contains(t, string(output), "SqlSettings") - assert.Contains(t, string(output), "MessageExportSettings") - assert.Contains(t, string(output), "AnnouncementSettings") + t.Run("successfully dumping config", func(t *testing.T) { + output := th.CheckCommand(t, "config", "show") + assert.Contains(t, string(output), "SqlSettings") + assert.Contains(t, string(output), "MessageExportSettings") + assert.Contains(t, string(output), "AnnouncementSettings") + }) } func TestSetConfig(t *testing.T) { + th := Setup() + defer th.TearDown() + // Error when no argument is given - assert.Error(t, RunCommand(t, "config", "set")) + assert.Error(t, th.RunCommand(t, "config", "set")) // No Error when more than one argument is given - CheckCommand(t, "config", "set", "ThemeSettings.AllowedThemes", "hello", "World") + th.CheckCommand(t, "config", "set", "ThemeSettings.AllowedThemes", "hello", "World") // No Error when two arguments are given - CheckCommand(t, "config", "set", "ThemeSettings.AllowedThemes", "hello") + th.CheckCommand(t, "config", "set", "ThemeSettings.AllowedThemes", "hello") // Error when only one argument is given - assert.Error(t, RunCommand(t, "config", "set", "ThemeSettings.AllowedThemes")) + assert.Error(t, th.RunCommand(t, "config", "set", "ThemeSettings.AllowedThemes")) // Error when config settings not in the config file are given - assert.Error(t, RunCommand(t, "config", "set", "Abc")) + assert.Error(t, th.RunCommand(t, "config", "set", "Abc")) } func TestUpdateMap(t *testing.T) { - // create a config to make changes config := TestNewConfig{ TestNewServiceSettings{ @@ -577,7 +576,6 @@ func TestUpdateMap(t *testing.T) { } func contains(configMap map[string]interface{}, v interface{}, configSettings []string) bool { - res := configMap[configSettings[0]] value := reflect.ValueOf(res) diff --git a/cmd/mattermost/commands/export_test.go b/cmd/mattermost/commands/export_test.go index 89ef45a6a91..a4a1ab1fdeb 100644 --- a/cmd/mattermost/commands/export_test.go +++ b/cmd/mattermost/commands/export_test.go @@ -4,15 +4,11 @@ package commands import ( - "io/ioutil" - "os" - "path/filepath" "testing" "github.com/stretchr/testify/require" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/utils" ) // There are no tests that actually run the Message Export job, because it can take a long time to complete depending @@ -20,47 +16,49 @@ import ( // fails fast if invalid flags are supplied func TestMessageExportNotEnabled(t *testing.T) { - configPath := writeTempConfig(t, false) - defer os.RemoveAll(filepath.Dir(configPath)) + th := Setup() + defer th.TearDown() + + config := th.Config() + config.MessageExportSettings.EnableExport = model.NewBool(false) + th.SetConfig(config) // should fail fast because the feature isn't enabled - require.Error(t, RunCommand(t, "--config", configPath, "export", "schedule")) + require.Error(t, th.RunCommand(t, "export", "schedule")) } func TestMessageExportInvalidFormat(t *testing.T) { - configPath := writeTempConfig(t, true) - defer os.RemoveAll(filepath.Dir(configPath)) + th := Setup() + defer th.TearDown() + + config := th.Config() + config.MessageExportSettings.EnableExport = model.NewBool(true) + th.SetConfig(config) // should fail fast because format isn't supported - require.Error(t, RunCommand(t, "--config", configPath, "--format", "not_actiance", "export", "schedule")) + require.Error(t, th.RunCommand(t, "--format", "not_actiance", "export", "schedule")) } func TestMessageExportNegativeExportFrom(t *testing.T) { - configPath := writeTempConfig(t, true) - defer os.RemoveAll(filepath.Dir(configPath)) + th := Setup() + defer th.TearDown() + + config := th.Config() + config.MessageExportSettings.EnableExport = model.NewBool(true) + th.SetConfig(config) // should fail fast because export from must be a valid timestamp - require.Error(t, RunCommand(t, "--config", configPath, "--format", "actiance", "--exportFrom", "-1", "export", "schedule")) + require.Error(t, th.RunCommand(t, "--format", "actiance", "--exportFrom", "-1", "export", "schedule")) } func TestMessageExportNegativeTimeoutSeconds(t *testing.T) { - configPath := writeTempConfig(t, true) - defer os.RemoveAll(filepath.Dir(configPath)) + th := Setup() + defer th.TearDown() + + config := th.Config() + config.MessageExportSettings.EnableExport = model.NewBool(true) + th.SetConfig(config) // should fail fast because timeout seconds must be a positive int - require.Error(t, RunCommand(t, "--config", configPath, "--format", "actiance", "--exportFrom", "0", "--timeoutSeconds", "-1", "export", "schedule")) -} - -func writeTempConfig(t *testing.T, isMessageExportEnabled bool) string { - dir, err := ioutil.TempDir("", "") - require.NoError(t, err) - - utils.TranslationsPreInit() - config, _, _, appErr := utils.LoadConfig("config.json") - require.Nil(t, appErr) - config.MessageExportSettings.EnableExport = model.NewBool(isMessageExportEnabled) - configPath := filepath.Join(dir, "foo.json") - require.NoError(t, ioutil.WriteFile(configPath, []byte(config.ToJson()), 0600)) - - return configPath + require.Error(t, th.RunCommand(t, "--format", "actiance", "--exportFrom", "0", "--timeoutSeconds", "-1", "export", "schedule")) } diff --git a/cmd/mattermost/commands/main_test.go b/cmd/mattermost/commands/main_test.go new file mode 100644 index 00000000000..4f622eef99a --- /dev/null +++ b/cmd/mattermost/commands/main_test.go @@ -0,0 +1,30 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package commands + +import ( + "flag" + "os" + "testing" + + "github.com/mattermost/mattermost-server/api4" + "github.com/mattermost/mattermost-server/testlib" +) + +func TestMain(m *testing.M) { + // Command tests are run by re-invoking the test binary in question, so avoid creating + // another container when we detect same. + flag.Parse() + if filter := flag.Lookup("test.run").Value.String(); filter == "ExecCommand" { + status := m.Run() + os.Exit(status) + return + } + + mainHelper = testlib.NewMainHelper() + defer mainHelper.Close() + api4.UseTestStore(mainHelper.Store) + + mainHelper.Main(m) +} diff --git a/cmd/mattermost/commands/permissions_test.go b/cmd/mattermost/commands/permissions_test.go index 54ccbddb825..3c7ddc49ba2 100644 --- a/cmd/mattermost/commands/permissions_test.go +++ b/cmd/mattermost/commands/permissions_test.go @@ -4,37 +4,26 @@ package commands import ( - "os" - "os/exec" - "strings" "testing" - "github.com/mattermost/mattermost-server/api4" + "github.com/stretchr/testify/assert" + "github.com/mattermost/mattermost-server/utils" ) func TestPermissionsExport_rejectsUnlicensed(t *testing.T) { - permissionsLicenseRequiredTest(t, "export") + th := Setup().InitBasic() + defer th.TearDown() + + actual, _ := th.RunCommandWithOutput(t, "permissions", "export") + assert.Contains(t, actual, utils.T("cli.license.critical")) } func TestPermissionsImport_rejectsUnlicensed(t *testing.T) { - permissionsLicenseRequiredTest(t, "import") -} - -func permissionsLicenseRequiredTest(t *testing.T, subcommand string) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() - path, err := os.Executable() - if err != nil { - t.Fail() - } - args := []string{"-test.run", "ExecCommand", "--", "--disableconfigwatch", "permissions", subcommand} - output, _ := exec.Command(path, args...).CombinedOutput() + actual, _ := th.RunCommandWithOutput(t, "permissions", "import") - actual := string(output) - expected := utils.T("cli.license.critical") - if !strings.Contains(actual, expected) { - t.Errorf("Expected '%v' but got '%v'.", expected, actual) - } + assert.Contains(t, actual, utils.T("cli.license.critical")) } diff --git a/cmd/mattermost/commands/plugin_test.go b/cmd/mattermost/commands/plugin_test.go index 9712ba0e575..e3c8fe18434 100644 --- a/cmd/mattermost/commands/plugin_test.go +++ b/cmd/mattermost/commands/plugin_test.go @@ -5,38 +5,43 @@ import ( "path/filepath" "testing" - "github.com/mattermost/mattermost-server/api4" "github.com/mattermost/mattermost-server/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestPlugin(t *testing.T) { + th := Setup().InitBasic() + defer th.TearDown() + + config := th.Config() + *config.PluginSettings.EnableUploads = true + *config.PluginSettings.Directory = "./test-plugins" + *config.PluginSettings.ClientDirectory = "./test-client-plugins" + th.SetConfig(config) + os.MkdirAll("./test-plugins", os.ModePerm) os.MkdirAll("./test-client-plugins", os.ModePerm) - th := api4.Setup().InitBasic() - defer th.TearDown() - path, _ := utils.FindDir("tests") os.Chdir(filepath.Join("..", "..", "..")) - CheckCommand(t, "--config", filepath.Join(path, "test-config.json"), "plugin", "add", filepath.Join(path, "testplugin.tar.gz")) + th.CheckCommand(t, "plugin", "add", filepath.Join(path, "testplugin.tar.gz")) - CheckCommand(t, "--config", filepath.Join(path, "test-config.json"), "plugin", "enable", "testplugin") - cfg, _, _, err := utils.LoadConfig(filepath.Join(path, "test-config.json")) + th.CheckCommand(t, "plugin", "enable", "testplugin") + cfg, _, _, err := utils.LoadConfig(th.ConfigPath()) require.Nil(t, err) assert.Equal(t, cfg.PluginSettings.PluginStates["testplugin"].Enable, true) - CheckCommand(t, "--config", filepath.Join(path, "test-config.json"), "plugin", "disable", "testplugin") - cfg, _, _, err = utils.LoadConfig(filepath.Join(path, "test-config.json")) + th.CheckCommand(t, "plugin", "disable", "testplugin") + cfg, _, _, err = utils.LoadConfig(th.ConfigPath()) require.Nil(t, err) assert.Equal(t, cfg.PluginSettings.PluginStates["testplugin"].Enable, false) - CheckCommand(t, "--config", filepath.Join(path, "test-config.json"), "plugin", "list") + th.CheckCommand(t, "plugin", "list") - CheckCommand(t, "--config", filepath.Join(path, "test-config.json"), "plugin", "delete", "testplugin") + th.CheckCommand(t, "plugin", "delete", "testplugin") os.Chdir(filepath.Join("cmd", "mattermost", "commands")) } diff --git a/cmd/mattermost/commands/roles_test.go b/cmd/mattermost/commands/roles_test.go index da33a73cc7b..2d768f669fd 100644 --- a/cmd/mattermost/commands/roles_test.go +++ b/cmd/mattermost/commands/roles_test.go @@ -6,15 +6,14 @@ package commands import ( "testing" - "github.com/mattermost/mattermost-server/api4" "github.com/mattermost/mattermost-server/model" ) func TestAssignRole(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() - CheckCommand(t, "roles", "system_admin", th.BasicUser.Email) + th.CheckCommand(t, "roles", "system_admin", th.BasicUser.Email) if result := <-th.App.Srv.Store.User().GetByEmail(th.BasicUser.Email); result.Err != nil { t.Fatal(result.Err) @@ -25,7 +24,7 @@ func TestAssignRole(t *testing.T) { } } - CheckCommand(t, "roles", "member", th.BasicUser.Email) + th.CheckCommand(t, "roles", "member", th.BasicUser.Email) if result := <-th.App.Srv.Store.User().GetByEmail(th.BasicUser.Email); result.Err != nil { t.Fatal(result.Err) diff --git a/cmd/mattermost/commands/sampledata_test.go b/cmd/mattermost/commands/sampledata_test.go index e447fe49245..4ea914e1d60 100644 --- a/cmd/mattermost/commands/sampledata_test.go +++ b/cmd/mattermost/commands/sampledata_test.go @@ -6,20 +6,19 @@ package commands import ( "testing" - "github.com/mattermost/mattermost-server/api4" "github.com/stretchr/testify/require" ) func TestSampledataBadParameters(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() // should fail because you need at least 1 worker - require.Error(t, RunCommand(t, "sampledata", "--workers", "0")) + require.Error(t, th.RunCommand(t, "sampledata", "--workers", "0")) // should fail because you have more team memberships than teams - require.Error(t, RunCommand(t, "sampledata", "--teams", "10", "--teams-memberships", "11")) + require.Error(t, th.RunCommand(t, "sampledata", "--teams", "10", "--teams-memberships", "11")) // should fail because you have more channel memberships than channels per team - require.Error(t, RunCommand(t, "sampledata", "--channels-per-team", "10", "--channel-memberships", "11")) + require.Error(t, th.RunCommand(t, "sampledata", "--channels-per-team", "10", "--channel-memberships", "11")) } diff --git a/cmd/mattermost/commands/team_test.go b/cmd/mattermost/commands/team_test.go index b7bbd090a38..a956e38bedb 100644 --- a/cmd/mattermost/commands/team_test.go +++ b/cmd/mattermost/commands/team_test.go @@ -7,19 +7,18 @@ import ( "strings" "testing" - "github.com/mattermost/mattermost-server/api4" "github.com/mattermost/mattermost-server/model" ) func TestCreateTeam(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() id := model.NewId() name := "name" + id displayName := "Name " + id - CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName) + th.CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName) found := th.SystemAdminClient.Must(th.SystemAdminClient.TeamExists(name, "")).(bool) @@ -29,10 +28,10 @@ func TestCreateTeam(t *testing.T) { } func TestJoinTeam(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() - CheckCommand(t, "team", "add", th.BasicTeam.Name, th.BasicUser.Email) + th.CheckCommand(t, "team", "add", th.BasicTeam.Name, th.BasicUser.Email) profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetUsersInTeam(th.BasicTeam.Id, 0, 1000, "")).([]*model.User) @@ -51,10 +50,10 @@ func TestJoinTeam(t *testing.T) { } func TestLeaveTeam(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() - CheckCommand(t, "team", "remove", th.BasicTeam.Name, th.BasicUser.Email) + th.CheckCommand(t, "team", "remove", th.BasicTeam.Name, th.BasicUser.Email) profiles := th.Client.Must(th.Client.GetUsersInTeam(th.BasicTeam.Id, 0, 1000, "")).([]*model.User) @@ -80,16 +79,16 @@ func TestLeaveTeam(t *testing.T) { } func TestListTeams(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() id := model.NewId() name := "name" + id displayName := "Name " + id - CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName) + th.CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName) - output := CheckCommand(t, "team", "list", th.BasicTeam.Name, th.BasicUser.Email) + output := th.CheckCommand(t, "team", "list", th.BasicTeam.Name, th.BasicUser.Email) if !strings.Contains(string(output), name) { t.Fatal("should have the created team") @@ -97,18 +96,18 @@ func TestListTeams(t *testing.T) { } func TestListArchivedTeams(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() id := model.NewId() name := "name" + id displayName := "Name " + id - CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName) + th.CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName) - CheckCommand(t, "team", "archive", name) + th.CheckCommand(t, "team", "archive", name) - output := CheckCommand(t, "team", "list", th.BasicTeam.Name, th.BasicUser.Email) + output := th.CheckCommand(t, "team", "list", th.BasicTeam.Name, th.BasicUser.Email) if !strings.Contains(string(output), name+" (archived)") { t.Fatal("should have archived team") @@ -116,16 +115,16 @@ func TestListArchivedTeams(t *testing.T) { } func TestSearchTeamsByName(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() id := model.NewId() name := "name" + id displayName := "Name " + id - CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName) + th.CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName) - output := CheckCommand(t, "team", "search", name) + output := th.CheckCommand(t, "team", "search", name) if !strings.Contains(string(output), name) { t.Fatal("should have the created team") @@ -133,16 +132,16 @@ func TestSearchTeamsByName(t *testing.T) { } func TestSearchTeamsByDisplayName(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() id := model.NewId() name := "name" + id displayName := "Name " + id - CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName) + th.CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName) - output := CheckCommand(t, "team", "search", displayName) + output := th.CheckCommand(t, "team", "search", displayName) if !strings.Contains(string(output), name) { t.Fatal("should have the created team") @@ -150,18 +149,18 @@ func TestSearchTeamsByDisplayName(t *testing.T) { } func TestSearchArchivedTeamsByName(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() id := model.NewId() name := "name" + id displayName := "Name " + id - CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName) + th.CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName) - CheckCommand(t, "team", "archive", name) + th.CheckCommand(t, "team", "archive", name) - output := CheckCommand(t, "team", "search", name) + output := th.CheckCommand(t, "team", "search", name) if !strings.Contains(string(output), "(archived)") { t.Fatal("should have archived team") @@ -169,18 +168,18 @@ func TestSearchArchivedTeamsByName(t *testing.T) { } func TestArchiveTeams(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() id := model.NewId() name := "name" + id displayName := "Name " + id - CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName) + th.CheckCommand(t, "team", "create", "--name", name, "--display_name", displayName) - CheckCommand(t, "team", "archive", name) + th.CheckCommand(t, "team", "archive", name) - output := CheckCommand(t, "team", "list") + output := th.CheckCommand(t, "team", "list") if !strings.Contains(string(output), name+" (archived)") { t.Fatal("should have archived team") diff --git a/cmd/mattermost/commands/user_test.go b/cmd/mattermost/commands/user_test.go index 80582c652b1..632b5efb5b7 100644 --- a/cmd/mattermost/commands/user_test.go +++ b/cmd/mattermost/commands/user_test.go @@ -6,22 +6,21 @@ package commands import ( "testing" - "github.com/mattermost/mattermost-server/api4" "github.com/mattermost/mattermost-server/model" "github.com/stretchr/testify/require" ) func TestCreateUserWithTeam(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() id := model.NewId() email := "success+" + id + "@simulator.amazonses.com" username := "name" + id - CheckCommand(t, "user", "create", "--email", email, "--password", "mypassword1", "--username", username) + th.CheckCommand(t, "user", "create", "--email", email, "--password", "mypassword1", "--username", username) - CheckCommand(t, "team", "add", th.BasicTeam.Id, email) + th.CheckCommand(t, "team", "add", th.BasicTeam.Id, email) profiles := th.SystemAdminClient.Must(th.SystemAdminClient.GetUsersInTeam(th.BasicTeam.Id, 0, 1000, "")).([]*model.User) @@ -40,14 +39,14 @@ func TestCreateUserWithTeam(t *testing.T) { } func TestCreateUserWithoutTeam(t *testing.T) { - th := api4.Setup() + th := Setup() defer th.TearDown() id := model.NewId() email := "success+" + id + "@simulator.amazonses.com" username := "name" + id - CheckCommand(t, "user", "create", "--email", email, "--password", "mypassword1", "--username", username) + th.CheckCommand(t, "user", "create", "--email", email, "--password", "mypassword1", "--username", username) if result := <-th.App.Srv.Store.User().GetByEmail(email); result.Err != nil { t.Fatal(result.Err) @@ -58,10 +57,10 @@ func TestCreateUserWithoutTeam(t *testing.T) { } func TestResetPassword(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() - CheckCommand(t, "user", "password", th.BasicUser.Email, "password2") + th.CheckCommand(t, "user", "password", th.BasicUser.Email, "password2") th.Client.Logout() th.BasicUser.Password = "password2" @@ -69,23 +68,23 @@ func TestResetPassword(t *testing.T) { } func TestMakeUserActiveAndInactive(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() // first inactivate the user - CheckCommand(t, "user", "deactivate", th.BasicUser.Email) + th.CheckCommand(t, "user", "deactivate", th.BasicUser.Email) // activate the inactive user - CheckCommand(t, "user", "activate", th.BasicUser.Email) + th.CheckCommand(t, "user", "activate", th.BasicUser.Email) } func TestChangeUserEmail(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() newEmail := model.NewId() + "@mattermost-test.com" - CheckCommand(t, "user", "email", th.BasicUser.Username, newEmail) + th.CheckCommand(t, "user", "email", th.BasicUser.Username, newEmail) if result := <-th.App.Srv.Store.User().GetByEmail(th.BasicUser.Email); result.Err == nil { t.Fatal("should've updated to the new email") } @@ -99,21 +98,21 @@ func TestChangeUserEmail(t *testing.T) { } // should fail because using an invalid email - require.Error(t, RunCommand(t, "user", "email", th.BasicUser.Username, "wrong$email.com")) + require.Error(t, th.RunCommand(t, "user", "email", th.BasicUser.Username, "wrong$email.com")) // should fail because missing one parameter - require.Error(t, RunCommand(t, "user", "email", th.BasicUser.Username)) + require.Error(t, th.RunCommand(t, "user", "email", th.BasicUser.Username)) // should fail because missing both parameters - require.Error(t, RunCommand(t, "user", "email")) + require.Error(t, th.RunCommand(t, "user", "email")) // should fail because have more than 2 parameters - require.Error(t, RunCommand(t, "user", "email", th.BasicUser.Username, "new@email.com", "extra!")) + require.Error(t, th.RunCommand(t, "user", "email", th.BasicUser.Username, "new@email.com", "extra!")) // should fail because user not found - require.Error(t, RunCommand(t, "user", "email", "invalidUser", newEmail)) + require.Error(t, th.RunCommand(t, "user", "email", "invalidUser", newEmail)) // should fail because email already in use - require.Error(t, RunCommand(t, "user", "email", th.BasicUser.Username, th.BasicUser2.Email)) + require.Error(t, th.RunCommand(t, "user", "email", th.BasicUser.Username, th.BasicUser2.Email)) } diff --git a/cmd/mattermost/commands/version_test.go b/cmd/mattermost/commands/version_test.go index 0ed2c8ec59e..630024d9292 100644 --- a/cmd/mattermost/commands/version_test.go +++ b/cmd/mattermost/commands/version_test.go @@ -8,5 +8,8 @@ import ( ) func TestVersion(t *testing.T) { - CheckCommand(t, "version") + th := Setup() + defer th.TearDown() + + th.CheckCommand(t, "version") } diff --git a/cmd/mattermost/commands/webhook_test.go b/cmd/mattermost/commands/webhook_test.go index 96711596d82..44610ee821f 100644 --- a/cmd/mattermost/commands/webhook_test.go +++ b/cmd/mattermost/commands/webhook_test.go @@ -15,10 +15,18 @@ import ( ) func TestListWebhooks(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() adminClient := th.SystemAdminClient + config := th.Config() + *config.ServiceSettings.EnableCommands = true + config.ServiceSettings.EnableIncomingWebhooks = true + config.ServiceSettings.EnableOutgoingWebhooks = true + config.ServiceSettings.EnablePostUsernameOverride = true + config.ServiceSettings.EnablePostIconOverride = true + th.SetConfig(config) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnablePostUsernameOverride = true }) @@ -41,7 +49,7 @@ func TestListWebhooks(t *testing.T) { _, resp = adminClient.CreateOutgoingWebhook(outHook) api4.CheckNoError(t, resp) - output := CheckCommand(t, "webhook", "list", th.BasicTeam.Name) + output := th.CheckCommand(t, "webhook", "list", th.BasicTeam.Name) if !strings.Contains(string(output), dispName) { t.Fatal("should have incoming webhooks") @@ -54,9 +62,17 @@ func TestListWebhooks(t *testing.T) { } func TestCreateIncomingWebhook(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() + config := th.Config() + *config.ServiceSettings.EnableCommands = true + config.ServiceSettings.EnableIncomingWebhooks = true + config.ServiceSettings.EnableOutgoingWebhooks = true + config.ServiceSettings.EnablePostUsernameOverride = true + config.ServiceSettings.EnablePostIconOverride = true + th.SetConfig(config) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnablePostUsernameOverride = true }) @@ -70,16 +86,16 @@ func TestCreateIncomingWebhook(t *testing.T) { th.RemovePermissionFromRole(model.PERMISSION_MANAGE_WEBHOOKS.Id, model.TEAM_USER_ROLE_ID) // should fail because you need to specify valid channel - require.Error(t, RunCommand(t, "webhook", "create-incoming")) - require.Error(t, RunCommand(t, "webhook", "create-incoming", "--channel", th.BasicTeam.Name+":doesnotexist")) + require.Error(t, th.RunCommand(t, "webhook", "create-incoming")) + require.Error(t, th.RunCommand(t, "webhook", "create-incoming", "--channel", th.BasicTeam.Name+":doesnotexist")) // should fail because you need to specify valid user - require.Error(t, RunCommand(t, "webhook", "create-incoming", "--channel", th.BasicChannel.Id)) - require.Error(t, RunCommand(t, "webhook", "create-incoming", "--channel", th.BasicChannel.Id, "--user", "doesnotexist")) + require.Error(t, th.RunCommand(t, "webhook", "create-incoming", "--channel", th.BasicChannel.Id)) + require.Error(t, th.RunCommand(t, "webhook", "create-incoming", "--channel", th.BasicChannel.Id, "--user", "doesnotexist")) description := "myhookinc" displayName := "myhookinc" - CheckCommand(t, "webhook", "create-incoming", "--channel", th.BasicChannel.Id, "--user", th.BasicUser.Email, "--description", description, "--display-name", displayName) + th.CheckCommand(t, "webhook", "create-incoming", "--channel", th.BasicChannel.Id, "--user", th.BasicUser.Email, "--description", description, "--display-name", displayName) webhooks, err := th.App.GetIncomingWebhooksPage(0, 1000) if err != nil { @@ -98,9 +114,17 @@ func TestCreateIncomingWebhook(t *testing.T) { } func TestModifyIncomingWebhook(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() + config := th.Config() + *config.ServiceSettings.EnableCommands = true + config.ServiceSettings.EnableIncomingWebhooks = true + config.ServiceSettings.EnableOutgoingWebhooks = true + config.ServiceSettings.EnablePostUsernameOverride = true + config.ServiceSettings.EnablePostIconOverride = true + th.SetConfig(config) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnablePostUsernameOverride = true }) @@ -131,9 +155,9 @@ func TestModifyIncomingWebhook(t *testing.T) { }() // should fail because you need to specify valid incoming webhook - require.Error(t, RunCommand(t, "webhook", "modify-incoming", "doesnotexist")) + require.Error(t, th.RunCommand(t, "webhook", "modify-incoming", "doesnotexist")) // should fail because you need to specify valid channel - require.Error(t, RunCommand(t, "webhook", "modify-incoming", oldHook.Id, "--channel", th.BasicTeam.Name+":doesnotexist")) + require.Error(t, th.RunCommand(t, "webhook", "modify-incoming", oldHook.Id, "--channel", th.BasicTeam.Name+":doesnotexist")) modifiedDescription := "myhookincdesc2" modifiedDisplayName := "myhookincname2" @@ -141,7 +165,7 @@ func TestModifyIncomingWebhook(t *testing.T) { modifiedChannelLocked := true modifiedChannelId := th.BasicChannel2.Id - CheckCommand(t, "webhook", "modify-incoming", oldHook.Id, "--channel", modifiedChannelId, "--description", modifiedDescription, "--display-name", modifiedDisplayName, "--icon", modifiedIconUrl, "--lock-to-channel", strconv.FormatBool(modifiedChannelLocked)) + th.CheckCommand(t, "webhook", "modify-incoming", oldHook.Id, "--channel", modifiedChannelId, "--description", modifiedDescription, "--display-name", modifiedDisplayName, "--icon", modifiedIconUrl, "--lock-to-channel", strconv.FormatBool(modifiedChannelLocked)) modifiedHook, err := th.App.GetIncomingWebhook(oldHook.Id) if err != nil { @@ -153,9 +177,17 @@ func TestModifyIncomingWebhook(t *testing.T) { } func TestCreateOutgoingWebhook(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() + config := th.Config() + *config.ServiceSettings.EnableCommands = true + config.ServiceSettings.EnableIncomingWebhooks = true + config.ServiceSettings.EnableOutgoingWebhooks = true + config.ServiceSettings.EnablePostUsernameOverride = true + config.ServiceSettings.EnablePostIconOverride = true + th.SetConfig(config) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnablePostUsernameOverride = true }) @@ -178,24 +210,24 @@ func TestCreateOutgoingWebhook(t *testing.T) { callbackURL2 := "http://localhost:8000/my-webhook-handler2" // should fail because team is not specified - require.Error(t, RunCommand(t, "webhook", "create-outgoing", "--display-name", displayName, "--trigger-word", triggerWord1, "--trigger-word", triggerWord2, "--url", callbackURL1, "--url", callbackURL2, "--user", user)) + require.Error(t, th.RunCommand(t, "webhook", "create-outgoing", "--display-name", displayName, "--trigger-word", triggerWord1, "--trigger-word", triggerWord2, "--url", callbackURL1, "--url", callbackURL2, "--user", user)) // should fail because user is not specified - require.Error(t, RunCommand(t, "webhook", "create-outgoing", "--team", team, "--display-name", displayName, "--trigger-word", triggerWord1, "--trigger-word", triggerWord2, "--url", callbackURL1, "--url", callbackURL2)) + require.Error(t, th.RunCommand(t, "webhook", "create-outgoing", "--team", team, "--display-name", displayName, "--trigger-word", triggerWord1, "--trigger-word", triggerWord2, "--url", callbackURL1, "--url", callbackURL2)) // should fail because display name is not specified - require.Error(t, RunCommand(t, "webhook", "create-outgoing", "--team", team, "--trigger-word", triggerWord1, "--trigger-word", triggerWord2, "--url", callbackURL1, "--url", callbackURL2, "--user", user)) + require.Error(t, th.RunCommand(t, "webhook", "create-outgoing", "--team", team, "--trigger-word", triggerWord1, "--trigger-word", triggerWord2, "--url", callbackURL1, "--url", callbackURL2, "--user", user)) // should fail because trigger words are not specified - require.Error(t, RunCommand(t, "webhook", "create-outgoing", "--team", team, "--display-name", displayName, "--url", callbackURL1, "--url", callbackURL2, "--user", user)) + require.Error(t, th.RunCommand(t, "webhook", "create-outgoing", "--team", team, "--display-name", displayName, "--url", callbackURL1, "--url", callbackURL2, "--user", user)) // should fail because callback URLs are not specified - require.Error(t, RunCommand(t, "webhook", "create-outgoing", "--team", team, "--display-name", displayName, "--trigger-word", triggerWord1, "--trigger-word", triggerWord2, "--user", user)) + require.Error(t, th.RunCommand(t, "webhook", "create-outgoing", "--team", team, "--display-name", displayName, "--trigger-word", triggerWord1, "--trigger-word", triggerWord2, "--user", user)) // should fail because outgoing webhooks cannot be made for private channels - require.Error(t, RunCommand(t, "webhook", "create-outgoing", "--team", team, "--channel", th.BasicPrivateChannel.Id, "--display-name", displayName, "--trigger-word", triggerWord1, "--trigger-word", triggerWord2, "--url", callbackURL1, "--url", callbackURL2, "--user", user)) + require.Error(t, th.RunCommand(t, "webhook", "create-outgoing", "--team", team, "--channel", th.BasicPrivateChannel.Id, "--display-name", displayName, "--trigger-word", triggerWord1, "--trigger-word", triggerWord2, "--url", callbackURL1, "--url", callbackURL2, "--user", user)) - CheckCommand(t, "webhook", "create-outgoing", "--team", team, "--channel", th.BasicChannel.Id, "--display-name", displayName, "--trigger-word", triggerWord1, "--trigger-word", triggerWord2, "--url", callbackURL1, "--url", callbackURL2, "--user", user) + th.CheckCommand(t, "webhook", "create-outgoing", "--team", team, "--channel", th.BasicChannel.Id, "--display-name", displayName, "--trigger-word", triggerWord1, "--trigger-word", triggerWord2, "--url", callbackURL1, "--url", callbackURL2, "--user", user) webhooks, err := th.App.GetOutgoingWebhooksPage(0, 1000) if err != nil { @@ -214,10 +246,18 @@ func TestCreateOutgoingWebhook(t *testing.T) { } func TestDeleteWebhooks(t *testing.T) { - th := api4.Setup().InitBasic() + th := Setup().InitBasic() defer th.TearDown() adminClient := th.SystemAdminClient + config := th.Config() + *config.ServiceSettings.EnableCommands = true + config.ServiceSettings.EnableIncomingWebhooks = true + config.ServiceSettings.EnableOutgoingWebhooks = true + config.ServiceSettings.EnablePostUsernameOverride = true + config.ServiceSettings.EnablePostIconOverride = true + th.SetConfig(config) + th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableIncomingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOutgoingWebhooks = true }) th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnablePostUsernameOverride = true }) @@ -240,7 +280,7 @@ func TestDeleteWebhooks(t *testing.T) { outgoingHook, resp := adminClient.CreateOutgoingWebhook(outHookStruct) api4.CheckNoError(t, resp) - hooksBeforeDeletion := CheckCommand(t, "webhook", "list", th.BasicTeam.Name) + hooksBeforeDeletion := th.CheckCommand(t, "webhook", "list", th.BasicTeam.Name) if !strings.Contains(string(hooksBeforeDeletion), dispName) { t.Fatal("Should have incoming webhooks") @@ -250,10 +290,10 @@ func TestDeleteWebhooks(t *testing.T) { t.Fatal("Should have outgoing webhooks") } - CheckCommand(t, "webhook", "delete", incomingHook.Id) - CheckCommand(t, "webhook", "delete", outgoingHook.Id) + th.CheckCommand(t, "webhook", "delete", incomingHook.Id) + th.CheckCommand(t, "webhook", "delete", outgoingHook.Id) - hooksAfterDeletion := CheckCommand(t, "webhook", "list", th.BasicTeam.Name) + hooksAfterDeletion := th.CheckCommand(t, "webhook", "list", th.BasicTeam.Name) if strings.Contains(string(hooksAfterDeletion), dispName) { t.Fatal("Should not have incoming webhooks") diff --git a/migrations/migrationstestlib.go b/migrations/helper_test.go similarity index 66% rename from migrations/migrationstestlib.go rename to migrations/helper_test.go index e44067a357d..586de27cf22 100644 --- a/migrations/migrationstestlib.go +++ b/migrations/helper_test.go @@ -10,12 +10,8 @@ import ( "time" "github.com/mattermost/mattermost-server/app" - "github.com/mattermost/mattermost-server/einterfaces" "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store" - "github.com/mattermost/mattermost-server/store/sqlstore" - "github.com/mattermost/mattermost-server/store/storetest" "github.com/mattermost/mattermost-server/utils" ) @@ -34,37 +30,8 @@ type TestHelper struct { tempWorkspace string } -type persistentTestStore struct { - store.Store -} - -func (*persistentTestStore) Close() {} - -var testStoreContainer *storetest.RunningContainer -var testStore *persistentTestStore -var testStoreSqlSupplier *sqlstore.SqlSupplier -var testClusterInterface *FakeClusterInterface - -// UseTestStore sets the container and corresponding settings to use for tests. Once the tests are -// complete (e.g. at the end of your TestMain implementation), you should call StopTestStore. -func UseTestStore(container *storetest.RunningContainer, settings *model.SqlSettings) { - testClusterInterface = &FakeClusterInterface{} - testStoreContainer = container - testStoreSqlSupplier = sqlstore.NewSqlSupplier(*settings, nil) - testStore = &persistentTestStore{store.NewLayeredStore(testStoreSqlSupplier, nil, testClusterInterface)} -} - -func StopTestStore() { - if testStoreContainer != nil { - testStoreContainer.Stop() - testStoreContainer = nil - } -} - func setupTestHelper(enterprise bool) *TestHelper { - if testStore != nil { - testStore.DropAllTables() - } + mainHelper.Store.DropAllTables() permConfig, err := os.Open(utils.FindConfigFile("config.json")) if err != nil { @@ -82,9 +49,7 @@ func setupTestHelper(enterprise bool) *TestHelper { } options := []app.Option{app.ConfigFile(tempConfig.Name()), app.DisableConfigWatch} - if testStore != nil { - options = append(options, app.StoreOverride(testStore)) - } + options = append(options, app.StoreOverride(mainHelper.Store)) s, err := app.NewServer(options...) if err != nil { @@ -100,9 +65,8 @@ func setupTestHelper(enterprise bool) *TestHelper { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.TeamSettings.MaxUsersPerTeam = 50 }) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.RateLimitSettings.Enable = false }) prevListenAddress := *th.App.Config().ServiceSettings.ListenAddress - if testStore != nil { - th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" }) - } + th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.ListenAddress = ":0" }) + serverErr := th.App.StartServer() if serverErr != nil { panic(serverErr) @@ -295,7 +259,6 @@ func (me *TestHelper) TearDown() { me.Server.Shutdown() os.Remove(me.tempConfigPath) if err := recover(); err != nil { - StopTestStore() panic(err) } if me.tempWorkspace != "" { @@ -304,13 +267,13 @@ func (me *TestHelper) TearDown() { } func (me *TestHelper) ResetRoleMigration() { - if _, err := testStoreSqlSupplier.GetMaster().Exec("DELETE from Roles"); err != nil { + if _, err := mainHelper.SqlSupplier.GetMaster().Exec("DELETE from Roles"); err != nil { panic(err) } - testClusterInterface.sendClearRoleCacheMessage() + mainHelper.ClusterInterface.SendClearRoleCacheMessage() - if _, err := testStoreSqlSupplier.GetMaster().Exec("DELETE from Systems where Name = :Name", map[string]interface{}{"Name": app.ADVANCED_PERMISSIONS_MIGRATION_KEY}); err != nil { + if _, err := mainHelper.SqlSupplier.GetMaster().Exec("DELETE from Systems where Name = :Name", map[string]interface{}{"Name": app.ADVANCED_PERMISSIONS_MIGRATION_KEY}); err != nil { panic(err) } } @@ -330,36 +293,3 @@ func (me *TestHelper) DeleteAllJobsByTypeAndMigrationKey(jobType string, migrati } } } - -type FakeClusterInterface struct { - clusterMessageHandler einterfaces.ClusterMessageHandler -} - -func (me *FakeClusterInterface) StartInterNodeCommunication() {} -func (me *FakeClusterInterface) StopInterNodeCommunication() {} -func (me *FakeClusterInterface) RegisterClusterMessageHandler(event string, crm einterfaces.ClusterMessageHandler) { - me.clusterMessageHandler = crm -} -func (me *FakeClusterInterface) GetClusterId() string { return "" } -func (me *FakeClusterInterface) IsLeader() bool { return false } -func (me *FakeClusterInterface) GetMyClusterInfo() *model.ClusterInfo { return nil } -func (me *FakeClusterInterface) GetClusterInfos() []*model.ClusterInfo { return nil } -func (me *FakeClusterInterface) SendClusterMessage(cluster *model.ClusterMessage) {} -func (me *FakeClusterInterface) NotifyMsg(buf []byte) {} -func (me *FakeClusterInterface) GetClusterStats() ([]*model.ClusterStats, *model.AppError) { - return nil, nil -} -func (me *FakeClusterInterface) GetLogs(page, perPage int) ([]string, *model.AppError) { - return []string{}, nil -} -func (me *FakeClusterInterface) ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError { - return nil -} -func (me *FakeClusterInterface) sendClearRoleCacheMessage() { - me.clusterMessageHandler(&model.ClusterMessage{ - Event: model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES, - }) -} -func (me *FakeClusterInterface) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { - return nil, nil -} diff --git a/migrations/main_test.go b/migrations/main_test.go new file mode 100644 index 00000000000..89d1c31072f --- /dev/null +++ b/migrations/main_test.go @@ -0,0 +1,19 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package migrations + +import ( + "testing" + + "github.com/mattermost/mattermost-server/testlib" +) + +var mainHelper *testlib.MainHelper + +func TestMain(m *testing.M) { + mainHelper = testlib.NewMainHelper() + defer mainHelper.Close() + + mainHelper.Main(m) +} diff --git a/migrations/migrations_test.go b/migrations/migrations_test.go index 30831943064..aabfd75a8b5 100644 --- a/migrations/migrations_test.go +++ b/migrations/migrations_test.go @@ -4,56 +4,13 @@ package migrations import ( - "flag" - "os" "testing" "github.com/stretchr/testify/assert" - "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store/storetest" - "github.com/mattermost/mattermost-server/utils" ) -func TestMain(m *testing.M) { - flag.Parse() - - // Setup a global logger to catch tests logging outside of app context - // The global logger will be stomped by apps initalizing but that's fine for testing. Ideally this won't happen. - mlog.InitGlobalLogger(mlog.NewLogger(&mlog.LoggerConfiguration{ - EnableConsole: true, - ConsoleJson: true, - ConsoleLevel: "error", - EnableFile: false, - })) - - utils.TranslationsPreInit() - - // In the case where a dev just wants to run a single test, it's faster to just use the default - // store. - if filter := flag.Lookup("test.run").Value.String(); filter != "" && filter != "." { - mlog.Info("-test.run used, not creating temporary containers") - os.Exit(m.Run()) - } - - status := 0 - - container, settings, err := storetest.NewMySQLContainer() - if err != nil { - panic(err) - } - - UseTestStore(container, settings) - - defer func() { - StopTestStore() - os.Exit(status) - }() - - status = m.Run() -} - func TestGetMigrationState(t *testing.T) { th := Setup() defer th.TearDown() diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 00000000000..7d4d620d115 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -o pipefail + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" + +GO=$1 +GOFLAGS=$2 +PACKAGES=$3 +TESTS=$4 +TESTFLAGS=$5 + +PACKAGES_COMMA=$(echo $PACKAGES | tr ' ' ',') + +echo "Packages to test: $PACKAGES" +find . -name 'cprofile*.out' -exec sh -c 'rm "{}"' \; +find . -type d -name data -not -path './vendor/*' | xargs rm -rf + +$GO test $GOFLAGS -run=$TESTS $TESTFLAGS -p 1 -v -timeout=2000s -covermode=count -coverpkg=$PACKAGES_COMMA -exec $DIR/test-xprog.sh $PACKAGES 2>&1 | tee output +EXIT_STATUS=$? + +cat output | $GOPATH/bin/go-junit-report > report.xml +rm output +find . -name 'cprofile*.out' -exec sh -c 'tail -n +2 {} >> cover.out ; rm "{}"' \; +rm -f config/*.crt +rm -f config/*.key + +exit $EXIT_STATUS diff --git a/testlib/cluster.go b/testlib/cluster.go new file mode 100644 index 00000000000..532672baad0 --- /dev/null +++ b/testlib/cluster.go @@ -0,0 +1,55 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package testlib + +import ( + "github.com/mattermost/mattermost-server/einterfaces" + "github.com/mattermost/mattermost-server/model" +) + +type FakeClusterInterface struct { + clusterMessageHandler einterfaces.ClusterMessageHandler +} + +func (c *FakeClusterInterface) StartInterNodeCommunication() {} + +func (c *FakeClusterInterface) StopInterNodeCommunication() {} + +func (c *FakeClusterInterface) RegisterClusterMessageHandler(event string, crm einterfaces.ClusterMessageHandler) { + c.clusterMessageHandler = crm +} + +func (c *FakeClusterInterface) GetClusterId() string { return "" } + +func (c *FakeClusterInterface) IsLeader() bool { return false } + +func (c *FakeClusterInterface) GetMyClusterInfo() *model.ClusterInfo { return nil } + +func (c *FakeClusterInterface) GetClusterInfos() []*model.ClusterInfo { return nil } + +func (c *FakeClusterInterface) SendClusterMessage(cluster *model.ClusterMessage) {} + +func (c *FakeClusterInterface) NotifyMsg(buf []byte) {} + +func (c *FakeClusterInterface) GetClusterStats() ([]*model.ClusterStats, *model.AppError) { + return nil, nil +} + +func (c *FakeClusterInterface) GetLogs(page, perPage int) ([]string, *model.AppError) { + return []string{}, nil +} + +func (c *FakeClusterInterface) ConfigChanged(previousConfig *model.Config, newConfig *model.Config, sendToOtherServer bool) *model.AppError { + return nil +} + +func (c *FakeClusterInterface) SendClearRoleCacheMessage() { + c.clusterMessageHandler(&model.ClusterMessage{ + Event: model.CLUSTER_EVENT_INVALIDATE_CACHE_FOR_ROLES, + }) +} + +func (c *FakeClusterInterface) GetPluginStatuses() (model.PluginStatuses, *model.AppError) { + return nil, nil +} diff --git a/testlib/doc.go b/testlib/doc.go new file mode 100644 index 00000000000..77897e96c15 --- /dev/null +++ b/testlib/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +// testlib exposes helper methods for running unit tests against a containerized test store. +package testlib diff --git a/testlib/helper.go b/testlib/helper.go new file mode 100644 index 00000000000..bcc19f60a7f --- /dev/null +++ b/testlib/helper.go @@ -0,0 +1,70 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package testlib + +import ( + "os" + "testing" + + "github.com/mattermost/mattermost-server/mlog" + "github.com/mattermost/mattermost-server/model" + "github.com/mattermost/mattermost-server/store" + "github.com/mattermost/mattermost-server/store/sqlstore" + "github.com/mattermost/mattermost-server/store/storetest" + "github.com/mattermost/mattermost-server/utils" +) + +type MainHelper struct { + Settings *model.SqlSettings + Store store.Store + SqlSupplier *sqlstore.SqlSupplier + ClusterInterface *FakeClusterInterface + + container *storetest.RunningContainer + status int +} + +func NewMainHelper() *MainHelper { + // Setup a global logger to catch tests logging outside of app context + // The global logger will be stomped by apps initalizing but that's fine for testing. + // Ideally this won't happen. + mlog.InitGlobalLogger(mlog.NewLogger(&mlog.LoggerConfiguration{ + EnableConsole: true, + ConsoleJson: true, + ConsoleLevel: "error", + EnableFile: false, + })) + + utils.TranslationsPreInit() + + container, settings, err := storetest.NewMySQLContainer() + if err != nil { + panic("failed to start mysql container: " + err.Error()) + } + + testClusterInterface := &FakeClusterInterface{} + testStoreSqlSupplier := sqlstore.NewSqlSupplier(*settings, nil) + testStore := &TestStore{store.NewLayeredStore(testStoreSqlSupplier, nil, testClusterInterface)} + + return &MainHelper{ + Settings: settings, + Store: testStore, + SqlSupplier: testStoreSqlSupplier, + ClusterInterface: testClusterInterface, + container: container, + } +} + +func (h *MainHelper) Main(m *testing.M) { + h.status = m.Run() +} + +func (h *MainHelper) Close() error { + h.container.Stop() + h.container = nil + + os.Exit(h.status) + + return nil +} diff --git a/testlib/store.go b/testlib/store.go new file mode 100644 index 00000000000..5d20d3754d7 --- /dev/null +++ b/testlib/store.go @@ -0,0 +1,15 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package testlib + +import ( + "github.com/mattermost/mattermost-server/store" +) + +type TestStore struct { + store.Store +} + +func (*TestStore) Close() { +} diff --git a/tests/test-config.json b/tests/test-config.json deleted file mode 100644 index 69b784ff5ad..00000000000 --- a/tests/test-config.json +++ /dev/null @@ -1,401 +0,0 @@ -{ - "ServiceSettings": { - "SiteURL": "", - "WebsocketURL": "", - "LicenseFileLocation": "", - "ListenAddress": ":8065", - "ConnectionSecurity": "", - "TLSCertFile": "", - "TLSKeyFile": "", - "UseLetsEncrypt": false, - "LetsEncryptCertificateCacheFile": "./config/letsencrypt.cache", - "Forward80To443": false, - "ReadTimeout": 300, - "WriteTimeout": 300, - "MaximumLoginAttempts": 10, - "GoroutineHealthThreshold": -1, - "GoogleDeveloperKey": "", - "EnableOAuthServiceProvider": false, - "EnableIncomingWebhooks": true, - "EnableOutgoingWebhooks": true, - "EnableCommands": true, - "EnableOnlyAdminIntegrations": true, - "EnablePostUsernameOverride": false, - "EnablePostIconOverride": false, - "EnableLinkPreviews": false, - "EnableTesting": false, - "EnableDeveloper": false, - "EnableSecurityFixAlert": true, - "EnableInsecureOutgoingConnections": false, - "AllowedUntrustedInternalConnections": "", - "EnableMultifactorAuthentication": false, - "EnforceMultifactorAuthentication": false, - "EnableUserAccessTokens": false, - "AllowCorsFrom": "", - "AllowCookiesForSubdomains": false, - "SessionLengthWebInDays": 30, - "SessionLengthMobileInDays": 30, - "SessionLengthSSOInDays": 30, - "SessionCacheInMinutes": 10, - "SessionIdleTimeoutInMinutes": 0, - "WebsocketSecurePort": 443, - "WebsocketPort": 80, - "WebserverMode": "gzip", - "EnableCustomEmoji": false, - "EnableEmojiPicker": true, - "RestrictCustomEmojiCreation": "all", - "RestrictPostDelete": "all", - "AllowEditPost": "always", - "PostEditTimeLimit": -1, - "TimeBetweenUserTypingUpdatesMilliseconds": 5000, - "EnablePostSearch": true, - "EnableUserTypingMessages": true, - "EnableChannelViewedMessages": true, - "EnableUserStatuses": true, - "ExperimentalEnableAuthenticationTransfer": true, - "ClusterLogTimeoutMilliseconds": 2000, - "CloseUnusedDirectMessages": false, - "EnablePreviewFeatures": true, - "EnableTutorial": true, - "ExperimentalEnableDefaultChannelLeaveJoinMessages": true, - "ExperimentalGroupUnreadChannels": "disabled", - "ImageProxyType": "", - "ImageProxyURL": "", - "ImageProxyOptions": "", - "EnableAPITeamDeletion": false, - "ExperimentalEnableHardenedMode": false - }, - "TeamSettings": { - "SiteName": "Mattermost", - "MaxUsersPerTeam": 50, - "EnableTeamCreation": true, - "EnableUserCreation": true, - "EnableOpenServer": false, - "EnableUserDeactivation": false, - "RestrictCreationToDomains": "", - "EnableCustomBrand": false, - "CustomBrandText": "", - "CustomDescriptionText": "", - "RestrictDirectMessage": "any", - "RestrictTeamInvite": "all", - "RestrictPublicChannelManagement": "all", - "RestrictPrivateChannelManagement": "all", - "RestrictPublicChannelCreation": "all", - "RestrictPrivateChannelCreation": "all", - "RestrictPublicChannelDeletion": "all", - "RestrictPrivateChannelDeletion": "all", - "RestrictPrivateChannelManageMembers": "all", - "EnableXToLeaveChannelsFromLHS": false, - "UserStatusAwayTimeout": 300, - "MaxChannelsPerTeam": 2000, - "MaxNotificationsPerChannel": 1000, - "EnableConfirmNotificationsToChannel": true, - "TeammateNameDisplay": "username", - "ExperimentalEnableAutomaticReplies": false, - "ExperimentalHideTownSquareinLHS": false, - "ExperimentalTownSquareIsReadOnly": false, - "ExperimentalPrimaryTeam": "" - }, - "ClientRequirements": { - "AndroidLatestVersion": "", - "AndroidMinVersion": "", - "DesktopLatestVersion": "", - "DesktopMinVersion": "", - "IosLatestVersion": "", - "IosMinVersion": "" - }, - "SqlSettings": { - "DriverName": "mysql", - "DataSource": "mmuser:mostest@tcp(dockerhost:3306)/mattermost_test?charset=utf8mb4,utf8\u0026readTimeout=30s\u0026writeTimeout=30s", - "DataSourceReplicas": [], - "DataSourceSearchReplicas": [], - "MaxIdleConns": 20, - "MaxOpenConns": 300, - "Trace": false, - "AtRestEncryptKey": "jdh9iergmse3w9mt53snasugmmi9r6it", - "QueryTimeout": 30 - }, - "LogSettings": { - "EnableConsole": true, - "ConsoleLevel": "DEBUG", - "ConsoleJson": true, - "EnableFile": true, - "FileLevel": "INFO", - "FileJson": true, - "FileLocation": "", - "EnableWebhookDebugging": true, - "EnableDiagnostics": true - }, - "PasswordSettings": { - "MinimumLength": 5, - "Lowercase": false, - "Number": false, - "Uppercase": false, - "Symbol": false - }, - "FileSettings": { - "EnableFileAttachments": true, - "EnableMobileUpload": true, - "EnableMobileDownload": true, - "MaxFileSize": 52428800, - "DriverName": "local", - "Directory": "./data/", - "EnablePublicLink": false, - "PublicLinkSalt": "3xh7ztscuezjp1jkdjybtejrtw59xjt1", - "InitialFont": "luximbi.ttf", - "AmazonS3AccessKeyId": "", - "AmazonS3SecretAccessKey": "", - "AmazonS3Bucket": "", - "AmazonS3Region": "", - "AmazonS3Endpoint": "s3.amazonaws.com", - "AmazonS3SSL": true, - "AmazonS3SignV2": false, - "AmazonS3SSE": false, - "AmazonS3Trace": false - }, - "EmailSettings": { - "EnableSignUpWithEmail": true, - "EnableSignInWithEmail": true, - "EnableSignInWithUsername": true, - "SendEmailNotifications": true, - "UseChannelInEmailNotifications": false, - "RequireEmailVerification": false, - "FeedbackName": "", - "FeedbackEmail": "test@example.com", - "FeedbackOrganization": "", - "EnableSMTPAuth": false, - "SMTPUsername": "", - "SMTPPassword": "", - "SMTPServer": "dockerhost", - "SMTPPort": "2500", - "ConnectionSecurity": "", - "InviteSalt": "n3mceqsek4j5ichs5hw9sudwx3cfbtqa", - "SendPushNotifications": false, - "PushNotificationServer": "", - "PushNotificationContents": "generic", - "EnableEmailBatching": false, - "EmailBatchingBufferSize": 256, - "EmailBatchingInterval": 30, - "EnablePreviewModeBanner": true, - "SkipServerCertificateVerification": false, - "EmailNotificationContentsType": "full", - "LoginButtonColor": "", - "LoginButtonBorderColor": "", - "LoginButtonTextColor": "" - }, - "RateLimitSettings": { - "Enable": false, - "PerSec": 10, - "MaxBurst": 100, - "MemoryStoreSize": 10000, - "VaryByRemoteAddr": true, - "VaryByUser": false, - "VaryByHeader": "" - }, - "PrivacySettings": { - "ShowEmailAddress": true, - "ShowFullName": true - }, - "SupportSettings": { - "TermsOfServiceLink": "https://about.mattermost.com/default-terms/", - "PrivacyPolicyLink": "https://about.mattermost.com/default-privacy-policy/", - "AboutLink": "https://about.mattermost.com/default-about/", - "HelpLink": "https://about.mattermost.com/default-help/", - "ReportAProblemLink": "https://about.mattermost.com/default-report-a-problem/", - "SupportEmail": "feedback@mattermost.com" - }, - "AnnouncementSettings": { - "EnableBanner": false, - "BannerText": "", - "BannerColor": "#f2a93b", - "BannerTextColor": "#333333", - "AllowBannerDismissal": true - }, - "ThemeSettings": { - "EnableThemeSelection": true, - "DefaultTheme": "default", - "AllowCustomThemes": true, - "AllowedThemes": [] - }, - "GitLabSettings": { - "Enable": false, - "Secret": "", - "Id": "", - "Scope": "", - "AuthEndpoint": "", - "TokenEndpoint": "", - "UserApiEndpoint": "" - }, - "GoogleSettings": { - "Enable": false, - "Secret": "", - "Id": "", - "Scope": "profile email", - "AuthEndpoint": "https://accounts.google.com/o/oauth2/v2/auth", - "TokenEndpoint": "https://www.googleapis.com/oauth2/v4/token", - "UserApiEndpoint": "https://www.googleapis.com/plus/v1/people/me" - }, - "Office365Settings": { - "Enable": false, - "Secret": "", - "Id": "", - "Scope": "User.Read", - "AuthEndpoint": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", - "TokenEndpoint": "https://login.microsoftonline.com/common/oauth2/v2.0/token", - "UserApiEndpoint": "https://graph.microsoft.com/v1.0/me" - }, - "LdapSettings": { - "Enable": false, - "EnableSync": false, - "LdapServer": "", - "LdapPort": 389, - "ConnectionSecurity": "", - "BaseDN": "", - "BindUsername": "", - "BindPassword": "", - "UserFilter": "", - "FirstNameAttribute": "", - "LastNameAttribute": "", - "EmailAttribute": "", - "UsernameAttribute": "", - "NicknameAttribute": "", - "IdAttribute": "", - "PositionAttribute": "", - "LoginIdAttribute": "", - "SyncIntervalMinutes": 60, - "SkipCertificateVerification": false, - "QueryTimeout": 60, - "MaxPageSize": 0, - "LoginFieldName": "", - "LoginButtonColor": "", - "LoginButtonBorderColor": "", - "LoginButtonTextColor": "" - }, - "ComplianceSettings": { - "Enable": false, - "Directory": "./data/", - "EnableDaily": false - }, - "LocalizationSettings": { - "DefaultServerLocale": "en", - "DefaultClientLocale": "en", - "AvailableLocales": "" - }, - "SamlSettings": { - "Enable": false, - "EnableSyncWithLdap": false, - "Verify": true, - "Encrypt": true, - "IdpUrl": "", - "IdpDescriptorUrl": "", - "AssertionConsumerServiceURL": "", - "ScopingIDPProviderId": "", - "ScopingIDPName": "", - "IdpCertificateFile": "", - "PublicCertificateFile": "", - "PrivateKeyFile": "", - "FirstNameAttribute": "", - "LastNameAttribute": "", - "EmailAttribute": "", - "UsernameAttribute": "", - "NicknameAttribute": "", - "LocaleAttribute": "", - "PositionAttribute": "", - "LoginButtonText": "With SAML", - "LoginButtonColor": "", - "LoginButtonBorderColor": "", - "LoginButtonTextColor": "" - }, - "NativeAppSettings": { - "AppDownloadLink": "https://about.mattermost.com/downloads/", - "AndroidAppDownloadLink": "https://about.mattermost.com/mattermost-android-app/", - "IosAppDownloadLink": "https://about.mattermost.com/mattermost-ios-app/" - }, - "ClusterSettings": { - "Enable": false, - "ClusterName": "", - "OverrideHostname": "", - "UseIpAddress": true, - "UseExperimentalGossip": false, - "ReadOnlyConfig": true, - "GossipPort": 8074, - "StreamingPort": 8075, - "MaxIdleConns": 100, - "MaxIdleConnsPerHost": 128, - "IdleConnTimeoutMilliseconds": 90000 - }, - "MetricsSettings": { - "Enable": false, - "BlockProfileRate": 0, - "ListenAddress": ":8067" - }, - "ExperimentalSettings": { - "ClientSideCertEnable": false, - "ClientSideCertCheck": "secondary" - }, - "AnalyticsSettings": { - "MaxUsersForStatistics": 2500 - }, - "ElasticsearchSettings": { - "ConnectionUrl": "http://dockerhost:9200", - "Username": "elastic", - "Password": "changeme", - "EnableIndexing": false, - "EnableSearching": false, - "Sniff": true, - "PostIndexReplicas": 1, - "PostIndexShards": 1, - "AggregatePostsAfterDays": 365, - "PostsAggregatorJobStartTime": "03:00", - "IndexPrefix": "", - "LiveIndexingBatchSize": 1, - "BulkIndexingTimeWindowSeconds": 3600, - "RequestTimeoutSeconds": 30 - }, - "DataRetentionSettings": { - "EnableMessageDeletion": false, - "EnableFileDeletion": false, - "MessageRetentionDays": 365, - "FileRetentionDays": 365, - "DeletionJobStartTime": "02:00" - }, - "MessageExportSettings": { - "EnableExport": false, - "ExportFormat": "actiance", - "DailyRunTime": "01:00", - "ExportFromTimestamp": 0, - "BatchSize": 10000, - "GlobalRelaySettings": { - "CustomerType": "A9", - "SmtpUsername": "", - "SmtpPassword": "", - "EmailAddress": "" - } - }, - "JobSettings": { - "RunJobs": true, - "RunScheduler": true - }, - "PluginSettings": { - "Enable": true, - "EnableUploads": true, - "Directory": "./test-plugins", - "ClientDirectory": "./test-client-plugins", - "Plugins": {}, - "PluginStates": { - "jira": { - "Enable": true - }, - "testplugin": { - "Enable": false - } - } - }, - "DisplaySettings": { - "CustomUrlSchemes": [], - "ExperimentalTimezone": false - }, - "TimezoneSettings": { - "SupportedTimezonesPath": "timezones.json" - } -} diff --git a/web/handlers_test.go b/web/handlers_test.go index 817352a112f..2cd3b195951 100644 --- a/web/handlers_test.go +++ b/web/handlers_test.go @@ -18,7 +18,7 @@ func handlerForHTTPErrors(c *Context, w http.ResponseWriter, r *http.Request) { } func TestHandlerServeHTTPErrors(t *testing.T) { - s, err := app.NewServer(app.StoreOverride(testStore), app.DisableConfigWatch) + s, err := app.NewServer(app.StoreOverride(mainHelper.Store), app.DisableConfigWatch) defer s.Shutdown() web := New(s, s.AppOptions, s.Router) @@ -61,7 +61,7 @@ func handlerForHTTPSecureTransport(c *Context, w http.ResponseWriter, r *http.Re } func TestHandlerServeHTTPSecureTransport(t *testing.T) { - s, err := app.NewServer(app.StoreOverride(testStore), app.DisableConfigWatch) + s, err := app.NewServer(app.StoreOverride(mainHelper.Store), app.DisableConfigWatch) defer s.Shutdown() a := s.FakeApp() diff --git a/web/main_test.go b/web/main_test.go new file mode 100644 index 00000000000..49b9f82a9a0 --- /dev/null +++ b/web/main_test.go @@ -0,0 +1,19 @@ +// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +package web + +import ( + "testing" + + "github.com/mattermost/mattermost-server/testlib" +) + +var mainHelper *testlib.MainHelper + +func TestMain(m *testing.M) { + mainHelper = testlib.NewMainHelper() + defer mainHelper.Close() + + mainHelper.Main(m) +} diff --git a/web/web_test.go b/web/web_test.go index 49b0d18acfb..9b422982826 100644 --- a/web/web_test.go +++ b/web/web_test.go @@ -5,37 +5,15 @@ package web import ( "fmt" - "os" "testing" "github.com/mattermost/mattermost-server/app" - "github.com/mattermost/mattermost-server/mlog" "github.com/mattermost/mattermost-server/model" - "github.com/mattermost/mattermost-server/store" - "github.com/mattermost/mattermost-server/store/sqlstore" - "github.com/mattermost/mattermost-server/store/storetest" - "github.com/mattermost/mattermost-server/utils" ) var ApiClient *model.Client4 var URL string -type persistentTestStore struct { - store.Store -} - -func (*persistentTestStore) Close() {} - -var testStoreContainer *storetest.RunningContainer -var testStore *persistentTestStore - -func StopTestStore() { - if testStoreContainer != nil { - testStoreContainer.Stop() - testStoreContainer = nil - } -} - type TestHelper struct { App *app.App Server *app.Server @@ -48,7 +26,9 @@ type TestHelper struct { } func Setup() *TestHelper { - s, err := app.NewServer(app.StoreOverride(testStore), app.DisableConfigWatch) + mainHelper.Store.DropAllTables() + + s, err := app.NewServer(app.StoreOverride(mainHelper.Store), app.DisableConfigWatch) if err != nil { panic(err) } @@ -103,7 +83,6 @@ func (th *TestHelper) InitBasic() *TestHelper { func (th *TestHelper) TearDown() { th.Server.Shutdown() if err := recover(); err != nil { - StopTestStore() panic(err) } } @@ -125,37 +104,6 @@ func TestStatic(t *testing.T) { } */ -func TestMain(m *testing.M) { - // Setup a global logger to catch tests logging outside of app context - // The global logger will be stomped by apps initalizing but that's fine for testing. Ideally this won't happen. - mlog.InitGlobalLogger(mlog.NewLogger(&mlog.LoggerConfiguration{ - EnableConsole: true, - ConsoleJson: true, - ConsoleLevel: "error", - EnableFile: false, - })) - - utils.TranslationsPreInit() - - status := 0 - - container, settings, err := storetest.NewPostgreSQLContainer() - if err != nil { - panic(err) - } - - testStoreContainer = container - testStore = &persistentTestStore{store.NewLayeredStore(sqlstore.NewSqlSupplier(*settings, nil), nil, nil)} - - defer func() { - StopTestStore() - os.Exit(status) - }() - - status = m.Run() - -} - func TestCheckClientCompatability(t *testing.T) { //Browser Name, UA String, expected result (if the browser should fail the test false and if it should pass the true) type uaTest struct {