mattermost/server/enterprise/message_export/shared/shared_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

286 lines
9.4 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.enterprise for license information.
package shared
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/mattermost/mattermost/server/public/model"
)
func TestGetJoinsAndLeavesForChannel(t *testing.T) {
channel := MetadataChannel{
StartTime: 100,
EndTime: 200,
ChannelId: "good-request-1",
TeamId: model.NewPointer("test"),
TeamName: model.NewPointer("test"),
TeamDisplayName: model.NewPointer("test"),
ChannelName: "test",
ChannelDisplayName: "test",
ChannelType: "O",
}
tt := []struct {
name string
channel MetadataChannel
membersHistory []*model.ChannelMemberHistoryResult
usersInPosts map[string]ChannelMember
expectedJoins int
expectedLeaves int
}{
{
name: "no-joins-no-leaves",
channel: channel,
membersHistory: nil,
usersInPosts: nil,
expectedJoins: 0,
expectedLeaves: 0,
},
{
name: "joins-and-leaves-outside-the-range",
channel: channel,
membersHistory: []*model.ChannelMemberHistoryResult{
{JoinTime: 1, LeaveTime: model.NewPointer(int64(10)), UserId: "test", UserEmail: "test", Username: "test"},
{JoinTime: 250, LeaveTime: model.NewPointer(int64(260)), UserId: "test", UserEmail: "test", Username: "test"},
{JoinTime: 300, UserId: "test", UserEmail: "test", Username: "test"},
},
usersInPosts: nil,
expectedJoins: 0,
expectedLeaves: 0,
},
{
name: "join-and-leave-during-the-range",
channel: channel,
membersHistory: []*model.ChannelMemberHistoryResult{
{JoinTime: 100, LeaveTime: model.NewPointer(int64(150)), UserId: "test", UserEmail: "test", Username: "test"},
},
usersInPosts: nil,
expectedJoins: 1,
expectedLeaves: 1,
},
{
name: "join-during-and-leave-after-the-range",
channel: channel,
membersHistory: []*model.ChannelMemberHistoryResult{
{JoinTime: 150, LeaveTime: model.NewPointer(int64(300)), UserId: "test", UserEmail: "test", Username: "test"},
},
usersInPosts: nil,
expectedJoins: 1,
expectedLeaves: 0,
},
{
name: "join-before-and-leave-during-the-range",
channel: channel,
membersHistory: []*model.ChannelMemberHistoryResult{
{JoinTime: 99, LeaveTime: model.NewPointer(int64(150)), UserId: "test", UserEmail: "test", Username: "test"},
},
usersInPosts: nil,
expectedJoins: 1,
expectedLeaves: 1,
},
{
name: "join-before-and-leave-after-the-range",
channel: channel,
membersHistory: []*model.ChannelMemberHistoryResult{
{JoinTime: 99, LeaveTime: model.NewPointer(int64(350)), UserId: "test", UserEmail: "test", Username: "test"},
},
usersInPosts: nil,
expectedJoins: 1,
expectedLeaves: 0,
},
{
name: "implicit-joins",
channel: channel,
membersHistory: nil,
usersInPosts: map[string]ChannelMember{
"test1": {UserId: "test1", Email: "test1", Username: "test1"},
"test2": {UserId: "test2", Email: "test2", Username: "test2"},
},
expectedJoins: 2,
expectedLeaves: 0,
},
{
name: "implicit-joins-with-explicit-joins",
channel: channel,
membersHistory: []*model.ChannelMemberHistoryResult{
{JoinTime: 130, LeaveTime: model.NewPointer(int64(150)), UserId: "test1", UserEmail: "test1", Username: "test1"},
{JoinTime: 130, LeaveTime: model.NewPointer(int64(150)), UserId: "test3", UserEmail: "test3", Username: "test3"},
},
usersInPosts: map[string]ChannelMember{
"test1": {UserId: "test1", Email: "test1", Username: "test1"},
"test2": {UserId: "test2", Email: "test2", Username: "test2"},
},
expectedJoins: 3,
expectedLeaves: 2,
},
{
name: "join-leave-and-join-again",
channel: channel,
membersHistory: []*model.ChannelMemberHistoryResult{
{JoinTime: 130, LeaveTime: model.NewPointer(int64(150)), UserId: "test1", UserEmail: "test1", Username: "test1"},
{JoinTime: 160, LeaveTime: model.NewPointer(int64(180)), UserId: "test1", UserEmail: "test1", Username: "test1"},
},
usersInPosts: nil,
expectedJoins: 2,
expectedLeaves: 2,
},
{
name: "deactivated-members-dont-show",
channel: channel,
membersHistory: []*model.ChannelMemberHistoryResult{
{JoinTime: 130, LeaveTime: model.NewPointer(int64(150)), UserId: "test1", UserEmail: "test1", Username: "test1", UserDeleteAt: 50},
{JoinTime: 160, LeaveTime: model.NewPointer(int64(180)), UserId: "test1", UserEmail: "test1", Username: "test1", UserDeleteAt: 50},
},
usersInPosts: nil,
expectedJoins: 0,
expectedLeaves: 0,
},
{
name: "deactivated-members-show-if-deleted-after-latest-export",
channel: channel,
membersHistory: []*model.ChannelMemberHistoryResult{
{JoinTime: 130, LeaveTime: model.NewPointer(int64(150)), UserId: "test1", UserEmail: "test1", Username: "test1", UserDeleteAt: 150},
{JoinTime: 160, LeaveTime: model.NewPointer(int64(180)), UserId: "test1", UserEmail: "test1", Username: "test1", UserDeleteAt: 150},
},
usersInPosts: nil,
expectedJoins: 2,
expectedLeaves: 2,
},
{
name: "deactivated-members-show-and-dont-show",
channel: channel,
membersHistory: []*model.ChannelMemberHistoryResult{
{JoinTime: 130, LeaveTime: model.NewPointer(int64(150)), UserId: "test1", UserEmail: "test1", Username: "test1", UserDeleteAt: 50},
{JoinTime: 160, LeaveTime: model.NewPointer(int64(180)), UserId: "test1", UserEmail: "test1", Username: "test1", UserDeleteAt: 150},
},
usersInPosts: nil,
expectedJoins: 1,
expectedLeaves: 1,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
joins, leaves := GetJoinsAndLeavesForChannel(tc.channel.StartTime, tc.channel.EndTime, tc.membersHistory, tc.usersInPosts)
assert.Len(t, joins, tc.expectedJoins)
assert.Len(t, leaves, tc.expectedLeaves)
})
}
}
func Test_GetBatchPath(t *testing.T) {
tests := []struct {
name string
exportDir string
prevPostUpdateAt int64
lastPostUpdateAt int64
batchNumber int
want string
}{
{
name: "all args given",
exportDir: "/export/test_dir",
prevPostUpdateAt: 123,
lastPostUpdateAt: 456,
batchNumber: 21,
want: "/export/test_dir/batch021-123-456.zip",
},
{
name: "exportDir blank",
exportDir: "",
prevPostUpdateAt: 12345,
lastPostUpdateAt: 456789,
batchNumber: 921,
want: model.ComplianceExportPath + "/" + time.Now().Format(model.ComplianceExportDirectoryFormat) + "/batch921-12345-456789.zip",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, GetBatchPath(tt.exportDir, tt.prevPostUpdateAt, tt.lastPostUpdateAt, tt.batchNumber), "GetBatchPath(%v, %v, %v, %v)", tt.exportDir, tt.prevPostUpdateAt, tt.lastPostUpdateAt, tt.batchNumber)
})
}
}
func TestJobDataToStringMap_and_StringMapToJobData(t *testing.T) {
jd := JobData{
JobDataExported: JobDataExported{
ExportType: "cli_message_export",
ExportDir: "/here/there/34234-123",
BatchStartTime: 45,
BatchStartId: "34arsitenaorsten",
JobStartTime: 99,
JobEndTime: 1234,
JobStartId: "99abcdef34",
BatchSize: 2000,
ChannelBatchSize: 30000,
ChannelHistoryBatchSize: 30,
BatchNumber: 4,
TotalPostsExpected: 999999,
MessagesExported: 343499,
WarningCount: 39,
},
ExportPeriodStartTime: 123456, // not exported
BatchEndTime: 999999999, // not exported
BatchPath: "/another/path/123-123", // not exported
}
strMap := JobDataToStringMap(jd)
expected := make(map[string]string)
expected[JobDataExportType] = "cli_message_export"
expected[JobDataExportDir] = "/here/there/34234-123"
expected[JobDataBatchStartTime] = "45"
expected[JobDataBatchStartId] = "34arsitenaorsten"
expected[JobDataJobStartTime] = "99"
expected[JobDataJobEndTime] = "1234"
expected[JobDataJobStartId] = "99abcdef34"
expected[JobDataBatchSize] = "2000"
expected[JobDataChannelBatchSize] = "30000"
expected[JobDataChannelHistoryBatchSize] = "30"
expected[JobDataBatchNumber] = "4"
expected[JobDataTotalPostsExpected] = "999999"
expected[JobDataMessagesExported] = "343499"
expected[JobDataWarningCount] = "39"
expected[JobDataIsDownloadable] = "false"
for k, v := range expected {
val, ok := strMap[k]
assert.True(t, ok)
assert.Equal(t, v, val)
}
// not exported:
for _, k := range []string{"export_period_start_time", "batch_end_time", "batch_path"} {
_, ok := strMap[k]
assert.False(t, ok)
}
// zero the fields that weren't exported:
jd.ExportPeriodStartTime = 0
jd.BatchEndTime = 0
jd.BatchPath = ""
// now convert back
jd2, err := StringMapToJobDataWithZeroValues(strMap)
assert.NoError(t, err)
assert.Equal(t, jd, jd2)
// and test bad conversion (just a couple):
badStrMap := map[string]string{JobDataJobStartTime: "56aaa"}
_, err = StringMapToJobDataWithZeroValues(badStrMap)
assert.Error(t, err)
badStrMap = map[string]string{JobDataJobEndTime: "blah blah"}
_, err = StringMapToJobDataWithZeroValues(badStrMap)
assert.Error(t, err)
// test that zero values are used when not present
emptyStrMap := make(map[string]string)
emptyJd, err := StringMapToJobDataWithZeroValues(emptyStrMap)
assert.NoError(t, err)
assert.Equal(t, JobData{}, emptyJd)
}