Files
mostlymatter/server/enterprise/message_export/worker_test.go
Christopher Poile aba4434dab MM-59966 - Compliance Export overhaul - feature branch (#29789)
* [MM-59089] Add a compliance export constant (#27919)

* add a useful constant

* i18n

* another constant

* another i18n

* [MM-60422] Add GetChannelsWithActivityDuring (#28301)

* modify GetUsersInChannelDuring to accept a slice of channelIds

* add GetChannelsWithActivityDuring

* add compliance export progress message; remove unused custom status

* linting

* tests running too fast

* add batch size config settings

* add store tests

* linting

* empty commit

* i18n changes

* fix i18n ordering

* MM-60570 - Server-side changes consolidating the export CLI with server/ent code (#28640)

* add an i18n field; add the CLI's export directory

* int64 -> int

* Add UntilUpdateAt for MessageExport and AnalyticsPostCount

to merge

* remove now-unused i18n strings

* add TranslationsPreInitFromBuffer to allow CLI to use i18n

* use GetBuilder to simplify; rename TranslationsPreInitFromFileBytes

* [MM-59089] Improve compliance export timings (#1733 - Enterprise repo)

* MM-60422 - Performance and logic fixes for Compliance Exports (#1757 - Enterprise repo)

* MM-60570 - Enterprise-side changes consolidating the export CLI with server/ent code (#1769 - Enterprise repo)

* merge conflicts; missed file from ent branch

* MM-61038 - Add an option to sqlstore.New (#28702)

remove useless comment

add test

add an option to sqlstore.New

* MM-60976: Remove RunExport command from Mattermost binary (#28805)

* remove RunExport command from mattermost binary

* remove the code it was calling

* fix i18n

* remove test (was only testing license, not functionality)

* empty commit

* fix flaky GetChannelsWithActivityDuring test

* MM-60063: Dedicated Export Filestore fix, redo of #1772 (enterprise) (#28803)

* redo filestore fix #1772 (enterprise repo) on top of MM-59966 feature

* add new e2e tests for export filestore

* golint

* ok, note to self: shadowing bad, actually (when there's a defer)

* empty commit

* MM-61137 - Message export: Support 7.8.11 era dbs (#28824)

* support 7.8.11 era dbs by wrapping the store using only what we need

* fix flaky GetChannelsWithActivityDuring test

* add a comment

* only need to define the MEFileInfoStore (the one that'll be overridden)

* blank commit

* MM-60974 - Message Export: Add performance metrics (#28836)

* support 7.8.11 era dbs by wrapping the store using only what we need

* fix flaky GetChannelsWithActivityDuring test

* add a comment

* only need to define the MEFileInfoStore (the one that'll be overridden)

* performance metrics

* cleanup unneeded named returns

* blank commit

* MM-60975 - Message export: Add startTime and endTime to export folder name (#28840)

* support 7.8.11 era dbs by wrapping the store using only what we need

* fix flaky GetChannelsWithActivityDuring test

* add a comment

* only need to define the MEFileInfoStore (the one that'll be overridden)

* performance metrics

* output startTime and endTime in export folder

* empty commit

* merge conflict

* MM-60978 - Message export: Improve xml fields; fix delete semantics (#28873)

* support 7.8.11 era dbs by wrapping the store using only what we need

* fix flaky GetChannelsWithActivityDuring test

* add a comment

* only need to define the MEFileInfoStore (the one that'll be overridden)

* performance metrics

* output startTime and endTime in export folder

* empty commit

* add xml fields, omit when empty, tests

* fix delete semantics; test (and test for update semantics)

* clarify comments

* simplify edited post detection, now there's no edge case.

* add some spacing to help fast running tests

* merge conflicts/updates needed for new deleted post semantics

* linting; fixing tests from upstream merge

* use SafeDereference

* linting

* stronger typing; better wrapped errors; better formatting

* blank commit

* goimports formatting

* fix merge mistake

* minor fixes due to changes in master

* MM-61755 - Simplifying and Support reporting to the db from the CLI (#29281)

* finally clean up JobData struct and stringMap; prep for CLI using db

* and now simplify using StringMapToJobDataWithZeroValues

* remove unused fn

* create JobDataExported; clean up errors

* MM-60176 - Message Export: Global relay cleanup (#29168)

* move global relay logic into global_relay_export

* blank commit

* blank commit

* improve errors

* MM-60693 - Refactor CSV to use same codepath as Actiance (#29191)

* move global relay logic into global_relay_export

* blank commit

* refactor (and simplify) ExportParams into shared

* blank commit

* remove unused fn

* csv now uses pre-calculated joins/leaves like actiance

* improve errors

* remove nil post check; remove ignoredPosts metric

* remove unneeded copy

* MM-61696 - Refactor GlobalRelay to use same codepath as Actiance (#29225)

* move global relay logic into global_relay_export

* blank commit

* refactor (and simplify) ExportParams into shared

* blank commit

* remove unused fn

* csv now uses pre-calculated joins/leaves like actiance

* remove newly unneeded function and its test. goodbye.

* refactor GetPostAttachments for csv + global relay to share

* refactor global_relay_export and fix tests (no changes to output)

* improve errors

* remove nil post check; remove ignoredPosts metric

* remove unneeded copy

* remove unneeded nil check

* PR comments

* MM-61715 - Generalize e2e to all export types 🤖  (#29369)

* move global relay logic into global_relay_export

* blank commit

* refactor (and simplify) ExportParams into shared

* blank commit

* remove unused fn

* csv now uses pre-calculated joins/leaves like actiance

* remove newly unneeded function and its test. goodbye.

* refactor GetPostAttachments for csv + global relay to share

* refactor global_relay_export and fix tests (no changes to output)

* improve errors

* remove nil post check; remove ignoredPosts metric

* remove unneeded copy

* remove unneeded nil check

* PR comments

* refactor isDeletedMsg for all export types

* fix start and endtime, nasty csv createAt bug; bring closer to Actiance

* align unit tests with new logic (e.g. starttime / endtime)

* refactor a TimestampConvert fn for code + tests

* bug: pass templates to global relay (hurray for e2e tests, otherwise...)

* add global relay zip to allowed list (only for tests)

* test helpers

* new templates for e2e tests

* e2e tests... phew.

* linting

* merge conflicts

* unexport PostToRow; add test helper marker

* cleanup, shortening, thanks to PR comments

* MM-61972 - Generalize export data path - Actiance (#29399)

* extract and generalize the export data generation functions

* finish moving test (bc of previous extraction)

* lift a function from common -> shared (to break an import cycle)

* actiance now takes general export data, processes it into actiance data

* bring tests in line with correct sorting rules (upadateAt, messageId)

* fixups, PR comments

* turn strings.Repeat into a more descriptive const

amended: one letter fix; bad rebase

* MM-62009 - e2e clock heisenbug (#29434)

* consolidate assertions; output debuggable diffs (keeping for future)

* refactor test output generator to generators file

* waitUntilZeroPosts + pass through until to job = fix all clock issues

* simplify messages to model.NewId(); remove unneeded waitUntilZeroPosts

* model.NewId() -> storetest.NewTestID()

* MM-61980 - Generalize export data path - CSV (#29482)

* simple refactoring

* increase sleep times for (very) rare test failures

* add extra information to the generic export for CSV

* adj Actiance to handle new generic export (no difference in its output)

* no longer need mergePosts (yay), move getJoinLeavePosts for everyone

* adjust tests for new csv semantics (detailed in summary)

* and need to add the new exported data to the export_data_tests

* rearrange csv writing to happen after data export (more logical)

* linting

* remove debug statements

* figured out what was wrong with global relay e2e test 3; solid now

* PR comments

* MM-61718 - Generalize export data path - Global Relay (#29508)

* move global relay over to using the generalized export data

* performance pass -- not much can be done

* Update server/enterprise/message_export/global_relay_export/global_relay_export.go

Co-authored-by: Claudio Costa <cstcld91@gmail.com>

---------

Co-authored-by: Claudio Costa <cstcld91@gmail.com>

* MM-62058 - Align CSV with Actiance (#29551)

* refactoring actiance files and var names for clarity

* bug found in exported attachments (we used to miss some start/ends)

* changes needed for actiance due to new generic exports

* bringing CSV up to actiance standards

* fixing global relay b/c of new semantics (adding a note on an edge case)

* aligning e2e tests, adding comments to clarify what is expected/tested

* necessary changes; 1 more test for added functionality (ignoreDeleted)

* comment style

* MM-62059 - Align Global Relay with Actiance/CSV; many fixes (#29665)

* core logic changes to general export_data and the specific export paths

* unit tests and e2e tests, covering all new edge cases and all logic

* linting

* better var naming, const value, and cleaning up functions calls

* MM-62436 - Temporarily skip cypress tests that require download link (#29772)

---------

Co-authored-by: Claudio Costa <cstcld91@gmail.com>
2025-01-10 16:56:02 -05:00

501 строка
18 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.enterprise for license information.
package message_export
import (
"context"
"errors"
"fmt"
"os"
"path"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/assert"
tmock "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/plugin/plugintest/mock"
"github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/channels/app"
"github.com/mattermost/mattermost/server/v8/channels/jobs"
"github.com/mattermost/mattermost/server/v8/channels/store/storetest"
st "github.com/mattermost/mattermost/server/v8/channels/store/storetest"
"github.com/mattermost/mattermost/server/v8/channels/utils/testutils"
"github.com/mattermost/mattermost/server/v8/einterfaces/mocks"
"github.com/mattermost/mattermost/server/v8/enterprise/message_export/shared"
)
func TestInitJobDataNoJobData(t *testing.T) {
logger := mlog.CreateConsoleTestLogger(t)
mockStore := &storetest.Store{}
defer mockStore.AssertExpectations(t)
job := &model.Job{
Id: st.NewTestID(),
CreateAt: model.GetMillis(),
Status: model.JobStatusPending,
Type: model.JobTypeMessageExport,
}
// mock job store doesn't return a previously successful job, forcing fallback to config
mockStore.JobStore.On("GetNewestJobByStatusesAndType", []string{model.JobStatusWarning, model.JobStatusSuccess}, model.JobTypeMessageExport).Return(nil, errors.New("test"))
worker := &MessageExportWorker{
jobServer: &jobs.JobServer{
Store: mockStore,
ConfigService: &testutils.StaticConfigService{
Cfg: &model.Config{
// mock config
MessageExportSettings: model.MessageExportSettings{
EnableExport: model.NewPointer(true),
ExportFormat: model.NewPointer(model.ComplianceExportTypeActiance),
DailyRunTime: model.NewPointer("01:00"),
ExportFromTimestamp: model.NewPointer(int64(0)),
BatchSize: model.NewPointer(10000),
ChannelBatchSize: model.NewPointer(100),
ChannelHistoryBatchSize: model.NewPointer(100),
},
},
},
},
logger: logger,
}
now := time.Now()
worker.initJobData(logger, job, now)
assert.Equal(t, model.ComplianceExportTypeActiance, job.Data[shared.JobDataExportType])
assert.Equal(t, strconv.Itoa(*worker.jobServer.Config().MessageExportSettings.BatchSize), job.Data[shared.JobDataBatchSize])
assert.Equal(t, strconv.FormatInt(*worker.jobServer.Config().MessageExportSettings.ExportFromTimestamp, 10), job.Data[shared.JobDataBatchStartTime])
expectedDir := path.Join(model.ComplianceExportPath, fmt.Sprintf("%s-%d-%d", now.Format(model.ComplianceExportDirectoryFormat), 0, now.UnixMilli()))
assert.Equal(t, expectedDir, job.Data[shared.JobDataExportDir])
}
func TestInitJobDataPreviousJobNoJobData(t *testing.T) {
logger := mlog.CreateConsoleTestLogger(t)
mockStore := &storetest.Store{}
defer mockStore.AssertExpectations(t)
previousJob := &model.Job{
Id: st.NewTestID(),
CreateAt: model.GetMillis(),
Status: model.JobStatusSuccess,
Type: model.JobTypeMessageExport,
StartAt: model.GetMillis() - 1000,
LastActivityAt: model.GetMillis() - 1000,
}
job := &model.Job{
Id: st.NewTestID(),
CreateAt: model.GetMillis(),
Status: model.JobStatusPending,
Type: model.JobTypeMessageExport,
}
// mock job store returns a previously successful job, but it doesn't have job data either, so we still fall back to config
mockStore.JobStore.On("GetNewestJobByStatusesAndType", []string{model.JobStatusWarning, model.JobStatusSuccess}, model.JobTypeMessageExport).Return(previousJob, nil)
worker := &MessageExportWorker{
jobServer: &jobs.JobServer{
Store: mockStore,
ConfigService: &testutils.StaticConfigService{
Cfg: &model.Config{
// mock config
MessageExportSettings: model.MessageExportSettings{
EnableExport: model.NewPointer(true),
ExportFormat: model.NewPointer(model.ComplianceExportTypeActiance),
DailyRunTime: model.NewPointer("01:00"),
ExportFromTimestamp: model.NewPointer(int64(0)),
BatchSize: model.NewPointer(10000),
ChannelBatchSize: model.NewPointer(100),
ChannelHistoryBatchSize: model.NewPointer(100),
},
},
},
},
logger: logger,
}
now := time.Now()
worker.initJobData(logger, job, now)
assert.Equal(t, model.ComplianceExportTypeActiance, job.Data[shared.JobDataExportType])
assert.Equal(t, strconv.Itoa(*worker.jobServer.Config().MessageExportSettings.BatchSize), job.Data[shared.JobDataBatchSize])
assert.Equal(t, strconv.FormatInt(*worker.jobServer.Config().MessageExportSettings.ExportFromTimestamp, 10), job.Data[shared.JobDataBatchStartTime])
expectedDir := path.Join(model.ComplianceExportPath, fmt.Sprintf("%s-%d-%d", now.Format(model.ComplianceExportDirectoryFormat), 0, now.UnixMilli()))
assert.Equal(t, expectedDir, job.Data[shared.JobDataExportDir])
}
func TestInitJobDataPreviousJobWithJobData(t *testing.T) {
logger := mlog.CreateConsoleTestLogger(t)
mockStore := &storetest.Store{}
defer mockStore.AssertExpectations(t)
previousJob := &model.Job{
Id: st.NewTestID(),
CreateAt: model.GetMillis(),
Status: model.JobStatusSuccess,
Type: model.JobTypeMessageExport,
StartAt: model.GetMillis() - 1000,
LastActivityAt: model.GetMillis() - 1000,
Data: map[string]string{shared.JobDataBatchStartTime: "123"},
}
job := &model.Job{
Id: st.NewTestID(),
CreateAt: model.GetMillis(),
Status: model.JobStatusPending,
Type: model.JobTypeMessageExport,
Data: map[string]string{shared.JobDataExportDir: "this-is-the-export-dir"},
}
// mock job store returns a previously successful job that has the config that we're looking for, so we use it
mockStore.JobStore.On("GetNewestJobByStatusesAndType", []string{model.JobStatusWarning, model.JobStatusSuccess}, model.JobTypeMessageExport).Return(previousJob, nil)
worker := &MessageExportWorker{
jobServer: &jobs.JobServer{
Store: mockStore,
ConfigService: &testutils.StaticConfigService{
Cfg: &model.Config{
// mock config
MessageExportSettings: model.MessageExportSettings{
EnableExport: model.NewPointer(true),
ExportFormat: model.NewPointer(model.ComplianceExportTypeActiance),
DailyRunTime: model.NewPointer("01:00"),
ExportFromTimestamp: model.NewPointer(int64(0)),
BatchSize: model.NewPointer(10000),
ChannelBatchSize: model.NewPointer(100),
ChannelHistoryBatchSize: model.NewPointer(100),
},
},
},
},
logger: logger,
}
now := time.Now()
worker.initJobData(logger, job, now)
assert.Equal(t, model.ComplianceExportTypeActiance, job.Data[shared.JobDataExportType])
assert.Equal(t, strconv.Itoa(*worker.jobServer.Config().MessageExportSettings.BatchSize), job.Data[shared.JobDataBatchSize])
assert.Equal(t, previousJob.Data[shared.JobDataBatchStartTime], job.Data[shared.JobDataBatchStartTime])
expectedDir := "this-is-the-export-dir"
assert.Equal(t, expectedDir, job.Data[shared.JobDataExportDir])
}
func TestDoJobNoPostsToExport(t *testing.T) {
logger := mlog.CreateConsoleTestLogger(t)
mockStore := &storetest.Store{}
defer mockStore.AssertExpectations(t)
mockMetrics := &mocks.MetricsInterface{}
defer mockMetrics.AssertExpectations(t)
job := &model.Job{
Id: st.NewTestID(),
CreateAt: model.GetMillis(),
Status: model.JobStatusPending,
Type: model.JobTypeMessageExport,
}
// claim job succeeds
mockStore.JobStore.On("UpdateStatusOptimistically", job.Id, model.JobStatusPending, model.JobStatusInProgress).Return(true, nil)
mockMetrics.On("IncrementJobActive", model.JobTypeMessageExport)
// no previous job, data will be loaded from config
mockStore.JobStore.On("GetNewestJobByStatusesAndType", []string{model.JobStatusWarning, model.JobStatusSuccess}, model.JobTypeMessageExport).Return(nil, errors.New("test"))
// no channels with activity
mockStore.ChannelMemberHistoryStore.On("GetChannelsWithActivityDuring", mock.Anything, mock.Anything).
Return(make([]string, 0), nil)
// no posts found to export
mockStore.ComplianceStore.On("MessageExport", mock.Anything, mock.AnythingOfType("model.MessageExportCursor"), 10001).Return(
make([]*model.MessageExport, 0), model.MessageExportCursor{}, nil,
)
mockStore.PostStore.On("AnalyticsPostCount", mock.Anything).Return(
int64(shared.EstimatedPostCount), nil,
)
// job completed successfully
mockStore.JobStore.On("UpdateOptimistically", job, model.JobStatusInProgress).Return(true, nil)
mockStore.JobStore.On("UpdateStatus", job.Id, model.JobStatusSuccess).Return(job, nil)
mockMetrics.On("DecrementJobActive", model.JobTypeMessageExport)
tempDir, err := os.MkdirTemp("", "")
require.NoError(t, err)
t.Cleanup(func() {
err = os.RemoveAll(tempDir)
assert.NoError(t, err)
})
worker := &MessageExportWorker{
jobServer: jobs.NewJobServer(
&testutils.StaticConfigService{
Cfg: &model.Config{
// mock config
FileSettings: model.FileSettings{
DriverName: model.NewPointer(model.ImageDriverLocal),
Directory: model.NewPointer(tempDir),
},
MessageExportSettings: model.MessageExportSettings{
EnableExport: model.NewPointer(true),
ExportFormat: model.NewPointer(model.ComplianceExportTypeActiance),
DailyRunTime: model.NewPointer("01:00"),
ExportFromTimestamp: model.NewPointer(int64(0)),
BatchSize: model.NewPointer(10000),
ChannelBatchSize: model.NewPointer(100),
ChannelHistoryBatchSize: model.NewPointer(100),
},
},
},
mockStore,
mockMetrics,
logger,
),
logger: logger,
}
// actually execute the code under test
worker.DoJob(job)
}
func TestDoJobWithDedicatedExportBackend(t *testing.T) {
logger := mlog.CreateConsoleTestLogger(t)
mockStore := &storetest.Store{}
defer mockStore.AssertExpectations(t)
mockMetrics := &mocks.MetricsInterface{}
defer mockMetrics.AssertExpectations(t)
job := &model.Job{
Id: st.NewTestID(),
CreateAt: model.GetMillis(),
Status: model.JobStatusPending,
Type: model.JobTypeMessageExport,
}
// claim job succeeds
mockStore.JobStore.On("UpdateStatusOptimistically", job.Id, model.JobStatusPending, model.JobStatusInProgress).Return(true, nil)
mockMetrics.On("IncrementJobActive", model.JobTypeMessageExport)
// no previous job, data will be loaded from config
mockStore.JobStore.On("GetNewestJobByStatusesAndType", []string{model.JobStatusWarning, model.JobStatusSuccess}, model.JobTypeMessageExport).Return(nil, errors.New("test"))
channelId := st.NewTestID()
channelName := st.NewTestID()
channelDisplayName := st.NewTestID()
channelType := model.ChannelTypeOpen
messages := []*model.MessageExport{
{
TeamId: model.NewPointer(st.NewTestID()),
ChannelId: model.NewPointer(channelId),
ChannelName: model.NewPointer(channelName),
UserId: model.NewPointer(st.NewTestID()),
UserEmail: model.NewPointer(st.NewTestID()),
Username: model.NewPointer(st.NewTestID()),
PostId: model.NewPointer(st.NewTestID()),
PostCreateAt: model.NewPointer[int64](123),
PostUpdateAt: model.NewPointer[int64](123),
PostDeleteAt: model.NewPointer[int64](123),
PostMessage: model.NewPointer(st.NewTestID()),
},
}
// need to export at least one post to make an export directory and file
mockStore.ChannelMemberHistoryStore.On("GetChannelsWithActivityDuring", mock.Anything, mock.Anything).
Return([]string{*messages[0].ChannelId}, nil)
mockStore.ChannelStore.On("GetMany", []string{channelId}, true).
Return(model.ChannelList{{
Id: channelId,
DisplayName: channelDisplayName,
Name: channelName,
Type: channelType,
}}, nil)
mockStore.ComplianceStore.On("MessageExport", mock.Anything, mock.AnythingOfType("model.MessageExportCursor"), 10001).Return(
messages, model.MessageExportCursor{}, nil,
).Once()
mockStore.ComplianceStore.On("MessageExport", mock.Anything, mock.AnythingOfType("model.MessageExportCursor"), 10001).Return(
make([]*model.MessageExport, 0), model.MessageExportCursor{}, nil,
).Once()
mockStore.ChannelMemberHistoryStore.On("GetUsersInChannelDuring", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
mockStore.PostStore.On("AnalyticsPostCount", mock.Anything).Return(
int64(1), nil,
)
// job completed successfully
mockStore.JobStore.On("UpdateOptimistically", job, model.JobStatusInProgress).Return(true, nil)
mockStore.JobStore.On("UpdateStatus", job.Id, model.JobStatusSuccess).Return(job, nil)
mockMetrics.On("DecrementJobActive", model.JobTypeMessageExport)
// create primary filestore directory
tempPrimaryDir, err := os.MkdirTemp("", "")
require.NoError(t, err)
defer os.RemoveAll(tempPrimaryDir)
// create dedicated filestore directory
tempDedicatedDir, err := os.MkdirTemp("", "")
require.NoError(t, err)
defer os.RemoveAll(tempDedicatedDir)
// setup worker with primary and dedicated filestores.
worker := &MessageExportWorker{
jobServer: jobs.NewJobServer(
&testutils.StaticConfigService{
Cfg: &model.Config{
// mock config
FileSettings: model.FileSettings{
DriverName: model.NewPointer(model.ImageDriverLocal),
Directory: model.NewPointer(tempPrimaryDir),
DedicatedExportStore: model.NewPointer(true),
ExportDriverName: model.NewPointer(model.ImageDriverLocal),
ExportDirectory: model.NewPointer(tempDedicatedDir),
},
MessageExportSettings: model.MessageExportSettings{
EnableExport: model.NewPointer(true),
ExportFormat: model.NewPointer(model.ComplianceExportTypeActiance),
DailyRunTime: model.NewPointer("01:00"),
ExportFromTimestamp: model.NewPointer(int64(0)),
BatchSize: model.NewPointer(10000),
ChannelBatchSize: model.NewPointer(100),
ChannelHistoryBatchSize: model.NewPointer(100),
},
},
},
mockStore,
mockMetrics,
logger,
),
logger: logger,
}
// actually execute the code under test
worker.DoJob(job)
// ensure no primary filestore files exist
files, err := os.ReadDir(tempPrimaryDir)
require.NoError(t, err)
assert.Zero(t, len(files))
// ensure some dedicated filestore files exist
files, err = os.ReadDir(tempDedicatedDir)
require.NoError(t, err)
assert.NotZero(t, len(files))
}
func TestDoJobCancel(t *testing.T) {
logger := mlog.CreateConsoleTestLogger(t)
mockStore := &storetest.Store{}
t.Cleanup(func() { mockStore.AssertExpectations(t) })
mockMetrics := &mocks.MetricsInterface{}
t.Cleanup(func() { mockMetrics.AssertExpectations(t) })
job := &model.Job{
Id: st.NewTestID(),
CreateAt: model.GetMillis(),
Status: model.JobStatusPending,
Type: model.JobTypeMessageExport,
}
tempDir, err := os.MkdirTemp("", "")
require.NoError(t, err)
t.Cleanup(func() { os.RemoveAll(tempDir) })
impl := MessageExportJobInterfaceImpl{
Server: &app.Server{
Jobs: jobs.NewJobServer(
&testutils.StaticConfigService{
Cfg: &model.Config{
// mock config
FileSettings: model.FileSettings{
DriverName: model.NewPointer(model.ImageDriverLocal),
Directory: model.NewPointer(tempDir),
},
MessageExportSettings: model.MessageExportSettings{
EnableExport: model.NewPointer(true),
ExportFormat: model.NewPointer(model.ComplianceExportTypeActiance),
DailyRunTime: model.NewPointer("01:00"),
ExportFromTimestamp: model.NewPointer(int64(0)),
BatchSize: model.NewPointer(10000),
ChannelBatchSize: model.NewPointer(100),
ChannelHistoryBatchSize: model.NewPointer(100),
},
},
},
mockStore,
mockMetrics,
logger,
),
},
}
worker, ok := impl.MakeWorker().(*MessageExportWorker)
require.True(t, ok)
// Claim job succeeds
mockStore.JobStore.On("UpdateStatusOptimistically", job.Id, model.JobStatusPending, model.JobStatusInProgress).Return(true, nil)
mockMetrics.On("IncrementJobActive", model.JobTypeMessageExport)
// No previous job, data will be loaded from config
mockStore.JobStore.On("GetNewestJobByStatusesAndType", []string{model.JobStatusWarning, model.JobStatusSuccess}, model.JobTypeMessageExport).Return(nil, errors.New("test"))
// Job updates the system console UI, once for getting channels, once for getting activity
mockStore.JobStore.On("UpdateOptimistically", mock.AnythingOfType("*model.Job"), model.JobStatusInProgress).Return(true, nil).Times(2)
// a few calls pass
mockStore.ChannelMemberHistoryStore.On("GetChannelsWithActivityDuring", mock.Anything, mock.Anything).
Return([]string{"channel-id"}, nil)
mockStore.ChannelStore.On("GetMany", []string{"channel-id"}, true).
Return(model.ChannelList{{
Id: "channel-id",
DisplayName: "channel-display-name",
Name: "channel-name",
Type: model.ChannelTypeDirect,
}}, nil)
mockStore.ChannelMemberHistoryStore.On("GetUsersInChannelDuring", mock.Anything, mock.Anything, []string{"channel-id"}).Return([]*model.ChannelMemberHistoryResult{}, nil)
cancelled := make(chan struct{})
// Cancel the worker and return an error
mockStore.ComplianceStore.On("MessageExport", mock.Anything, mock.AnythingOfType("model.MessageExportCursor"), 10001).Run(func(args tmock.Arguments) {
worker.cancel()
rctx, ok := args.Get(0).(request.CTX)
require.True(t, ok)
assert.Error(t, rctx.Context().Err())
assert.ErrorIs(t, rctx.Context().Err(), context.Canceled)
cancelled <- struct{}{}
}).Return(
nil, model.MessageExportCursor{}, context.Canceled,
)
mockStore.PostStore.On("AnalyticsPostCount", mock.Anything).Return(
int64(shared.EstimatedPostCount), nil,
)
// Job marked as pending
mockStore.JobStore.On("UpdateStatus", job.Id, model.JobStatusPending).Return(job, nil)
mockMetrics.On("DecrementJobActive", model.JobTypeMessageExport)
go worker.Run()
worker.JobChannel() <- *job
// Wait for the cancelation
<-cancelled
// Cleanup
worker.Stop()
}