mirror of
https://github.com/mattermost/mattermost.git
synced 2026-04-13 04:57:45 -04:00
* 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>
1206 lines
38 KiB
Go
1206 lines
38 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package api4
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/mattermost/mattermost/server/v8/channels/utils/fileutils"
|
|
)
|
|
|
|
func TestGetPing(t *testing.T) {
|
|
th := Setup(t)
|
|
|
|
th.TestForAllClients(t, func(t *testing.T, client *model.Client4) {
|
|
t.Run("healthy", func(t *testing.T) {
|
|
status, _, err := client.GetPing(context.Background())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, model.StatusOk, status)
|
|
})
|
|
|
|
t.Run("unhealthy", func(t *testing.T) {
|
|
goRoutineHealthThreshold := *th.App.Config().ServiceSettings.GoroutineHealthThreshold
|
|
defer func() {
|
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.GoroutineHealthThreshold = goRoutineHealthThreshold })
|
|
}()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.GoroutineHealthThreshold = 10 })
|
|
status, resp, err := client.GetPing(context.Background())
|
|
require.Error(t, err)
|
|
CheckInternalErrorStatus(t, resp)
|
|
assert.Equal(t, model.StatusUnhealthy, status)
|
|
})
|
|
}, "basic ping")
|
|
|
|
th.TestForAllClients(t, func(t *testing.T, client *model.Client4) {
|
|
t.Run("healthy", func(t *testing.T) {
|
|
status, _, err := client.GetPingWithServerStatus(context.Background())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, model.StatusOk, status)
|
|
})
|
|
}, "with server status")
|
|
|
|
th.TestForAllClients(t, func(t *testing.T, client *model.Client4) {
|
|
err := th.App.ReloadConfig()
|
|
require.NoError(t, err)
|
|
respMap, resp, err := client.GetPingWithOptions(context.Background(), model.SystemPingOptions{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
_, ok := respMap["TestFeatureFlag"]
|
|
assert.Equal(t, false, ok)
|
|
|
|
// Feature flags in ping response come from env var overrides, not config.
|
|
// Must use real env vars + ReloadConfig to test this path.
|
|
t.Setenv("MM_FEATUREFLAGS_TESTFEATURE", "testvalueunique")
|
|
err = th.App.ReloadConfig()
|
|
require.NoError(t, err)
|
|
|
|
respMap, resp, err = client.GetPingWithOptions(context.Background(), model.SystemPingOptions{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
_, ok = respMap["TestFeatureFlag"]
|
|
assert.Equal(t, true, ok)
|
|
}, "ping feature flag test")
|
|
|
|
t.Run("ping root_status test", func(t *testing.T) {
|
|
respMap, resp, err := th.SystemAdminClient.GetPingWithOptions(context.Background(), model.SystemPingOptions{FullStatus: true})
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
_, ok := respMap["root_status"]
|
|
assert.Equal(t, true, ok)
|
|
})
|
|
|
|
t.Run("ping root_status test with client user", func(t *testing.T) {
|
|
respMap, resp, err := th.Client.GetPingWithOptions(context.Background(), model.SystemPingOptions{FullStatus: true})
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
_, ok := respMap["root_status"]
|
|
assert.Equal(t, false, ok)
|
|
})
|
|
|
|
th.TestForAllClients(t, func(t *testing.T, client *model.Client4) {
|
|
err := th.App.ReloadConfig()
|
|
require.NoError(t, err)
|
|
resp, err := client.DoAPIGet(context.Background(), "/system/ping?device_id=platform:id", "")
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
var respMap map[string]any
|
|
err = json.NewDecoder(resp.Body).Decode(&respMap)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "unknown", respMap["CanReceiveNotifications"]) // Unrecognized platform
|
|
}, "ping and test push notification")
|
|
}
|
|
|
|
func TestGetAudits(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
client := th.Client
|
|
|
|
audits, _, err := th.SystemAdminClient.GetAudits(context.Background(), 0, 100, "")
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, audits, "should not be empty")
|
|
|
|
audits, _, err = th.SystemAdminClient.GetAudits(context.Background(), 0, 1, "")
|
|
require.NoError(t, err)
|
|
require.Len(t, audits, 1, "should only be 1")
|
|
|
|
audits, _, err = th.SystemAdminClient.GetAudits(context.Background(), 1, 1, "")
|
|
require.NoError(t, err)
|
|
require.Len(t, audits, 1, "should only be 1")
|
|
|
|
_, _, err = th.SystemAdminClient.GetAudits(context.Background(), -1, -1, "")
|
|
require.NoError(t, err)
|
|
|
|
_, resp, err := client.GetAudits(context.Background(), 0, 100, "")
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
_, resp, err = client.GetAudits(context.Background(), 0, 100, "")
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
}
|
|
|
|
func TestEmailTest(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
client := th.Client
|
|
|
|
dir, err := os.MkdirTemp("", "")
|
|
require.NoError(t, err)
|
|
defer os.RemoveAll(dir)
|
|
|
|
es := model.EmailSettings{}
|
|
es.SetDefaults(false)
|
|
|
|
es.SMTPServer = model.NewPointer("")
|
|
es.SMTPPort = model.NewPointer("")
|
|
es.SMTPPassword = model.NewPointer("")
|
|
es.FeedbackName = model.NewPointer("")
|
|
es.FeedbackEmail = model.NewPointer("some-addr@test.com")
|
|
es.ReplyToAddress = model.NewPointer("some-addr@test.com")
|
|
es.ConnectionSecurity = model.NewPointer("")
|
|
es.SMTPUsername = model.NewPointer("")
|
|
es.EnableSMTPAuth = model.NewPointer(false)
|
|
es.SkipServerCertificateVerification = model.NewPointer(true)
|
|
es.SendEmailNotifications = model.NewPointer(false)
|
|
es.SMTPServerTimeout = model.NewPointer(15)
|
|
|
|
config := model.Config{
|
|
ServiceSettings: model.ServiceSettings{
|
|
SiteURL: model.NewPointer(""),
|
|
},
|
|
EmailSettings: es,
|
|
FileSettings: model.FileSettings{
|
|
DriverName: model.NewPointer(model.ImageDriverLocal),
|
|
Directory: model.NewPointer(dir),
|
|
},
|
|
}
|
|
|
|
t.Run("as system user", func(t *testing.T) {
|
|
resp, err := client.TestEmail(context.Background(), &config)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("as system admin", func(t *testing.T) {
|
|
resp, err := th.SystemAdminClient.TestEmail(context.Background(), &config)
|
|
CheckErrorID(t, err, "api.admin.test_email.missing_server")
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
inbucket_host := os.Getenv("CI_INBUCKET_HOST")
|
|
if inbucket_host == "" {
|
|
inbucket_host = "localhost"
|
|
}
|
|
|
|
inbucket_port := os.Getenv("CI_INBUCKET_SMTP_PORT")
|
|
if inbucket_port == "" {
|
|
inbucket_port = "10025"
|
|
}
|
|
|
|
*config.EmailSettings.SMTPServer = inbucket_host
|
|
*config.EmailSettings.SMTPPort = inbucket_port
|
|
resp, err = th.SystemAdminClient.TestEmail(context.Background(), &config)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
})
|
|
|
|
t.Run("as restricted system admin", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
|
|
|
resp, err := th.SystemAdminClient.TestEmail(context.Background(), &config)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("empty email settings", func(t *testing.T) {
|
|
config.EmailSettings = model.EmailSettings{}
|
|
resp, err := th.SystemAdminClient.TestEmail(context.Background(), &config)
|
|
require.Error(t, err)
|
|
CheckErrorID(t, err, "api.file.test_connection_email_settings_nil.app_error")
|
|
CheckBadRequestStatus(t, resp)
|
|
})
|
|
}
|
|
|
|
func TestGenerateSupportPacket(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
|
|
th.LoginSystemManager(t)
|
|
|
|
t.Run("system admin and local client can generate Support Packet", func(t *testing.T) {
|
|
l := model.NewTestLicense()
|
|
th.App.Srv().SetLicense(l)
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
|
|
file, filename, resp, err := th.SystemAdminClient.GenerateSupportPacket(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
assert.Contains(t, filename, "mm_support_packet_My_awesome_Company_")
|
|
|
|
d, err := io.ReadAll(file)
|
|
require.NoError(t, err)
|
|
assert.NotZero(t, len(d))
|
|
|
|
// Verify that the Cache-Control header is set to prevent caching
|
|
assert.Equal(t, "no-cache, no-store, must-revalidate", resp.Header.Get("Cache-Control"))
|
|
})
|
|
})
|
|
|
|
t.Run("Using system admin and local client but with RestrictSystemAdmin true", func(t *testing.T) {
|
|
originalRestrictSystemAdminVal := *th.App.Config().ExperimentalSettings.RestrictSystemAdmin
|
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
|
defer func() {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.ExperimentalSettings.RestrictSystemAdmin = originalRestrictSystemAdminVal
|
|
})
|
|
}()
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
|
|
_, _, resp, err := th.SystemAdminClient.GenerateSupportPacket(context.Background())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
})
|
|
|
|
t.Run("As a system role, not system admin", func(t *testing.T) {
|
|
_, _, resp, err := th.SystemManagerClient.GenerateSupportPacket(context.Background())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("As a Regular User", func(t *testing.T) {
|
|
_, _, resp, err := th.Client.GenerateSupportPacket(context.Background())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("Server with no License", func(t *testing.T) {
|
|
_, err := th.SystemAdminClient.RemoveLicenseFile(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
_, _, resp, err := th.SystemAdminClient.GenerateSupportPacket(context.Background())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
}
|
|
|
|
func TestSupportPacketFileName(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
tests := map[string]struct {
|
|
now time.Time
|
|
customerName string
|
|
expected string
|
|
}{
|
|
"standard case": {
|
|
now: time.Date(2023, 11, 12, 13, 14, 15, 0, time.UTC),
|
|
customerName: "TestCustomer",
|
|
expected: "mm_support_packet_TestCustomer_2023-11-12T13-14.zip",
|
|
},
|
|
"customer name with special characters": {
|
|
now: time.Date(2023, 11, 12, 13, 14, 15, 0, time.UTC),
|
|
customerName: "Test/Customer:Name",
|
|
expected: "mm_support_packet_Test_Customer_Name_2023-11-12T13-14.zip",
|
|
},
|
|
"empty customer name": {
|
|
now: time.Date(2023, 10, 10, 10, 10, 10, 0, time.UTC),
|
|
customerName: "",
|
|
expected: "mm_support_packet__2023-10-10T10-10.zip",
|
|
},
|
|
}
|
|
|
|
for name, tt := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
result := supportPacketFileName(tt.now, tt.customerName)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSiteURLTest(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
client := th.Client
|
|
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if strings.HasSuffix(r.URL.Path, "/valid/api/v4/system/ping") {
|
|
w.WriteHeader(200)
|
|
} else {
|
|
w.WriteHeader(400)
|
|
}
|
|
}))
|
|
defer ts.Close()
|
|
|
|
validSiteURL := ts.URL + "/valid"
|
|
invalidSiteURL := ts.URL + "/invalid"
|
|
|
|
t.Run("as system admin", func(t *testing.T) {
|
|
resp, err := th.SystemAdminClient.TestSiteURL(context.Background(), "")
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
resp, err = th.SystemAdminClient.TestSiteURL(context.Background(), invalidSiteURL)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
resp, err = th.SystemAdminClient.TestSiteURL(context.Background(), validSiteURL)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
})
|
|
|
|
t.Run("as system user", func(t *testing.T) {
|
|
resp, err := client.TestSiteURL(context.Background(), validSiteURL)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("as restricted system admin", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
|
|
|
resp, err := client.TestSiteURL(context.Background(), validSiteURL)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
}
|
|
|
|
func TestDatabaseRecycle(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
client := th.Client
|
|
|
|
t.Run("as system user", func(t *testing.T) {
|
|
resp, err := client.DatabaseRecycle(context.Background())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("as system admin", func(t *testing.T) {
|
|
_, err := th.SystemAdminClient.DatabaseRecycle(context.Background())
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("as restricted system admin", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
|
|
|
resp, err := th.SystemAdminClient.DatabaseRecycle(context.Background())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
}
|
|
|
|
func TestInvalidateCaches(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
client := th.Client
|
|
|
|
t.Run("as system user", func(t *testing.T) {
|
|
resp, err := client.InvalidateCaches(context.Background())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("as system admin", func(t *testing.T) {
|
|
_, err := th.SystemAdminClient.InvalidateCaches(context.Background())
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("as restricted system admin", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
|
|
|
resp, err := th.SystemAdminClient.InvalidateCaches(context.Background())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
}
|
|
|
|
func TestGetLogs(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
|
|
for i := range 20 {
|
|
th.TestLogger.Info(strconv.Itoa(i))
|
|
}
|
|
|
|
err := th.TestLogger.Flush()
|
|
require.NoError(t, err, "failed to flush log")
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
|
|
logs, _, err2 := c.GetLogs(context.Background(), 0, 10)
|
|
require.NoError(t, err2)
|
|
require.Len(t, logs, 10)
|
|
|
|
for i := 10; i < 20; i++ {
|
|
assert.Containsf(t, logs[i-10], fmt.Sprintf(`"msg":"%d"`, i), "Log line doesn't contain correct message")
|
|
}
|
|
|
|
logs, _, err = c.GetLogs(context.Background(), 1, 10)
|
|
require.NoError(t, err)
|
|
require.Len(t, logs, 10)
|
|
|
|
logs, _, err = c.GetLogs(context.Background(), -1, -1)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, logs, "should not be empty")
|
|
})
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
|
_, resp, err2 := th.Client.GetLogs(context.Background(), 0, 10)
|
|
require.Error(t, err2)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
_, resp, err := th.Client.GetLogs(context.Background(), 0, 10)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
_, err = th.Client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
_, resp, err = th.Client.GetLogs(context.Background(), 0, 10)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
}
|
|
|
|
func TestDownloadLogs(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
|
|
for i := range 20 {
|
|
th.TestLogger.Info(strconv.Itoa(i))
|
|
}
|
|
err := th.TestLogger.Flush()
|
|
require.NoError(t, err, "failed to flush log")
|
|
|
|
t.Run("Download Logs as system admin", func(t *testing.T) {
|
|
resData, resp, err2 := th.SystemAdminClient.DownloadLogs(context.Background())
|
|
require.NoError(t, err2)
|
|
|
|
require.Equal(t, "text/plain", resp.Header.Get("Content-Type"))
|
|
require.Contains(t, resp.Header.Get("Content-Disposition"), "attachment;filename=\"mattermost.log\"")
|
|
|
|
bodyString := string(resData)
|
|
for i := range 20 {
|
|
assert.Contains(t, bodyString, fmt.Sprintf(`"msg":"%d"`, i))
|
|
}
|
|
})
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ExperimentalSettings.RestrictSystemAdmin = true })
|
|
_, resp, err2 := th.Client.DownloadLogs(context.Background())
|
|
require.Error(t, err2)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
_, resp, err := th.Client.DownloadLogs(context.Background())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
_, err = th.Client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
_, resp, err = th.Client.DownloadLogs(context.Background())
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
}
|
|
|
|
func TestPostLog(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
client := th.Client
|
|
|
|
enableDev := *th.App.Config().ServiceSettings.EnableDeveloper
|
|
defer func() {
|
|
*th.App.Config().ServiceSettings.EnableDeveloper = enableDev
|
|
}()
|
|
*th.App.Config().ServiceSettings.EnableDeveloper = true
|
|
|
|
message := make(map[string]string)
|
|
message["level"] = "ERROR"
|
|
message["message"] = "this is a test"
|
|
|
|
_, _, err := client.PostLog(context.Background(), message)
|
|
require.NoError(t, err)
|
|
|
|
*th.App.Config().ServiceSettings.EnableDeveloper = false
|
|
|
|
_, _, err = client.PostLog(context.Background(), message)
|
|
require.NoError(t, err)
|
|
|
|
*th.App.Config().ServiceSettings.EnableDeveloper = true
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = client.PostLog(context.Background(), message)
|
|
require.NoError(t, err)
|
|
|
|
*th.App.Config().ServiceSettings.EnableDeveloper = false
|
|
|
|
_, resp, err := client.PostLog(context.Background(), message)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
|
|
logMessage, _, err := th.SystemAdminClient.PostLog(context.Background(), message)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, logMessage, "should return the log message")
|
|
}
|
|
|
|
func TestGetAnalyticsOld(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
client := th.Client
|
|
|
|
rows, resp, err := client.GetAnalyticsOld(context.Background(), "", "")
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
require.Nil(t, rows, "should be nil")
|
|
rows, _, err = th.SystemAdminClient.GetAnalyticsOld(context.Background(), "", "")
|
|
require.NoError(t, err)
|
|
|
|
found := false
|
|
found2 := false
|
|
for _, row := range rows {
|
|
if row.Name == "unique_user_count" {
|
|
found = true
|
|
} else if row.Name == "inactive_user_count" {
|
|
found2 = true
|
|
assert.True(t, row.Value >= 0)
|
|
}
|
|
}
|
|
|
|
assert.True(t, found, "should return unique user count")
|
|
assert.True(t, found2, "should return inactive user count")
|
|
|
|
_, _, err = th.SystemAdminClient.GetAnalyticsOld(context.Background(), "post_counts_day", "")
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = th.SystemAdminClient.GetAnalyticsOld(context.Background(), "user_counts_with_posts_day", "")
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = th.SystemAdminClient.GetAnalyticsOld(context.Background(), "extra_counts", "")
|
|
require.NoError(t, err)
|
|
|
|
rows, _, err = th.SystemAdminClient.GetAnalyticsOld(context.Background(), "", th.BasicTeam.Id)
|
|
require.NoError(t, err)
|
|
|
|
for _, row := range rows {
|
|
if row.Name == "inactive_user_count" {
|
|
assert.Equal(t, float64(-1), row.Value, "inactive user count should be -1 when team specified")
|
|
}
|
|
}
|
|
|
|
rows2, _, err := th.SystemAdminClient.GetAnalyticsOld(context.Background(), "standard", "")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "total_websocket_connections", rows2[5].Name)
|
|
assert.Equal(t, float64(0), rows2[5].Value)
|
|
|
|
WebSocketClient := th.CreateConnectedWebSocketClient(t)
|
|
rows2, _, err = th.SystemAdminClient.GetAnalyticsOld(context.Background(), "standard", "")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "total_websocket_connections", rows2[5].Name)
|
|
assert.Equal(t, float64(1), rows2[5].Value)
|
|
WebSocketClient.Close()
|
|
|
|
// Give it a second for internal webhub counters to be updated after the client disconnects.
|
|
// Test can be flaky otherwise.
|
|
time.Sleep(time.Second)
|
|
|
|
rows2, _, err = th.SystemAdminClient.GetAnalyticsOld(context.Background(), "standard", "")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "total_websocket_connections", rows2[5].Name)
|
|
assert.Equal(t, float64(0), rows2[5].Value)
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
_, resp, err = client.GetAnalyticsOld(context.Background(), "", th.BasicTeam.Id)
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
}
|
|
|
|
func TestS3TestConnection(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
client := th.Client
|
|
|
|
s3Host := os.Getenv("CI_MINIO_HOST")
|
|
if s3Host == "" {
|
|
s3Host = "localhost"
|
|
}
|
|
|
|
s3Port := os.Getenv("CI_MINIO_PORT")
|
|
if s3Port == "" {
|
|
s3Port = "9000"
|
|
}
|
|
|
|
s3Endpoint := fmt.Sprintf("%s:%s", s3Host, s3Port)
|
|
|
|
fs := model.FileSettings{}
|
|
fs.SetDefaults(false)
|
|
|
|
fs.DriverName = model.NewPointer(model.ImageDriverS3)
|
|
fs.AmazonS3AccessKeyId = model.NewPointer(model.MinioAccessKey)
|
|
fs.AmazonS3SecretAccessKey = model.NewPointer(model.MinioSecretKey)
|
|
fs.AmazonS3Bucket = model.NewPointer("")
|
|
fs.AmazonS3Endpoint = model.NewPointer(s3Endpoint)
|
|
fs.AmazonS3Region = model.NewPointer("")
|
|
fs.AmazonS3PathPrefix = model.NewPointer("")
|
|
fs.AmazonS3SSL = model.NewPointer(false)
|
|
|
|
config := model.Config{
|
|
FileSettings: fs,
|
|
}
|
|
|
|
t.Run("as system user", func(t *testing.T) {
|
|
resp, err := client.TestS3Connection(context.Background(), &config)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("as system admin", func(t *testing.T) {
|
|
resp, err := th.SystemAdminClient.TestS3Connection(context.Background(), &config)
|
|
CheckBadRequestStatus(t, resp)
|
|
CheckErrorMessage(t, err, "S3 Bucket is required")
|
|
// If this fails, check the test configuration to ensure minio is setup with the
|
|
// `mattermost-test` bucket defined by model.MINIO_BUCKET.
|
|
*config.FileSettings.AmazonS3Bucket = model.MinioBucket
|
|
config.FileSettings.AmazonS3PathPrefix = model.NewPointer("")
|
|
*config.FileSettings.AmazonS3Region = "us-east-1"
|
|
resp, err = th.SystemAdminClient.TestS3Connection(context.Background(), &config)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
|
|
config.FileSettings.AmazonS3Region = model.NewPointer("")
|
|
resp, err = th.SystemAdminClient.TestS3Connection(context.Background(), &config)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
|
|
config.FileSettings.AmazonS3Bucket = model.NewPointer("Wrong_bucket")
|
|
resp, err = th.SystemAdminClient.TestS3Connection(context.Background(), &config)
|
|
CheckInternalErrorStatus(t, resp)
|
|
CheckErrorID(t, err, "api.file.test_connection_s3_bucket_does_not_exist.app_error")
|
|
|
|
*config.FileSettings.AmazonS3Bucket = "shouldnotcreatenewbucket"
|
|
resp, err = th.SystemAdminClient.TestS3Connection(context.Background(), &config)
|
|
CheckInternalErrorStatus(t, resp)
|
|
CheckErrorID(t, err, "api.file.test_connection_s3_bucket_does_not_exist.app_error")
|
|
})
|
|
|
|
t.Run("with incorrect credentials", func(t *testing.T) {
|
|
configCopy := config
|
|
*configCopy.FileSettings.AmazonS3AccessKeyId = "invalidaccesskey"
|
|
resp, err := th.SystemAdminClient.TestS3Connection(context.Background(), &configCopy)
|
|
CheckInternalErrorStatus(t, resp)
|
|
CheckErrorID(t, err, "api.file.test_connection_s3_auth.app_error")
|
|
})
|
|
|
|
t.Run("empty file settings", func(t *testing.T) {
|
|
config.FileSettings = model.FileSettings{}
|
|
resp, err := th.SystemAdminClient.TestS3Connection(context.Background(), &config)
|
|
require.Error(t, err)
|
|
CheckErrorID(t, err, "api.file.test_connection_s3_settings_nil.app_error")
|
|
CheckBadRequestStatus(t, resp)
|
|
})
|
|
}
|
|
|
|
func TestSupportedTimezones(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
client := th.Client
|
|
|
|
supportedTimezonesFromConfig := th.App.Timezones().GetSupported()
|
|
supportedTimezones, _, err := client.GetSupportedTimezone(context.Background())
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, supportedTimezonesFromConfig, supportedTimezones)
|
|
}
|
|
|
|
func TestRedirectLocation(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
expected := "https://mattermost.com/wp-content/themes/mattermostv2/img/logo-light.svg"
|
|
|
|
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
res.Header().Set("Location", expected)
|
|
res.WriteHeader(http.StatusFound)
|
|
_, err := res.Write([]byte("body"))
|
|
require.NoError(t, err)
|
|
}))
|
|
defer func() { testServer.Close() }()
|
|
|
|
mockBitlyLink := testServer.URL
|
|
|
|
th := Setup(t)
|
|
client := th.Client
|
|
enableLinkPreviews := *th.App.Config().ServiceSettings.EnableLinkPreviews
|
|
defer func() {
|
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableLinkPreviews = enableLinkPreviews })
|
|
}()
|
|
|
|
*th.App.Config().ServiceSettings.EnableLinkPreviews = true
|
|
*th.App.Config().ServiceSettings.AllowedUntrustedInternalConnections = "127.0.0.1"
|
|
|
|
_, _, err := th.SystemAdminClient.GetRedirectLocation(context.Background(), "https://mattermost.com/", "")
|
|
require.NoError(t, err)
|
|
|
|
_, resp, err := th.SystemAdminClient.GetRedirectLocation(context.Background(), "", "")
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
|
|
actual, _, err := th.SystemAdminClient.GetRedirectLocation(context.Background(), mockBitlyLink, "")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, expected, actual)
|
|
|
|
// Check cached value
|
|
actual, _, err = th.SystemAdminClient.GetRedirectLocation(context.Background(), mockBitlyLink, "")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, expected, actual)
|
|
|
|
*th.App.Config().ServiceSettings.EnableLinkPreviews = false
|
|
actual, _, err = th.SystemAdminClient.GetRedirectLocation(context.Background(), "https://mattermost.com/", "")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, actual, "")
|
|
|
|
actual, _, err = th.SystemAdminClient.GetRedirectLocation(context.Background(), "", "")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, actual, "")
|
|
|
|
actual, _, err = th.SystemAdminClient.GetRedirectLocation(context.Background(), mockBitlyLink, "")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, actual, "")
|
|
|
|
_, err = client.Logout(context.Background())
|
|
require.NoError(t, err)
|
|
|
|
_, resp, err = client.GetRedirectLocation(context.Background(), "", "")
|
|
require.Error(t, err)
|
|
CheckUnauthorizedStatus(t, resp)
|
|
|
|
// Check that too-long redirect locations are ignored
|
|
*th.App.Config().ServiceSettings.EnableLinkPreviews = true
|
|
urlPrefix := "https://example.co"
|
|
almostTooLongUrl := urlPrefix + strings.Repeat("a", 2100-len(urlPrefix))
|
|
testServer2 := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
res.Header().Set("Location", almostTooLongUrl)
|
|
res.WriteHeader(http.StatusFound)
|
|
_, err = res.Write([]byte("body"))
|
|
require.NoError(t, err)
|
|
}))
|
|
defer func() { testServer2.Close() }()
|
|
|
|
actual, _, err = th.SystemAdminClient.GetRedirectLocation(context.Background(), testServer2.URL, "")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, almostTooLongUrl, actual)
|
|
|
|
tooLongUrl := urlPrefix + strings.Repeat("a", 2101-len(urlPrefix))
|
|
testServer3 := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
res.Header().Set("Location", tooLongUrl)
|
|
res.WriteHeader(http.StatusFound)
|
|
_, err = res.Write([]byte("body"))
|
|
require.NoError(t, err)
|
|
}))
|
|
defer func() { testServer3.Close() }()
|
|
|
|
actual, _, err = th.SystemAdminClient.GetRedirectLocation(context.Background(), testServer3.URL, "")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "", actual)
|
|
}
|
|
|
|
func TestSetServerBusy(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
|
|
const secs = 30
|
|
|
|
t.Run("as system user", func(t *testing.T) {
|
|
resp, err := th.Client.SetServerBusy(context.Background(), secs)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
require.False(t, th.App.Srv().Platform().Busy.IsBusy(), "server should not be marked busy")
|
|
})
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
|
|
_, err := c.SetServerBusy(context.Background(), secs)
|
|
require.NoError(t, err)
|
|
require.True(t, th.App.Srv().Platform().Busy.IsBusy(), "server should be marked busy")
|
|
}, "as system admin")
|
|
}
|
|
|
|
func TestSetServerBusyInvalidParam(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
|
|
params := []int{-1, 0, MaxServerBusySeconds + 1}
|
|
for _, p := range params {
|
|
resp, err := c.SetServerBusy(context.Background(), p)
|
|
require.Error(t, err)
|
|
CheckBadRequestStatus(t, resp)
|
|
require.False(t, th.App.Srv().Platform().Busy.IsBusy(), "server should not be marked busy due to invalid param ", p)
|
|
}
|
|
}, "as system admin, invalid param")
|
|
}
|
|
|
|
func TestClearServerBusy(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
|
|
th.App.Srv().Platform().Busy.Set(time.Second * 30)
|
|
t.Run("as system user", func(t *testing.T) {
|
|
resp, err := th.Client.ClearServerBusy(context.Background())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
require.True(t, th.App.Srv().Platform().Busy.IsBusy(), "server should be marked busy")
|
|
})
|
|
|
|
th.App.Srv().Platform().Busy.Set(time.Second * 30)
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
|
|
_, err := c.ClearServerBusy(context.Background())
|
|
require.NoError(t, err)
|
|
require.False(t, th.App.Srv().Platform().Busy.IsBusy(), "server should not be marked busy")
|
|
}, "as system admin")
|
|
}
|
|
|
|
func TestGetServerBusy(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
|
|
th.App.Srv().Platform().Busy.Set(time.Second * 30)
|
|
|
|
t.Run("as system user", func(t *testing.T) {
|
|
_, resp, err := th.Client.GetServerBusy(context.Background())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
|
|
sbs, _, err := c.GetServerBusy(context.Background())
|
|
expires := time.Unix(sbs.Expires, 0)
|
|
require.NoError(t, err)
|
|
require.Greater(t, expires.Unix(), time.Now().Unix())
|
|
}, "as system admin")
|
|
}
|
|
|
|
func TestServerBusy503(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
|
|
th.App.Srv().Platform().Busy.Set(time.Second * 30)
|
|
|
|
t.Run("search users while busy", func(t *testing.T) {
|
|
us := &model.UserSearch{Term: "test"}
|
|
_, resp, err := th.SystemAdminClient.SearchUsers(context.Background(), us)
|
|
require.Error(t, err)
|
|
CheckServiceUnavailableStatus(t, resp)
|
|
})
|
|
|
|
t.Run("search teams while busy", func(t *testing.T) {
|
|
ts := &model.TeamSearch{}
|
|
_, resp, err := th.SystemAdminClient.SearchTeams(context.Background(), ts)
|
|
require.Error(t, err)
|
|
CheckServiceUnavailableStatus(t, resp)
|
|
})
|
|
|
|
t.Run("search channels while busy", func(t *testing.T) {
|
|
cs := &model.ChannelSearch{}
|
|
_, resp, err := th.SystemAdminClient.SearchChannels(context.Background(), "foo", cs)
|
|
require.Error(t, err)
|
|
CheckServiceUnavailableStatus(t, resp)
|
|
})
|
|
|
|
th.App.Srv().Platform().Busy.Clear()
|
|
|
|
t.Run("search users while not busy", func(t *testing.T) {
|
|
us := &model.UserSearch{Term: "test"}
|
|
_, _, err := th.SystemAdminClient.SearchUsers(context.Background(), us)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestPushNotificationAck(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
api, err := Init(th.Server)
|
|
require.NoError(t, err)
|
|
session, appErr := th.App.GetSession(th.Client.AuthToken)
|
|
require.Nil(t, appErr)
|
|
|
|
t.Run("should return error when the ack body is not passed", func(t *testing.T) {
|
|
handler := api.APIHandler(pushNotificationAck)
|
|
resp := httptest.NewRecorder()
|
|
req := httptest.NewRequest("POST", "/api/v4/notifications/ack", nil)
|
|
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
|
|
|
handler.ServeHTTP(resp, req)
|
|
assert.Equal(t, http.StatusBadRequest, resp.Code)
|
|
assert.NotNil(t, resp.Body)
|
|
})
|
|
|
|
t.Run("should return error when the ack post is not authorized for the user", func(t *testing.T) {
|
|
privateChannel := th.CreateChannelWithClient(t, th.SystemAdminClient, model.ChannelTypePrivate)
|
|
privatePost := th.CreatePostWithClient(t, th.SystemAdminClient, privateChannel)
|
|
|
|
handler := api.APIHandler(pushNotificationAck)
|
|
resp := httptest.NewRecorder()
|
|
req := httptest.NewRequest("POST", "/api/v4/notifications/ack", nil)
|
|
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
|
req.Body = io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"id":"123", "is_id_loaded":true, "post_id":"%s", "type": "%s"}`, privatePost.Id, model.PushTypeMessage)))
|
|
|
|
handler.ServeHTTP(resp, req)
|
|
assert.Equal(t, http.StatusForbidden, resp.Code)
|
|
assert.NotNil(t, resp.Body)
|
|
})
|
|
|
|
ttcc := []struct {
|
|
name string
|
|
propValue string
|
|
platform string
|
|
expectedValue string
|
|
}{
|
|
{
|
|
name: "should set session prop device notification disabled to false if an ack is sent from iOS",
|
|
propValue: "true",
|
|
platform: "ios",
|
|
expectedValue: "false",
|
|
},
|
|
{
|
|
name: "no change if empty",
|
|
propValue: "",
|
|
platform: "ios",
|
|
expectedValue: "",
|
|
},
|
|
{
|
|
name: "no change if false",
|
|
propValue: "false",
|
|
platform: "ios",
|
|
expectedValue: "false",
|
|
},
|
|
{
|
|
name: "no change on Android",
|
|
propValue: "true",
|
|
platform: "android",
|
|
expectedValue: "true",
|
|
},
|
|
}
|
|
for _, tc := range ttcc {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
defer func() {
|
|
session.AddProp(model.SessionPropDeviceNotificationDisabled, "")
|
|
err = th.Server.Store().Session().UpdateProps(session)
|
|
require.NoError(t, err)
|
|
th.App.ClearSessionCacheForUser(session.UserId)
|
|
}()
|
|
|
|
session.AddProp(model.SessionPropDeviceNotificationDisabled, tc.propValue)
|
|
err := th.Server.Store().Session().UpdateProps(session)
|
|
th.App.ClearSessionCacheForUser(session.UserId)
|
|
assert.NoError(t, err)
|
|
|
|
handler := api.APIHandler(pushNotificationAck)
|
|
resp := httptest.NewRecorder()
|
|
req := httptest.NewRequest("POST", "/api/v4/notifications/ack", nil)
|
|
req.Header.Set(model.HeaderAuth, "Bearer "+session.Token)
|
|
req.Body = io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"id":"123", "is_id_loaded":true, "platform": "%s", "post_id":"%s", "type": "%s"}`, tc.platform, th.BasicPost.Id, model.PushTypeMessage)))
|
|
|
|
handler.ServeHTTP(resp, req)
|
|
updatedSession, _ := th.App.GetSession(th.Client.AuthToken)
|
|
assert.Equal(t, tc.expectedValue, updatedSession.Props[model.SessionPropDeviceNotificationDisabled])
|
|
storeSession, _ := th.Server.Store().Session().Get(th.Context, session.Id)
|
|
assert.Equal(t, tc.expectedValue, storeSession.Props[model.SessionPropDeviceNotificationDisabled])
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompleteOnboarding(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
|
|
path, _ := fileutils.FindDir("tests")
|
|
signatureFilename := "testplugin2.tar.gz.sig"
|
|
signatureFileReader, err := os.Open(filepath.Join(path, signatureFilename))
|
|
require.NoError(t, err)
|
|
sigFile, err := io.ReadAll(signatureFileReader)
|
|
require.NoError(t, err)
|
|
pluginSignature := base64.StdEncoding.EncodeToString(sigFile)
|
|
|
|
tarData, err := os.ReadFile(filepath.Join(path, "testplugin2.tar.gz"))
|
|
require.NoError(t, err)
|
|
pluginServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
res.WriteHeader(http.StatusOK)
|
|
_, err = res.Write(tarData)
|
|
require.NoError(t, err)
|
|
}))
|
|
defer pluginServer.Close()
|
|
|
|
samplePlugins := []*model.MarketplacePlugin{{
|
|
BaseMarketplacePlugin: &model.BaseMarketplacePlugin{
|
|
HomepageURL: "https://example.com/mattermost/mattermost-plugin-nps",
|
|
IconData: "https://example.com/icon.svg",
|
|
DownloadURL: pluginServer.URL,
|
|
Manifest: &model.Manifest{
|
|
Id: "testplugin2",
|
|
Name: "testplugin2",
|
|
Description: "a second plugin",
|
|
Version: "1.2.3",
|
|
MinServerVersion: "",
|
|
},
|
|
Signature: pluginSignature,
|
|
},
|
|
InstalledVersion: "",
|
|
}}
|
|
|
|
marketplaceServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
res.WriteHeader(http.StatusOK)
|
|
var data []byte
|
|
data, err = json.Marshal(samplePlugins)
|
|
require.NoError(t, err)
|
|
_, err = res.Write(data)
|
|
require.NoError(t, err)
|
|
}))
|
|
defer marketplaceServer.Close()
|
|
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.Enable = true
|
|
*cfg.PluginSettings.EnableMarketplace = false
|
|
*cfg.PluginSettings.EnableRemoteMarketplace = true
|
|
*cfg.PluginSettings.MarketplaceURL = marketplaceServer.URL
|
|
*cfg.PluginSettings.AllowInsecureDownloadURL = true
|
|
})
|
|
|
|
key, err := os.Open(filepath.Join(path, "development-private-key.asc"))
|
|
require.NoError(t, err)
|
|
appErr := th.App.AddPublicKey("pub_key", key)
|
|
require.Nil(t, appErr)
|
|
|
|
t.Cleanup(func() {
|
|
appErr = th.App.DeletePublicKey("pub_key")
|
|
require.Nil(t, appErr)
|
|
})
|
|
|
|
req := &model.CompleteOnboardingRequest{
|
|
InstallPlugins: []string{"testplugin2"},
|
|
Organization: "my-org",
|
|
}
|
|
|
|
t.Run("as a regular user", func(t *testing.T) {
|
|
resp, err := th.Client.CompleteOnboarding(context.Background(), req)
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("as a system admin", func(t *testing.T) {
|
|
resp, err := th.SystemAdminClient.CompleteOnboarding(context.Background(), req)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
|
|
t.Cleanup(func() {
|
|
resp, err = th.SystemAdminClient.RemovePlugin(context.Background(), "testplugin2")
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
})
|
|
|
|
received := make(chan struct{})
|
|
|
|
go func() {
|
|
for {
|
|
installedPlugins, resp, err := th.SystemAdminClient.GetPlugins(context.Background())
|
|
if err != nil || resp.StatusCode != http.StatusOK {
|
|
time.Sleep(500 * time.Millisecond)
|
|
continue
|
|
}
|
|
|
|
for _, p := range installedPlugins.Active {
|
|
if p.Id == "testplugin2" {
|
|
received <- struct{}{}
|
|
return
|
|
}
|
|
}
|
|
time.Sleep(500 * time.Millisecond)
|
|
}
|
|
}()
|
|
|
|
select {
|
|
case <-received:
|
|
break
|
|
case <-time.After(15 * time.Second):
|
|
require.Fail(t, "timed out waiting testplugin2 to be installed and enabled ")
|
|
}
|
|
})
|
|
|
|
t.Run("as a system admin when plugins are disabled", func(t *testing.T) {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.Enable = false
|
|
})
|
|
|
|
t.Cleanup(func() {
|
|
th.App.UpdateConfig(func(cfg *model.Config) {
|
|
*cfg.PluginSettings.Enable = true
|
|
})
|
|
})
|
|
|
|
resp, err := th.SystemAdminClient.CompleteOnboarding(context.Background(), req)
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
})
|
|
}
|
|
|
|
func TestGetAppliedSchemaMigrations(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
|
|
t.Run("as a regular user", func(t *testing.T) {
|
|
_, resp, err := th.Client.GetAppliedSchemaMigrations(context.Background())
|
|
require.Error(t, err)
|
|
CheckForbiddenStatus(t, resp)
|
|
})
|
|
|
|
t.Run("as a system manager role", func(t *testing.T) {
|
|
_, appErr := th.App.UpdateUserRoles(th.Context, th.BasicUser2.Id, model.SystemManagerRoleId, false)
|
|
require.Nil(t, appErr)
|
|
th.LoginBasic2(t)
|
|
|
|
_, resp, err := th.Client.GetAppliedSchemaMigrations(context.Background())
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
})
|
|
|
|
th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
|
|
_, resp, err := c.GetAppliedSchemaMigrations(context.Background())
|
|
require.NoError(t, err)
|
|
CheckOKStatus(t, resp)
|
|
})
|
|
}
|
|
|
|
func TestCheckHasNilFields(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
t.Run("check if the empty struct has nil fields", func(t *testing.T) {
|
|
var s model.FileSettings
|
|
res := checkHasNilFields(&s)
|
|
require.True(t, res)
|
|
})
|
|
|
|
t.Run("check if the struct has any nil fields", func(t *testing.T) {
|
|
s := model.FileSettings{
|
|
DriverName: model.NewPointer(model.ImageDriverLocal),
|
|
}
|
|
res := checkHasNilFields(&s)
|
|
require.True(t, res)
|
|
})
|
|
|
|
t.Run("struct has all fields set", func(t *testing.T) {
|
|
var s model.FileSettings
|
|
s.SetDefaults(false)
|
|
res := checkHasNilFields(&s)
|
|
require.False(t, res)
|
|
})
|
|
|
|
t.Run("embedded struct, with nil fields", func(t *testing.T) {
|
|
type myStr struct {
|
|
Name string
|
|
Surname *string
|
|
}
|
|
s := myStr{}
|
|
res := checkHasNilFields(&s)
|
|
require.True(t, res)
|
|
})
|
|
}
|