From 4b77485e8f3e031d152b546e97d77afdb0eeb6fa Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Tue, 8 Jul 2025 11:57:48 -0400 Subject: [PATCH] MM-64718 Improve validation of thread follower imports (#33287) * MM-64718 Improve validation of thread follower imports * Add additional test cases and restucture tests --- .../channels/app/imports/import_validators.go | 28 ++++++++++ .../app/imports/import_validators_test.go | 52 +++++++++++++++++++ server/i18n/en.json | 16 ++++++ 3 files changed, 96 insertions(+) diff --git a/server/channels/app/imports/import_validators.go b/server/channels/app/imports/import_validators.go index f414ae1e9c5..8fa3f7ff1b6 100644 --- a/server/channels/app/imports/import_validators.go +++ b/server/channels/app/imports/import_validators.go @@ -563,6 +563,14 @@ func ValidatePostImportData(data *PostImportData, maxPostSize int) *model.AppErr } } + if data.ThreadFollowers != nil { + for _, follower := range *data.ThreadFollowers { + if err := ValidateThreadFollowerImportData(&follower); err != nil { + return model.NewAppError("BulkImport", "app.import.validate_post_import_data.thread_follower.error", nil, "", http.StatusBadRequest).Wrap(err) + } + } + } + return nil } @@ -687,6 +695,14 @@ func ValidateDirectPostImportData(data *DirectPostImportData, maxPostSize int) * } } + if data.ThreadFollowers != nil { + for _, follower := range *data.ThreadFollowers { + if err := ValidateThreadFollowerImportData(&follower); err != nil { + return model.NewAppError("BulkImport", "app.import.validate_direct_post_import_data.thread_follower.error", nil, "", http.StatusBadRequest).Wrap(err) + } + } + } + return nil } @@ -717,6 +733,18 @@ func ValidateEmojiImportData(data *EmojiImportData) *model.AppError { return nil } +func ValidateThreadFollowerImportData(data *ThreadFollowerImportData) *model.AppError { + if data == nil { + return model.NewAppError("BulkImport", "app.import.validate_thread_follower_data.empty.error", nil, "", http.StatusBadRequest) + } + + if data.User == nil || *data.User == "" { + return model.NewAppError("BulkImport", "app.import.validate_thread_follower_data.user_missing.error", nil, "", http.StatusBadRequest) + } + + return nil +} + func isValidTrueOrFalseString(value string) bool { return value == "true" || value == "false" } diff --git a/server/channels/app/imports/import_validators_test.go b/server/channels/app/imports/import_validators_test.go index f975fe87088..17b6647c8cc 100644 --- a/server/channels/app/imports/import_validators_test.go +++ b/server/channels/app/imports/import_validators_test.go @@ -1541,6 +1541,58 @@ func TestImportValidateEmojiImportData(t *testing.T) { } } +func TestImportValidateThreadFollowerImportData(t *testing.T) { + testCases := []struct { + testName string + input *ThreadFollowerImportData + expectError bool + }{ + { + testName: "success", + input: &ThreadFollowerImportData{ + LastViewed: model.NewPointer(int64(0)), + UnreadMentions: model.NewPointer(int64(0)), + User: model.NewPointer("user1"), + }, + expectError: false, + }, + { + testName: "nil", + input: nil, + expectError: true, + }, + { + testName: "nil user", + input: &ThreadFollowerImportData{ + LastViewed: model.NewPointer(int64(0)), + UnreadMentions: model.NewPointer(int64(0)), + User: nil, + }, + expectError: true, + }, + { + testName: "empty user", + input: &ThreadFollowerImportData{ + LastViewed: model.NewPointer(int64(0)), + UnreadMentions: model.NewPointer(int64(0)), + User: model.NewPointer(""), + }, + expectError: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + err := ValidateThreadFollowerImportData(tc.input) + if tc.expectError { + require.NotNil(t, err) + } else { + assert.Nil(t, err) + } + }) + } +} + func checkError(t *testing.T, err *model.AppError) { require.NotNil(t, err, "Should have returned an error.") } diff --git a/server/i18n/en.json b/server/i18n/en.json index 889d77d2d39..624dc154703 100644 --- a/server/i18n/en.json +++ b/server/i18n/en.json @@ -5762,6 +5762,10 @@ "id": "app.import.validate_direct_post_import_data.message_missing.error", "translation": "Missing required direct post property: message" }, + { + "id": "app.import.validate_direct_post_import_data.thread_follower.error", + "translation": "Failed to validate direct post thread follower data." + }, { "id": "app.import.validate_direct_post_import_data.unknown_flagger.error", "translation": "Direct post can only be flagged by members of the channel it is in. \"{{.Username}}\" is not a member." @@ -5818,6 +5822,10 @@ "id": "app.import.validate_post_import_data.team_missing.error", "translation": "Missing required Post property: Team." }, + { + "id": "app.import.validate_post_import_data.thread_follower.error", + "translation": "Failed to validate post thread follower data." + }, { "id": "app.import.validate_post_import_data.user_missing.error", "translation": "Missing required Post property: User." @@ -5950,6 +5958,14 @@ "id": "app.import.validate_team_import_data.type_missing.error", "translation": "Missing required team property: type." }, + { + "id": "app.import.validate_thread_follower_data.empty.error", + "translation": "Import follower data empty." + }, + { + "id": "app.import.validate_thread_follower_data.user_missing.error", + "translation": "Missing required follower property: user." + }, { "id": "app.import.validate_user_channels_import_data.channel_name_missing.error", "translation": "Channel name missing from User's Channel Membership."