2019-11-29 06:59:40 -05:00
|
|
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
|
|
|
// See LICENSE.txt for license information.
|
2017-01-30 08:30:02 -05:00
|
|
|
|
|
|
|
|
package api4
|
|
|
|
|
|
|
|
|
|
import (
|
2020-08-12 13:53:47 -04:00
|
|
|
"context"
|
2026-02-10 15:12:14 -05:00
|
|
|
"encoding/json"
|
2021-08-13 07:12:16 -04:00
|
|
|
"errors"
|
2017-10-02 04:50:56 -04:00
|
|
|
"fmt"
|
2022-02-11 02:07:05 -05:00
|
|
|
"io"
|
2020-05-23 17:01:31 -04:00
|
|
|
"math/rand"
|
2017-10-12 15:24:54 -04:00
|
|
|
"net"
|
2017-01-30 08:30:02 -05:00
|
|
|
"net/http"
|
2017-02-17 10:31:21 -05:00
|
|
|
"os"
|
2020-01-15 13:38:55 -05:00
|
|
|
"path/filepath"
|
2025-07-18 06:54:51 -04:00
|
|
|
"slices"
|
2017-01-30 08:30:02 -05:00
|
|
|
"strings"
|
2020-03-03 08:19:54 -05:00
|
|
|
"sync"
|
2017-01-30 08:30:02 -05:00
|
|
|
"testing"
|
2017-01-31 09:31:53 -05:00
|
|
|
"time"
|
2017-01-30 08:30:02 -05:00
|
|
|
|
2021-11-16 09:13:43 -05:00
|
|
|
"github.com/gorilla/websocket"
|
2021-01-07 12:12:43 -05:00
|
|
|
s3 "github.com/minio/minio-go/v7"
|
|
|
|
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
|
2023-06-11 01:24:35 -04:00
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
|
|
|
"github.com/mattermost/mattermost/server/public/plugin/plugintest/mock"
|
|
|
|
|
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
2023-09-05 03:47:30 -04:00
|
|
|
"github.com/mattermost/mattermost/server/public/shared/request"
|
2023-06-11 01:24:35 -04:00
|
|
|
"github.com/mattermost/mattermost/server/v8/channels/app"
|
|
|
|
|
"github.com/mattermost/mattermost/server/v8/channels/store"
|
|
|
|
|
"github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
|
|
|
|
|
"github.com/mattermost/mattermost/server/v8/channels/testlib"
|
|
|
|
|
"github.com/mattermost/mattermost/server/v8/channels/web"
|
|
|
|
|
"github.com/mattermost/mattermost/server/v8/channels/wsapi"
|
|
|
|
|
"github.com/mattermost/mattermost/server/v8/config"
|
|
|
|
|
"github.com/mattermost/mattermost/server/v8/platform/services/searchengine"
|
2017-01-30 08:30:02 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type TestHelper struct {
|
2019-03-06 15:06:45 -05:00
|
|
|
App *app.App
|
|
|
|
|
Server *app.Server
|
2020-10-29 18:54:39 -04:00
|
|
|
ConfigStore *config.Store
|
2025-05-30 07:58:26 -04:00
|
|
|
Store store.Store
|
2017-09-06 18:12:54 -04:00
|
|
|
|
2021-05-11 06:00:44 -04:00
|
|
|
Context *request.Context
|
2019-12-11 14:18:36 -05:00
|
|
|
Client *model.Client4
|
|
|
|
|
BasicUser *model.User
|
|
|
|
|
BasicUser2 *model.User
|
|
|
|
|
TeamAdminUser *model.User
|
|
|
|
|
BasicTeam *model.Team
|
|
|
|
|
BasicChannel *model.Channel
|
|
|
|
|
BasicPrivateChannel *model.Channel
|
|
|
|
|
BasicPrivateChannel2 *model.Channel
|
|
|
|
|
BasicDeletedChannel *model.Channel
|
|
|
|
|
BasicChannel2 *model.Channel
|
|
|
|
|
BasicPost *model.Post
|
|
|
|
|
Group *model.Group
|
2017-01-31 09:31:53 -05:00
|
|
|
|
|
|
|
|
SystemAdminClient *model.Client4
|
|
|
|
|
SystemAdminUser *model.User
|
2018-06-25 15:33:13 -04:00
|
|
|
tempWorkspace string
|
2020-03-04 08:18:03 -05:00
|
|
|
|
2021-01-12 14:45:17 -05:00
|
|
|
SystemManagerClient *model.Client4
|
|
|
|
|
SystemManagerUser *model.User
|
|
|
|
|
|
2020-05-19 12:20:41 -04:00
|
|
|
LocalClient *model.Client4
|
|
|
|
|
|
2020-03-04 08:18:03 -05:00
|
|
|
IncludeCacheLayer bool
|
2021-08-17 16:08:04 -04:00
|
|
|
|
2023-03-27 12:19:29 -04:00
|
|
|
LogBuffer *mlog.Buffer
|
|
|
|
|
TestLogger *mlog.Logger
|
2017-01-30 08:30:02 -05:00
|
|
|
}
|
|
|
|
|
|
2020-03-02 11:13:39 -05:00
|
|
|
var mainHelper *testlib.MainHelper
|
2018-11-20 20:16:25 -05:00
|
|
|
|
2020-03-11 11:25:45 -04:00
|
|
|
func SetMainHelper(mh *testlib.MainHelper) {
|
|
|
|
|
mainHelper = mh
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-30 07:58:26 -04:00
|
|
|
func setupTestHelper(tb testing.TB, dbStore store.Store, sqlSettings *model.SqlSettings, searchEngine *searchengine.Broker, enterprise bool, includeCache bool,
|
2025-01-29 08:58:43 -05:00
|
|
|
updateConfig func(*model.Config), options []app.Option,
|
|
|
|
|
) *TestHelper {
|
2022-08-09 07:25:46 -04:00
|
|
|
tempWorkspace, err := os.MkdirTemp("", "apptest")
|
2025-05-21 10:35:00 -04:00
|
|
|
require.NoError(tb, err)
|
2020-01-15 13:38:55 -05:00
|
|
|
|
2019-08-09 11:33:59 -04:00
|
|
|
memoryStore, err := config.NewMemoryStoreWithOptions(&config.MemoryStoreOptions{IgnoreEnvironmentOverrides: true})
|
2025-05-21 10:35:00 -04:00
|
|
|
require.NoError(tb, err, "failed to initialize memory store")
|
2018-01-17 13:38:37 -05:00
|
|
|
|
2023-04-18 07:58:33 -04:00
|
|
|
memoryConfig := &model.Config{
|
2025-05-30 07:58:26 -04:00
|
|
|
SqlSettings: model.SafeDereference(sqlSettings),
|
2023-04-18 07:58:33 -04:00
|
|
|
}
|
2020-10-29 18:54:39 -04:00
|
|
|
memoryConfig.SetDefaults()
|
2025-05-30 07:58:26 -04:00
|
|
|
*memoryConfig.ServiceSettings.LicenseFileLocation = filepath.Join(tempWorkspace, "license.json")
|
|
|
|
|
*memoryConfig.FileSettings.Directory = filepath.Join(tempWorkspace, "data")
|
2020-10-29 18:54:39 -04:00
|
|
|
*memoryConfig.PluginSettings.Directory = filepath.Join(tempWorkspace, "plugins")
|
|
|
|
|
*memoryConfig.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp")
|
2025-05-21 10:35:00 -04:00
|
|
|
*memoryConfig.FileSettings.Directory = filepath.Join(tempWorkspace, "data")
|
|
|
|
|
*memoryConfig.ServiceSettings.EnableLocalMode = true
|
2020-10-29 18:54:39 -04:00
|
|
|
*memoryConfig.ServiceSettings.LocalModeSocketLocation = filepath.Join(tempWorkspace, "mattermost_local.sock")
|
2023-11-30 04:47:04 -05:00
|
|
|
*memoryConfig.LogSettings.EnableSentry = false // disable error reporting during tests
|
2025-06-07 17:35:08 -04:00
|
|
|
|
|
|
|
|
// Check for environment variable override for console log level (useful for debugging tests)
|
|
|
|
|
consoleLevel := os.Getenv("MM_LOGSETTINGS_CONSOLELEVEL")
|
|
|
|
|
if consoleLevel == "" {
|
|
|
|
|
consoleLevel = mlog.LvlStdLog.Name
|
|
|
|
|
}
|
|
|
|
|
*memoryConfig.LogSettings.ConsoleLevel = consoleLevel
|
2026-01-29 13:29:55 -05:00
|
|
|
// Use a subdirectory within the logging root (from MM_LOG_PATH or default)
|
|
|
|
|
// to ensure the path is within the allowed logging root for security validation.
|
|
|
|
|
// Each test gets its own subdirectory based on the tempWorkspace name for isolation.
|
|
|
|
|
testLogsDir := filepath.Join(config.GetLogRootPath(), filepath.Base(tempWorkspace))
|
|
|
|
|
err = os.MkdirAll(testLogsDir, 0700)
|
|
|
|
|
require.NoError(tb, err, "failed to create test logs directory")
|
|
|
|
|
*memoryConfig.LogSettings.FileLocation = testLogsDir
|
2020-10-29 18:54:39 -04:00
|
|
|
*memoryConfig.AnnouncementSettings.AdminNoticesEnabled = false
|
|
|
|
|
*memoryConfig.AnnouncementSettings.UserNoticesEnabled = false
|
2021-01-04 16:21:02 -05:00
|
|
|
*memoryConfig.PluginSettings.AutomaticPrepackagedPlugins = false
|
2024-09-18 09:43:44 -04:00
|
|
|
// Enabling Redis with Postgres.
|
2025-05-30 07:58:26 -04:00
|
|
|
if *memoryConfig.SqlSettings.DriverName == model.DatabaseDriverPostgres && !mainHelper.Options.RunParallel {
|
2024-09-18 09:43:44 -04:00
|
|
|
*memoryConfig.CacheSettings.CacheType = model.CacheTypeRedis
|
|
|
|
|
redisHost := "localhost"
|
|
|
|
|
if os.Getenv("IS_CI") == "true" {
|
|
|
|
|
redisHost = "redis"
|
|
|
|
|
}
|
|
|
|
|
*memoryConfig.CacheSettings.RedisAddress = redisHost + ":6379"
|
|
|
|
|
*memoryConfig.CacheSettings.DisableClientCache = true
|
|
|
|
|
*memoryConfig.CacheSettings.RedisDB = 0
|
2025-02-24 22:52:15 -05:00
|
|
|
*memoryConfig.CacheSettings.RedisCachePrefix = model.NewId()
|
2024-11-15 02:57:23 -05:00
|
|
|
options = append(options, app.ForceEnableRedis())
|
2024-09-18 09:43:44 -04:00
|
|
|
}
|
2020-01-15 13:38:55 -05:00
|
|
|
if updateConfig != nil {
|
2020-10-29 18:54:39 -04:00
|
|
|
updateConfig(memoryConfig)
|
|
|
|
|
}
|
2025-11-12 07:00:51 -05:00
|
|
|
err = memoryStore.Set(memoryConfig)
|
|
|
|
|
require.NoError(tb, err)
|
2025-06-24 14:11:02 -04:00
|
|
|
for _, signaturePublicKeyFile := range memoryConfig.PluginSettings.SignaturePublicKeyFiles {
|
|
|
|
|
var signaturePublicKey []byte
|
|
|
|
|
signaturePublicKey, err = os.ReadFile(signaturePublicKeyFile)
|
|
|
|
|
require.NoError(tb, err, "failed to read signature public key file %s", signaturePublicKeyFile)
|
2025-11-12 07:00:51 -05:00
|
|
|
err = memoryStore.SetFile(signaturePublicKeyFile, signaturePublicKey)
|
|
|
|
|
require.NoError(tb, err)
|
2025-06-24 14:11:02 -04:00
|
|
|
}
|
2020-10-29 18:54:39 -04:00
|
|
|
|
2021-01-28 14:04:17 -05:00
|
|
|
configStore, err := config.NewStoreFromBacking(memoryStore, nil, false)
|
2025-05-21 10:35:00 -04:00
|
|
|
require.NoError(tb, err)
|
2020-01-15 13:38:55 -05:00
|
|
|
|
2020-10-29 18:54:39 -04:00
|
|
|
options = append(options, app.ConfigStore(configStore))
|
2020-11-17 10:43:35 -05:00
|
|
|
if includeCache {
|
|
|
|
|
// Adds the cache layer to the test store
|
2022-10-06 04:04:21 -04:00
|
|
|
options = append(options, app.StoreOverrideWithCache(dbStore))
|
2020-11-17 10:43:35 -05:00
|
|
|
} else {
|
|
|
|
|
options = append(options, app.StoreOverride(dbStore))
|
|
|
|
|
}
|
2017-10-12 15:24:54 -04:00
|
|
|
|
2022-10-26 03:45:16 -04:00
|
|
|
buffer := &mlog.Buffer{}
|
|
|
|
|
|
2025-05-21 10:35:00 -04:00
|
|
|
testLogger, err := mlog.NewLogger()
|
|
|
|
|
require.NoError(tb, err)
|
|
|
|
|
logCfg, err := config.MloggerConfigFromLoggerConfig(&memoryConfig.LogSettings, nil, config.GetLogFileLocation)
|
|
|
|
|
require.NoError(tb, err)
|
|
|
|
|
err = testLogger.ConfigureTargets(logCfg, nil)
|
|
|
|
|
require.NoError(tb, err, "failed to configure test logger")
|
|
|
|
|
err = mlog.AddWriterTarget(testLogger, buffer, true, mlog.StdAll...)
|
|
|
|
|
require.NoError(tb, err, "failed to add writer target to test logger")
|
2021-08-17 16:08:04 -04:00
|
|
|
// lock logger config so server init cannot override it during testing.
|
|
|
|
|
testLogger.LockConfiguration()
|
|
|
|
|
options = append(options, app.SetLogger(testLogger))
|
|
|
|
|
|
2018-11-28 13:56:21 -05:00
|
|
|
s, err := app.NewServer(options...)
|
2025-05-21 10:35:00 -04:00
|
|
|
require.NoError(tb, err)
|
2018-01-11 16:23:41 -05:00
|
|
|
|
2017-09-12 10:19:52 -04:00
|
|
|
th := &TestHelper{
|
2023-03-27 12:19:29 -04:00
|
|
|
App: app.New(app.ServerConnector(s.Channels())),
|
|
|
|
|
Server: s,
|
2023-11-06 06:26:17 -05:00
|
|
|
Context: request.EmptyContext(testLogger),
|
2023-03-27 12:19:29 -04:00
|
|
|
ConfigStore: configStore,
|
|
|
|
|
IncludeCacheLayer: includeCache,
|
|
|
|
|
TestLogger: testLogger,
|
|
|
|
|
LogBuffer: buffer,
|
2025-05-30 07:58:26 -04:00
|
|
|
Store: dbStore,
|
2025-11-12 07:00:51 -05:00
|
|
|
tempWorkspace: tempWorkspace,
|
2021-05-11 06:00:44 -04:00
|
|
|
}
|
|
|
|
|
|
2020-03-30 13:17:40 -04:00
|
|
|
if searchEngine != nil {
|
|
|
|
|
th.App.SetSearchEngine(searchEngine)
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-09 10:47:12 -05:00
|
|
|
th.App.Srv().SetLicense(getLicense(enterprise, memoryConfig))
|
|
|
|
|
|
2017-10-31 10:39:31 -04:00
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
|
|
|
*cfg.TeamSettings.MaxUsersPerTeam = 50
|
|
|
|
|
*cfg.RateLimitSettings.Enable = false
|
2019-01-31 08:12:01 -05:00
|
|
|
*cfg.EmailSettings.SendEmailNotifications = true
|
2020-09-08 05:44:18 -04:00
|
|
|
*cfg.ServiceSettings.SiteURL = ""
|
2019-07-26 18:53:12 -04:00
|
|
|
|
|
|
|
|
// Disable sniffing, otherwise elastic client fails to connect to docker node
|
|
|
|
|
// More details: https://github.com/olivere/elastic/wiki/Sniffing
|
|
|
|
|
*cfg.ElasticsearchSettings.Sniff = false
|
2020-11-16 07:55:32 -05:00
|
|
|
|
|
|
|
|
*cfg.TeamSettings.EnableOpenServer = true
|
|
|
|
|
|
2023-04-06 08:59:58 -04:00
|
|
|
*cfg.ServiceSettings.ListenAddress = "localhost:0"
|
2017-10-31 10:39:31 -04:00
|
|
|
})
|
2025-05-30 07:58:26 -04:00
|
|
|
|
|
|
|
|
// Support updating feature flags without resorting to os.Setenv which
|
|
|
|
|
// isn't concurrently safe.
|
|
|
|
|
if updateConfig != nil {
|
|
|
|
|
configStore.SetReadOnlyFF(false)
|
|
|
|
|
th.App.UpdateConfig(updateConfig)
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-21 10:35:00 -04:00
|
|
|
err = th.Server.Start()
|
|
|
|
|
require.NoError(tb, err)
|
2018-02-07 03:11:15 -05:00
|
|
|
|
2025-05-21 10:35:00 -04:00
|
|
|
_, err = Init(th.App.Srv())
|
|
|
|
|
require.NoError(tb, err)
|
2021-10-15 10:27:05 -04:00
|
|
|
web.New(th.App.Srv())
|
2020-06-12 07:43:50 -04:00
|
|
|
wsapi.Init(th.App.Srv())
|
2019-05-21 14:03:36 -04:00
|
|
|
|
2017-03-13 08:26:23 -04:00
|
|
|
th.Client = th.CreateClient()
|
|
|
|
|
th.SystemAdminClient = th.CreateClient()
|
2021-01-12 14:45:17 -05:00
|
|
|
th.SystemManagerClient = th.CreateClient()
|
2018-06-25 15:33:13 -04:00
|
|
|
|
2020-05-23 17:01:31 -04:00
|
|
|
// Verify handling of the supported true/false values by randomizing on each run.
|
|
|
|
|
trueValues := []string{"1", "t", "T", "TRUE", "true", "True"}
|
|
|
|
|
falseValues := []string{"0", "f", "F", "FALSE", "false", "False"}
|
|
|
|
|
trueString := trueValues[rand.Intn(len(trueValues))]
|
|
|
|
|
falseString := falseValues[rand.Intn(len(falseValues))]
|
2023-10-31 06:00:22 -04:00
|
|
|
testLogger.Debug("Configured Client4 bool string values", mlog.String("true", trueString), mlog.String("false", falseString))
|
2020-05-23 17:01:31 -04:00
|
|
|
th.Client.SetBoolString(true, trueString)
|
|
|
|
|
th.Client.SetBoolString(false, falseString)
|
|
|
|
|
|
2020-10-29 18:54:39 -04:00
|
|
|
th.LocalClient = th.CreateLocalClient(*memoryConfig.ServiceSettings.LocalModeSocketLocation)
|
2020-05-19 12:20:41 -04:00
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
tb.Cleanup(func() {
|
|
|
|
|
if th.IncludeCacheLayer {
|
|
|
|
|
// Clean all the caches
|
|
|
|
|
appErr := th.App.Srv().InvalidateAllCaches()
|
|
|
|
|
require.Nil(tb, appErr)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
th.ShutdownApp()
|
|
|
|
|
|
|
|
|
|
if th.tempWorkspace != "" {
|
|
|
|
|
err := os.RemoveAll(th.tempWorkspace)
|
|
|
|
|
require.NoError(tb, err)
|
|
|
|
|
}
|
|
|
|
|
})
|
2018-06-25 15:33:13 -04:00
|
|
|
|
2017-03-13 08:26:23 -04:00
|
|
|
return th
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-09 10:47:12 -05:00
|
|
|
func getLicense(enterprise bool, cfg *model.Config) *model.License {
|
2024-09-10 16:33:00 -04:00
|
|
|
if *cfg.ConnectedWorkspacesSettings.EnableRemoteClusterService || *cfg.ConnectedWorkspacesSettings.EnableSharedChannels {
|
2024-02-09 10:47:12 -05:00
|
|
|
return model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)
|
|
|
|
|
}
|
|
|
|
|
if enterprise {
|
|
|
|
|
return model.NewTestLicense()
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-30 07:58:26 -04:00
|
|
|
func setupStores(tb testing.TB) (store.Store, *model.SqlSettings, *searchengine.Broker) {
|
|
|
|
|
var dbStore store.Store
|
|
|
|
|
var dbSettings *model.SqlSettings
|
|
|
|
|
var searchEngine *searchengine.Broker
|
|
|
|
|
if mainHelper.Options.RunParallel {
|
|
|
|
|
dbStore, _, dbSettings, searchEngine = mainHelper.GetNewStores(tb)
|
|
|
|
|
tb.Cleanup(func() {
|
|
|
|
|
dbStore.Close()
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
dbStore = mainHelper.GetStore()
|
|
|
|
|
dbStore.DropAllTables()
|
|
|
|
|
dbStore.MarkSystemRanUnitTests()
|
|
|
|
|
mainHelper.PreloadMigrations()
|
|
|
|
|
searchEngine = mainHelper.GetSearchEngine()
|
|
|
|
|
dbSettings = mainHelper.Settings
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dbStore, dbSettings, searchEngine
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-02 03:59:29 -04:00
|
|
|
func SetupEnterprise(tb testing.TB, options ...app.Option) *TestHelper {
|
2020-03-02 11:13:39 -05:00
|
|
|
if testing.Short() {
|
|
|
|
|
tb.SkipNow()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if mainHelper == nil {
|
|
|
|
|
tb.SkipNow()
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-07 17:35:08 -04:00
|
|
|
removeSpuriousErrors := func(config *model.Config) {
|
|
|
|
|
// If not set, you will receive an unactionable error in the console
|
|
|
|
|
*config.ServiceSettings.SiteURL = "http://localhost:8065"
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-30 07:58:26 -04:00
|
|
|
dbStore, dbSettings, searchEngine := setupStores(tb)
|
2025-06-07 17:35:08 -04:00
|
|
|
th := setupTestHelper(tb, dbStore, dbSettings, searchEngine, true, true, removeSpuriousErrors, options)
|
2025-05-21 10:35:00 -04:00
|
|
|
th.InitLogin(tb)
|
2025-05-30 07:58:26 -04:00
|
|
|
|
2020-07-22 04:20:33 -04:00
|
|
|
return th
|
2017-09-12 10:19:52 -04:00
|
|
|
}
|
2017-07-20 11:25:35 -04:00
|
|
|
|
2020-02-10 13:31:41 -05:00
|
|
|
func Setup(tb testing.TB) *TestHelper {
|
2020-03-02 11:13:39 -05:00
|
|
|
if testing.Short() {
|
|
|
|
|
tb.SkipNow()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if mainHelper == nil {
|
|
|
|
|
tb.SkipNow()
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-30 07:58:26 -04:00
|
|
|
dbStore, dbSettings, searchEngine := setupStores(tb)
|
|
|
|
|
th := setupTestHelper(tb, dbStore, dbSettings, searchEngine, false, true, nil, nil)
|
2025-05-21 10:35:00 -04:00
|
|
|
th.InitLogin(tb)
|
2025-05-30 07:58:26 -04:00
|
|
|
|
2020-07-22 04:20:33 -04:00
|
|
|
return th
|
2018-07-26 11:31:22 -04:00
|
|
|
}
|
|
|
|
|
|
2022-01-12 02:31:46 -05:00
|
|
|
func SetupAndApplyConfigBeforeLogin(tb testing.TB, updateConfig func(cfg *model.Config)) *TestHelper {
|
|
|
|
|
if testing.Short() {
|
|
|
|
|
tb.SkipNow()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if mainHelper == nil {
|
|
|
|
|
tb.SkipNow()
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-30 07:58:26 -04:00
|
|
|
dbStore, dbSettings, searchEngine := setupStores(tb)
|
|
|
|
|
th := setupTestHelper(tb, dbStore, dbSettings, searchEngine, false, true, nil, nil)
|
2022-01-12 02:31:46 -05:00
|
|
|
th.App.UpdateConfig(updateConfig)
|
2025-05-21 10:35:00 -04:00
|
|
|
th.InitLogin(tb)
|
2025-05-30 07:58:26 -04:00
|
|
|
|
2022-01-12 02:31:46 -05:00
|
|
|
return th
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-10 13:31:41 -05:00
|
|
|
func SetupConfig(tb testing.TB, updateConfig func(cfg *model.Config)) *TestHelper {
|
2020-03-02 11:13:39 -05:00
|
|
|
if testing.Short() {
|
|
|
|
|
tb.SkipNow()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if mainHelper == nil {
|
|
|
|
|
tb.SkipNow()
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-30 07:58:26 -04:00
|
|
|
dbStore, dbSettings, searchEngine := setupStores(tb)
|
|
|
|
|
th := setupTestHelper(tb, dbStore, dbSettings, searchEngine, false, true, updateConfig, nil)
|
2025-05-21 10:35:00 -04:00
|
|
|
th.InitLogin(tb)
|
2025-05-30 07:58:26 -04:00
|
|
|
|
2020-07-22 04:20:33 -04:00
|
|
|
return th
|
2020-03-02 11:13:39 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func SetupConfigWithStoreMock(tb testing.TB, updateConfig func(cfg *model.Config)) *TestHelper {
|
2025-05-30 07:58:26 -04:00
|
|
|
th := setupTestHelper(tb, testlib.GetMockStoreForSetupFunctions(), nil, nil, false, false, updateConfig, nil)
|
2021-06-16 14:38:26 -04:00
|
|
|
statusMock := mocks.StatusStore{}
|
|
|
|
|
statusMock.On("UpdateExpiredDNDStatuses").Return([]*model.Status{}, nil)
|
2021-07-12 14:05:36 -04:00
|
|
|
statusMock.On("Get", "user1").Return(&model.Status{UserId: "user1", Status: model.StatusOnline}, nil)
|
2021-06-16 14:38:26 -04:00
|
|
|
statusMock.On("UpdateLastActivityAt", "user1", mock.Anything).Return(nil)
|
|
|
|
|
statusMock.On("SaveOrUpdate", mock.AnythingOfType("*model.Status")).Return(nil)
|
2020-03-02 11:13:39 -05:00
|
|
|
emptyMockStore := mocks.Store{}
|
|
|
|
|
emptyMockStore.On("Close").Return(nil)
|
2021-06-16 14:38:26 -04:00
|
|
|
emptyMockStore.On("Status").Return(&statusMock)
|
2022-10-06 04:04:21 -04:00
|
|
|
th.App.Srv().SetStore(&emptyMockStore)
|
2020-03-02 11:13:39 -05:00
|
|
|
return th
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func SetupWithStoreMock(tb testing.TB) *TestHelper {
|
2025-05-30 07:58:26 -04:00
|
|
|
th := setupTestHelper(tb, testlib.GetMockStoreForSetupFunctions(), nil, nil, false, false, nil, nil)
|
2021-06-16 14:38:26 -04:00
|
|
|
statusMock := mocks.StatusStore{}
|
|
|
|
|
statusMock.On("UpdateExpiredDNDStatuses").Return([]*model.Status{}, nil)
|
2021-07-12 14:05:36 -04:00
|
|
|
statusMock.On("Get", "user1").Return(&model.Status{UserId: "user1", Status: model.StatusOnline}, nil)
|
2021-06-16 14:38:26 -04:00
|
|
|
statusMock.On("UpdateLastActivityAt", "user1", mock.Anything).Return(nil)
|
|
|
|
|
statusMock.On("SaveOrUpdate", mock.AnythingOfType("*model.Status")).Return(nil)
|
2020-03-02 11:13:39 -05:00
|
|
|
emptyMockStore := mocks.Store{}
|
|
|
|
|
emptyMockStore.On("Close").Return(nil)
|
2021-06-16 14:38:26 -04:00
|
|
|
emptyMockStore.On("Status").Return(&statusMock)
|
2022-10-06 04:04:21 -04:00
|
|
|
th.App.Srv().SetStore(&emptyMockStore)
|
2020-03-02 11:13:39 -05:00
|
|
|
return th
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-02 03:59:29 -04:00
|
|
|
func SetupEnterpriseWithStoreMock(tb testing.TB, options ...app.Option) *TestHelper {
|
2025-06-07 17:35:08 -04:00
|
|
|
removeSpuriousErrors := func(config *model.Config) {
|
|
|
|
|
// If not set, you will receive an unactionable error in the console
|
|
|
|
|
*config.ServiceSettings.SiteURL = "http://localhost:8065"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
th := setupTestHelper(tb, testlib.GetMockStoreForSetupFunctions(), nil, nil, true, false, removeSpuriousErrors, options)
|
2021-06-16 14:38:26 -04:00
|
|
|
statusMock := mocks.StatusStore{}
|
|
|
|
|
statusMock.On("UpdateExpiredDNDStatuses").Return([]*model.Status{}, nil)
|
2021-07-12 14:05:36 -04:00
|
|
|
statusMock.On("Get", "user1").Return(&model.Status{UserId: "user1", Status: model.StatusOnline}, nil)
|
2021-06-16 14:38:26 -04:00
|
|
|
statusMock.On("UpdateLastActivityAt", "user1", mock.Anything).Return(nil)
|
|
|
|
|
statusMock.On("SaveOrUpdate", mock.AnythingOfType("*model.Status")).Return(nil)
|
2020-03-02 11:13:39 -05:00
|
|
|
emptyMockStore := mocks.Store{}
|
|
|
|
|
emptyMockStore.On("Close").Return(nil)
|
2021-06-16 14:38:26 -04:00
|
|
|
emptyMockStore.On("Status").Return(&statusMock)
|
2022-10-06 04:04:21 -04:00
|
|
|
th.App.Srv().SetStore(&emptyMockStore)
|
2020-03-02 11:13:39 -05:00
|
|
|
return th
|
2017-01-30 08:30:02 -05:00
|
|
|
}
|
|
|
|
|
|
2021-05-21 03:04:39 -04:00
|
|
|
func SetupWithServerOptions(tb testing.TB, options []app.Option) *TestHelper {
|
|
|
|
|
if testing.Short() {
|
|
|
|
|
tb.SkipNow()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if mainHelper == nil {
|
|
|
|
|
tb.SkipNow()
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-30 07:58:26 -04:00
|
|
|
dbStore, dbSettings, searchEngine := setupStores(tb)
|
|
|
|
|
th := setupTestHelper(tb, dbStore, dbSettings, searchEngine, false, true, nil, options)
|
2025-05-21 10:35:00 -04:00
|
|
|
th.InitLogin(tb)
|
2025-05-30 07:58:26 -04:00
|
|
|
|
2021-05-21 03:04:39 -04:00
|
|
|
return th
|
|
|
|
|
}
|
|
|
|
|
|
ci: enable fullyparallel mode for server tests (#35816)
* ci: enable fullyparallel mode for server tests
Replace os.Setenv, os.Chdir, and global state mutations with
parallel-safe alternatives (t.Setenv, t.Chdir, test hooks) across
37 files. Refactor GetLogRootPath and MM_INSTALL_TYPE to use
package-level test hooks instead of environment variables.
This enables gotestsum --fullparallel, allowing all test packages
to run with maximum parallelism within each shard.
Co-authored-by: Claude <claude@anthropic.com>
* ci: split fullyparallel from continue-on-error in workflow template
- Add new boolean input 'allow-failure' separate from 'fullyparallel'
- Change continue-on-error to use allow-failure instead of fullyparallel
- Update server-ci.yml to pass allow-failure: true for test coverage job
- Allows independent control of parallel execution and failure tolerance
Co-authored-by: Claude <claude@anthropic.com>
* fix: protect TestOverrideLogRootPath with sync.Mutex for parallel tests
- Replace global var TestOverrideLogRootPath with mutex-protected functions
- Add SetTestOverrideLogRootPath() and getTestOverrideLogRootPath() functions
- Update GetLogRootPath() to use thread-safe getter
- Update all test files to use SetTestOverrideLogRootPath() with t.Cleanup()
- Fixes race condition when running tests with t.Parallel()
Co-authored-by: Claude <claude@anthropic.com>
* fix: configure audit settings before server setup in tests
- Move ExperimentalAuditSettings from UpdateConfig() to config defaults
- Pass audit config via app.Config() option in SetupWithServerOptions()
- Fixes audit test setup ordering to configure BEFORE server initialization
- Resolves CodeRabbit's audit config timing issue in api4 tests
Co-authored-by: Claude <claude@anthropic.com>
* fix: implement SetTestOverrideLogRootPath mutex in logger.go
The previous commit updated test callers to use SetTestOverrideLogRootPath()
but didn't actually create the function in config/logger.go, causing build
failures across all CI shards. This commit:
- Replaces the exported var TestOverrideLogRootPath with mutex-protected
unexported state (testOverrideLogRootPath + testOverrideLogRootMu)
- Adds exported SetTestOverrideLogRootPath() setter
- Adds unexported getTestOverrideLogRootPath() getter
- Updates GetLogRootPath() to use the thread-safe getter
- Fixes log_test.go callers that were missed in the previous commit
Co-authored-by: Claude <claude@anthropic.com>
* fix(test): use SetupConfig for access_control feature flag registration
InitAccessControlPolicy() checks FeatureFlags.AttributeBasedAccessControl
at route registration time during server startup. Setting the flag via
UpdateConfig after Setup() is too late — routes are never registered
and API calls return 404.
Use SetupConfig() to pass the feature flag in the initial config before
server startup, ensuring routes are properly registered.
Co-authored-by: Claude <claude@anthropic.com>
* fix(test): restore BurnOnRead flag state in TestRevealPost subtest
The 'feature not enabled' subtest disables BurnOnRead without restoring
it via t.Cleanup. Subsequent subtests inherit the disabled state, which
can cause 501 errors when they expect the feature to be available.
Add t.Cleanup to restore FeatureFlags.BurnOnRead = true after the
subtest completes.
Co-authored-by: Claude <claude@anthropic.com>
* fix(test): restore EnableSharedChannelsMemberSync flag via t.Cleanup
The test disables EnableSharedChannelsMemberSync without restoring it.
If the subtest exits early (e.g., require failure), later sibling
subtests inherit a disabled flag and become flaky.
Add t.Cleanup to restore the flag after the subtest completes.
Co-authored-by: Claude <claude@anthropic.com>
* Fix test parallelism: use instance-scoped overrides and init-time audit config
Replace package-level test globals (TestOverrideInstallType,
SetTestOverrideLogRootPath) with fields on PlatformService so each test
gets its own instance without process-wide mutation. Fix three audit
tests (TestUserLoginAudit, TestLogoutAuditAuthStatus,
TestUpdatePasswordAudit) that configured the audit logger after server
init — the audit logger only reads config at startup, so pass audit
settings via app.Config() at init time instead.
Also revert the Go 1.24.13 downgrade and bump mattermost-govet to
v2.0.2 for Go 1.25.8 compatibility.
* Fix audit unit tests
* Fix MMCLOUDURL unit tests
* Fixed unit tests using MM_NOTIFY_ADMIN_COOL_OFF_DAYS
* Make app migrations idempotent for parallel test safety
Change System().Save() to System().SaveOrUpdate() in all migration
completion markers. When two parallel tests share a database pool entry,
both may race through the check-then-insert migration pattern. Save()
causes a duplicate key fatal crash; SaveOrUpdate() makes the second
write a harmless no-op.
* test: address review feedback on fullyparallel PR
- Use SetLogRootPathOverride() setter instead of direct field access
in platform/support_packet_test.go and platform/log_test.go (pvev)
- Restore TestGetLogRootPath in config/logger_test.go to keep
MM_LOG_PATH env var coverage; test uses t.Setenv so it runs
serially which is fine (pvev)
- Fix misleading comment in config_test.go: code uses t.Setenv,
not os.Setenv (jgheithcock)
Co-authored-by: Claude <claude@anthropic.com>
* fix: add missing os import in post_test.go
The os import was dropped during a merge conflict resolution while
burn-on-read shared channel tests from master still use os.Setenv.
Co-authored-by: Claude <claude@anthropic.com>
---------
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: wiggin77 <wiggin77@warpmail.net>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-04-08 20:48:36 -04:00
|
|
|
func SetupWithServerOptionsAndConfig(tb testing.TB, options []app.Option, updateConfig func(*model.Config)) *TestHelper {
|
|
|
|
|
if testing.Short() {
|
|
|
|
|
tb.SkipNow()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if mainHelper == nil {
|
|
|
|
|
tb.SkipNow()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbStore, dbSettings, searchEngine := setupStores(tb)
|
|
|
|
|
th := setupTestHelper(tb, dbStore, dbSettings, searchEngine, false, true, updateConfig, options)
|
|
|
|
|
th.InitLogin(tb)
|
|
|
|
|
|
|
|
|
|
return th
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-09 14:49:02 -04:00
|
|
|
func SetupEnterpriseWithServerOptions(tb testing.TB, options []app.Option) *TestHelper {
|
|
|
|
|
if testing.Short() {
|
|
|
|
|
tb.SkipNow()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if mainHelper == nil {
|
|
|
|
|
tb.SkipNow()
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-30 07:58:26 -04:00
|
|
|
dbStore, dbSettings, searchEngine := setupStores(tb)
|
|
|
|
|
th := setupTestHelper(tb, dbStore, dbSettings, searchEngine, true, true, nil, options)
|
2025-05-21 10:35:00 -04:00
|
|
|
th.InitLogin(tb)
|
2025-05-30 07:58:26 -04:00
|
|
|
|
2024-05-09 14:49:02 -04:00
|
|
|
return th
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-11 23:15:17 -05:00
|
|
|
func (th *TestHelper) ShutdownApp() {
|
2018-11-22 04:53:44 -05:00
|
|
|
done := make(chan bool)
|
2017-04-20 09:55:02 -04:00
|
|
|
go func() {
|
2020-12-11 23:15:17 -05:00
|
|
|
th.Server.Shutdown()
|
2018-11-22 04:53:44 -05:00
|
|
|
close(done)
|
2017-04-20 09:55:02 -04:00
|
|
|
}()
|
|
|
|
|
|
2018-11-22 04:53:44 -05:00
|
|
|
select {
|
|
|
|
|
case <-done:
|
|
|
|
|
case <-time.After(30 * time.Second):
|
2020-02-13 11:53:23 -05:00
|
|
|
// panic instead of fatal to terminate all tests in this package, otherwise the
|
2018-11-22 04:53:44 -05:00
|
|
|
// still running App could spuriously fail subsequent tests.
|
|
|
|
|
panic("failed to shutdown App within 30 seconds")
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-04-20 09:55:02 -04:00
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) RemoveLicense(tb testing.TB) {
|
2025-10-02 10:54:29 -04:00
|
|
|
err := th.App.Srv().RemoveLicense()
|
2025-11-12 07:00:51 -05:00
|
|
|
require.Nil(tb, err)
|
2025-10-02 10:54:29 -04:00
|
|
|
}
|
|
|
|
|
|
2022-02-11 02:07:05 -05:00
|
|
|
func closeBody(r *http.Response) {
|
|
|
|
|
if r.Body != nil {
|
2022-07-27 05:40:33 -04:00
|
|
|
_, _ = io.Copy(io.Discard, r.Body)
|
2022-02-11 02:07:05 -05:00
|
|
|
_ = r.Body.Close()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-21 10:35:00 -04:00
|
|
|
func (th *TestHelper) InitLogin(tb testing.TB) *TestHelper {
|
|
|
|
|
th.waitForConnectivity(tb)
|
2017-10-12 15:24:54 -04:00
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
th.SystemAdminUser = th.CreateUser(tb)
|
2026-04-08 15:49:43 -04:00
|
|
|
systemAdminPassword := th.SystemAdminUser.Password
|
2025-11-12 07:00:51 -05:00
|
|
|
_, appErr := th.App.UpdateUserRoles(th.Context, th.SystemAdminUser.Id, model.SystemUserRoleId+" "+model.SystemAdminRoleId, false)
|
|
|
|
|
require.Nil(tb, appErr)
|
|
|
|
|
th.SystemAdminUser, appErr = th.App.GetUser(th.SystemAdminUser.Id)
|
|
|
|
|
require.Nil(tb, appErr)
|
2025-05-30 07:58:26 -04:00
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
th.SystemManagerUser = th.CreateUser(tb)
|
2026-04-08 15:49:43 -04:00
|
|
|
systemManagerPassword := th.SystemManagerUser.Password
|
2025-11-12 07:00:51 -05:00
|
|
|
_, appErr = th.App.UpdateUserRoles(th.Context, th.SystemManagerUser.Id, model.SystemUserRoleId+" "+model.SystemManagerRoleId, false)
|
|
|
|
|
require.Nil(tb, appErr)
|
|
|
|
|
th.SystemManagerUser, appErr = th.App.GetUser(th.SystemManagerUser.Id)
|
|
|
|
|
require.Nil(tb, appErr)
|
2020-12-11 23:15:17 -05:00
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
th.TeamAdminUser = th.CreateUser(tb)
|
2026-04-08 15:49:43 -04:00
|
|
|
teamAdminPassword := th.TeamAdminUser.Password
|
2025-11-12 07:00:51 -05:00
|
|
|
_, appErr = th.App.UpdateUserRoles(th.Context, th.TeamAdminUser.Id, model.SystemUserRoleId, false)
|
|
|
|
|
require.Nil(tb, appErr)
|
|
|
|
|
th.TeamAdminUser, appErr = th.App.GetUser(th.TeamAdminUser.Id)
|
|
|
|
|
require.Nil(tb, appErr)
|
2025-05-30 07:58:26 -04:00
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
th.BasicUser = th.CreateUser(tb)
|
2026-04-08 15:49:43 -04:00
|
|
|
basicUserPassword := th.BasicUser.Password
|
2025-11-12 07:00:51 -05:00
|
|
|
th.BasicUser, appErr = th.App.GetUser(th.BasicUser.Id)
|
|
|
|
|
require.Nil(tb, appErr)
|
2022-02-09 09:26:16 -05:00
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
th.BasicUser2 = th.CreateUser(tb)
|
2026-04-08 15:49:43 -04:00
|
|
|
basicUser2Password := th.BasicUser2.Password
|
2025-11-12 07:00:51 -05:00
|
|
|
th.BasicUser2, appErr = th.App.GetUser(th.BasicUser2.Id)
|
|
|
|
|
require.Nil(tb, appErr)
|
2022-02-09 09:26:16 -05:00
|
|
|
|
2026-04-08 15:49:43 -04:00
|
|
|
// restore non-hashed password for login
|
|
|
|
|
th.SystemAdminUser.Password = systemAdminPassword
|
|
|
|
|
th.SystemManagerUser.Password = systemManagerPassword
|
|
|
|
|
th.TeamAdminUser.Password = teamAdminPassword
|
|
|
|
|
th.BasicUser.Password = basicUserPassword
|
|
|
|
|
th.BasicUser2.Password = basicUser2Password
|
2018-11-20 20:16:25 -05:00
|
|
|
|
2020-07-22 04:20:33 -04:00
|
|
|
var wg sync.WaitGroup
|
2022-09-06 04:25:06 -04:00
|
|
|
wg.Add(2)
|
2020-07-22 04:20:33 -04:00
|
|
|
go func() {
|
2025-11-12 07:00:51 -05:00
|
|
|
th.LoginSystemAdmin(tb)
|
2020-07-22 04:20:33 -04:00
|
|
|
wg.Done()
|
|
|
|
|
}()
|
|
|
|
|
go func() {
|
2025-11-12 07:00:51 -05:00
|
|
|
th.LoginTeamAdmin(tb)
|
2020-07-22 04:20:33 -04:00
|
|
|
wg.Done()
|
|
|
|
|
}()
|
2025-05-30 07:58:26 -04:00
|
|
|
|
2020-07-22 04:20:33 -04:00
|
|
|
wg.Wait()
|
2025-05-30 07:58:26 -04:00
|
|
|
|
2020-12-11 23:15:17 -05:00
|
|
|
return th
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) InitBasic(tb testing.TB) *TestHelper {
|
|
|
|
|
th.BasicTeam = th.CreateTeam(tb)
|
|
|
|
|
th.BasicChannel = th.CreatePublicChannel(tb)
|
|
|
|
|
th.BasicPrivateChannel = th.CreatePrivateChannel(tb)
|
|
|
|
|
th.BasicPrivateChannel2 = th.CreatePrivateChannel(tb)
|
|
|
|
|
th.BasicDeletedChannel = th.CreatePublicChannel(tb)
|
|
|
|
|
th.BasicChannel2 = th.CreatePublicChannel(tb)
|
|
|
|
|
th.BasicPost = th.CreatePost(tb)
|
|
|
|
|
th.LinkUserToTeam(tb, th.BasicUser, th.BasicTeam)
|
|
|
|
|
th.LinkUserToTeam(tb, th.BasicUser2, th.BasicTeam)
|
|
|
|
|
_, appErr := th.App.AddUserToChannel(th.Context, th.BasicUser, th.BasicChannel, false)
|
|
|
|
|
require.Nil(tb, appErr)
|
|
|
|
|
_, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser2, th.BasicChannel, false)
|
|
|
|
|
require.Nil(tb, appErr)
|
|
|
|
|
_, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser, th.BasicChannel2, false)
|
|
|
|
|
require.Nil(tb, appErr)
|
|
|
|
|
_, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser2, th.BasicChannel2, false)
|
|
|
|
|
require.Nil(tb, appErr)
|
|
|
|
|
_, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser, th.BasicPrivateChannel, false)
|
|
|
|
|
require.Nil(tb, appErr)
|
|
|
|
|
_, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser2, th.BasicPrivateChannel, false)
|
|
|
|
|
require.Nil(tb, appErr)
|
|
|
|
|
_, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser, th.BasicDeletedChannel, false)
|
|
|
|
|
require.Nil(tb, appErr)
|
|
|
|
|
_, appErr = th.App.AddUserToChannel(th.Context, th.BasicUser2, th.BasicDeletedChannel, false)
|
|
|
|
|
require.Nil(tb, appErr)
|
|
|
|
|
_, appErr = th.App.UpdateUserRoles(th.Context, th.BasicUser.Id, model.SystemUserRoleId, false)
|
|
|
|
|
require.Nil(tb, appErr)
|
|
|
|
|
_, err := th.Client.DeleteChannel(context.Background(), th.BasicDeletedChannel.Id)
|
|
|
|
|
require.NoError(tb, err)
|
|
|
|
|
th.LoginBasic(tb)
|
|
|
|
|
th.Group = th.CreateGroup(tb)
|
2020-12-11 23:15:17 -05:00
|
|
|
|
|
|
|
|
return th
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) DeleteBots(tb testing.TB) *TestHelper {
|
2024-03-11 08:24:35 -04:00
|
|
|
preexistingBots, _ := th.App.GetBots(th.Context, &model.BotGetOptions{Page: 0, PerPage: 100})
|
2023-04-18 07:58:33 -04:00
|
|
|
for _, bot := range preexistingBots {
|
2025-11-12 07:00:51 -05:00
|
|
|
appErr := th.App.PermanentDeleteBot(th.Context, bot.UserId)
|
|
|
|
|
require.Nil(tb, appErr)
|
2023-04-18 07:58:33 -04:00
|
|
|
}
|
|
|
|
|
return th
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-21 10:35:00 -04:00
|
|
|
func (th *TestHelper) waitForConnectivity(tb testing.TB) {
|
2025-07-18 06:54:51 -04:00
|
|
|
for range 1000 {
|
2020-12-11 23:15:17 -05:00
|
|
|
conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%v", th.App.Srv().ListenAddr.Port))
|
2017-10-12 15:24:54 -04:00
|
|
|
if err == nil {
|
2017-10-16 11:09:43 -04:00
|
|
|
conn.Close()
|
2017-10-12 15:24:54 -04:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
time.Sleep(time.Millisecond * 20)
|
|
|
|
|
}
|
2025-05-21 10:35:00 -04:00
|
|
|
tb.Fatal("unable to connect")
|
2017-10-12 15:24:54 -04:00
|
|
|
}
|
|
|
|
|
|
2020-12-11 23:15:17 -05:00
|
|
|
func (th *TestHelper) CreateClient() *model.Client4 {
|
|
|
|
|
return model.NewAPIv4Client(fmt.Sprintf("http://localhost:%v", th.App.Srv().ListenAddr.Port))
|
2017-01-30 08:30:02 -05:00
|
|
|
}
|
|
|
|
|
|
2020-05-19 12:20:41 -04:00
|
|
|
// ToDo: maybe move this to NewAPIv4SocketClient and reuse it in mmctl
|
2020-12-11 23:15:17 -05:00
|
|
|
func (th *TestHelper) CreateLocalClient(socketPath string) *model.Client4 {
|
2020-05-19 12:20:41 -04:00
|
|
|
httpClient := &http.Client{
|
|
|
|
|
Transport: &http.Transport{
|
|
|
|
|
Dial: func(network, addr string) (net.Conn, error) {
|
|
|
|
|
return net.Dial("unix", socketPath)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &model.Client4{
|
2021-08-16 13:46:44 -04:00
|
|
|
APIURL: "http://_" + model.APIURLSuffix,
|
2021-08-12 05:49:16 -04:00
|
|
|
HTTPClient: httpClient,
|
2020-05-19 12:20:41 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) createConnectedWebSocketClient(tb testing.TB, client *model.Client4) *model.WebSocketClient {
|
|
|
|
|
tb.Helper()
|
2025-01-29 08:58:43 -05:00
|
|
|
wsClient, err := th.CreateWebSocketClientWithClient(client)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.NoError(tb, err)
|
|
|
|
|
require.NotNil(tb, wsClient, "webSocketClient should not be nil")
|
2025-01-23 09:16:21 -05:00
|
|
|
wsClient.Listen()
|
2025-11-12 07:00:51 -05:00
|
|
|
tb.Cleanup(wsClient.Close)
|
2025-01-23 09:16:21 -05:00
|
|
|
|
|
|
|
|
// Ensure WS is connected. First event should be hello message.
|
|
|
|
|
select {
|
|
|
|
|
case ev := <-wsClient.EventChannel:
|
2025-11-12 07:00:51 -05:00
|
|
|
require.Equal(tb, model.WebsocketEventHello, ev.EventType())
|
2025-01-23 09:16:21 -05:00
|
|
|
case <-time.After(5 * time.Second):
|
2025-11-12 07:00:51 -05:00
|
|
|
require.FailNow(tb, "hello event was not received within the timeout period")
|
2025-01-23 09:16:21 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return wsClient
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreateConnectedWebSocketClient(tb testing.TB) *model.WebSocketClient {
|
|
|
|
|
return th.createConnectedWebSocketClient(tb, th.Client)
|
2025-01-29 08:58:43 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreateConnectedWebSocketClientWithClient(tb testing.TB, client *model.Client4) *model.WebSocketClient {
|
|
|
|
|
return th.createConnectedWebSocketClient(tb, client)
|
2025-01-29 08:58:43 -05:00
|
|
|
}
|
|
|
|
|
|
2021-08-13 07:12:16 -04:00
|
|
|
func (th *TestHelper) CreateWebSocketClient() (*model.WebSocketClient, error) {
|
2020-12-11 23:15:17 -05:00
|
|
|
return model.NewWebSocketClient4(fmt.Sprintf("ws://localhost:%v", th.App.Srv().ListenAddr.Port), th.Client.AuthToken)
|
2017-03-28 04:58:19 -04:00
|
|
|
}
|
|
|
|
|
|
2021-11-16 09:13:43 -05:00
|
|
|
func (th *TestHelper) CreateReliableWebSocketClient(connID string, seqNo int) (*model.WebSocketClient, error) {
|
|
|
|
|
return model.NewReliableWebSocketClientWithDialer(websocket.DefaultDialer, fmt.Sprintf("ws://localhost:%v", th.App.Srv().ListenAddr.Port), th.Client.AuthToken, connID, seqNo, true)
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-13 07:12:16 -04:00
|
|
|
func (th *TestHelper) CreateWebSocketClientWithClient(client *model.Client4) (*model.WebSocketClient, error) {
|
2020-12-11 23:15:17 -05:00
|
|
|
return model.NewWebSocketClient4(fmt.Sprintf("ws://localhost:%v", th.App.Srv().ListenAddr.Port), client.AuthToken)
|
2019-11-28 09:24:04 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreateBotWithSystemAdminClient(tb testing.TB) *model.Bot {
|
|
|
|
|
return th.CreateBotWithClient(tb, th.SystemAdminClient)
|
2020-01-29 11:01:06 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreateBotWithClient(tb testing.TB, client *model.Client4) *model.Bot {
|
2020-01-29 11:01:06 -05:00
|
|
|
bot := &model.Bot{
|
|
|
|
|
Username: GenerateTestUsername(),
|
|
|
|
|
DisplayName: "a bot",
|
|
|
|
|
Description: "bot",
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-06 17:29:29 -04:00
|
|
|
rbot, _, err := client.CreateBot(context.Background(), bot)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.NoError(tb, err)
|
2020-01-29 11:01:06 -05:00
|
|
|
return rbot
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreateUser(tb testing.TB) *model.User {
|
|
|
|
|
return th.CreateUserWithClient(tb, th.Client)
|
2017-01-31 09:31:53 -05:00
|
|
|
}
|
|
|
|
|
|
2025-03-19 17:27:33 -04:00
|
|
|
func (th *TestHelper) CreateGuestUser(tb testing.TB) *model.User {
|
|
|
|
|
tb.Helper()
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
guestUser := th.CreateUserWithClient(tb, th.Client)
|
2025-03-19 17:27:33 -04:00
|
|
|
|
|
|
|
|
_, appErr := th.App.UpdateUserRoles(th.Context, guestUser.Id, model.SystemGuestRoleId, false)
|
|
|
|
|
require.Nil(tb, appErr)
|
|
|
|
|
|
|
|
|
|
return guestUser
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreateTeam(tb testing.TB) *model.Team {
|
|
|
|
|
return th.CreateTeamWithClient(tb, th.Client)
|
2017-01-31 09:31:53 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreateTeamWithClient(tb testing.TB, client *model.Client4) *model.Team {
|
2017-01-31 09:31:53 -05:00
|
|
|
id := model.NewId()
|
|
|
|
|
team := &model.Team{
|
|
|
|
|
DisplayName: "dn_" + id,
|
|
|
|
|
Name: GenerateTestTeamName(),
|
2020-12-11 23:15:17 -05:00
|
|
|
Email: th.GenerateTestEmail(),
|
2021-07-12 14:05:36 -04:00
|
|
|
Type: model.TeamOpen,
|
2017-01-31 09:31:53 -05:00
|
|
|
}
|
|
|
|
|
|
2023-06-06 17:29:29 -04:00
|
|
|
rteam, _, err := client.CreateTeam(context.Background(), team)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.NoError(tb, err)
|
2017-01-31 09:31:53 -05:00
|
|
|
return rteam
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreateUserWithClient(tb testing.TB, client *model.Client4) *model.User {
|
2017-01-30 08:30:02 -05:00
|
|
|
id := model.NewId()
|
|
|
|
|
|
|
|
|
|
user := &model.User{
|
2020-12-11 23:15:17 -05:00
|
|
|
Email: th.GenerateTestEmail(),
|
2017-01-30 08:30:02 -05:00
|
|
|
Username: GenerateTestUsername(),
|
|
|
|
|
Nickname: "nn_" + id,
|
|
|
|
|
FirstName: "f_" + id,
|
|
|
|
|
LastName: "l_" + id,
|
2026-04-08 15:49:43 -04:00
|
|
|
Password: model.NewTestPassword(),
|
2017-01-30 08:30:02 -05:00
|
|
|
}
|
|
|
|
|
|
2023-06-06 17:29:29 -04:00
|
|
|
ruser, _, err := client.CreateUser(context.Background(), user)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.NoError(tb, err)
|
2018-03-21 14:27:14 -04:00
|
|
|
|
2026-04-08 15:49:43 -04:00
|
|
|
ruser.Password = user.Password
|
2022-10-06 04:04:21 -04:00
|
|
|
_, err = th.App.Srv().Store().User().VerifyEmail(ruser.Id, ruser.Email)
|
2019-06-12 13:30:50 -04:00
|
|
|
if err != nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2017-01-30 08:30:02 -05:00
|
|
|
return ruser
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreateUserWithAuth(tb testing.TB, authService string) *model.User {
|
2021-03-22 14:02:16 -04:00
|
|
|
id := model.NewId()
|
|
|
|
|
user := &model.User{
|
|
|
|
|
Email: "success+" + id + "@simulator.amazonses.com",
|
|
|
|
|
Username: "un_" + id,
|
|
|
|
|
Nickname: "nn_" + id,
|
|
|
|
|
EmailVerified: true,
|
|
|
|
|
AuthService: authService,
|
|
|
|
|
}
|
2021-05-11 06:00:44 -04:00
|
|
|
user, err := th.App.CreateUser(th.Context, user)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.Nil(tb, err)
|
2021-03-22 14:02:16 -04:00
|
|
|
return user
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-12 10:36:05 -04:00
|
|
|
// CreateGuestAndClient creates a guest user, adds them to the basic
|
|
|
|
|
// team, basic channel and basic private channel, and generates an API
|
|
|
|
|
// client ready to use
|
2025-04-22 09:12:22 -04:00
|
|
|
func (th *TestHelper) CreateGuestAndClient(tb testing.TB) (*model.User, *model.Client4) {
|
|
|
|
|
tb.Helper()
|
2024-03-12 10:36:05 -04:00
|
|
|
id := model.NewId()
|
|
|
|
|
|
|
|
|
|
// create a guest user and add it to the basic team and public/private channels
|
2026-04-08 15:49:43 -04:00
|
|
|
password := model.NewTestPassword()
|
2024-03-12 10:36:05 -04:00
|
|
|
guest, cgErr := th.App.CreateGuest(th.Context, &model.User{
|
|
|
|
|
Email: "test_guest" + id + "@sample.com",
|
|
|
|
|
Username: "guest_" + id,
|
|
|
|
|
Nickname: "guest_" + id,
|
2026-04-08 15:49:43 -04:00
|
|
|
Password: password,
|
2024-03-12 10:36:05 -04:00
|
|
|
EmailVerified: true,
|
|
|
|
|
})
|
2025-04-22 09:12:22 -04:00
|
|
|
require.Nil(tb, cgErr)
|
2024-03-12 10:36:05 -04:00
|
|
|
|
2025-04-22 09:12:22 -04:00
|
|
|
_, _, appErr := th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, guest.Id, th.SystemAdminUser.Id)
|
|
|
|
|
require.Nil(tb, appErr)
|
2025-11-12 07:00:51 -05:00
|
|
|
th.AddUserToChannel(tb, guest, th.BasicChannel)
|
|
|
|
|
th.AddUserToChannel(tb, guest, th.BasicPrivateChannel)
|
2024-03-12 10:36:05 -04:00
|
|
|
|
|
|
|
|
// create a client and login the guest
|
|
|
|
|
guestClient := th.CreateClient()
|
2026-04-08 15:49:43 -04:00
|
|
|
_, _, err := guestClient.Login(context.Background(), guest.Email, password)
|
2025-04-22 09:12:22 -04:00
|
|
|
require.NoError(tb, err)
|
2024-03-12 10:36:05 -04:00
|
|
|
|
2026-04-08 15:49:43 -04:00
|
|
|
guest.Password = password
|
2024-03-12 10:36:05 -04:00
|
|
|
return guest, guestClient
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-22 14:02:16 -04:00
|
|
|
func (th *TestHelper) SetupLdapConfig() {
|
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
|
|
|
*cfg.ServiceSettings.EnableMultifactorAuthentication = true
|
|
|
|
|
*cfg.LdapSettings.Enable = true
|
|
|
|
|
*cfg.LdapSettings.EnableSync = true
|
|
|
|
|
*cfg.LdapSettings.LdapServer = "dockerhost"
|
|
|
|
|
*cfg.LdapSettings.BaseDN = "dc=mm,dc=test,dc=com"
|
|
|
|
|
*cfg.LdapSettings.BindUsername = "cn=admin,dc=mm,dc=test,dc=com"
|
|
|
|
|
*cfg.LdapSettings.BindPassword = "mostest"
|
|
|
|
|
*cfg.LdapSettings.FirstNameAttribute = "cn"
|
|
|
|
|
*cfg.LdapSettings.LastNameAttribute = "sn"
|
|
|
|
|
*cfg.LdapSettings.NicknameAttribute = "cn"
|
|
|
|
|
*cfg.LdapSettings.EmailAttribute = "mail"
|
|
|
|
|
*cfg.LdapSettings.UsernameAttribute = "uid"
|
|
|
|
|
*cfg.LdapSettings.IdAttribute = "cn"
|
|
|
|
|
*cfg.LdapSettings.LoginIdAttribute = "uid"
|
|
|
|
|
*cfg.LdapSettings.SkipCertificateVerification = true
|
|
|
|
|
*cfg.LdapSettings.GroupFilter = ""
|
|
|
|
|
*cfg.LdapSettings.GroupDisplayNameAttribute = "cN"
|
|
|
|
|
*cfg.LdapSettings.GroupIdAttribute = "entRyUuId"
|
|
|
|
|
*cfg.LdapSettings.MaxPageSize = 0
|
|
|
|
|
})
|
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicense("ldap"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (th *TestHelper) SetupSamlConfig() {
|
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
|
|
|
*cfg.SamlSettings.Enable = true
|
|
|
|
|
*cfg.SamlSettings.Verify = false
|
|
|
|
|
*cfg.SamlSettings.Encrypt = false
|
2022-02-28 04:31:00 -05:00
|
|
|
*cfg.SamlSettings.IdpURL = "https://does.notmatter.example"
|
2021-08-16 13:46:44 -04:00
|
|
|
*cfg.SamlSettings.IdpDescriptorURL = "https://localhost/adfs/services/trust"
|
2021-03-22 14:02:16 -04:00
|
|
|
*cfg.SamlSettings.AssertionConsumerServiceURL = "https://localhost/login/sso/saml"
|
|
|
|
|
*cfg.SamlSettings.ServiceProviderIdentifier = "https://localhost/login/sso/saml"
|
|
|
|
|
*cfg.SamlSettings.IdpCertificateFile = app.SamlIdpCertificateName
|
|
|
|
|
*cfg.SamlSettings.PrivateKeyFile = app.SamlPrivateKeyName
|
|
|
|
|
*cfg.SamlSettings.PublicCertificateFile = app.SamlPublicCertificateName
|
|
|
|
|
*cfg.SamlSettings.EmailAttribute = "Email"
|
|
|
|
|
*cfg.SamlSettings.UsernameAttribute = "Username"
|
|
|
|
|
*cfg.SamlSettings.FirstNameAttribute = "FirstName"
|
|
|
|
|
*cfg.SamlSettings.LastNameAttribute = "LastName"
|
|
|
|
|
*cfg.SamlSettings.NicknameAttribute = ""
|
|
|
|
|
*cfg.SamlSettings.PositionAttribute = ""
|
|
|
|
|
*cfg.SamlSettings.LocaleAttribute = ""
|
2021-07-12 14:05:36 -04:00
|
|
|
*cfg.SamlSettings.SignatureAlgorithm = model.SamlSettingsSignatureAlgorithmSha256
|
|
|
|
|
*cfg.SamlSettings.CanonicalAlgorithm = model.SamlSettingsCanonicalAlgorithmC14n11
|
2021-03-22 14:02:16 -04:00
|
|
|
})
|
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicense("saml"))
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreatePublicChannel(tb testing.TB) *model.Channel {
|
|
|
|
|
return th.CreateChannelWithClient(tb, th.Client, model.ChannelTypeOpen)
|
2017-02-02 09:04:36 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreatePrivateChannel(tb testing.TB) *model.Channel {
|
|
|
|
|
return th.CreateChannelWithClient(tb, th.Client, model.ChannelTypePrivate)
|
2017-02-02 09:04:36 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreateChannelWithClient(tb testing.TB, client *model.Client4, channelType model.ChannelType) *model.Channel {
|
|
|
|
|
return th.CreateChannelWithClientAndTeam(tb, client, channelType, th.BasicTeam.Id)
|
2017-10-17 13:21:12 -04:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreateChannelWithClientAndTeam(tb testing.TB, client *model.Client4, channelType model.ChannelType, teamID string) *model.Channel {
|
2017-02-02 09:04:36 -05:00
|
|
|
id := model.NewId()
|
|
|
|
|
|
|
|
|
|
channel := &model.Channel{
|
|
|
|
|
DisplayName: "dn_" + id,
|
|
|
|
|
Name: GenerateTestChannelName(),
|
|
|
|
|
Type: channelType,
|
2023-12-20 00:46:54 -05:00
|
|
|
TeamId: teamID,
|
2017-02-02 09:04:36 -05:00
|
|
|
}
|
|
|
|
|
|
2023-06-06 17:29:29 -04:00
|
|
|
rchannel, _, err := client.CreateChannel(context.Background(), channel)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.NoError(tb, err)
|
2017-02-02 09:04:36 -05:00
|
|
|
return rchannel
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreatePost(tb testing.TB) *model.Post {
|
|
|
|
|
return th.CreatePostWithClient(tb, th.Client, th.BasicChannel)
|
2017-02-13 10:52:50 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreatePinnedPost(tb testing.TB) *model.Post {
|
|
|
|
|
return th.CreatePinnedPostWithClient(tb, th.Client, th.BasicChannel)
|
2017-03-29 11:09:05 -04:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreateMessagePost(tb testing.TB, message string) *model.Post {
|
|
|
|
|
return th.CreateMessagePostWithClient(tb, th.Client, th.BasicChannel, message)
|
2017-02-21 07:36:52 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreatePostWithFiles(tb testing.TB, files ...*model.FileInfo) *model.Post {
|
|
|
|
|
return th.CreatePostWithFilesWithClient(tb, th.Client, th.BasicChannel, files...)
|
2022-03-25 16:28:14 -04:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreatePostInChannelWithFiles(tb testing.TB, channel *model.Channel, files ...*model.FileInfo) *model.Post {
|
|
|
|
|
return th.CreatePostWithFilesWithClient(tb, th.Client, channel, files...)
|
2022-03-25 16:28:14 -04:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreatePostWithFilesWithClient(tb testing.TB, client *model.Client4, channel *model.Channel, files ...*model.FileInfo) *model.Post {
|
2022-03-25 16:28:14 -04:00
|
|
|
var fileIds model.StringArray
|
|
|
|
|
for i := range files {
|
|
|
|
|
fileIds = append(fileIds, files[i].Id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
post := &model.Post{
|
|
|
|
|
ChannelId: channel.Id,
|
|
|
|
|
Message: "message_" + model.NewId(),
|
|
|
|
|
FileIds: fileIds,
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-06 17:29:29 -04:00
|
|
|
rpost, _, err := client.CreatePost(context.Background(), post)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.NoError(tb, err)
|
2022-03-25 16:28:14 -04:00
|
|
|
return rpost
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreatePostWithClient(tb testing.TB, client *model.Client4, channel *model.Channel) *model.Post {
|
2017-02-13 10:52:50 -05:00
|
|
|
id := model.NewId()
|
|
|
|
|
|
|
|
|
|
post := &model.Post{
|
|
|
|
|
ChannelId: channel.Id,
|
|
|
|
|
Message: "message_" + id,
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-06 17:29:29 -04:00
|
|
|
rpost, _, err := client.CreatePost(context.Background(), post)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.NoError(tb, err)
|
2017-02-13 10:52:50 -05:00
|
|
|
return rpost
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreatePinnedPostWithClient(tb testing.TB, client *model.Client4, channel *model.Channel) *model.Post {
|
2017-03-29 11:09:05 -04:00
|
|
|
id := model.NewId()
|
|
|
|
|
|
|
|
|
|
post := &model.Post{
|
|
|
|
|
ChannelId: channel.Id,
|
|
|
|
|
Message: "message_" + id,
|
|
|
|
|
IsPinned: true,
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-06 17:29:29 -04:00
|
|
|
rpost, _, err := client.CreatePost(context.Background(), post)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.NoError(tb, err)
|
2017-03-29 11:09:05 -04:00
|
|
|
return rpost
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreateMessagePostWithClient(tb testing.TB, client *model.Client4, channel *model.Channel, message string) *model.Post {
|
2017-02-21 07:36:52 -05:00
|
|
|
post := &model.Post{
|
|
|
|
|
ChannelId: channel.Id,
|
|
|
|
|
Message: message,
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-06 17:29:29 -04:00
|
|
|
rpost, _, err := client.CreatePost(context.Background(), post)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.NoError(tb, err)
|
2017-02-21 07:36:52 -05:00
|
|
|
return rpost
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreateMessagePostNoClient(tb testing.TB, channel *model.Channel, message string, createAtTime int64) *model.Post {
|
2024-02-20 11:07:20 -05:00
|
|
|
post, err := th.App.Srv().Store().Post().Save(th.Context, &model.Post{
|
2020-12-11 23:15:17 -05:00
|
|
|
UserId: th.BasicUser.Id,
|
2018-08-28 13:09:32 -04:00
|
|
|
ChannelId: channel.Id,
|
|
|
|
|
Message: message,
|
|
|
|
|
CreateAt: createAtTime,
|
2019-06-14 06:02:33 -04:00
|
|
|
})
|
2025-11-12 07:00:51 -05:00
|
|
|
require.NoError(tb, err)
|
2018-08-28 13:09:32 -04:00
|
|
|
|
|
|
|
|
return post
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreateDmChannel(tb testing.TB, user *model.User) *model.Channel {
|
|
|
|
|
channel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, user.Id)
|
|
|
|
|
require.Nil(tb, appErr)
|
2018-09-27 15:15:47 -04:00
|
|
|
return channel
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) PatchChannelModerationsForMembers(tb testing.TB, channelId, name string, val bool) {
|
2024-03-12 10:36:05 -04:00
|
|
|
patch := []*model.ChannelModerationPatch{{
|
|
|
|
|
Name: &name,
|
2024-08-05 23:45:00 -04:00
|
|
|
Roles: &model.ChannelModeratedRolesPatch{Members: model.NewPointer(val)},
|
2024-03-12 10:36:05 -04:00
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
channel, err := th.App.GetChannel(th.Context, channelId)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.Nil(tb, err)
|
2024-03-12 10:36:05 -04:00
|
|
|
|
|
|
|
|
_, err = th.App.PatchChannelModerationsForChannel(th.Context, channel, patch)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.Nil(tb, err)
|
2024-03-12 10:36:05 -04:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) LoginBasic(tb testing.TB) {
|
|
|
|
|
th.LoginBasicWithClient(tb, th.Client)
|
2017-01-31 09:31:53 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) LoginBasic2(tb testing.TB) {
|
|
|
|
|
th.LoginBasic2WithClient(tb, th.Client)
|
2017-01-31 09:31:53 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) LoginTeamAdmin(tb testing.TB) {
|
|
|
|
|
th.LoginTeamAdminWithClient(tb, th.Client)
|
2017-01-31 09:31:53 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) LoginSystemAdmin(tb testing.TB) {
|
|
|
|
|
th.LoginSystemAdminWithClient(tb, th.SystemAdminClient)
|
2017-01-31 09:31:53 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) LoginSystemManager(tb testing.TB) {
|
|
|
|
|
th.LoginSystemManagerWithClient(tb, th.SystemManagerClient)
|
2021-01-12 14:45:17 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) LoginBasicWithClient(tb testing.TB, client *model.Client4) {
|
2023-06-06 17:29:29 -04:00
|
|
|
_, _, err := client.Login(context.Background(), th.BasicUser.Email, th.BasicUser.Password)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.NoError(tb, err)
|
2017-01-30 08:30:02 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) LoginBasic2WithClient(tb testing.TB, client *model.Client4) {
|
2023-06-06 17:29:29 -04:00
|
|
|
_, _, err := client.Login(context.Background(), th.BasicUser2.Email, th.BasicUser2.Password)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.NoError(tb, err)
|
2017-01-31 09:31:53 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) LoginTeamAdminWithClient(tb testing.TB, client *model.Client4) {
|
2023-06-06 17:29:29 -04:00
|
|
|
_, _, err := client.Login(context.Background(), th.TeamAdminUser.Email, th.TeamAdminUser.Password)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.NoError(tb, err)
|
2017-01-31 09:31:53 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) LoginSystemManagerWithClient(tb testing.TB, client *model.Client4) {
|
2023-06-06 17:29:29 -04:00
|
|
|
_, _, err := client.Login(context.Background(), th.SystemManagerUser.Email, th.SystemManagerUser.Password)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.NoError(tb, err)
|
2021-01-12 14:45:17 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) LoginSystemAdminWithClient(tb testing.TB, client *model.Client4) {
|
2023-06-06 17:29:29 -04:00
|
|
|
_, _, err := client.Login(context.Background(), th.SystemAdminUser.Email, th.SystemAdminUser.Password)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.NoError(tb, err)
|
2017-01-31 09:31:53 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) UpdateActiveUser(tb testing.TB, user *model.User, active bool) {
|
2021-05-11 06:00:44 -04:00
|
|
|
_, err := th.App.UpdateActive(th.Context, user, active)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.Nil(tb, err)
|
2017-02-17 15:50:17 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) LinkUserToTeam(tb testing.TB, user *model.User, team *model.Team) {
|
2021-05-11 06:00:44 -04:00
|
|
|
_, err := th.App.JoinUserToTeam(th.Context, team, user, "")
|
2025-11-12 07:00:51 -05:00
|
|
|
require.Nil(tb, err)
|
2017-01-30 08:30:02 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) UnlinkUserFromTeam(tb testing.TB, user *model.User, team *model.Team) {
|
2022-02-09 15:41:30 -05:00
|
|
|
err := th.App.RemoveUserFromTeam(th.Context, team.Id, user.Id, "")
|
2025-11-12 07:00:51 -05:00
|
|
|
require.Nil(tb, err)
|
2022-02-09 15:41:30 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) AddUserToChannel(tb testing.TB, user *model.User, channel *model.Channel) *model.ChannelMember {
|
2022-07-14 05:01:29 -04:00
|
|
|
member, err := th.App.AddUserToChannel(th.Context, user, channel, false)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.Nil(tb, err)
|
2018-03-09 07:48:30 -05:00
|
|
|
return member
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) RemoveUserFromChannel(tb testing.TB, user *model.User, channel *model.Channel) {
|
2022-02-09 15:41:30 -05:00
|
|
|
err := th.App.RemoveUserFromChannel(th.Context, user.Id, "", channel)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.Nil(tb, err)
|
2022-02-09 15:41:30 -05:00
|
|
|
}
|
|
|
|
|
|
2020-12-11 23:15:17 -05:00
|
|
|
func (th *TestHelper) GenerateTestEmail() string {
|
|
|
|
|
if *th.App.Config().EmailSettings.SMTPServer != "localhost" && os.Getenv("CI_INBUCKET_PORT") == "" {
|
2017-10-18 17:13:14 -04:00
|
|
|
return strings.ToLower("success+" + model.NewId() + "@simulator.amazonses.com")
|
|
|
|
|
}
|
2019-08-02 10:53:00 -04:00
|
|
|
return strings.ToLower(model.NewId() + "@localhost")
|
2017-01-30 08:30:02 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) CreateGroup(tb testing.TB) *model.Group {
|
2019-04-09 07:09:57 -04:00
|
|
|
id := model.NewId()
|
|
|
|
|
group := &model.Group{
|
2024-08-05 23:45:00 -04:00
|
|
|
Name: model.NewPointer("n-" + id),
|
2019-04-09 07:09:57 -04:00
|
|
|
DisplayName: "dn_" + id,
|
|
|
|
|
Source: model.GroupSourceLdap,
|
2024-08-05 23:45:00 -04:00
|
|
|
RemoteId: model.NewPointer("ri_" + model.NewId()),
|
2019-04-09 07:09:57 -04:00
|
|
|
}
|
|
|
|
|
|
2020-12-11 23:15:17 -05:00
|
|
|
group, err := th.App.CreateGroup(group)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.Nil(tb, err)
|
2019-04-09 07:09:57 -04:00
|
|
|
return group
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-19 12:20:41 -04:00
|
|
|
// TestForSystemAdminAndLocal runs a test function for both
|
|
|
|
|
// SystemAdmin and Local clients. Several endpoints work in the same
|
|
|
|
|
// way when used by a fully privileged user and through the local
|
|
|
|
|
// mode, so this helper facilitates checking both
|
2020-12-11 23:15:17 -05:00
|
|
|
func (th *TestHelper) TestForSystemAdminAndLocal(t *testing.T, f func(*testing.T, *model.Client4), name ...string) {
|
2020-05-19 12:20:41 -04:00
|
|
|
var testName string
|
|
|
|
|
if len(name) > 0 {
|
|
|
|
|
testName = name[0] + "/"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t.Run(testName+"SystemAdminClient", func(t *testing.T) {
|
2020-12-11 23:15:17 -05:00
|
|
|
f(t, th.SystemAdminClient)
|
2020-05-19 12:20:41 -04:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run(testName+"LocalClient", func(t *testing.T) {
|
2020-12-11 23:15:17 -05:00
|
|
|
f(t, th.LocalClient)
|
2020-05-19 12:20:41 -04:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestForAllClients runs a test function for all the clients
|
|
|
|
|
// registered in the TestHelper
|
2020-12-11 23:15:17 -05:00
|
|
|
func (th *TestHelper) TestForAllClients(t *testing.T, f func(*testing.T, *model.Client4), name ...string) {
|
2020-05-19 12:20:41 -04:00
|
|
|
var testName string
|
|
|
|
|
if len(name) > 0 {
|
|
|
|
|
testName = name[0] + "/"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t.Run(testName+"Client", func(t *testing.T) {
|
2020-12-11 23:15:17 -05:00
|
|
|
f(t, th.Client)
|
2020-05-19 12:20:41 -04:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run(testName+"SystemAdminClient", func(t *testing.T) {
|
2020-12-11 23:15:17 -05:00
|
|
|
f(t, th.SystemAdminClient)
|
2020-05-19 12:20:41 -04:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run(testName+"LocalClient", func(t *testing.T) {
|
2020-12-11 23:15:17 -05:00
|
|
|
f(t, th.LocalClient)
|
2020-05-19 12:20:41 -04:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-11 01:59:50 -05:00
|
|
|
// TestForRegularAndSystemAdminClients runs a test function for regular and system admin the clients
|
|
|
|
|
// registered in the TestHelper
|
|
|
|
|
func (th *TestHelper) TestForRegularAndSystemAdminClients(t *testing.T, f func(*testing.T, *model.Client4), name ...string) {
|
|
|
|
|
var testName string
|
|
|
|
|
if len(name) > 0 {
|
|
|
|
|
testName = name[0] + "/"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t.Run(testName+"Client", func(t *testing.T) {
|
|
|
|
|
f(t, th.Client)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run(testName+"SystemAdminClient", func(t *testing.T) {
|
|
|
|
|
f(t, th.SystemAdminClient)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2017-01-30 08:30:02 -05:00
|
|
|
func GenerateTestUsername() string {
|
2017-04-20 09:55:02 -04:00
|
|
|
return "fakeuser" + model.NewRandomString(10)
|
2017-01-31 09:31:53 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GenerateTestTeamName() string {
|
2017-02-14 10:28:08 -05:00
|
|
|
return "faketeam" + model.NewRandomString(6)
|
2017-01-30 08:30:02 -05:00
|
|
|
}
|
|
|
|
|
|
2017-02-02 09:04:36 -05:00
|
|
|
func GenerateTestChannelName() string {
|
2017-02-14 10:28:08 -05:00
|
|
|
return "fakechannel" + model.NewRandomString(10)
|
2017-02-02 09:04:36 -05:00
|
|
|
}
|
|
|
|
|
|
2017-04-20 09:55:02 -04:00
|
|
|
func GenerateTestAppName() string {
|
|
|
|
|
return "fakeoauthapp" + model.NewRandomString(10)
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-20 00:46:54 -05:00
|
|
|
func GenerateTestID() string {
|
2017-02-24 08:27:47 -05:00
|
|
|
return model.NewId()
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
func CheckUserSanitization(tb testing.TB, user *model.User) {
|
|
|
|
|
tb.Helper()
|
2018-02-20 08:51:01 -05:00
|
|
|
|
2025-12-10 14:15:31 -05:00
|
|
|
require.Empty(tb, user.Password, "password wasn't blank")
|
2021-12-17 05:48:31 -05:00
|
|
|
require.Empty(tb, user.AuthData, "auth data wasn't blank")
|
2025-12-10 14:15:31 -05:00
|
|
|
require.Empty(tb, user.MfaSecret, "mfa secret wasn't blank")
|
2017-01-30 08:30:02 -05:00
|
|
|
}
|
|
|
|
|
|
2022-07-05 02:46:50 -04:00
|
|
|
func CheckEtag(tb testing.TB, data any, resp *model.Response) {
|
2021-12-17 05:48:31 -05:00
|
|
|
tb.Helper()
|
2018-02-20 08:51:01 -05:00
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
require.Empty(tb, data)
|
|
|
|
|
require.Equal(tb, http.StatusNotModified, resp.StatusCode, "wrong status code for etag")
|
2017-01-30 08:30:02 -05:00
|
|
|
}
|
|
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
func checkHTTPStatus(tb testing.TB, resp *model.Response, expectedStatus int) {
|
|
|
|
|
tb.Helper()
|
2018-02-20 08:51:01 -05:00
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
require.NotNilf(tb, resp, "Unexpected nil response, expected http status:%v", expectedStatus)
|
|
|
|
|
require.Equalf(tb, expectedStatus, resp.StatusCode, "Expected http status:%v, got %v", expectedStatus, resp.StatusCode)
|
2017-01-30 08:30:02 -05:00
|
|
|
}
|
|
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
func CheckOKStatus(tb testing.TB, resp *model.Response) {
|
|
|
|
|
tb.Helper()
|
|
|
|
|
checkHTTPStatus(tb, resp, http.StatusOK)
|
MM-7633: Optimize memory utilization during file uploads (#9835)
* MM-7633: Optimize memory utilization during file uploads
Refactored the file upload code to reduce redundant buffering and stream
directly to the file store. Added tests.
Benchmark results:
```
levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app
...
BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op
BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op
BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op
BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op
BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op
BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op
BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op
BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op
BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op
BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op
BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op
BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op
...
```
https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801
[Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.]
- [x] Added or updated unit tests (required for all new features)
- [ ] Added API documentation (required for all new APIs)
- [ ] All new/modified APIs include changes to the drivers
*N/A*???
- [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates
Overview of changes:
- api4
- Replaced `uploadFile` handler with `uploadFileStream` that reduces
unnecessary buffering.
- Added/refactored tests for the new API.
- Refactored apitestlib/Check...Status functions.
- app
- Added App.UploadFileTask, a more efficient refactor of UploadFile.
- Consistently set `FileInfo.HasPreviewImage`
- Added benchmarks for the new and prior implementations
- Replaced passing around `*image.Image` with `image.Image` in the
existing code.
- model
- Added a more capable `client4.UploadFiles` API to match the new server
API’s capabilities.
- I18n
- Replaced `api.file.upload_file.bad_parse.app_error` with a more generic
`api.file.upload_file.read_request.app_error`
- plugin
- Removed type `plugin.multiPluginHookRunnerFunc` in favor of using
`func(hooks Hooks) bool` explicitly, to help with testing
- tests
- Added test files for testing images
Still remaining, but can be separate PRs - please let me know the preferred
course of action
- Investigate JS client API - how does it do multipart?
- Performance loss from old code on (small) image processing?
- Deprecate the old functions, change other API implementations to use
UploadFileTask
Definitely separate future PRs - should I file tickets foe these?
- Only invoke t.readAll() if there are indeed applicable plugins to run
- Find a way to leverage goexif buffer rather than re-reading
Suggested long-term improvements - should I file separate tickets for these?
- Actually allow uploading of large (GB-sized) files. This may require a
change in how the file is passed to plugins.
- (Many) api4 tests should probably be subtests and share a server setup -
will be much faster
- Performance improvements in image processing (goexif/thumbnail/preview)
(maybe use https://mattermost.atlassian.net/browse/MM-10188 for this)
Questions:
1. I am commiting MBs of test images, are there better alternatives? I can
probably create much less dense images that would take up considerably less
space, even at pretty large sizes
2. I18n: Do I need to do anything special for the string change? Or just wait
until it gets picked up and translated/updated?
3. The image dimensions are flipped in resulting FileInfo to match the actual
orientation. Is this by design? Should add a test for it, perhaps?
4. What to do in the case of partial success? S3 but not DB, some files but not
others? For now, just doing what the old code did, I think.
5. Make maxUploadDrainBytes configurable? Also, should this be the systemic
behavior of all APIs with non-empty body? Otherwise dropped altogether?
Check all other ioutil.ReadAll() from sockets. Find a way to set a total
byte limit on request Body?
* WIP - Fixed for GetPluginsEnvironment() changes
* WIP - PR feedback
1. Refactored checkHTTPStatus to improve failure messages
2. Use `var name []type` rather than `name := ([]type)(nil)`
3. Replaced single-letter `p` with a more intention-revealing `part`
4. Added tests for full image size, `HasPreviewImage`
* WIP - rebased (c.Session->c.App.Session)
* WIP - PR feedback: eliminated use of Request.MultipartReader
Instead of hacking the request object to use r.MultipartReader now have own
functions `parseMultipartRequestHeader` and `multipartReader` eliminating the
need to hack the request object to use Request.MultipartReader limitations.
* WIP - PR feedback: UploadFileX with functional options
* WIP - PR feedback: style
* WIP - PR feedback: errors cleanup
* WIP - clarified File Upload benchmarks
* WIP - PR feedback: display the value of erroneous formname
* WIP - PR feedback: fixed handling of multiple channel_ids
* WIP - rebased from master - fixed tests
* PR Feedback
* PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
|
|
|
}
|
2018-02-20 08:51:01 -05:00
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
func CheckCreatedStatus(tb testing.TB, resp *model.Response) {
|
|
|
|
|
tb.Helper()
|
|
|
|
|
checkHTTPStatus(tb, resp, http.StatusCreated)
|
MM-7633: Optimize memory utilization during file uploads (#9835)
* MM-7633: Optimize memory utilization during file uploads
Refactored the file upload code to reduce redundant buffering and stream
directly to the file store. Added tests.
Benchmark results:
```
levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app
...
BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op
BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op
BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op
BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op
BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op
BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op
BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op
BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op
BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op
BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op
BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op
BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op
...
```
https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801
[Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.]
- [x] Added or updated unit tests (required for all new features)
- [ ] Added API documentation (required for all new APIs)
- [ ] All new/modified APIs include changes to the drivers
*N/A*???
- [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates
Overview of changes:
- api4
- Replaced `uploadFile` handler with `uploadFileStream` that reduces
unnecessary buffering.
- Added/refactored tests for the new API.
- Refactored apitestlib/Check...Status functions.
- app
- Added App.UploadFileTask, a more efficient refactor of UploadFile.
- Consistently set `FileInfo.HasPreviewImage`
- Added benchmarks for the new and prior implementations
- Replaced passing around `*image.Image` with `image.Image` in the
existing code.
- model
- Added a more capable `client4.UploadFiles` API to match the new server
API’s capabilities.
- I18n
- Replaced `api.file.upload_file.bad_parse.app_error` with a more generic
`api.file.upload_file.read_request.app_error`
- plugin
- Removed type `plugin.multiPluginHookRunnerFunc` in favor of using
`func(hooks Hooks) bool` explicitly, to help with testing
- tests
- Added test files for testing images
Still remaining, but can be separate PRs - please let me know the preferred
course of action
- Investigate JS client API - how does it do multipart?
- Performance loss from old code on (small) image processing?
- Deprecate the old functions, change other API implementations to use
UploadFileTask
Definitely separate future PRs - should I file tickets foe these?
- Only invoke t.readAll() if there are indeed applicable plugins to run
- Find a way to leverage goexif buffer rather than re-reading
Suggested long-term improvements - should I file separate tickets for these?
- Actually allow uploading of large (GB-sized) files. This may require a
change in how the file is passed to plugins.
- (Many) api4 tests should probably be subtests and share a server setup -
will be much faster
- Performance improvements in image processing (goexif/thumbnail/preview)
(maybe use https://mattermost.atlassian.net/browse/MM-10188 for this)
Questions:
1. I am commiting MBs of test images, are there better alternatives? I can
probably create much less dense images that would take up considerably less
space, even at pretty large sizes
2. I18n: Do I need to do anything special for the string change? Or just wait
until it gets picked up and translated/updated?
3. The image dimensions are flipped in resulting FileInfo to match the actual
orientation. Is this by design? Should add a test for it, perhaps?
4. What to do in the case of partial success? S3 but not DB, some files but not
others? For now, just doing what the old code did, I think.
5. Make maxUploadDrainBytes configurable? Also, should this be the systemic
behavior of all APIs with non-empty body? Otherwise dropped altogether?
Check all other ioutil.ReadAll() from sockets. Find a way to set a total
byte limit on request Body?
* WIP - Fixed for GetPluginsEnvironment() changes
* WIP - PR feedback
1. Refactored checkHTTPStatus to improve failure messages
2. Use `var name []type` rather than `name := ([]type)(nil)`
3. Replaced single-letter `p` with a more intention-revealing `part`
4. Added tests for full image size, `HasPreviewImage`
* WIP - rebased (c.Session->c.App.Session)
* WIP - PR feedback: eliminated use of Request.MultipartReader
Instead of hacking the request object to use r.MultipartReader now have own
functions `parseMultipartRequestHeader` and `multipartReader` eliminating the
need to hack the request object to use Request.MultipartReader limitations.
* WIP - PR feedback: UploadFileX with functional options
* WIP - PR feedback: style
* WIP - PR feedback: errors cleanup
* WIP - clarified File Upload benchmarks
* WIP - PR feedback: display the value of erroneous formname
* WIP - PR feedback: fixed handling of multiple channel_ids
* WIP - rebased from master - fixed tests
* PR Feedback
* PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
|
|
|
}
|
2017-01-30 08:30:02 -05:00
|
|
|
|
2024-07-04 04:35:26 -04:00
|
|
|
func CheckNoContentStatus(tb testing.TB, resp *model.Response) {
|
|
|
|
|
tb.Helper()
|
|
|
|
|
checkHTTPStatus(tb, resp, http.StatusNoContent)
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
func CheckForbiddenStatus(tb testing.TB, resp *model.Response) {
|
|
|
|
|
tb.Helper()
|
|
|
|
|
checkHTTPStatus(tb, resp, http.StatusForbidden)
|
2017-01-30 08:30:02 -05:00
|
|
|
}
|
|
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
func CheckUnauthorizedStatus(tb testing.TB, resp *model.Response) {
|
|
|
|
|
tb.Helper()
|
|
|
|
|
checkHTTPStatus(tb, resp, http.StatusUnauthorized)
|
2017-01-30 08:30:02 -05:00
|
|
|
}
|
|
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
func CheckNotFoundStatus(tb testing.TB, resp *model.Response) {
|
|
|
|
|
tb.Helper()
|
|
|
|
|
checkHTTPStatus(tb, resp, http.StatusNotFound)
|
2017-01-30 08:30:02 -05:00
|
|
|
}
|
|
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
func CheckBadRequestStatus(tb testing.TB, resp *model.Response) {
|
|
|
|
|
tb.Helper()
|
|
|
|
|
checkHTTPStatus(tb, resp, http.StatusBadRequest)
|
2017-01-30 08:30:02 -05:00
|
|
|
}
|
|
|
|
|
|
2024-07-04 04:35:26 -04:00
|
|
|
func CheckUnprocessableEntityStatus(tb testing.TB, resp *model.Response) {
|
|
|
|
|
tb.Helper()
|
|
|
|
|
checkHTTPStatus(tb, resp, http.StatusUnprocessableEntity)
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
func CheckNotImplementedStatus(tb testing.TB, resp *model.Response) {
|
|
|
|
|
tb.Helper()
|
|
|
|
|
checkHTTPStatus(tb, resp, http.StatusNotImplemented)
|
2017-02-21 19:42:34 -05:00
|
|
|
}
|
|
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
func CheckRequestEntityTooLargeStatus(tb testing.TB, resp *model.Response) {
|
|
|
|
|
tb.Helper()
|
|
|
|
|
checkHTTPStatus(tb, resp, http.StatusRequestEntityTooLarge)
|
MM-7633: Optimize memory utilization during file uploads (#9835)
* MM-7633: Optimize memory utilization during file uploads
Refactored the file upload code to reduce redundant buffering and stream
directly to the file store. Added tests.
Benchmark results:
```
levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app
...
BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op
BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op
BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op
BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op
BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op
BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op
BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op
BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op
BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op
BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op
BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op
BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op
...
```
https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801
[Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.]
- [x] Added or updated unit tests (required for all new features)
- [ ] Added API documentation (required for all new APIs)
- [ ] All new/modified APIs include changes to the drivers
*N/A*???
- [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates
Overview of changes:
- api4
- Replaced `uploadFile` handler with `uploadFileStream` that reduces
unnecessary buffering.
- Added/refactored tests for the new API.
- Refactored apitestlib/Check...Status functions.
- app
- Added App.UploadFileTask, a more efficient refactor of UploadFile.
- Consistently set `FileInfo.HasPreviewImage`
- Added benchmarks for the new and prior implementations
- Replaced passing around `*image.Image` with `image.Image` in the
existing code.
- model
- Added a more capable `client4.UploadFiles` API to match the new server
API’s capabilities.
- I18n
- Replaced `api.file.upload_file.bad_parse.app_error` with a more generic
`api.file.upload_file.read_request.app_error`
- plugin
- Removed type `plugin.multiPluginHookRunnerFunc` in favor of using
`func(hooks Hooks) bool` explicitly, to help with testing
- tests
- Added test files for testing images
Still remaining, but can be separate PRs - please let me know the preferred
course of action
- Investigate JS client API - how does it do multipart?
- Performance loss from old code on (small) image processing?
- Deprecate the old functions, change other API implementations to use
UploadFileTask
Definitely separate future PRs - should I file tickets foe these?
- Only invoke t.readAll() if there are indeed applicable plugins to run
- Find a way to leverage goexif buffer rather than re-reading
Suggested long-term improvements - should I file separate tickets for these?
- Actually allow uploading of large (GB-sized) files. This may require a
change in how the file is passed to plugins.
- (Many) api4 tests should probably be subtests and share a server setup -
will be much faster
- Performance improvements in image processing (goexif/thumbnail/preview)
(maybe use https://mattermost.atlassian.net/browse/MM-10188 for this)
Questions:
1. I am commiting MBs of test images, are there better alternatives? I can
probably create much less dense images that would take up considerably less
space, even at pretty large sizes
2. I18n: Do I need to do anything special for the string change? Or just wait
until it gets picked up and translated/updated?
3. The image dimensions are flipped in resulting FileInfo to match the actual
orientation. Is this by design? Should add a test for it, perhaps?
4. What to do in the case of partial success? S3 but not DB, some files but not
others? For now, just doing what the old code did, I think.
5. Make maxUploadDrainBytes configurable? Also, should this be the systemic
behavior of all APIs with non-empty body? Otherwise dropped altogether?
Check all other ioutil.ReadAll() from sockets. Find a way to set a total
byte limit on request Body?
* WIP - Fixed for GetPluginsEnvironment() changes
* WIP - PR feedback
1. Refactored checkHTTPStatus to improve failure messages
2. Use `var name []type` rather than `name := ([]type)(nil)`
3. Replaced single-letter `p` with a more intention-revealing `part`
4. Added tests for full image size, `HasPreviewImage`
* WIP - rebased (c.Session->c.App.Session)
* WIP - PR feedback: eliminated use of Request.MultipartReader
Instead of hacking the request object to use r.MultipartReader now have own
functions `parseMultipartRequestHeader` and `multipartReader` eliminating the
need to hack the request object to use Request.MultipartReader limitations.
* WIP - PR feedback: UploadFileX with functional options
* WIP - PR feedback: style
* WIP - PR feedback: errors cleanup
* WIP - clarified File Upload benchmarks
* WIP - PR feedback: display the value of erroneous formname
* WIP - PR feedback: fixed handling of multiple channel_ids
* WIP - rebased from master - fixed tests
* PR Feedback
* PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
|
|
|
}
|
2018-02-20 08:51:01 -05:00
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
func CheckInternalErrorStatus(tb testing.TB, resp *model.Response) {
|
|
|
|
|
tb.Helper()
|
|
|
|
|
checkHTTPStatus(tb, resp, http.StatusInternalServerError)
|
2017-03-11 17:40:56 -05:00
|
|
|
}
|
|
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
func CheckServiceUnavailableStatus(tb testing.TB, resp *model.Response) {
|
|
|
|
|
tb.Helper()
|
|
|
|
|
checkHTTPStatus(tb, resp, http.StatusServiceUnavailable)
|
2019-11-27 20:41:09 -05:00
|
|
|
}
|
|
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
func CheckErrorID(tb testing.TB, err error, errorId string) {
|
|
|
|
|
tb.Helper()
|
2018-02-20 08:51:01 -05:00
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
require.Error(tb, err, "should have errored with id: %s", errorId)
|
2021-08-13 07:12:16 -04:00
|
|
|
|
|
|
|
|
var appError *model.AppError
|
|
|
|
|
ok := errors.As(err, &appError)
|
2021-12-17 05:48:31 -05:00
|
|
|
require.True(tb, ok, "should have been a model.AppError")
|
2021-08-13 07:12:16 -04:00
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
require.Equalf(tb, errorId, appError.Id, "incorrect error id, actual: %s, expected: %s", appError.Id, errorId)
|
2021-08-13 07:12:16 -04:00
|
|
|
}
|
|
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
func CheckErrorMessage(tb testing.TB, err error, message string) {
|
|
|
|
|
tb.Helper()
|
2021-08-13 07:12:16 -04:00
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
require.Error(tb, err, "should have errored with message: %s", message)
|
2021-08-13 07:12:16 -04:00
|
|
|
|
|
|
|
|
var appError *model.AppError
|
|
|
|
|
ok := errors.As(err, &appError)
|
2021-12-17 05:48:31 -05:00
|
|
|
require.True(tb, ok, "should have been a model.AppError")
|
2021-08-13 07:12:16 -04:00
|
|
|
|
2021-12-17 05:48:31 -05:00
|
|
|
require.Equalf(tb, message, appError.Message, "incorrect error message, actual: %s, expected: %s", appError.Id, message)
|
2017-01-30 08:30:02 -05:00
|
|
|
}
|
2017-02-17 10:31:21 -05:00
|
|
|
|
2017-07-31 12:22:52 -04:00
|
|
|
// Similar to s3.New() but allows initialization of signature v2 or signature v4 client.
|
|
|
|
|
// If signV2 input is false, function always returns signature v4.
|
|
|
|
|
//
|
|
|
|
|
// Additionally this function also takes a user defined region, if set
|
|
|
|
|
// disables automatic region lookup.
|
|
|
|
|
func s3New(endpoint, accessKey, secretKey string, secure bool, signV2 bool, region string) (*s3.Client, error) {
|
|
|
|
|
var creds *credentials.Credentials
|
2017-05-30 19:12:24 -04:00
|
|
|
if signV2 {
|
2017-07-31 12:22:52 -04:00
|
|
|
creds = credentials.NewStatic(accessKey, secretKey, "", credentials.SignatureV2)
|
|
|
|
|
} else {
|
|
|
|
|
creds = credentials.NewStatic(accessKey, secretKey, "", credentials.SignatureV4)
|
2017-05-30 19:12:24 -04:00
|
|
|
}
|
2020-08-12 13:53:47 -04:00
|
|
|
|
|
|
|
|
opts := s3.Options{
|
|
|
|
|
Creds: creds,
|
|
|
|
|
Secure: secure,
|
|
|
|
|
Region: region,
|
|
|
|
|
}
|
|
|
|
|
return s3.New(endpoint, &opts)
|
2017-05-30 19:12:24 -04:00
|
|
|
}
|
|
|
|
|
|
2020-12-11 23:15:17 -05:00
|
|
|
func (th *TestHelper) cleanupTestFile(info *model.FileInfo) error {
|
|
|
|
|
cfg := th.App.Config()
|
2021-07-12 14:05:36 -04:00
|
|
|
if *cfg.FileSettings.DriverName == model.ImageDriverS3 {
|
2019-01-31 08:12:01 -05:00
|
|
|
endpoint := *cfg.FileSettings.AmazonS3Endpoint
|
|
|
|
|
accessKey := *cfg.FileSettings.AmazonS3AccessKeyId
|
|
|
|
|
secretKey := *cfg.FileSettings.AmazonS3SecretAccessKey
|
2017-11-09 15:46:20 -05:00
|
|
|
secure := *cfg.FileSettings.AmazonS3SSL
|
|
|
|
|
signV2 := *cfg.FileSettings.AmazonS3SignV2
|
2019-01-31 08:12:01 -05:00
|
|
|
region := *cfg.FileSettings.AmazonS3Region
|
2017-07-31 12:22:52 -04:00
|
|
|
s3Clnt, err := s3New(endpoint, accessKey, secretKey, secure, signV2, region)
|
2017-02-17 10:31:21 -05:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2019-01-31 08:12:01 -05:00
|
|
|
bucket := *cfg.FileSettings.AmazonS3Bucket
|
2020-08-12 13:53:47 -04:00
|
|
|
if err := s3Clnt.RemoveObject(context.Background(), bucket, info.Path, s3.RemoveObjectOptions{}); err != nil {
|
2017-02-17 10:31:21 -05:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if info.ThumbnailPath != "" {
|
2020-08-12 13:53:47 -04:00
|
|
|
if err := s3Clnt.RemoveObject(context.Background(), bucket, info.ThumbnailPath, s3.RemoveObjectOptions{}); err != nil {
|
2017-02-17 10:31:21 -05:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if info.PreviewPath != "" {
|
2020-08-12 13:53:47 -04:00
|
|
|
if err := s3Clnt.RemoveObject(context.Background(), bucket, info.PreviewPath, s3.RemoveObjectOptions{}); err != nil {
|
2017-02-17 10:31:21 -05:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-07-12 14:05:36 -04:00
|
|
|
} else if *cfg.FileSettings.DriverName == model.ImageDriverLocal {
|
2019-01-31 08:12:01 -05:00
|
|
|
if err := os.Remove(*cfg.FileSettings.Directory + info.Path); err != nil {
|
2017-02-17 10:31:21 -05:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if info.ThumbnailPath != "" {
|
2019-01-31 08:12:01 -05:00
|
|
|
if err := os.Remove(*cfg.FileSettings.Directory + info.ThumbnailPath); err != nil {
|
2017-02-17 10:31:21 -05:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if info.PreviewPath != "" {
|
2019-01-31 08:12:01 -05:00
|
|
|
if err := os.Remove(*cfg.FileSettings.Directory + info.PreviewPath); err != nil {
|
2017-02-17 10:31:21 -05:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2017-03-14 08:08:58 -04:00
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) MakeUserChannelAdmin(tb testing.TB, user *model.User, channel *model.Channel) {
|
|
|
|
|
cm, err := th.App.Srv().Store().Channel().GetMember(th.Context, channel.Id, user.Id)
|
|
|
|
|
require.NoError(tb, err)
|
|
|
|
|
cm.SchemeAdmin = true
|
|
|
|
|
_, err = th.App.Srv().Store().Channel().UpdateMember(th.Context, cm)
|
|
|
|
|
require.NoError(tb, err)
|
2017-03-14 08:08:58 -04:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) UpdateUserToTeamAdmin(tb testing.TB, user *model.User, team *model.Team) {
|
|
|
|
|
tm, err := th.App.Srv().Store().Team().GetMember(th.Context, team.Id, user.Id)
|
|
|
|
|
require.NoError(tb, err)
|
|
|
|
|
tm.SchemeAdmin = true
|
|
|
|
|
_, err = th.App.Srv().Store().Team().UpdateMember(th.Context, tm)
|
|
|
|
|
require.NoError(tb, err)
|
2017-03-14 08:08:58 -04:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) UpdateUserToNonTeamAdmin(tb testing.TB, user *model.User, team *model.Team) {
|
|
|
|
|
tm, err := th.App.Srv().Store().Team().GetMember(th.Context, team.Id, user.Id)
|
|
|
|
|
require.NoError(tb, err)
|
|
|
|
|
tm.SchemeAdmin = false
|
|
|
|
|
_, err = th.App.Srv().Store().Team().UpdateMember(th.Context, tm)
|
|
|
|
|
require.NoError(tb, err)
|
2017-03-14 08:08:58 -04:00
|
|
|
}
|
2018-02-06 10:34:08 -05:00
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) SaveDefaultRolePermissions(tb testing.TB) map[string][]string {
|
2018-02-06 10:34:08 -05:00
|
|
|
results := make(map[string][]string)
|
|
|
|
|
|
|
|
|
|
for _, roleName := range []string{
|
|
|
|
|
"system_user",
|
|
|
|
|
"system_admin",
|
|
|
|
|
"team_user",
|
|
|
|
|
"team_admin",
|
|
|
|
|
"channel_user",
|
|
|
|
|
"channel_admin",
|
|
|
|
|
} {
|
2025-09-18 10:14:24 -04:00
|
|
|
role, err1 := th.App.GetRoleByName(th.Context, roleName)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.Nil(tb, err1)
|
2018-02-06 10:34:08 -05:00
|
|
|
|
|
|
|
|
results[roleName] = role.Permissions
|
|
|
|
|
}
|
|
|
|
|
return results
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) RestoreDefaultRolePermissions(tb testing.TB, data map[string][]string) {
|
2018-02-06 10:34:08 -05:00
|
|
|
for roleName, permissions := range data {
|
2025-09-18 10:14:24 -04:00
|
|
|
role, err1 := th.App.GetRoleByName(th.Context, roleName)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.Nil(tb, err1)
|
2018-02-06 10:34:08 -05:00
|
|
|
|
|
|
|
|
if strings.Join(role.Permissions, " ") == strings.Join(permissions, " ") {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
role.Permissions = permissions
|
|
|
|
|
|
2020-12-11 23:15:17 -05:00
|
|
|
_, err2 := th.App.UpdateRole(role)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.Nil(tb, err2)
|
2018-02-06 10:34:08 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) RemovePermissionFromRole(tb testing.TB, permission string, roleName string) {
|
2025-09-18 10:14:24 -04:00
|
|
|
role, err1 := th.App.GetRoleByName(th.Context, roleName)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.Nil(tb, err1)
|
2018-02-06 10:34:08 -05:00
|
|
|
|
|
|
|
|
var newPermissions []string
|
|
|
|
|
for _, p := range role.Permissions {
|
|
|
|
|
if p != permission {
|
|
|
|
|
newPermissions = append(newPermissions, p)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if strings.Join(role.Permissions, " ") == strings.Join(newPermissions, " ") {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
role.Permissions = newPermissions
|
|
|
|
|
|
2020-12-11 23:15:17 -05:00
|
|
|
_, err2 := th.App.UpdateRole(role)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.Nil(tb, err2)
|
2018-02-06 10:34:08 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) AddPermissionToRole(tb testing.TB, permission string, roleName string) {
|
2025-09-18 10:14:24 -04:00
|
|
|
role, err1 := th.App.GetRoleByName(th.Context, roleName)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.Nil(tb, err1)
|
2018-02-06 10:34:08 -05:00
|
|
|
|
2025-07-18 06:54:51 -04:00
|
|
|
if slices.Contains(role.Permissions, permission) {
|
|
|
|
|
return
|
2018-02-06 10:34:08 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
role.Permissions = append(role.Permissions, permission)
|
|
|
|
|
|
2020-12-11 23:15:17 -05:00
|
|
|
_, err2 := th.App.UpdateRole(role)
|
2025-11-12 07:00:51 -05:00
|
|
|
require.Nil(tb, err2)
|
2018-02-06 10:34:08 -05:00
|
|
|
}
|
2020-03-05 10:04:34 -05:00
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) SetupTeamScheme(tb testing.TB) *model.Scheme {
|
|
|
|
|
return th.SetupScheme(tb, model.SchemeScopeTeam)
|
2020-03-05 10:04:34 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) SetupChannelScheme(tb testing.TB) *model.Scheme {
|
|
|
|
|
return th.SetupScheme(tb, model.SchemeScopeChannel)
|
2020-03-05 10:04:34 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-12 07:00:51 -05:00
|
|
|
func (th *TestHelper) SetupScheme(tb testing.TB, scope string) *model.Scheme {
|
2020-12-21 10:50:47 -05:00
|
|
|
scheme, err := th.App.CreateScheme(&model.Scheme{
|
2020-03-05 10:04:34 -05:00
|
|
|
Name: model.NewId(),
|
|
|
|
|
DisplayName: model.NewId(),
|
|
|
|
|
Scope: scope,
|
2020-12-21 10:50:47 -05:00
|
|
|
})
|
2025-11-12 07:00:51 -05:00
|
|
|
require.Nil(tb, err)
|
2020-12-21 10:50:47 -05:00
|
|
|
return scheme
|
2020-03-05 10:04:34 -05:00
|
|
|
}
|
2025-05-30 07:58:26 -04:00
|
|
|
|
|
|
|
|
func (th *TestHelper) Parallel(t *testing.T) {
|
|
|
|
|
mainHelper.Parallel(t)
|
|
|
|
|
}
|
2026-02-10 15:12:14 -05:00
|
|
|
|
|
|
|
|
// AuditEntry represents a parsed audit log entry for testing
|
|
|
|
|
type AuditEntry struct {
|
|
|
|
|
EventName string
|
|
|
|
|
Status string
|
|
|
|
|
UserID string
|
|
|
|
|
SessionID string
|
|
|
|
|
Parameters map[string]any
|
|
|
|
|
Raw map[string]any
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FindAuditEntry searches audit log data for an entry matching the given event name
|
|
|
|
|
// and optionally a user ID. Returns the first matching entry or nil if not found.
|
|
|
|
|
func FindAuditEntry(data string, eventName string, userID string) *AuditEntry {
|
|
|
|
|
for line := range strings.SplitSeq(data, "\n") {
|
|
|
|
|
if line == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
var entry map[string]any
|
|
|
|
|
if err := json.Unmarshal([]byte(line), &entry); err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if entry["event_name"] != eventName {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auditEntry := &AuditEntry{
|
|
|
|
|
EventName: eventName,
|
|
|
|
|
Raw: entry,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if status, ok := entry["status"].(string); ok {
|
|
|
|
|
auditEntry.Status = status
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if actor, ok := entry["actor"].(map[string]any); ok {
|
|
|
|
|
if uid, ok := actor["user_id"].(string); ok {
|
|
|
|
|
auditEntry.UserID = uid
|
|
|
|
|
}
|
|
|
|
|
if sid, ok := actor["session_id"].(string); ok {
|
|
|
|
|
auditEntry.SessionID = sid
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if event, ok := entry["event"].(map[string]any); ok {
|
|
|
|
|
if params, ok := event["parameters"].(map[string]any); ok {
|
|
|
|
|
auditEntry.Parameters = params
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If userID filter is specified, check it matches
|
|
|
|
|
if userID != "" && auditEntry.UserID != userID {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return auditEntry
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|