Files
team/server/channels/app/support_packet_test.go
fa36e8d0c3 [MM-64485] Remove separate notification log file (#33473)
- Remove NotificationLogSettings configuration entirely
- Add new notification-specific log levels (NotificationError, NotificationWarn, NotificationInfo, NotificationDebug, NotificationTrace)
- Consolidate all notification logs into standard mattermost.log file
- Update all notification logging code to use new multi-level logging (MlvlNotification*)
- Remove notification logger infrastructure and support packet integration
- Update test configurations and remove deprecated functionality tests
- Add comprehensive tests for new notification log levels

This change simplifies log analysis by unifying all application logging while maintaining flexibility through Advanced Logging configuration for administrators who need separate notification logs.

---------

Co-authored-by: Hanzo Dev <[email protected]>
2025-08-20 10:17:45 +02:00

778 lines
24 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"bytes"
"encoding/json"
"errors"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
"github.com/mattermost/mattermost/server/public/model"
smocks "github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
"github.com/mattermost/mattermost/server/v8/channels/utils/fileutils"
"github.com/mattermost/mattermost/server/v8/config"
)
func TestGenerateSupportPacket(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
defer th.TearDown()
err := th.App.SetPhase2PermissionsMigrationStatus(true)
require.NoError(t, err)
dir, err := os.MkdirTemp("", "")
require.NoError(t, err)
t.Cleanup(func() {
err = os.RemoveAll(dir)
assert.NoError(t, err)
})
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.LogSettings.FileLocation = dir
})
logLocation := config.GetLogFileLocation(dir)
genMockLogFiles := func() {
d1 := []byte("hello\ngo\n")
genErr := os.WriteFile(logLocation, d1, 0777)
require.NoError(t, genErr)
}
genMockLogFiles()
getFileNames := func(t *testing.T, fileDatas []model.FileData) []string {
var rFileNames []string
for _, fileData := range fileDatas {
require.NotNil(t, fileData)
assert.Positive(t, len(fileData.Body))
rFileNames = append(rFileNames, fileData.Filename)
}
return rFileNames
}
expectedFileNames := []string{
"metadata.yaml",
"stats.yaml",
"jobs.yaml",
"permissions.yaml",
"plugins.json",
"sanitized_config.json",
"diagnostics.yaml",
"cpu.prof",
"heap.prof",
"goroutines",
}
// database_schema.yaml is only generated for Postgres
if *th.App.Config().SqlSettings.DriverName == model.DatabaseDriverPostgres {
expectedFileNames = append(expectedFileNames, "database_schema.yaml")
}
expectedFileNamesWithLogs := append(expectedFileNames, "mattermost.log")
t.Run("generate Support Packet with logs", func(t *testing.T) {
fileDatas := th.App.GenerateSupportPacket(th.Context, &model.SupportPacketOptions{
IncludeLogs: true,
})
rFileNames := getFileNames(t, fileDatas)
assert.ElementsMatch(t, expectedFileNamesWithLogs, rFileNames)
})
t.Run("generate Support Packet without logs", func(t *testing.T) {
fileDatas := th.App.GenerateSupportPacket(th.Context, &model.SupportPacketOptions{
IncludeLogs: false,
})
rFileNames := getFileNames(t, fileDatas)
assert.ElementsMatch(t, expectedFileNames, rFileNames)
})
t.Run("remove the log files and ensure that warning.txt file is generated", func(t *testing.T) {
err = os.Remove(logLocation)
require.NoError(t, err)
t.Cleanup(genMockLogFiles)
fileDatas := th.App.GenerateSupportPacket(th.Context, &model.SupportPacketOptions{
IncludeLogs: true,
})
rFileNames := getFileNames(t, fileDatas)
assert.ElementsMatch(t, append(expectedFileNames, "warning.txt"), rFileNames)
})
t.Run("steps that generated an error should still return file data", func(t *testing.T) {
mockStore := smocks.Store{}
// Mock the post store to trigger an error
ps := &smocks.PostStore{}
ps.On("AnalyticsPostCount", &model.PostCountOptions{}).Return(int64(0), errors.New("all broken"))
ps.On("ClearCaches")
mockStore.On("Post").Return(ps)
mockStore.On("User").Return(th.App.Srv().Store().User())
mockStore.On("Channel").Return(th.App.Srv().Store().Channel())
mockStore.On("Post").Return(th.App.Srv().Store().Post())
mockStore.On("Team").Return(th.App.Srv().Store().Team())
mockStore.On("Job").Return(th.App.Srv().Store().Job())
mockStore.On("FileInfo").Return(th.App.Srv().Store().FileInfo())
mockStore.On("Webhook").Return(th.App.Srv().Store().Webhook())
mockStore.On("System").Return(th.App.Srv().Store().System())
mockStore.On("License").Return(th.App.Srv().Store().License())
mockStore.On("Command").Return(th.App.Srv().Store().Command())
mockStore.On("Role").Return(th.App.Srv().Store().Role())
mockStore.On("Scheme").Return(th.App.Srv().Store().Scheme())
mockStore.On("Close").Return(nil)
mockStore.On("GetDBSchemaVersion").Return(1, nil)
mockStore.On("GetDbVersion", false).Return("1.0.0", nil)
mockStore.On("TotalMasterDbConnections").Return(30)
mockStore.On("TotalReadDbConnections").Return(20)
mockStore.On("TotalSearchDbConnections").Return(10)
mockStore.On("GetSchemaDefinition").Return(&model.SupportPacketDatabaseSchema{
Tables: []model.DatabaseTable{},
}, nil)
oldStore := th.App.Srv().Store()
t.Cleanup(func() {
th.App.Srv().SetStore(oldStore)
})
th.App.Srv().SetStore(&mockStore)
fileDatas := th.App.GenerateSupportPacket(th.Context, &model.SupportPacketOptions{
IncludeLogs: false,
})
rFileNames := getFileNames(t, fileDatas)
assert.Contains(t, rFileNames, "warning.txt")
assert.Contains(t, rFileNames, "stats.yaml")
assert.ElementsMatch(t, append(expectedFileNames, "warning.txt"), rFileNames)
})
pluginID := "testplugin"
pluginCode := `
package main
import (
"github.com/mattermost/mattermost/server/public/plugin"
"github.com/mattermost/mattermost/server/public/model"
)
type TestPlugin struct {
plugin.MattermostPlugin
}
func (p *TestPlugin) GenerateSupportData(c *plugin.Context) ([]*model.FileData, error) {
return []*model.FileData{{
Filename: "testplugin/diagnostics.yaml",
Body: []byte("foo"),
}}, nil
}
func main() {
plugin.ClientMain(&TestPlugin{})
}`
t.Run("Support Packet always contains plugin data if the plugin doesn't define the support_packet prop", func(t *testing.T) {
pluginManifest := `{"id": "testplugin", "server": {"executable": "backend.exe"}}`
setupPluginAPITest(t, pluginCode, pluginManifest, pluginID, th.App, th.Context)
t.Cleanup(func() {
appErr := th.App.ch.RemovePlugin(pluginID)
require.Nil(t, appErr)
})
fileDatas := th.App.GenerateSupportPacket(th.Context, &model.SupportPacketOptions{
IncludeLogs: false,
})
rFileNames := getFileNames(t, fileDatas)
assert.ElementsMatch(t, append(expectedFileNames, "testplugin/diagnostics.yaml"), rFileNames)
})
t.Run("Support Packet contains plugin data if the plugin defines the support_packet prop and it gets queried", func(t *testing.T) {
pluginManifest := `{"id": "testplugin", "server": {"executable": "backend.exe"}, "props": {"support_packet": "some text"}}`
setupPluginAPITest(t, pluginCode, pluginManifest, pluginID, th.App, th.Context)
t.Cleanup(func() {
appErr := th.App.ch.RemovePlugin(pluginID)
require.Nil(t, appErr)
})
fileDatas := th.App.GenerateSupportPacket(th.Context, &model.SupportPacketOptions{
IncludeLogs: false,
PluginPackets: []string{pluginID},
})
rFileNames := getFileNames(t, fileDatas)
assert.ElementsMatch(t, append(expectedFileNames, "testplugin/diagnostics.yaml"), rFileNames)
})
t.Run("Support Packet doesn't contain plugin data if the plugin defines the support_packet prop and it doesn't get queried", func(t *testing.T) {
pluginManifest := `{"id": "testplugin", "server": {"executable": "backend.exe"}, "props": {"support_packet": "some text"}}`
setupPluginAPITest(t, pluginCode, pluginManifest, pluginID, th.App, th.Context)
t.Cleanup(func() {
appErr := th.App.ch.RemovePlugin(pluginID)
require.Nil(t, appErr)
})
fileDatas := th.App.GenerateSupportPacket(th.Context, &model.SupportPacketOptions{
IncludeLogs: false,
})
rFileNames := getFileNames(t, fileDatas)
assert.ElementsMatch(t, expectedFileNames, rFileNames)
})
t.Run("Plugin config values in the Support Packet are obfuscated, if the plugin marks them as secrets", func(t *testing.T) {
pluginManifest := `{"id": "testplugin", "server": {"executable": "backend.exe"}, "settings_schema": {"settings": [{"key": "foo", "type": "text"}, {"key": "bar", "type": "text", "secret": true}]}}`
setupPluginAPITest(t, pluginCode, pluginManifest, pluginID, th.App, th.Context)
t.Cleanup(func() {
appErr := th.App.ch.RemovePlugin(pluginID)
require.Nil(t, appErr)
})
th.App.UpdateConfig(func(cfg *model.Config) {
cfg.PluginSettings.Plugins[pluginID] = map[string]any{
"foo": "foo_value",
"bar": "bar_value",
}
})
fileDatas := th.App.GenerateSupportPacket(th.Context, &model.SupportPacketOptions{
IncludeLogs: false,
})
found := false
for _, f := range fileDatas {
if f.Filename != "sanitized_config.json" {
continue
}
var config model.Config
err = json.Unmarshal(f.Body, &config)
require.NoError(t, err)
assert.Equal(t, "foo_value", config.PluginSettings.Plugins[pluginID]["foo"])
assert.Equal(t, model.FakeSetting, config.PluginSettings.Plugins[pluginID]["bar"])
found = true
}
assert.True(t, found)
})
}
func TestGetPluginsFile(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
defer th.TearDown()
getJobList := func(t *testing.T) *model.SupportPacketPluginList {
t.Helper()
fileData, err := th.App.getPluginsFile(th.Context)
assert.NoError(t, err)
require.NotNil(t, fileData)
assert.Equal(t, "plugins.json", fileData.Filename)
assert.Positive(t, len(fileData.Body))
var pl model.SupportPacketPluginList
err = json.Unmarshal(fileData.Body, &pl)
require.NoError(t, err)
return &pl
}
t.Run("no errors if no plugins are installed", func(t *testing.T) {
pl := getJobList(t)
assert.Len(t, pl.Enabled, 0)
assert.Len(t, pl.Disabled, 0)
})
t.Run("two plugins are installed", func(t *testing.T) {
path, found := fileutils.FindDir("tests")
require.True(t, found, "tests directory not found")
bundle1, err := os.ReadFile(filepath.Join(path, "testplugin.tar.gz"))
require.NoError(t, err)
manifest1, appErr := th.App.InstallPlugin(bytes.NewReader(bundle1), false)
require.Nil(t, appErr)
require.Equal(t, "testplugin", manifest1.Id)
appErr = th.App.EnablePlugin(manifest1.Id)
require.Nil(t, appErr)
bundle2, err := os.ReadFile(filepath.Join(path, "testplugin2.tar.gz"))
require.NoError(t, err)
manifest2, appErr := th.App.InstallPlugin(bytes.NewReader(bundle2), false)
require.Nil(t, appErr)
require.Equal(t, "testplugin2", manifest2.Id)
pl := getJobList(t)
require.Len(t, pl.Enabled, 1)
assert.Equal(t, "testplugin", pl.Enabled[0].Id)
require.Len(t, pl.Disabled, 1)
assert.Equal(t, "testplugin2", pl.Disabled[0].Id)
})
t.Run("error if plugin are disabled", func(t *testing.T) {
// Turn off plugins so we can get an error
th.App.UpdateConfig(func(cfg *model.Config) {
*cfg.PluginSettings.Enable = false
})
// Plugins off in settings so no fileData and we get a warning instead
fileData, err := th.App.getPluginsFile(th.Context)
assert.Nil(t, fileData)
assert.ErrorContains(t, err, "failed to get plugin list for Support Packet")
})
}
func TestGetSupportPacketStats(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
generateStats := func(t *testing.T) *model.SupportPacketStats {
t.Helper()
require.NoError(t, th.App.Srv().Store().Post().RefreshPostStats())
fileData, err := th.App.getSupportPacketStats(th.Context)
require.NotNil(t, fileData)
assert.Equal(t, "stats.yaml", fileData.Filename)
assert.Positive(t, len(fileData.Body))
assert.NoError(t, err)
var packet model.SupportPacketStats
err = yaml.Unmarshal(fileData.Body, &packet)
require.NoError(t, err)
return &packet
}
t.Run("fresh server", func(t *testing.T) {
sp := generateStats(t)
assert.Equal(t, int64(0), sp.RegisteredUsers)
assert.Equal(t, int64(0), sp.ActiveUsers)
assert.Equal(t, int64(0), sp.DailyActiveUsers)
assert.Equal(t, int64(0), sp.MonthlyActiveUsers)
assert.Equal(t, int64(0), sp.DeactivatedUsers)
assert.Equal(t, int64(0), sp.Guests)
assert.Equal(t, int64(0), sp.BotAccounts)
assert.Equal(t, int64(0), sp.Posts)
assert.Equal(t, int64(0), sp.Channels)
assert.Equal(t, int64(0), sp.Teams)
assert.Equal(t, int64(0), sp.SlashCommands)
assert.Equal(t, int64(0), sp.IncomingWebhooks)
assert.Equal(t, int64(0), sp.OutgoingWebhooks)
})
t.Run("Happy path", func(t *testing.T) {
var user *model.User
for range 4 {
user = th.CreateUser()
}
th.BasicUser = user
for range 3 {
deactivatedUser := th.CreateUser()
require.NotNil(t, deactivatedUser)
_, appErr := th.App.UpdateActive(th.Context, deactivatedUser, false)
require.Nil(t, appErr)
}
for range 2 {
guest := th.CreateGuest()
require.NotNil(t, guest)
}
th.CreateBot()
team := th.CreateTeam()
channel := th.CreateChannel(th.Context, team)
for range 3 {
p := th.CreatePost(channel)
require.NotNil(t, p)
}
cmd, appErr := th.App.CreateCommand(&model.Command{
CreatorId: user.Id,
TeamId: team.Id,
Trigger: "test",
Method: model.CommandMethodGet,
URL: "http://nowhere.com/",
})
require.Nil(t, appErr)
require.NotNil(t, cmd)
webhookIn, appErr := th.App.CreateIncomingWebhookForChannel(user.Id, channel, &model.IncomingWebhook{ChannelId: channel.Id})
require.Nil(t, appErr)
require.NotNil(t, webhookIn)
webhookOut, appErr := th.App.CreateOutgoingWebhook(&model.OutgoingWebhook{
ChannelId: channel.Id,
TeamId: channel.TeamId,
CreatorId: th.BasicUser.Id,
CallbackURLs: []string{"http://nowhere.com/"},
})
require.Nil(t, appErr)
require.NotNil(t, webhookOut)
sp := generateStats(t)
assert.Equal(t, int64(9), sp.RegisteredUsers)
assert.Equal(t, int64(6), sp.ActiveUsers)
assert.Equal(t, int64(0), sp.DailyActiveUsers)
assert.Equal(t, int64(0), sp.MonthlyActiveUsers)
assert.Equal(t, int64(3), sp.DeactivatedUsers)
assert.Equal(t, int64(2), sp.Guests)
assert.Equal(t, int64(1), sp.BotAccounts)
assert.Equal(t, int64(4), sp.Posts) // 1 from the bot creation and 3 created directly
assert.Equal(t, int64(3), sp.Channels) // 2 from the team creation and 1 created directly
assert.Equal(t, int64(1), sp.Teams)
assert.Equal(t, int64(1), sp.SlashCommands)
assert.Equal(t, int64(1), sp.IncomingWebhooks)
assert.Equal(t, int64(1), sp.OutgoingWebhooks)
})
// Reset test server
th.TearDown()
th = Setup(t).InitBasic()
defer th.TearDown()
t.Run("post count should be present if number of users extends AnalyticsSettings.MaxUsersForStatistics", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) {
cfg.AnalyticsSettings.MaxUsersForStatistics = model.NewPointer(1)
})
for range 5 {
p := th.CreatePost(th.BasicChannel)
require.NotNil(t, p)
}
// InitBasic() already creats 5 posts
packet := generateStats(t)
assert.Equal(t, int64(10), packet.Posts)
})
}
func TestGetSupportPacketJobList(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
defer th.TearDown()
getJobList := func(t *testing.T) *model.SupportPacketJobList {
t.Helper()
fileData, err := th.App.getSupportPacketJobList(th.Context)
require.NoError(t, err)
require.NotNil(t, fileData)
assert.Equal(t, "jobs.yaml", fileData.Filename)
assert.Positive(t, len(fileData.Body))
var jobs model.SupportPacketJobList
err = yaml.Unmarshal(fileData.Body, &jobs)
require.NoError(t, err)
return &jobs
}
t.Run("no jobs run yet", func(t *testing.T) {
jobs := getJobList(t)
assert.Empty(t, jobs.LDAPSyncJobs)
assert.Empty(t, jobs.DataRetentionJobs)
assert.Empty(t, jobs.MessageExportJobs)
assert.Empty(t, jobs.ElasticPostIndexingJobs)
assert.Empty(t, jobs.ElasticPostAggregationJobs)
assert.Empty(t, jobs.MigrationJobs)
})
t.Run("jobs exist", func(t *testing.T) {
getJob := func(jobType string) *model.Job {
return &model.Job{
Id: model.NewId(),
Type: jobType,
Priority: 1,
CreateAt: model.GetMillis() - 1,
StartAt: model.GetMillis(),
LastActivityAt: model.GetMillis() + 1,
Status: model.JobStatusPending,
Progress: 51,
Data: model.StringMap{"key": "value"},
}
}
// Create some jobs
jobsToCreate := []*model.Job{
getJob(model.JobTypeLdapSync),
getJob(model.JobTypeDataRetention),
getJob(model.JobTypeMessageExport),
getJob(model.JobTypeElasticsearchPostIndexing),
getJob(model.JobTypeElasticsearchPostAggregation),
getJob(model.JobTypeMigrations),
}
var expectedJobs []*model.Job
for _, job := range jobsToCreate {
// Create the job using the store directly.
// Creating the job at the job service would error as the workers require enterprise code.
rJob, err := th.App.Srv().Store().Job().Save(job)
require.NoError(t, err)
expectedJobs = append(expectedJobs, rJob)
}
jobs := getJobList(t)
// Helper to verify job content matches
verifyJob := func(t *testing.T, expected, actual *model.Job) {
t.Helper()
assert.Equal(t, expected.Id, actual.Id)
assert.Equal(t, expected.Type, actual.Type)
assert.Equal(t, expected.Priority, actual.Priority)
assert.Equal(t, expected.CreateAt, actual.CreateAt)
assert.Equal(t, expected.StartAt, actual.StartAt)
assert.Equal(t, expected.LastActivityAt, actual.LastActivityAt)
assert.Equal(t, expected.Status, actual.Status)
assert.Equal(t, expected.Progress, actual.Progress)
assert.Equal(t, expected.Data, actual.Data)
}
// Verify LDAP sync jobs
require.Len(t, jobs.LDAPSyncJobs, 1, "Should have 1 LDAP sync job")
verifyJob(t, expectedJobs[0], jobs.LDAPSyncJobs[0])
// Verify data retention jobs
require.Len(t, jobs.DataRetentionJobs, 1, "Should have 1 data retention job")
verifyJob(t, expectedJobs[1], jobs.DataRetentionJobs[0])
// Verify message export jobs
require.Len(t, jobs.MessageExportJobs, 1, "Should have 1 message export job")
verifyJob(t, expectedJobs[2], jobs.MessageExportJobs[0])
// Verify elasticsearch post indexing jobs
require.Len(t, jobs.ElasticPostIndexingJobs, 1, "Should have 1 elasticsearch post indexing job")
verifyJob(t, expectedJobs[3], jobs.ElasticPostIndexingJobs[0])
// Verify elasticsearch post aggregation jobs
require.Len(t, jobs.ElasticPostAggregationJobs, 1, "Should have 1 elasticsearch post aggregation job")
verifyJob(t, expectedJobs[4], jobs.ElasticPostAggregationJobs[0])
// Verify migration jobs
require.Len(t, jobs.MigrationJobs, 1, "Should have 1 migration job")
verifyJob(t, expectedJobs[5], jobs.MigrationJobs[0])
})
}
func TestGetSupportPacketPermissionsInfo(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic()
defer th.TearDown()
err := th.App.SetPhase2PermissionsMigrationStatus(true)
require.NoError(t, err)
generatePermissionInfo := func(t *testing.T) *model.SupportPacketPermissionInfo {
t.Helper()
fileData, err := th.App.getSupportPacketPermissionsInfo(th.Context)
require.NotNil(t, fileData)
assert.Equal(t, "permissions.yaml", fileData.Filename)
assert.Positive(t, len(fileData.Body))
assert.NoError(t, err)
var permissions model.SupportPacketPermissionInfo
err = yaml.Unmarshal(fileData.Body, &permissions)
require.NoError(t, err)
return &permissions
}
t.Run("No custom permissions", func(t *testing.T) {
permissions := generatePermissionInfo(t)
assert.Len(t, permissions.Roles, 23)
assert.Empty(t, permissions.Schemes)
})
scheme, appErr := th.App.CreateScheme(&model.Scheme{
Name: "custom_scheme",
DisplayName: "Custom Scheme",
Scope: model.SchemeScopeTeam,
})
require.Nil(t, appErr)
t.Run("with custom scheme", func(t *testing.T) {
permissions := generatePermissionInfo(t)
assert.Len(t, permissions.Roles, 33) // 23 default roles + 10 custom roles from the scheme
require.Len(t, permissions.Schemes, 1)
assert.Equal(t, scheme.Id, permissions.Schemes[0].Id)
assert.Equal(t, model.FakeSetting, permissions.Schemes[0].Name, "Name should be obfuscated")
assert.Equal(t, model.FakeSetting, permissions.Schemes[0].DisplayName, "DisplayName should be obfuscated")
assert.Equal(t, model.FakeSetting, permissions.Schemes[0].Description, "Description should be obfuscated")
})
t.Run("with custom role", func(t *testing.T) {
role, appErr := th.App.CreateRole(&model.Role{
Name: "custom_role",
DisplayName: "Custom Role",
})
require.Nil(t, appErr)
t.Cleanup(func() {
_, appErr := th.App.DeleteRole(role.Id)
require.Nil(t, appErr)
})
permissions := generatePermissionInfo(t)
require.Len(t, permissions.Schemes, 1)
require.Len(t, permissions.Roles, 34) // 23 default roles + 10 custom roles from the scheme + 1 custom role
found := false
for _, r := range permissions.Roles {
// Confirm that sensitive fields are obfuscated
assert.Equal(t, model.FakeSetting, r.DisplayName, "DisplayName should be obfuscated")
assert.Equal(t, model.FakeSetting, r.Description, "Description should be obfuscated")
// Fine the custom role
if r.Id == role.Id {
assert.Equal(t, role.Name, r.Name)
assert.Equal(t, role.CreateAt, r.CreateAt)
assert.Equal(t, role.UpdateAt, r.UpdateAt)
assert.Equal(t, role.DeleteAt, r.DeleteAt)
assert.Equal(t, role.Permissions, r.Permissions)
assert.Equal(t, role.SchemeManaged, r.SchemeManaged)
assert.Equal(t, role.BuiltIn, r.BuiltIn)
found = true
break
}
}
assert.True(t, found)
})
}
func TestGetSupportPacketMetadata(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
defer th.TearDown()
t.Run("Happy path", func(t *testing.T) {
fileData, err := th.App.getSupportPacketMetadata(th.Context)
require.NoError(t, err)
require.NotNil(t, fileData)
assert.Equal(t, "metadata.yaml", fileData.Filename)
assert.Positive(t, len(fileData.Body))
metadate, err := model.ParsePacketMetadata(fileData.Body)
assert.NoError(t, err)
require.NotNil(t, metadate)
assert.Equal(t, model.SupportPacketType, metadate.Type)
assert.Equal(t, model.CurrentVersion, metadate.ServerVersion)
assert.NotEmpty(t, metadate.ServerID)
assert.NotEmpty(t, metadate.GeneratedAt)
})
}
func TestGetSupportPacketDatabaseSchema(t *testing.T) {
th := Setup(t)
defer th.TearDown()
// Mock store for testing
mockStore := &smocks.Store{}
originalStore := th.App.Srv().Store()
th.App.Srv().SetStore(mockStore)
defer th.App.Srv().SetStore(originalStore)
// Set up mock return for schema definition
mockStore.On("GetSchemaDefinition").Return(&model.SupportPacketDatabaseSchema{
Tables: []model.DatabaseTable{
{
Name: "users",
Columns: []model.DatabaseColumn{
{Name: "id", DataType: "varchar", IsNullable: false},
{Name: "username", DataType: "varchar", IsNullable: false},
{Name: "email", DataType: "varchar", IsNullable: false},
},
},
{
Name: "channels",
Columns: []model.DatabaseColumn{
{Name: "id", DataType: "varchar", IsNullable: false},
{Name: "name", DataType: "varchar", IsNullable: false},
},
},
{
Name: "teams",
Columns: []model.DatabaseColumn{
{Name: "id", DataType: "varchar", IsNullable: false},
{Name: "name", DataType: "varchar", IsNullable: false},
},
},
{
Name: "posts",
Columns: []model.DatabaseColumn{
{Name: "id", DataType: "varchar", IsNullable: false},
{Name: "message", DataType: "text", IsNullable: false},
},
},
},
}, nil)
// Test with Postgres
oldDriverName := *th.App.Config().SqlSettings.DriverName
*th.App.Config().SqlSettings.DriverName = model.DatabaseDriverPostgres
defer func() {
*th.App.Config().SqlSettings.DriverName = oldDriverName
}()
fileData, err := th.App.getSupportPacketDatabaseSchema(th.Context)
require.NoError(t, err)
require.NotNil(t, fileData)
assert.Equal(t, "database_schema.yaml", fileData.Filename)
assert.Positive(t, len(fileData.Body))
var schema model.SupportPacketDatabaseSchema
err = yaml.Unmarshal(fileData.Body, &schema)
require.NoError(t, err)
// Verify schema structure
assert.NotEmpty(t, schema.Tables)
// Verify that common tables are present
tableNames := make([]string, 0, len(schema.Tables))
for _, table := range schema.Tables {
tableNames = append(tableNames, table.Name)
}
// Verify some core tables
expectedTables := []string{"users", "channels", "teams", "posts"}
for _, expected := range expectedTables {
assert.Contains(t, tableNames, expected)
}
// Verify table structure
for _, table := range schema.Tables {
if table.Name == "users" {
// Check user table has key columns
columnNames := make([]string, 0, len(table.Columns))
for _, column := range table.Columns {
columnNames = append(columnNames, column.Name)
}
expectedColumns := []string{"id", "username", "email"}
for _, expected := range expectedColumns {
assert.Contains(t, columnNames, expected)
}
break
}
}
}