mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
* Fix gofmt formatting in content_flagging.go
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Mattermost Build <[email protected]>
Co-authored-by: Hanzo Dev <[email protected]>
3092 lines
111 KiB
Go
3092 lines
111 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func getBaseConfig(th *TestHelper) model.ContentFlaggingSettingsRequest {
|
|
config := model.ContentFlaggingSettingsRequest{}
|
|
config.SetDefaults()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(true)
|
|
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id}
|
|
config.ReviewerSettings.ReviewerSettings.TeamAdminsAsReviewers = model.NewPointer(false)
|
|
config.ReviewerSettings.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(false)
|
|
config.AdditionalSettings.ReporterCommentRequired = model.NewPointer(false)
|
|
config.AdditionalSettings.HideFlaggedContent = model.NewPointer(false)
|
|
config.AdditionalSettings.Reasons = &[]string{"spam", "harassment", "inappropriate"}
|
|
return config
|
|
}
|
|
|
|
func setBaseConfig(th *TestHelper) *model.AppError {
|
|
appErr := th.App.SaveContentFlaggingConfig(getBaseConfig(th))
|
|
if appErr != nil {
|
|
return appErr
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func searchPropertyValue(t *testing.T, th *TestHelper, postId, fieldName string) []*model.PropertyValue {
|
|
t.Helper()
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
values, err := th.Server.propertyAccessService.SearchPropertyValues("", groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{postId},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[fieldName].ID,
|
|
})
|
|
require.NoError(t, err)
|
|
return values
|
|
}
|
|
|
|
func setupFlaggedPost(t *testing.T, th *TestHelper) *model.Post {
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
flagData := model.FlagContentRequest{
|
|
Reason: "spam",
|
|
Comment: "This is spam content",
|
|
}
|
|
|
|
appErr := th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
|
require.Nil(t, appErr)
|
|
|
|
time.Sleep(2 * time.Second)
|
|
|
|
return post
|
|
}
|
|
|
|
func TestContentFlaggingEnabledForTeam(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t)
|
|
|
|
t.Run("should return true for common reviewers", func(t *testing.T) {
|
|
config := model.ContentFlaggingSettingsRequest{
|
|
ReviewerSettings: &model.ReviewSettingsRequest{
|
|
ReviewerSettings: model.ReviewerSettings{
|
|
CommonReviewers: model.NewPointer(true),
|
|
},
|
|
ReviewerIDsSettings: model.ReviewerIDsSettings{
|
|
CommonReviewerIds: []string{"reviewer_user_id_1", "reviewer_user_id_2"},
|
|
},
|
|
},
|
|
}
|
|
config.SetDefaults()
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
status, appErr := th.App.ContentFlaggingEnabledForTeam("team1")
|
|
require.Nil(t, appErr)
|
|
require.True(t, status, "expected team post reporting feature to be enabled for common reviewers")
|
|
})
|
|
|
|
t.Run("should return true when configured for specified team", func(t *testing.T) {
|
|
config := model.ContentFlaggingSettingsRequest{
|
|
ReviewerSettings: &model.ReviewSettingsRequest{
|
|
ReviewerSettings: model.ReviewerSettings{
|
|
CommonReviewers: model.NewPointer(false),
|
|
},
|
|
ReviewerIDsSettings: model.ReviewerIDsSettings{
|
|
TeamReviewersSetting: map[string]*model.TeamReviewerSetting{
|
|
"team1": {
|
|
Enabled: model.NewPointer(true),
|
|
ReviewerIds: []string{"reviewer_user_id_1"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
config.SetDefaults()
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
status, appErr := th.App.ContentFlaggingEnabledForTeam("team1")
|
|
require.Nil(t, appErr)
|
|
require.True(t, status, "expected team post reporting feature to be disabled for team without reviewers")
|
|
})
|
|
|
|
t.Run("should return true when using Additional Reviewers", func(t *testing.T) {
|
|
config := model.ContentFlaggingSettingsRequest{
|
|
ReviewerSettings: &model.ReviewSettingsRequest{
|
|
ReviewerSettings: model.ReviewerSettings{
|
|
CommonReviewers: model.NewPointer(false),
|
|
TeamAdminsAsReviewers: model.NewPointer(true),
|
|
},
|
|
ReviewerIDsSettings: model.ReviewerIDsSettings{
|
|
TeamReviewersSetting: map[string]*model.TeamReviewerSetting{
|
|
"team1": {
|
|
Enabled: model.NewPointer(true),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
config.SetDefaults()
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
status, appErr := th.App.ContentFlaggingEnabledForTeam("team1")
|
|
require.Nil(t, appErr)
|
|
require.True(t, status)
|
|
|
|
config.ReviewerSettings.TeamAdminsAsReviewers = model.NewPointer(false)
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(true)
|
|
appErr = th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
status, appErr = th.App.ContentFlaggingEnabledForTeam("team1")
|
|
require.Nil(t, appErr)
|
|
require.True(t, status)
|
|
|
|
config.ReviewerSettings.TeamAdminsAsReviewers = model.NewPointer(true)
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(true)
|
|
appErr = th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
status, appErr = th.App.ContentFlaggingEnabledForTeam("team1")
|
|
require.Nil(t, appErr)
|
|
require.True(t, status)
|
|
})
|
|
|
|
t.Run("should return true for default state", func(t *testing.T) {
|
|
config := model.ContentFlaggingSettingsRequest{}
|
|
config.SetDefaults()
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
status, appErr := th.App.ContentFlaggingEnabledForTeam("team1")
|
|
require.Nil(t, appErr)
|
|
require.True(t, status, "expected team post reporting feature to be enabled for common reviewers")
|
|
})
|
|
}
|
|
|
|
func TestAssignFlaggedPostReviewer(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
|
|
t.Run("should successfully assign reviewer to pending flagged post", func(t *testing.T) {
|
|
require.Nil(t, setBaseConfig(th))
|
|
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
appErr := th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify status was updated to assigned
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, `"`+model.ContentFlaggingStatusAssigned+`"`, string(statusValue.Value))
|
|
|
|
// Verify reviewer property was created
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
reviewerValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameReviewerUserID].ID,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, reviewerValues, 1)
|
|
require.Equal(t, `"`+th.BasicUser.Id+`"`, string(reviewerValues[0].Value))
|
|
})
|
|
|
|
t.Run("should successfully reassign reviewer to already assigned flagged post", func(t *testing.T) {
|
|
require.Nil(t, setBaseConfig(th))
|
|
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
// First assignment
|
|
appErr := th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Second assignment (reassignment)
|
|
appErr = th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser2.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify status remains assigned
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, `"`+model.ContentFlaggingStatusAssigned+`"`, string(statusValue.Value))
|
|
|
|
// Verify reviewer property was updated
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
reviewerValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameReviewerUserID].ID,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, reviewerValues, 1)
|
|
require.Equal(t, `"`+th.BasicUser2.Id+`"`, string(reviewerValues[0].Value))
|
|
})
|
|
|
|
t.Run("should fail when trying to assign reviewer to non-flagged post", func(t *testing.T) {
|
|
require.Nil(t, setBaseConfig(th))
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
appErr := th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, http.StatusNotFound, appErr.StatusCode)
|
|
})
|
|
|
|
t.Run("should handle assignment with same reviewer ID", func(t *testing.T) {
|
|
require.Nil(t, setBaseConfig(th))
|
|
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
// Assign reviewer
|
|
appErr := th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Assign same reviewer again
|
|
appErr = th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify status remains assigned
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, `"`+model.ContentFlaggingStatusAssigned+`"`, string(statusValue.Value))
|
|
|
|
// Verify reviewer property still exists with correct value
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
reviewerValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameReviewerUserID].ID,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, reviewerValues, 1)
|
|
require.Equal(t, `"`+th.BasicUser.Id+`"`, string(reviewerValues[0].Value))
|
|
})
|
|
|
|
t.Run("should handle assignment with empty reviewer ID", func(t *testing.T) {
|
|
require.Nil(t, setBaseConfig(th))
|
|
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
appErr := th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, "", th.SystemAdminUser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify status was updated to assigned
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, `"`+model.ContentFlaggingStatusAssigned+`"`, string(statusValue.Value))
|
|
|
|
// Verify reviewer property was created with empty value
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
reviewerValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameReviewerUserID].ID,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, reviewerValues, 1)
|
|
require.Equal(t, `""`, string(reviewerValues[0].Value))
|
|
})
|
|
|
|
t.Run("should handle assignment with invalid post ID", func(t *testing.T) {
|
|
require.Nil(t, setBaseConfig(th))
|
|
appErr := th.App.AssignFlaggedPostReviewer(th.Context, "invalid_post_id", th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, http.StatusNotFound, appErr.StatusCode)
|
|
})
|
|
|
|
t.Run("should allow assigning reviewer at all stages", func(t *testing.T) {
|
|
require.Nil(t, setBaseConfig(th))
|
|
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
|
|
// Set the status to Assigned
|
|
statusValue.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusAssigned))
|
|
_, err := th.App.PropertyAccessService().UpdatePropertyValue(anonymousCallerId, groupId, statusValue)
|
|
require.NoError(t, err)
|
|
|
|
appErr = th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
statusValue, appErr = th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, `"`+model.ContentFlaggingStatusAssigned+`"`, string(statusValue.Value))
|
|
|
|
// Set the status to Removed
|
|
statusValue.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusRemoved))
|
|
_, err = th.App.PropertyAccessService().UpdatePropertyValue(anonymousCallerId, groupId, statusValue)
|
|
require.NoError(t, err)
|
|
|
|
appErr = th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
statusValue, appErr = th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, `"`+model.ContentFlaggingStatusRemoved+`"`, string(statusValue.Value))
|
|
|
|
// Set the status to Retained
|
|
statusValue.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusRetained))
|
|
_, err = th.App.PropertyAccessService().UpdatePropertyValue(anonymousCallerId, groupId, statusValue)
|
|
require.NoError(t, err)
|
|
|
|
appErr = th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
statusValue, appErr = th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, `"`+model.ContentFlaggingStatusRetained+`"`, string(statusValue.Value))
|
|
})
|
|
}
|
|
|
|
func TestSaveContentFlaggingConfig(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
|
|
t.Run("should save content flagging config successfully", func(t *testing.T) {
|
|
config := model.ContentFlaggingSettingsRequest{
|
|
ContentFlaggingSettingsBase: model.ContentFlaggingSettingsBase{
|
|
EnableContentFlagging: model.NewPointer(true),
|
|
AdditionalSettings: &model.AdditionalContentFlaggingSettings{
|
|
ReporterCommentRequired: model.NewPointer(true),
|
|
HideFlaggedContent: model.NewPointer(false),
|
|
Reasons: &[]string{"spam", "harassment", "inappropriate"},
|
|
},
|
|
},
|
|
ReviewerSettings: &model.ReviewSettingsRequest{
|
|
ReviewerSettings: model.ReviewerSettings{
|
|
CommonReviewers: model.NewPointer(true),
|
|
SystemAdminsAsReviewers: model.NewPointer(true),
|
|
TeamAdminsAsReviewers: model.NewPointer(false),
|
|
},
|
|
ReviewerIDsSettings: model.ReviewerIDsSettings{
|
|
CommonReviewerIds: []string{th.BasicUser.Id, th.BasicUser2.Id},
|
|
},
|
|
},
|
|
}
|
|
config.SetDefaults()
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify system config was updated
|
|
savedConfig := th.App.Config()
|
|
require.Equal(t, *config.EnableContentFlagging, *savedConfig.ContentFlaggingSettings.EnableContentFlagging)
|
|
require.Equal(t, *config.ReviewerSettings.CommonReviewers, *savedConfig.ContentFlaggingSettings.ReviewerSettings.CommonReviewers)
|
|
require.Equal(t, *config.ReviewerSettings.SystemAdminsAsReviewers, *savedConfig.ContentFlaggingSettings.ReviewerSettings.SystemAdminsAsReviewers)
|
|
require.Equal(t, *config.ReviewerSettings.TeamAdminsAsReviewers, *savedConfig.ContentFlaggingSettings.ReviewerSettings.TeamAdminsAsReviewers)
|
|
require.Equal(t, *config.AdditionalSettings.ReporterCommentRequired, *savedConfig.ContentFlaggingSettings.AdditionalSettings.ReporterCommentRequired)
|
|
require.Equal(t, *config.AdditionalSettings.HideFlaggedContent, *savedConfig.ContentFlaggingSettings.AdditionalSettings.HideFlaggedContent)
|
|
require.Equal(t, *config.AdditionalSettings.Reasons, *savedConfig.ContentFlaggingSettings.AdditionalSettings.Reasons)
|
|
|
|
// Verify reviewer IDs were saved separately
|
|
reviewerIDs, appErr := th.App.GetContentFlaggingConfigReviewerIDs()
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, config.ReviewerSettings.CommonReviewerIds, reviewerIDs.CommonReviewerIds)
|
|
})
|
|
|
|
t.Run("should save config with team reviewers", func(t *testing.T) {
|
|
config := model.ContentFlaggingSettingsRequest{
|
|
ContentFlaggingSettingsBase: model.ContentFlaggingSettingsBase{
|
|
EnableContentFlagging: model.NewPointer(true),
|
|
},
|
|
ReviewerSettings: &model.ReviewSettingsRequest{
|
|
ReviewerSettings: model.ReviewerSettings{
|
|
CommonReviewers: model.NewPointer(false),
|
|
SystemAdminsAsReviewers: model.NewPointer(false),
|
|
TeamAdminsAsReviewers: model.NewPointer(false),
|
|
},
|
|
ReviewerIDsSettings: model.ReviewerIDsSettings{
|
|
TeamReviewersSetting: map[string]*model.TeamReviewerSetting{
|
|
th.BasicTeam.Id: {
|
|
Enabled: model.NewPointer(true),
|
|
ReviewerIds: []string{th.BasicUser.Id},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
config.SetDefaults()
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify team reviewers were saved
|
|
reviewerIDs, appErr := th.App.GetContentFlaggingConfigReviewerIDs()
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, reviewerIDs.TeamReviewersSetting)
|
|
|
|
teamSettings := (reviewerIDs.TeamReviewersSetting)[th.BasicTeam.Id]
|
|
require.True(t, *teamSettings.Enabled)
|
|
require.Equal(t, []string{th.BasicUser.Id}, teamSettings.ReviewerIds)
|
|
})
|
|
|
|
t.Run("should handle empty config", func(t *testing.T) {
|
|
config := model.ContentFlaggingSettingsRequest{}
|
|
config.SetDefaults()
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify defaults were applied
|
|
savedConfig := th.App.Config()
|
|
require.NotNil(t, savedConfig.ContentFlaggingSettings.EnableContentFlagging)
|
|
require.NotNil(t, savedConfig.ContentFlaggingSettings.ReviewerSettings.CommonReviewers)
|
|
})
|
|
}
|
|
|
|
func TestGetContentFlaggingConfigReviewerIDs(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
|
|
t.Run("should return reviewer IDs after saving config", func(t *testing.T) {
|
|
config := model.ContentFlaggingSettingsRequest{
|
|
ReviewerSettings: &model.ReviewSettingsRequest{
|
|
ReviewerSettings: model.ReviewerSettings{
|
|
CommonReviewers: model.NewPointer(true),
|
|
},
|
|
ReviewerIDsSettings: model.ReviewerIDsSettings{
|
|
CommonReviewerIds: []string{th.BasicUser.Id, th.BasicUser2.Id},
|
|
},
|
|
},
|
|
}
|
|
config.SetDefaults()
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
reviewerIDs, appErr := th.App.GetContentFlaggingConfigReviewerIDs()
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, reviewerIDs)
|
|
require.Equal(t, []string{th.BasicUser.Id, th.BasicUser2.Id}, reviewerIDs.CommonReviewerIds)
|
|
})
|
|
|
|
t.Run("should return team reviewer settings", func(t *testing.T) {
|
|
config := model.ContentFlaggingSettingsRequest{
|
|
ReviewerSettings: &model.ReviewSettingsRequest{
|
|
ReviewerSettings: model.ReviewerSettings{
|
|
CommonReviewers: model.NewPointer(false),
|
|
},
|
|
ReviewerIDsSettings: model.ReviewerIDsSettings{
|
|
TeamReviewersSetting: map[string]*model.TeamReviewerSetting{
|
|
th.BasicTeam.Id: {
|
|
Enabled: model.NewPointer(true),
|
|
ReviewerIds: []string{th.BasicUser.Id},
|
|
},
|
|
"team2": {
|
|
Enabled: model.NewPointer(false),
|
|
ReviewerIds: []string{th.BasicUser2.Id},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
config.SetDefaults()
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
reviewerIDs, appErr := th.App.GetContentFlaggingConfigReviewerIDs()
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, reviewerIDs.TeamReviewersSetting)
|
|
|
|
teamSettings := reviewerIDs.TeamReviewersSetting
|
|
require.Len(t, teamSettings, 2)
|
|
|
|
// Check first team
|
|
team1Settings := teamSettings[th.BasicTeam.Id]
|
|
require.True(t, *team1Settings.Enabled)
|
|
require.Equal(t, []string{th.BasicUser.Id}, team1Settings.ReviewerIds)
|
|
|
|
// Check second team
|
|
team2Settings := teamSettings["team2"]
|
|
require.False(t, *team2Settings.Enabled)
|
|
require.Equal(t, []string{th.BasicUser2.Id}, team2Settings.ReviewerIds)
|
|
})
|
|
|
|
t.Run("should return empty settings when no config saved", func(t *testing.T) {
|
|
// Clear any existing config by saving empty config
|
|
config := model.ContentFlaggingSettingsRequest{}
|
|
config.SetDefaults()
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
reviewerIDs, appErr := th.App.GetContentFlaggingConfigReviewerIDs()
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, reviewerIDs)
|
|
|
|
// Should have default empty values
|
|
if reviewerIDs.CommonReviewerIds != nil {
|
|
require.Empty(t, reviewerIDs.CommonReviewerIds)
|
|
}
|
|
if reviewerIDs.TeamReviewersSetting != nil {
|
|
require.Empty(t, reviewerIDs.TeamReviewersSetting)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestGetContentReviewChannels(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
|
|
getBaseConfig := func() model.ContentFlaggingSettingsRequest {
|
|
config := model.ContentFlaggingSettingsRequest{
|
|
ReviewerSettings: &model.ReviewSettingsRequest{
|
|
ReviewerSettings: model.ReviewerSettings{
|
|
TeamAdminsAsReviewers: model.NewPointer(true),
|
|
SystemAdminsAsReviewers: model.NewPointer(true),
|
|
CommonReviewers: model.NewPointer(true),
|
|
},
|
|
ReviewerIDsSettings: model.ReviewerIDsSettings{
|
|
CommonReviewerIds: []string{th.BasicUser.Id, th.BasicUser2.Id},
|
|
},
|
|
},
|
|
}
|
|
config.SetDefaults()
|
|
return config
|
|
}
|
|
|
|
t.Run("should return channels for common reviewers", func(t *testing.T) {
|
|
appErr := th.App.SaveContentFlaggingConfig(getBaseConfig())
|
|
require.Nil(t, appErr)
|
|
|
|
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, contentReviewBot)
|
|
|
|
channels, appErr := th.App.getContentReviewChannels(th.Context, th.BasicTeam.Id, contentReviewBot.UserId)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, channels, 2)
|
|
|
|
for _, channel := range channels {
|
|
require.Equal(t, model.ChannelTypeDirect, channel.Type)
|
|
otherUserId := channel.GetOtherUserIdForDM(contentReviewBot.UserId)
|
|
require.True(t, otherUserId == th.BasicUser.Id || otherUserId == th.BasicUser2.Id)
|
|
}
|
|
})
|
|
|
|
t.Run("should return channels for system admins as additional reviewers", func(t *testing.T) {
|
|
config := getBaseConfig()
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(true)
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
// Sysadmin explicitly need to be a team member to be returned as reviewer
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, th.SystemAdminUser.Id, "")
|
|
defer func() {
|
|
_ = th.App.RemoveUserFromTeam(th.Context, th.BasicTeam.Id, th.SystemAdminUser.Id, "")
|
|
}()
|
|
|
|
require.Nil(t, appErr)
|
|
|
|
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, contentReviewBot)
|
|
|
|
channels, appErr := th.App.getContentReviewChannels(th.Context, th.BasicTeam.Id, contentReviewBot.UserId)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, channels, 3)
|
|
|
|
require.Equal(t, model.ChannelTypeDirect, channels[0].Type)
|
|
require.Equal(t, model.ChannelTypeDirect, channels[1].Type)
|
|
require.Equal(t, model.ChannelTypeDirect, channels[2].Type)
|
|
|
|
reviewerIds := []string{
|
|
channels[0].GetOtherUserIdForDM(contentReviewBot.UserId),
|
|
channels[1].GetOtherUserIdForDM(contentReviewBot.UserId),
|
|
channels[2].GetOtherUserIdForDM(contentReviewBot.UserId),
|
|
}
|
|
require.Contains(t, reviewerIds, th.BasicUser.Id)
|
|
require.Contains(t, reviewerIds, th.BasicUser2.Id)
|
|
require.Contains(t, reviewerIds, th.SystemAdminUser.Id)
|
|
})
|
|
|
|
t.Run("should return channels for team admins as additional reviewers", func(t *testing.T) {
|
|
config := getBaseConfig()
|
|
config.ReviewerSettings.TeamAdminsAsReviewers = model.NewPointer(true)
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
// Create a new user and make them team admin
|
|
teamAdmin := th.CreateUser(t)
|
|
defer func() {
|
|
_ = th.App.PermanentDeleteUser(th.Context, teamAdmin)
|
|
}()
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, teamAdmin.Id, "")
|
|
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.UpdateTeamMemberRoles(th.Context, th.BasicTeam.Id, teamAdmin.Id, model.TeamUserRoleId+" "+model.TeamAdminRoleId)
|
|
require.Nil(t, appErr)
|
|
|
|
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, contentReviewBot)
|
|
|
|
channels, appErr := th.App.getContentReviewChannels(th.Context, th.BasicTeam.Id, contentReviewBot.UserId)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, channels, 3)
|
|
|
|
require.Equal(t, model.ChannelTypeDirect, channels[0].Type)
|
|
require.Equal(t, model.ChannelTypeDirect, channels[1].Type)
|
|
require.Equal(t, model.ChannelTypeDirect, channels[2].Type)
|
|
|
|
reviewerIds := []string{
|
|
channels[0].GetOtherUserIdForDM(contentReviewBot.UserId),
|
|
channels[1].GetOtherUserIdForDM(contentReviewBot.UserId),
|
|
channels[2].GetOtherUserIdForDM(contentReviewBot.UserId),
|
|
}
|
|
require.Contains(t, reviewerIds, th.BasicUser.Id)
|
|
require.Contains(t, reviewerIds, th.BasicUser2.Id)
|
|
require.Contains(t, reviewerIds, teamAdmin.Id)
|
|
})
|
|
|
|
t.Run("should return channels for team reviewers", func(t *testing.T) {
|
|
config := getBaseConfig()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(false)
|
|
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id}
|
|
|
|
config.ReviewerSettings.TeamAdminsAsReviewers = model.NewPointer(false)
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(false)
|
|
|
|
config.ReviewerSettings.TeamReviewersSetting = map[string]*model.TeamReviewerSetting{
|
|
th.BasicTeam.Id: {
|
|
Enabled: model.NewPointer(true),
|
|
ReviewerIds: []string{th.BasicUser2.Id},
|
|
},
|
|
}
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, contentReviewBot)
|
|
|
|
channels, appErr := th.App.getContentReviewChannels(th.Context, th.BasicTeam.Id, contentReviewBot.UserId)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, channels, 1)
|
|
|
|
require.Equal(t, model.ChannelTypeDirect, channels[0].Type)
|
|
otherUserId := channels[0].GetOtherUserIdForDM(contentReviewBot.UserId)
|
|
require.Equal(t, th.BasicUser2.Id, otherUserId)
|
|
})
|
|
|
|
t.Run("should not return channels for team reviewers when disabled for the team", func(t *testing.T) {
|
|
config := getBaseConfig()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(false)
|
|
config.ReviewerSettings.TeamReviewersSetting = map[string]*model.TeamReviewerSetting{
|
|
th.BasicTeam.Id: {
|
|
Enabled: model.NewPointer(false),
|
|
ReviewerIds: []string{th.BasicUser.Id},
|
|
},
|
|
}
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, contentReviewBot)
|
|
|
|
channels, appErr := th.App.getContentReviewChannels(th.Context, th.BasicTeam.Id, contentReviewBot.UserId)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, channels, 0)
|
|
})
|
|
|
|
t.Run("should return channels for additional reviewers with team reviewers", func(t *testing.T) {
|
|
config := getBaseConfig()
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(true)
|
|
config.ReviewerSettings.TeamAdminsAsReviewers = model.NewPointer(true)
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(false)
|
|
|
|
config.ReviewerSettings.TeamReviewersSetting = map[string]*model.TeamReviewerSetting{
|
|
th.BasicTeam.Id: {
|
|
Enabled: model.NewPointer(true),
|
|
ReviewerIds: []string{th.BasicUser2.Id},
|
|
},
|
|
}
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, th.SystemAdminUser.Id, "")
|
|
defer func() {
|
|
_ = th.App.RemoveUserFromTeam(th.Context, th.BasicTeam.Id, th.SystemAdminUser.Id, "")
|
|
}()
|
|
require.Nil(t, appErr)
|
|
|
|
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
|
require.Nil(t, appErr)
|
|
require.NotNil(t, contentReviewBot)
|
|
|
|
channels, appErr := th.App.getContentReviewChannels(th.Context, th.BasicTeam.Id, contentReviewBot.UserId)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, channels, 2)
|
|
|
|
reviewerIds := []string{
|
|
channels[0].GetOtherUserIdForDM(contentReviewBot.UserId),
|
|
channels[1].GetOtherUserIdForDM(contentReviewBot.UserId),
|
|
}
|
|
|
|
require.Contains(t, reviewerIds, th.BasicUser2.Id)
|
|
require.Contains(t, reviewerIds, th.SystemAdminUser.Id)
|
|
})
|
|
}
|
|
|
|
func TestGetReviewersForTeam(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
|
|
t.Run("should return common reviewers", func(t *testing.T) {
|
|
config := &model.ContentFlaggingSettingsRequest{}
|
|
config.SetDefaults()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(true)
|
|
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id, th.BasicUser2.Id}
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(true)
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(*config)
|
|
require.Nil(t, appErr)
|
|
|
|
reviewers, appErr := th.App.getReviewersForTeam(th.BasicTeam.Id, true)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 2)
|
|
require.Contains(t, reviewers, th.BasicUser.Id)
|
|
require.Contains(t, reviewers, th.BasicUser2.Id)
|
|
})
|
|
|
|
t.Run("should return system admins as additional reviewers", func(t *testing.T) {
|
|
config := &model.ContentFlaggingSettingsRequest{}
|
|
config.SetDefaults()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(true)
|
|
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id}
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(true)
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(*config)
|
|
require.Nil(t, appErr)
|
|
|
|
// Sysadmin explicitly need to be a team member to be returned as reviewer
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, th.SystemAdminUser.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
reviewers, appErr := th.App.getReviewersForTeam(th.BasicTeam.Id, true)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 2)
|
|
require.Contains(t, reviewers, th.BasicUser.Id)
|
|
require.Contains(t, reviewers, th.SystemAdminUser.Id)
|
|
|
|
config.ReviewerSettings.CommonReviewerIds = []string{}
|
|
appErr = th.App.SaveContentFlaggingConfig(*config)
|
|
require.Nil(t, appErr)
|
|
|
|
reviewers, appErr = th.App.getReviewersForTeam(th.BasicTeam.Id, true)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 1)
|
|
require.Contains(t, reviewers, th.SystemAdminUser.Id)
|
|
|
|
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id}
|
|
appErr = th.App.SaveContentFlaggingConfig(*config)
|
|
require.Nil(t, appErr)
|
|
|
|
// If sysadmin is not a team member, they should not be returned as a reviewer
|
|
appErr = th.App.RemoveUserFromTeam(th.Context, th.BasicTeam.Id, th.SystemAdminUser.Id, "")
|
|
require.Nil(t, appErr)
|
|
reviewers, appErr = th.App.getReviewersForTeam(th.BasicTeam.Id, true)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 1)
|
|
require.Contains(t, reviewers, th.BasicUser.Id)
|
|
})
|
|
|
|
t.Run("should return team admins as additional reviewers", func(t *testing.T) {
|
|
config := &model.ContentFlaggingSettingsRequest{}
|
|
config.SetDefaults()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(true)
|
|
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id}
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(true)
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(*config)
|
|
require.Nil(t, appErr)
|
|
|
|
// Create a new user and make them team admin
|
|
teamAdmin := th.CreateUser(t)
|
|
defer func() {
|
|
_ = th.App.PermanentDeleteUser(th.Context, teamAdmin)
|
|
}()
|
|
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, teamAdmin.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.UpdateTeamMemberRoles(th.Context, th.BasicTeam.Id, teamAdmin.Id, model.TeamUserRoleId+" "+model.TeamAdminRoleId)
|
|
require.Nil(t, appErr)
|
|
|
|
reviewers, appErr := th.App.getReviewersForTeam(th.BasicTeam.Id, true)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 2)
|
|
require.Contains(t, reviewers, th.BasicUser.Id)
|
|
require.Contains(t, reviewers, teamAdmin.Id)
|
|
|
|
config.ReviewerSettings.CommonReviewerIds = []string{}
|
|
appErr = th.App.SaveContentFlaggingConfig(*config)
|
|
require.Nil(t, appErr)
|
|
|
|
reviewers, appErr = th.App.getReviewersForTeam(th.BasicTeam.Id, true)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 1)
|
|
require.Contains(t, reviewers, teamAdmin.Id)
|
|
|
|
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id}
|
|
appErr = th.App.SaveContentFlaggingConfig(*config)
|
|
require.Nil(t, appErr)
|
|
|
|
// If team admin is not a team member, they should not be returned as a reviewer
|
|
appErr = th.App.RemoveUserFromTeam(th.Context, th.BasicTeam.Id, teamAdmin.Id, "")
|
|
require.Nil(t, appErr)
|
|
reviewers, appErr = th.App.getReviewersForTeam(th.BasicTeam.Id, true)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 1)
|
|
require.Contains(t, reviewers, th.BasicUser.Id)
|
|
})
|
|
|
|
t.Run("should return team reviewers", func(t *testing.T) {
|
|
team2 := th.CreateTeam(t)
|
|
config := &model.ContentFlaggingSettingsRequest{}
|
|
config.SetDefaults()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(false)
|
|
config.ReviewerSettings.TeamReviewersSetting = map[string]*model.TeamReviewerSetting{
|
|
th.BasicTeam.Id: {
|
|
Enabled: model.NewPointer(true),
|
|
ReviewerIds: []string{th.BasicUser2.Id},
|
|
},
|
|
}
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(*config)
|
|
require.Nil(t, appErr)
|
|
|
|
// Reviewers configured for th.BasicTeam
|
|
reviewers, appErr := th.App.getReviewersForTeam(th.BasicTeam.Id, true)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 1)
|
|
require.Contains(t, reviewers, th.BasicUser2.Id)
|
|
|
|
// NO reviewers configured for team2
|
|
reviewers, appErr = th.App.getReviewersForTeam(team2.Id, true)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 0)
|
|
})
|
|
|
|
t.Run("should not return reviewers when disabled for the team", func(t *testing.T) {
|
|
config := &model.ContentFlaggingSettingsRequest{}
|
|
config.SetDefaults()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(false)
|
|
config.ReviewerSettings.TeamReviewersSetting = map[string]*model.TeamReviewerSetting{
|
|
th.BasicTeam.Id: {
|
|
Enabled: model.NewPointer(false),
|
|
ReviewerIds: []string{th.BasicUser.Id},
|
|
},
|
|
}
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(*config)
|
|
require.Nil(t, appErr)
|
|
|
|
reviewers, appErr := th.App.getReviewersForTeam(th.BasicTeam.Id, true)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 0)
|
|
})
|
|
|
|
t.Run("should return additional reviewers with team reviewers", func(t *testing.T) {
|
|
config := &model.ContentFlaggingSettingsRequest{}
|
|
config.SetDefaults()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(false)
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(true)
|
|
config.ReviewerSettings.TeamAdminsAsReviewers = model.NewPointer(true)
|
|
config.ReviewerSettings.TeamReviewersSetting = map[string]*model.TeamReviewerSetting{
|
|
th.BasicTeam.Id: {
|
|
Enabled: model.NewPointer(true),
|
|
ReviewerIds: []string{th.BasicUser2.Id},
|
|
},
|
|
}
|
|
appErr := th.App.SaveContentFlaggingConfig(*config)
|
|
require.Nil(t, appErr)
|
|
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, th.SystemAdminUser.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
reviewers, appErr := th.App.getReviewersForTeam(th.BasicTeam.Id, true)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 2)
|
|
require.Contains(t, reviewers, th.BasicUser2.Id)
|
|
require.Contains(t, reviewers, th.SystemAdminUser.Id)
|
|
})
|
|
|
|
t.Run("should return unique reviewers", func(t *testing.T) {
|
|
config := &model.ContentFlaggingSettingsRequest{}
|
|
config.SetDefaults()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(true)
|
|
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id, th.SystemAdminUser.Id}
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(true)
|
|
appErr := th.App.SaveContentFlaggingConfig(*config)
|
|
require.Nil(t, appErr)
|
|
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, th.SystemAdminUser.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
reviewers, appErr := th.App.getReviewersForTeam(th.BasicTeam.Id, true)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 2)
|
|
require.Contains(t, reviewers, th.BasicUser.Id)
|
|
require.Contains(t, reviewers, th.SystemAdminUser.Id)
|
|
})
|
|
}
|
|
|
|
func TestCanFlagPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
|
|
t.Run("should be able to flag post which has not already been flagged", func(t *testing.T) {
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
appErr = th.App.canFlagPost(groupId, post.Id, "en")
|
|
require.Nil(t, appErr)
|
|
})
|
|
|
|
t.Run("should not be able to flag post which has already been flagged", func(t *testing.T) {
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
statusField, err := th.Server.propertyAccessService.GetPropertyFieldByName(anonymousCallerId, groupId, "", ContentFlaggingPropertyNameStatus)
|
|
require.NoError(t, err)
|
|
|
|
propertyValue, err := th.Server.propertyAccessService.CreatePropertyValue(anonymousCallerId, &model.PropertyValue{
|
|
TargetID: post.Id,
|
|
GroupID: groupId,
|
|
FieldID: statusField.ID,
|
|
TargetType: "post",
|
|
Value: json.RawMessage(`"` + model.ContentFlaggingStatusPending + `"`),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Can't fleg when post already flagged in pending status
|
|
appErr = th.App.canFlagPost(groupId, post.Id, "en")
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, "Cannot quarantine this post as it is already quarantined for review.", appErr.Id)
|
|
|
|
// Can't fleg when post already flagged in assigned status
|
|
propertyValue.Value = json.RawMessage(`"` + model.ContentFlaggingStatusAssigned + `"`)
|
|
_, err = th.Server.propertyAccessService.UpdatePropertyValue(anonymousCallerId, groupId, propertyValue)
|
|
require.NoError(t, err)
|
|
|
|
appErr = th.App.canFlagPost(groupId, post.Id, "en")
|
|
require.NotNil(t, appErr)
|
|
|
|
// Can't fleg when post already flagged in retained status
|
|
propertyValue.Value = json.RawMessage(`"` + model.ContentFlaggingStatusRetained + `"`)
|
|
_, err = th.Server.propertyAccessService.UpdatePropertyValue(anonymousCallerId, groupId, propertyValue)
|
|
require.NoError(t, err)
|
|
|
|
appErr = th.App.canFlagPost(groupId, post.Id, "en")
|
|
require.NotNil(t, appErr)
|
|
|
|
// Can't fleg when post already flagged in removed status
|
|
propertyValue.Value = json.RawMessage(`"` + model.ContentFlaggingStatusRemoved + `"`)
|
|
_, err = th.Server.propertyAccessService.UpdatePropertyValue(anonymousCallerId, groupId, propertyValue)
|
|
require.NoError(t, err)
|
|
|
|
appErr = th.App.canFlagPost(groupId, post.Id, "en")
|
|
require.NotNil(t, appErr)
|
|
})
|
|
}
|
|
|
|
func TestFlagPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
getBaseConfig := func() model.ContentFlaggingSettingsRequest {
|
|
cfg := model.ContentFlaggingSettingsRequest{}
|
|
cfg.SetDefaults()
|
|
cfg.ReviewerSettings.CommonReviewers = model.NewPointer(true)
|
|
cfg.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id}
|
|
cfg.AdditionalSettings.ReporterCommentRequired = model.NewPointer(false)
|
|
cfg.AdditionalSettings.HideFlaggedContent = model.NewPointer(false)
|
|
cfg.AdditionalSettings.Reasons = &[]string{"spam", "harassment", "inappropriate"}
|
|
return cfg
|
|
}
|
|
|
|
t.Run("should successfully flag a post with valid data", func(t *testing.T) {
|
|
appErr := th.App.SaveContentFlaggingConfig(getBaseConfig())
|
|
require.Nil(t, appErr)
|
|
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
flagData := model.FlagContentRequest{
|
|
Reason: "spam",
|
|
Comment: "This is spam content",
|
|
}
|
|
|
|
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify property values were created
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
// Check status property
|
|
statusValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[ContentFlaggingPropertyNameStatus].ID,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, statusValues, 1)
|
|
require.Equal(t, `"`+model.ContentFlaggingStatusPending+`"`, string(statusValues[0].Value))
|
|
|
|
// Check reporting user property
|
|
userValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameReportingUserID].ID,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, userValues, 1)
|
|
require.Equal(t, `"`+th.BasicUser2.Id+`"`, string(userValues[0].Value))
|
|
|
|
// Check reason property
|
|
reasonValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameReportingReason].ID,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, reasonValues, 1)
|
|
require.Equal(t, `"spam"`, string(reasonValues[0].Value))
|
|
|
|
// Check comment property
|
|
commentValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameReportingComment].ID,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, commentValues, 1)
|
|
require.Equal(t, `"This is spam content"`, string(commentValues[0].Value))
|
|
})
|
|
|
|
t.Run("should fail with invalid reason", func(t *testing.T) {
|
|
appErr := th.App.SaveContentFlaggingConfig(getBaseConfig())
|
|
require.Nil(t, appErr)
|
|
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
flagData := model.FlagContentRequest{
|
|
Reason: "invalid_reason",
|
|
Comment: "This is spam content",
|
|
}
|
|
|
|
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, "api.data_spillage.error.reason_invalid", appErr.Id)
|
|
})
|
|
|
|
t.Run("should fail when comment is required but not provided", func(t *testing.T) {
|
|
config := getBaseConfig()
|
|
config.AdditionalSettings.ReporterCommentRequired = model.NewPointer(true)
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
flagData := model.FlagContentRequest{
|
|
Reason: "spam",
|
|
Comment: "",
|
|
}
|
|
|
|
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
|
require.NotNil(t, appErr)
|
|
})
|
|
|
|
t.Run("should fail when trying to flag already flagged post", func(t *testing.T) {
|
|
appErr := th.App.SaveContentFlaggingConfig(getBaseConfig())
|
|
require.Nil(t, appErr)
|
|
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
flagData := model.FlagContentRequest{
|
|
Reason: "spam",
|
|
Comment: "\"This is spam content\"",
|
|
}
|
|
|
|
// Flag the post first time
|
|
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
|
require.Nil(t, appErr)
|
|
|
|
// Try to flag the same post again
|
|
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, "Cannot quarantine this post as it is already quarantined for review.", appErr.Id)
|
|
})
|
|
|
|
t.Run("should hide flagged content when configured", func(t *testing.T) {
|
|
config := getBaseConfig()
|
|
config.AdditionalSettings.HideFlaggedContent = model.NewPointer(true)
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
flagData := model.FlagContentRequest{
|
|
Reason: "spam",
|
|
Comment: "\"This is spam content\"",
|
|
}
|
|
|
|
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify post was deleted
|
|
deletedPost, appErr := th.App.GetSinglePost(th.Context, post.Id, false)
|
|
require.NotNil(t, appErr)
|
|
require.Nil(t, deletedPost)
|
|
})
|
|
|
|
t.Run("should create content review post for reviewers", func(t *testing.T) {
|
|
appErr := th.App.SaveContentFlaggingConfig(getBaseConfig())
|
|
require.Nil(t, appErr)
|
|
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
flagData := model.FlagContentRequest{
|
|
Reason: "harassment",
|
|
Comment: "\"This is harassment\"",
|
|
}
|
|
|
|
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
|
|
|
require.Nil(t, appErr)
|
|
|
|
// The reviewer posts are created async in a go routine. Wait for a short time to allow it to complete.
|
|
// 2 seconds is the minimum time when the test consistently passes locally and in CI.
|
|
time.Sleep(5 * time.Second)
|
|
|
|
// Get the content review bot
|
|
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
|
require.Nil(t, appErr)
|
|
|
|
// Get direct channel between reviewer and bot
|
|
dmChannel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, contentReviewBot.UserId)
|
|
require.Nil(t, appErr)
|
|
|
|
// Check if review post was created in the DM channel
|
|
posts, appErr := th.App.GetPostsPage(th.Context, model.GetPostsOptions{
|
|
ChannelId: dmChannel.Id,
|
|
Page: 0,
|
|
PerPage: 10,
|
|
})
|
|
require.Nil(t, appErr)
|
|
require.NotEmpty(t, posts.Posts)
|
|
|
|
// Find the content review post
|
|
var reviewPost *model.Post
|
|
for _, p := range posts.Posts {
|
|
if p.Type == "custom_spillage_report" {
|
|
reviewPost = p
|
|
break
|
|
}
|
|
}
|
|
require.NotNil(t, reviewPost)
|
|
})
|
|
|
|
t.Run("should work with empty comment when not required", func(t *testing.T) {
|
|
appErr := th.App.SaveContentFlaggingConfig(getBaseConfig())
|
|
require.Nil(t, appErr)
|
|
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
flagData := model.FlagContentRequest{
|
|
Reason: "inappropriate",
|
|
Comment: "",
|
|
}
|
|
|
|
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify property values were created with empty comment
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
commentValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameReportingComment].ID,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, commentValues, 1)
|
|
require.Equal(t, `""`, string(commentValues[0].Value))
|
|
})
|
|
|
|
t.Run("should set reporting time property", func(t *testing.T) {
|
|
appErr := th.App.SaveContentFlaggingConfig(getBaseConfig())
|
|
require.Nil(t, appErr)
|
|
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
flagData := model.FlagContentRequest{
|
|
Reason: "spam",
|
|
Comment: "\"Test comment\"",
|
|
}
|
|
|
|
beforeTime := model.GetMillis()
|
|
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
|
|
|
afterTime := model.GetMillis()
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify reporting time property was set
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
timeValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameReportingTime].ID,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, timeValues, 1)
|
|
|
|
var reportingTime int64
|
|
err = json.Unmarshal(timeValues[0].Value, &reportingTime)
|
|
require.NoError(t, err)
|
|
require.True(t, reportingTime >= beforeTime && reportingTime <= afterTime)
|
|
})
|
|
}
|
|
|
|
func TestSearchReviewers(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
|
|
getBaseConfig := func() model.ContentFlaggingSettingsRequest {
|
|
config := model.ContentFlaggingSettingsRequest{}
|
|
config.SetDefaults()
|
|
return config
|
|
}
|
|
|
|
allProfilesSanitized := func(reviewers []*model.User) {
|
|
for _, reviewer := range reviewers {
|
|
require.Empty(t, reviewer.LastPasswordUpdate)
|
|
require.Empty(t, reviewer.NotifyProps)
|
|
}
|
|
}
|
|
|
|
t.Run("should return common reviewers when searching", func(t *testing.T) {
|
|
config := getBaseConfig()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(true)
|
|
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id, th.BasicUser2.Id}
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(false)
|
|
config.ReviewerSettings.TeamAdminsAsReviewers = model.NewPointer(false)
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
// Search for users by username
|
|
reviewers, appErr := th.App.SearchReviewers(th.Context, th.BasicUser.Username, th.BasicTeam.Id)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 1)
|
|
require.Equal(t, th.BasicUser.Id, reviewers[0].Id)
|
|
|
|
// Search for users by partial username
|
|
reviewers, appErr = th.App.SearchReviewers(th.Context, th.BasicUser.Username[:3], th.BasicTeam.Id)
|
|
require.Nil(t, appErr)
|
|
require.True(t, len(reviewers) >= 1)
|
|
allProfilesSanitized(reviewers)
|
|
|
|
// Verify the basic user is in the results
|
|
found := false
|
|
for _, reviewer := range reviewers {
|
|
if reviewer.Id == th.BasicUser.Id {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
require.True(t, found)
|
|
})
|
|
|
|
t.Run("should return team reviewers when common reviewers disabled", func(t *testing.T) {
|
|
config := getBaseConfig()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(false)
|
|
config.ReviewerSettings.TeamReviewersSetting = map[string]*model.TeamReviewerSetting{
|
|
th.BasicTeam.Id: {
|
|
Enabled: model.NewPointer(true),
|
|
ReviewerIds: []string{th.BasicUser2.Id},
|
|
},
|
|
}
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(false)
|
|
config.ReviewerSettings.TeamAdminsAsReviewers = model.NewPointer(false)
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
// Search for team reviewer
|
|
reviewers, appErr := th.App.SearchReviewers(th.Context, th.BasicUser2.Username, th.BasicTeam.Id)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 1)
|
|
require.Equal(t, th.BasicUser2.Id, reviewers[0].Id)
|
|
allProfilesSanitized(reviewers)
|
|
|
|
// Search should not return users not configured as team reviewers
|
|
reviewers, appErr = th.App.SearchReviewers(th.Context, th.BasicUser.Username, th.BasicTeam.Id)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 0)
|
|
})
|
|
|
|
t.Run("should return system admins as additional reviewers", func(t *testing.T) {
|
|
config := getBaseConfig()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(false)
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(true)
|
|
config.ReviewerSettings.TeamAdminsAsReviewers = model.NewPointer(false)
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
// Add system admin to team
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, th.SystemAdminUser.Id, "")
|
|
require.Nil(t, appErr)
|
|
defer func() {
|
|
_ = th.App.RemoveUserFromTeam(th.Context, th.BasicTeam.Id, th.SystemAdminUser.Id, "")
|
|
}()
|
|
|
|
// Search for system admin
|
|
reviewers, appErr := th.App.SearchReviewers(th.Context, th.SystemAdminUser.Username, th.BasicTeam.Id)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 1)
|
|
require.Equal(t, th.SystemAdminUser.Id, reviewers[0].Id)
|
|
allProfilesSanitized(reviewers)
|
|
})
|
|
|
|
t.Run("should return team admins as additional reviewers", func(t *testing.T) {
|
|
config := getBaseConfig()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(false)
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(false)
|
|
config.ReviewerSettings.TeamAdminsAsReviewers = model.NewPointer(true)
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
// Create a new user and make them team admin
|
|
teamAdmin := th.CreateUser(t)
|
|
defer func() {
|
|
_ = th.App.PermanentDeleteUser(th.Context, teamAdmin)
|
|
}()
|
|
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, teamAdmin.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.UpdateTeamMemberRoles(th.Context, th.BasicTeam.Id, teamAdmin.Id, model.TeamUserRoleId+" "+model.TeamAdminRoleId)
|
|
require.Nil(t, appErr)
|
|
|
|
// Search for team admin
|
|
reviewers, appErr := th.App.SearchReviewers(th.Context, teamAdmin.Username, th.BasicTeam.Id)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 1)
|
|
require.Equal(t, teamAdmin.Id, reviewers[0].Id)
|
|
allProfilesSanitized(reviewers)
|
|
})
|
|
|
|
t.Run("should return combined reviewers from multiple sources", func(t *testing.T) {
|
|
config := getBaseConfig()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(true)
|
|
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id}
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(true)
|
|
config.ReviewerSettings.TeamAdminsAsReviewers = model.NewPointer(true)
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
// Add system admin to team
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, th.SystemAdminUser.Id, "")
|
|
require.Nil(t, appErr)
|
|
defer func() {
|
|
_ = th.App.RemoveUserFromTeam(th.Context, th.BasicTeam.Id, th.SystemAdminUser.Id, "")
|
|
}()
|
|
|
|
// Create a team admin
|
|
teamAdmin := th.CreateUser(t)
|
|
defer func() {
|
|
_ = th.App.PermanentDeleteUser(th.Context, teamAdmin)
|
|
}()
|
|
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, teamAdmin.Id, "")
|
|
require.Nil(t, appErr)
|
|
|
|
_, appErr = th.App.UpdateTeamMemberRoles(th.Context, th.BasicTeam.Id, teamAdmin.Id, model.TeamUserRoleId+" "+model.TeamAdminRoleId)
|
|
require.Nil(t, appErr)
|
|
|
|
// Search with empty term should return all reviewers
|
|
reviewers, appErr := th.App.SearchReviewers(th.Context, "", th.BasicTeam.Id)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, 3, len(reviewers))
|
|
allProfilesSanitized(reviewers)
|
|
|
|
// Verify all expected reviewers are present
|
|
reviewerIds := make([]string, len(reviewers))
|
|
for i, reviewer := range reviewers {
|
|
reviewerIds[i] = reviewer.Id
|
|
}
|
|
require.Contains(t, reviewerIds, th.BasicUser.Id)
|
|
require.Contains(t, reviewerIds, th.SystemAdminUser.Id)
|
|
require.Contains(t, reviewerIds, teamAdmin.Id)
|
|
})
|
|
|
|
t.Run("should deduplicate reviewers from multiple sources", func(t *testing.T) {
|
|
config := getBaseConfig()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(true)
|
|
config.ReviewerSettings.CommonReviewerIds = []string{th.SystemAdminUser.Id}
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(true)
|
|
config.ReviewerSettings.TeamAdminsAsReviewers = model.NewPointer(false)
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
// Add system admin to team
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, th.SystemAdminUser.Id, "")
|
|
require.Nil(t, appErr)
|
|
defer func() {
|
|
_ = th.App.RemoveUserFromTeam(th.Context, th.BasicTeam.Id, th.SystemAdminUser.Id, "")
|
|
}()
|
|
|
|
// Search for system admin (who is both common reviewer and system admin)
|
|
reviewers, appErr := th.App.SearchReviewers(th.Context, th.SystemAdminUser.Username, th.BasicTeam.Id)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 1)
|
|
require.Equal(t, th.SystemAdminUser.Id, reviewers[0].Id)
|
|
allProfilesSanitized(reviewers)
|
|
})
|
|
|
|
t.Run("should return empty results when no reviewers match search term", func(t *testing.T) {
|
|
config := getBaseConfig()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(true)
|
|
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id}
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(false)
|
|
config.ReviewerSettings.TeamAdminsAsReviewers = model.NewPointer(false)
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
// Search for non-existent user
|
|
reviewers, appErr := th.App.SearchReviewers(th.Context, "nonexistentuser", th.BasicTeam.Id)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 0)
|
|
allProfilesSanitized(reviewers)
|
|
})
|
|
|
|
t.Run("should return empty results when no reviewers configured", func(t *testing.T) {
|
|
config := getBaseConfig()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(false)
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(false)
|
|
config.ReviewerSettings.TeamAdminsAsReviewers = model.NewPointer(false)
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
// Search should return no results
|
|
reviewers, appErr := th.App.SearchReviewers(th.Context, th.BasicUser.Username, th.BasicTeam.Id)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewers, 0)
|
|
allProfilesSanitized(reviewers)
|
|
})
|
|
|
|
t.Run("should work with team reviewers and additional reviewers combined", func(t *testing.T) {
|
|
config := getBaseConfig()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(false)
|
|
config.ReviewerSettings.TeamReviewersSetting = map[string]*model.TeamReviewerSetting{
|
|
th.BasicTeam.Id: {
|
|
Enabled: model.NewPointer(true),
|
|
ReviewerIds: []string{th.BasicUser.Id},
|
|
},
|
|
}
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(true)
|
|
config.ReviewerSettings.TeamAdminsAsReviewers = model.NewPointer(false)
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
// Add system admin to team
|
|
_, _, appErr = th.App.AddUserToTeam(th.Context, th.BasicTeam.Id, th.SystemAdminUser.Id, "")
|
|
require.Nil(t, appErr)
|
|
defer func() {
|
|
_ = th.App.RemoveUserFromTeam(th.Context, th.BasicTeam.Id, th.SystemAdminUser.Id, "")
|
|
}()
|
|
|
|
// Search with empty term should return both team reviewer and system admin
|
|
reviewers, appErr := th.App.SearchReviewers(th.Context, "", th.BasicTeam.Id)
|
|
require.Nil(t, appErr)
|
|
require.True(t, len(reviewers) >= 2)
|
|
allProfilesSanitized(reviewers)
|
|
|
|
reviewerIds := make([]string, len(reviewers))
|
|
for i, reviewer := range reviewers {
|
|
reviewerIds[i] = reviewer.Id
|
|
}
|
|
require.Contains(t, reviewerIds, th.BasicUser.Id)
|
|
require.Contains(t, reviewerIds, th.SystemAdminUser.Id)
|
|
})
|
|
|
|
t.Run("should handle search by email and full name", func(t *testing.T) {
|
|
config := getBaseConfig()
|
|
config.ReviewerSettings.CommonReviewers = model.NewPointer(true)
|
|
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id}
|
|
config.ReviewerSettings.SystemAdminsAsReviewers = model.NewPointer(false)
|
|
config.ReviewerSettings.TeamAdminsAsReviewers = model.NewPointer(false)
|
|
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
// Search by first name (if the user has one set)
|
|
if th.BasicUser.FirstName != "" {
|
|
reviewers, appErr := th.App.SearchReviewers(th.Context, th.BasicUser.FirstName, th.BasicTeam.Id)
|
|
require.Nil(t, appErr)
|
|
require.True(t, len(reviewers) >= 1)
|
|
|
|
found := false
|
|
for _, reviewer := range reviewers {
|
|
if reviewer.Id == th.BasicUser.Id {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
require.True(t, found)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestGetReviewerPostsForFlaggedPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
|
|
t.Run("should return reviewer posts for flagged post", func(t *testing.T) {
|
|
require.Nil(t, setBaseConfig(th))
|
|
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
flaggedPostIdField, ok := mappedFields[contentFlaggingPropertyNameFlaggedPostId]
|
|
require.True(t, ok)
|
|
|
|
reviewerPostIds, appErr := th.App.getReviewerPostsForFlaggedPost(groupId, post.Id, flaggedPostIdField.ID)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewerPostIds, 1)
|
|
|
|
// Verify the reviewer post exists and has the correct properties
|
|
reviewerPost, appErr := th.App.GetSinglePost(th.Context, reviewerPostIds[0], false)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, model.ContentFlaggingPostType, reviewerPost.Type)
|
|
require.Contains(t, reviewerPost.GetProps(), POST_PROP_KEY_FLAGGED_POST_ID)
|
|
require.Equal(t, post.Id, reviewerPost.GetProps()[POST_PROP_KEY_FLAGGED_POST_ID])
|
|
})
|
|
|
|
t.Run("should return empty list when no reviewer posts exist", func(t *testing.T) {
|
|
require.Nil(t, setBaseConfig(th))
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
flaggedPostIdField, ok := mappedFields[contentFlaggingPropertyNameFlaggedPostId]
|
|
require.True(t, ok)
|
|
|
|
reviewerPostIds, appErr := th.App.getReviewerPostsForFlaggedPost(groupId, post.Id, flaggedPostIdField.ID)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewerPostIds, 0)
|
|
})
|
|
|
|
t.Run("should handle multiple reviewer posts for same flagged post", func(t *testing.T) {
|
|
// Create a config with multiple reviewers
|
|
config := getBaseConfig(th)
|
|
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id, th.BasicUser2.Id}
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
flagData := model.FlagContentRequest{
|
|
Reason: "spam",
|
|
Comment: "This is spam content",
|
|
}
|
|
|
|
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.SystemAdminUser.Id, flagData)
|
|
require.Nil(t, appErr)
|
|
|
|
// Wait for async reviewer post creation to complete
|
|
time.Sleep(2 * time.Second)
|
|
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
flaggedPostIdField, ok := mappedFields[contentFlaggingPropertyNameFlaggedPostId]
|
|
require.True(t, ok)
|
|
|
|
reviewerPostIds, appErr := th.App.getReviewerPostsForFlaggedPost(groupId, post.Id, flaggedPostIdField.ID)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewerPostIds, 2)
|
|
|
|
// Verify both reviewer posts exist and have correct properties
|
|
for _, postId := range reviewerPostIds {
|
|
reviewerPost, appErr := th.App.GetSinglePost(th.Context, postId, false)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, model.ContentFlaggingPostType, reviewerPost.Type)
|
|
require.Contains(t, reviewerPost.GetProps(), POST_PROP_KEY_FLAGGED_POST_ID)
|
|
require.Equal(t, post.Id, reviewerPost.GetProps()[POST_PROP_KEY_FLAGGED_POST_ID])
|
|
}
|
|
})
|
|
|
|
t.Run("should handle invalid flagged post ID", func(t *testing.T) {
|
|
require.Nil(t, setBaseConfig(th))
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
flaggedPostIdField, ok := mappedFields[contentFlaggingPropertyNameFlaggedPostId]
|
|
require.True(t, ok)
|
|
|
|
reviewerPostIds, appErr := th.App.getReviewerPostsForFlaggedPost(groupId, "invalid_post_id", flaggedPostIdField.ID)
|
|
require.Nil(t, appErr)
|
|
require.Len(t, reviewerPostIds, 0)
|
|
})
|
|
}
|
|
|
|
func TestPostReviewerMessage(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
|
|
t.Run("should post reviewer message to thread", func(t *testing.T) {
|
|
require.Nil(t, setBaseConfig(th))
|
|
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
testMessage := "Test reviewer message"
|
|
_, appErr = th.App.postReviewerMessage(th.Context, testMessage, groupId, post.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify message was posted to the reviewer thread
|
|
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
|
require.Nil(t, appErr)
|
|
|
|
dmChannel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, contentReviewBot.UserId)
|
|
require.Nil(t, appErr)
|
|
|
|
posts, appErr := th.App.GetPostsPage(th.Context, model.GetPostsOptions{
|
|
ChannelId: dmChannel.Id,
|
|
Page: 0,
|
|
PerPage: 10,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
// Find the original review post and the test message
|
|
var reviewPost *model.Post
|
|
var testMessagePost *model.Post
|
|
for _, p := range posts.Posts {
|
|
if p.Type == "custom_spillage_report" {
|
|
reviewPost = p
|
|
} else if p.RootId != "" && p.Message == testMessage {
|
|
testMessagePost = p
|
|
}
|
|
}
|
|
require.NotNil(t, reviewPost)
|
|
require.NotNil(t, testMessagePost)
|
|
require.Equal(t, reviewPost.Id, testMessagePost.RootId)
|
|
require.Equal(t, contentReviewBot.UserId, testMessagePost.UserId)
|
|
})
|
|
|
|
t.Run("should handle multiple reviewer channels", func(t *testing.T) {
|
|
// Create a config with multiple reviewers
|
|
config := getBaseConfig(th)
|
|
config.ReviewerSettings.CommonReviewerIds = []string{th.BasicUser.Id, th.BasicUser2.Id}
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
flagData := model.FlagContentRequest{
|
|
Reason: "spam",
|
|
Comment: "This is spam content",
|
|
}
|
|
|
|
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.SystemAdminUser.Id, flagData)
|
|
require.Nil(t, appErr)
|
|
|
|
// Wait for async reviewer post creation to complete
|
|
time.Sleep(2 * time.Second)
|
|
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
testMessage := "Test message for multiple reviewers"
|
|
_, appErr = th.App.postReviewerMessage(th.Context, testMessage, groupId, post.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify message was posted to both reviewer threads
|
|
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
|
require.Nil(t, appErr)
|
|
|
|
// Check first reviewer's channel
|
|
dmChannel1, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, contentReviewBot.UserId)
|
|
require.Nil(t, appErr)
|
|
|
|
posts1, appErr := th.App.GetPostsPage(th.Context, model.GetPostsOptions{
|
|
ChannelId: dmChannel1.Id,
|
|
Page: 0,
|
|
PerPage: 10,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
// Check second reviewer's channel
|
|
dmChannel2, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser2.Id, contentReviewBot.UserId)
|
|
require.Nil(t, appErr)
|
|
|
|
posts2, appErr := th.App.GetPostsPage(th.Context, model.GetPostsOptions{
|
|
ChannelId: dmChannel2.Id,
|
|
Page: 0,
|
|
PerPage: 10,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify test message exists in both channels
|
|
var testMessagePost1, testMessagePost2 *model.Post
|
|
for _, p := range posts1.Posts {
|
|
if p.RootId != "" && p.Message == testMessage {
|
|
testMessagePost1 = p
|
|
break
|
|
}
|
|
}
|
|
for _, p := range posts2.Posts {
|
|
if p.RootId != "" && p.Message == testMessage {
|
|
testMessagePost2 = p
|
|
break
|
|
}
|
|
}
|
|
require.NotNil(t, testMessagePost1)
|
|
require.NotNil(t, testMessagePost2)
|
|
require.Equal(t, contentReviewBot.UserId, testMessagePost1.UserId)
|
|
require.Equal(t, contentReviewBot.UserId, testMessagePost2.UserId)
|
|
})
|
|
|
|
t.Run("should handle case when no reviewer posts exist", func(t *testing.T) {
|
|
require.Nil(t, setBaseConfig(th))
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
testMessage := "Test message for non-flagged post"
|
|
_, appErr = th.App.postReviewerMessage(th.Context, testMessage, groupId, post.Id)
|
|
require.Nil(t, appErr)
|
|
})
|
|
|
|
t.Run("should handle message with special characters", func(t *testing.T) {
|
|
require.Nil(t, setBaseConfig(th))
|
|
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
testMessage := "Test message with special chars: @user #channel ~team & <script>alert('xss')</script>"
|
|
_, appErr = th.App.postReviewerMessage(th.Context, testMessage, groupId, post.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify message was posted correctly with special characters preserved
|
|
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
|
require.Nil(t, appErr)
|
|
|
|
dmChannel, appErr := th.App.GetOrCreateDirectChannel(th.Context, th.BasicUser.Id, contentReviewBot.UserId)
|
|
require.Nil(t, appErr)
|
|
|
|
posts, appErr := th.App.GetPostsPage(th.Context, model.GetPostsOptions{
|
|
ChannelId: dmChannel.Id,
|
|
Page: 0,
|
|
PerPage: 10,
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
// Find the test message
|
|
var testMessagePost *model.Post
|
|
for _, p := range posts.Posts {
|
|
if p.RootId != "" && p.Message == testMessage {
|
|
testMessagePost = p
|
|
break
|
|
}
|
|
}
|
|
require.NotNil(t, testMessagePost)
|
|
require.Equal(t, testMessage, testMessagePost.Message)
|
|
})
|
|
}
|
|
|
|
// Helper function to setup notification config for testing
|
|
func setupNotificationConfig(th *TestHelper, eventTargetMapping map[model.ContentFlaggingEvent][]model.NotificationTarget) *model.AppError {
|
|
config := getBaseConfig(th)
|
|
config.NotificationSettings = &model.ContentFlaggingNotificationSettings{
|
|
EventTargetMapping: eventTargetMapping,
|
|
}
|
|
return th.App.SaveContentFlaggingConfig(config)
|
|
}
|
|
|
|
// Helper function to verify post message content and properties
|
|
func verifyNotificationPost(t *testing.T, post *model.Post, expectedMessage string, expectedUserId string, expectedChannelId string) {
|
|
require.NotNil(t, post)
|
|
require.Equal(t, expectedMessage, post.Message)
|
|
require.Equal(t, expectedUserId, post.UserId)
|
|
require.Equal(t, expectedChannelId, post.ChannelId)
|
|
require.True(t, post.CreateAt > 0)
|
|
require.True(t, post.UpdateAt > 0)
|
|
}
|
|
|
|
func TestSendFlaggedPostRemovalNotification(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
|
|
t.Run("should send notifications to all configured targets", func(t *testing.T) {
|
|
// Setup notification config for all targets
|
|
appErr := setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
|
model.EventContentRemoved: {model.TargetReviewers, model.TargetAuthor, model.TargetReporter},
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
actorComment := "This post violates community guidelines"
|
|
createdPosts := th.App.sendFlaggedPostRemovalNotification(th.Context, post, th.SystemAdminUser.Id, actorComment, groupId)
|
|
|
|
// Should create 3 posts: reviewer message, author message, reporter message
|
|
require.Len(t, createdPosts, 3)
|
|
|
|
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify reviewer message
|
|
reviewerMessage := fmt.Sprintf("The quarantined message was removed by @%s\n\nWith comment:\n\n> %s", th.SystemAdminUser.Username, actorComment)
|
|
var reviewerPost *model.Post
|
|
for _, p := range createdPosts {
|
|
if p.Message == reviewerMessage {
|
|
reviewerPost = p
|
|
break
|
|
}
|
|
}
|
|
require.NotNil(t, reviewerPost)
|
|
verifyNotificationPost(t, reviewerPost, reviewerMessage, contentReviewBot.UserId, reviewerPost.ChannelId)
|
|
require.NotEmpty(t, reviewerPost.RootId) // Should be a thread reply to the flag review post
|
|
|
|
// Verify author message
|
|
authorMessage := fmt.Sprintf("Your post having ID `%s` in the channel `%s` which was quarantined for review has been permanently removed by a reviewer.", post.Id, th.BasicChannel.DisplayName)
|
|
var authorPost *model.Post
|
|
for _, p := range createdPosts {
|
|
if p.Message == authorMessage {
|
|
authorPost = p
|
|
break
|
|
}
|
|
}
|
|
require.NotNil(t, authorPost)
|
|
verifyNotificationPost(t, authorPost, authorMessage, contentReviewBot.UserId, authorPost.ChannelId)
|
|
|
|
// Verify reporter message
|
|
reporterMessage := fmt.Sprintf("The post having ID `%s` in the channel `%s` which you quarantined for review has been permanently removed by a reviewer.", post.Id, th.BasicChannel.DisplayName)
|
|
var reporterPost *model.Post
|
|
for _, p := range createdPosts {
|
|
if p.Message == reporterMessage {
|
|
reporterPost = p
|
|
break
|
|
}
|
|
}
|
|
require.NotNil(t, reporterPost)
|
|
verifyNotificationPost(t, reporterPost, reporterMessage, contentReviewBot.UserId, reporterPost.ChannelId)
|
|
})
|
|
|
|
t.Run("should send notifications only to configured targets", func(t *testing.T) {
|
|
// Setup notification config for only author
|
|
appErr := setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
|
model.EventContentRemoved: {model.TargetReviewers},
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
// Setup notification config for only author
|
|
appErr = setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
|
model.EventContentRemoved: {model.TargetReviewers},
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
createdPosts := th.App.sendFlaggedPostRemovalNotification(th.Context, post, th.SystemAdminUser.Id, "Test comment", groupId)
|
|
|
|
// Should create only 1 post for author
|
|
require.Len(t, createdPosts, 1)
|
|
|
|
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
|
require.Nil(t, appErr)
|
|
|
|
expectedMessage := fmt.Sprintf("The quarantined message was removed by @%s\n\nWith comment:\n\n> %s", th.SystemAdminUser.Username, "Test comment")
|
|
verifyNotificationPost(t, createdPosts[0], expectedMessage, contentReviewBot.UserId, createdPosts[0].ChannelId)
|
|
})
|
|
|
|
t.Run("should handle empty comment", func(t *testing.T) {
|
|
appErr := setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
|
model.EventContentRemoved: {model.TargetReviewers},
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
createdPosts := th.App.sendFlaggedPostRemovalNotification(th.Context, post, th.SystemAdminUser.Id, "", groupId)
|
|
|
|
require.Len(t, createdPosts, 1)
|
|
|
|
expectedMessage := fmt.Sprintf("The quarantined message was removed by @%s", th.SystemAdminUser.Username)
|
|
verifyNotificationPost(t, createdPosts[0], expectedMessage, createdPosts[0].UserId, createdPosts[0].ChannelId)
|
|
})
|
|
|
|
t.Run("should handle special characters in comment", func(t *testing.T) {
|
|
appErr := setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
|
model.EventContentRemoved: {model.TargetReviewers},
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
specialComment := "Comment with @mentions #channels ~teams & <script>alert('xss')</script>"
|
|
createdPosts := th.App.sendFlaggedPostRemovalNotification(th.Context, post, th.SystemAdminUser.Id, specialComment, groupId)
|
|
|
|
require.Len(t, createdPosts, 1)
|
|
|
|
expectedMessage := fmt.Sprintf("The quarantined message was removed by @%s\n\nWith comment:\n\n> %s", th.SystemAdminUser.Username, specialComment)
|
|
verifyNotificationPost(t, createdPosts[0], expectedMessage, createdPosts[0].UserId, createdPosts[0].ChannelId)
|
|
})
|
|
}
|
|
|
|
func TestSendKeepFlaggedPostNotification(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
|
|
t.Run("should send notifications to all configured targets", func(t *testing.T) {
|
|
// Setup notification config for all targets
|
|
appErr := setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
|
model.EventContentDismissed: {model.TargetReviewers, model.TargetAuthor, model.TargetReporter},
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
actorComment := "This post is acceptable after review"
|
|
createdPosts := th.App.sendKeepFlaggedPostNotification(th.Context, post, th.SystemAdminUser.Id, actorComment, groupId)
|
|
|
|
// Should create 3 posts: reviewer message, author message, reporter message
|
|
require.Len(t, createdPosts, 3)
|
|
|
|
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify reviewer message
|
|
reviewerMessage := fmt.Sprintf("The quarantined message was retained by @%s\n\nWith comment:\n\n> %s", th.SystemAdminUser.Username, actorComment)
|
|
var reviewerPost *model.Post
|
|
for _, p := range createdPosts {
|
|
if p.Message == reviewerMessage {
|
|
reviewerPost = p
|
|
break
|
|
}
|
|
}
|
|
require.NotNil(t, reviewerPost)
|
|
verifyNotificationPost(t, reviewerPost, reviewerMessage, contentReviewBot.UserId, reviewerPost.ChannelId)
|
|
require.NotEmpty(t, reviewerPost.RootId) // Should be a thread reply
|
|
|
|
// Verify author message
|
|
authorMessage := fmt.Sprintf("Your post having ID `%s` in the channel `%s` which was quarantined for review has been restored by a reviewer.", post.Id, th.BasicChannel.DisplayName)
|
|
var authorPost *model.Post
|
|
for _, p := range createdPosts {
|
|
if p.Message == authorMessage {
|
|
authorPost = p
|
|
break
|
|
}
|
|
}
|
|
require.NotNil(t, authorPost)
|
|
verifyNotificationPost(t, authorPost, authorMessage, contentReviewBot.UserId, authorPost.ChannelId)
|
|
|
|
// Verify reporter message
|
|
reporterMessage := fmt.Sprintf("The post having ID `%s` in the channel `%s` which you quarantined for review has been restored by a reviewer.", post.Id, th.BasicChannel.DisplayName)
|
|
var reporterPost *model.Post
|
|
for _, p := range createdPosts {
|
|
if p.Message == reporterMessage {
|
|
reporterPost = p
|
|
break
|
|
}
|
|
}
|
|
require.NotNil(t, reporterPost)
|
|
verifyNotificationPost(t, reporterPost, reporterMessage, contentReviewBot.UserId, reporterPost.ChannelId)
|
|
})
|
|
|
|
t.Run("should send notifications only to configured targets", func(t *testing.T) {
|
|
// Setup notification config for only reporter
|
|
appErr := setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
|
model.EventContentDismissed: {model.TargetReviewers},
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
comment := "Test comment"
|
|
createdPosts := th.App.sendKeepFlaggedPostNotification(th.Context, post, th.SystemAdminUser.Id, comment, groupId)
|
|
|
|
// Should create only 1 post for reporter
|
|
require.Len(t, createdPosts, 1)
|
|
|
|
contentReviewBot, appErr := th.App.getContentReviewBot(th.Context)
|
|
require.Nil(t, appErr)
|
|
|
|
expectedMessage := fmt.Sprintf("The quarantined message was retained by @%s\n\nWith comment:\n\n> %s", th.SystemAdminUser.Username, comment)
|
|
verifyNotificationPost(t, createdPosts[0], expectedMessage, contentReviewBot.UserId, createdPosts[0].ChannelId)
|
|
})
|
|
|
|
t.Run("should handle empty comment", func(t *testing.T) {
|
|
appErr := setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
|
model.EventContentDismissed: {model.TargetReviewers},
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
createdPosts := th.App.sendKeepFlaggedPostNotification(th.Context, post, th.SystemAdminUser.Id, "", groupId)
|
|
|
|
require.Len(t, createdPosts, 1)
|
|
|
|
expectedMessage := fmt.Sprintf("The quarantined message was retained by @%s", th.SystemAdminUser.Username)
|
|
verifyNotificationPost(t, createdPosts[0], expectedMessage, createdPosts[0].UserId, createdPosts[0].ChannelId)
|
|
})
|
|
|
|
t.Run("should handle special characters in comment", func(t *testing.T) {
|
|
appErr := setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
|
model.EventContentDismissed: {model.TargetReviewers},
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
specialComment := "Comment with @mentions #channels ~teams & <script>alert('xss')</script>"
|
|
createdPosts := th.App.sendKeepFlaggedPostNotification(th.Context, post, th.SystemAdminUser.Id, specialComment, groupId)
|
|
|
|
require.Len(t, createdPosts, 1)
|
|
|
|
expectedMessage := fmt.Sprintf("The quarantined message was retained by @%s\n\nWith comment:\n\n> %s", th.SystemAdminUser.Username, specialComment)
|
|
verifyNotificationPost(t, createdPosts[0], expectedMessage, createdPosts[0].UserId, createdPosts[0].ChannelId)
|
|
})
|
|
|
|
t.Run("should handle different actor users", func(t *testing.T) {
|
|
appErr := setupNotificationConfig(th, map[model.ContentFlaggingEvent][]model.NotificationTarget{
|
|
model.EventContentDismissed: {model.TargetReviewers},
|
|
})
|
|
require.Nil(t, appErr)
|
|
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
// Use BasicUser as actor instead of SystemAdminUser
|
|
createdPosts := th.App.sendKeepFlaggedPostNotification(th.Context, post, th.BasicUser.Id, "Reviewed by different user", groupId)
|
|
|
|
require.Len(t, createdPosts, 1)
|
|
|
|
expectedMessage := fmt.Sprintf("The quarantined message was retained by @%s\n\nWith comment:\n\n> %s", th.BasicUser.Username, "Reviewed by different user")
|
|
verifyNotificationPost(t, createdPosts[0], expectedMessage, createdPosts[0].UserId, createdPosts[0].ChannelId)
|
|
})
|
|
}
|
|
|
|
func TestPermanentDeleteFlaggedPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.Nil(t, setBaseConfig(th))
|
|
|
|
t.Run("should successfully permanently delete pending flagged post", func(t *testing.T) {
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: "This post violates community guidelines",
|
|
}
|
|
|
|
appErr := th.App.PermanentDeleteFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, post)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify post content was scrubbed
|
|
updatedPost, appErr := th.App.GetSinglePost(th.Context, post.Id, true)
|
|
require.Nil(t, appErr) // Post should be deleted
|
|
require.Greater(t, updatedPost.DeleteAt, int64(0))
|
|
|
|
// Verify status was updated to removed
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, `"`+model.ContentFlaggingStatusRemoved+`"`, string(statusValue.Value))
|
|
|
|
// Verify actor properties were created
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
// Check actor user property
|
|
actorValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameActorUserID].ID,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, actorValues, 1)
|
|
require.Equal(t, `"`+th.SystemAdminUser.Id+`"`, string(actorValues[0].Value))
|
|
|
|
// Check actor comment property
|
|
commentValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameActorComment].ID,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, commentValues, 1)
|
|
require.Equal(t, `"This post violates community guidelines"`, string(commentValues[0].Value))
|
|
|
|
// Check action time property
|
|
timeValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameActionTime].ID,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, timeValues, 1)
|
|
|
|
var actionTime int64
|
|
err = json.Unmarshal(timeValues[0].Value, &actionTime)
|
|
require.NoError(t, err)
|
|
require.True(t, actionTime > 0)
|
|
})
|
|
|
|
t.Run("should successfully permanently delete pending flagged post when flagged posts are hidden", func(t *testing.T) {
|
|
baseConfig := getBaseConfig(th)
|
|
baseConfig.AdditionalSettings.HideFlaggedContent = model.NewPointer(true)
|
|
appErr := th.App.SaveContentFlaggingConfig(baseConfig)
|
|
require.Nil(t, appErr)
|
|
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: "This post violates community guidelines",
|
|
}
|
|
|
|
appErr = th.App.PermanentDeleteFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, post)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify post content was scrubbed
|
|
updatedPost, appErr := th.App.GetSinglePost(th.Context, post.Id, true)
|
|
require.Nil(t, appErr) // Post should be deleted
|
|
require.Greater(t, updatedPost.DeleteAt, int64(0))
|
|
})
|
|
|
|
t.Run("should successfully permanently delete assigned flagged post", func(t *testing.T) {
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
// Assign the post to a reviewer first
|
|
appErr := th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: "Assigned post needs removal",
|
|
}
|
|
|
|
appErr = th.App.PermanentDeleteFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, post)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify status was updated to removed
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, `"`+model.ContentFlaggingStatusRemoved+`"`, string(statusValue.Value))
|
|
})
|
|
|
|
t.Run("should fail when trying to delete already removed post", func(t *testing.T) {
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
// Set status to removed
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
|
|
statusValue.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusRemoved))
|
|
_, err := th.App.PropertyAccessService().UpdatePropertyValue(anonymousCallerId, groupId, statusValue)
|
|
require.NoError(t, err)
|
|
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: "Trying to delete already removed post",
|
|
}
|
|
|
|
appErr = th.App.PermanentDeleteFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, post)
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, "api.data_spillage.error.post_not_in_progress", appErr.Id)
|
|
require.Equal(t, http.StatusBadRequest, appErr.StatusCode)
|
|
})
|
|
|
|
t.Run("should fail when trying to delete already retained post", func(t *testing.T) {
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
// Set status to retained
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
|
|
statusValue.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusRetained))
|
|
_, err := th.App.PropertyAccessService().UpdatePropertyValue(anonymousCallerId, groupId, statusValue)
|
|
require.NoError(t, err)
|
|
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: "Trying to delete retained post",
|
|
}
|
|
|
|
appErr = th.App.PermanentDeleteFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, post)
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, "api.data_spillage.error.post_not_in_progress", appErr.Id)
|
|
require.Equal(t, http.StatusBadRequest, appErr.StatusCode)
|
|
})
|
|
|
|
t.Run("should fail when trying to delete non-flagged post", func(t *testing.T) {
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: "Trying to delete non-flagged post",
|
|
}
|
|
|
|
appErr := th.App.PermanentDeleteFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, post)
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, http.StatusNotFound, appErr.StatusCode)
|
|
})
|
|
|
|
t.Run("should handle empty comment", func(t *testing.T) {
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: "",
|
|
}
|
|
|
|
appErr := th.App.PermanentDeleteFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, post)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify empty comment was stored
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
commentValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameActorComment].ID,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, commentValues, 1)
|
|
require.Equal(t, `""`, string(commentValues[0].Value))
|
|
})
|
|
|
|
t.Run("should handle special characters in comment", func(t *testing.T) {
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
specialComment := "Comment with @mentions #channels ~teams & <script>alert('xss')</script>"
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: specialComment,
|
|
}
|
|
|
|
appErr := th.App.PermanentDeleteFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, post)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify special characters were properly escaped and stored
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
commentValues, err := th.Server.propertyAccessService.SearchPropertyValues(anonymousCallerId, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameActorComment].ID,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, commentValues, 1)
|
|
|
|
var storedComment string
|
|
err = json.Unmarshal(commentValues[0].Value, &storedComment)
|
|
require.NoError(t, err)
|
|
require.Equal(t, specialComment, storedComment)
|
|
})
|
|
|
|
t.Run("should handle post with file attachments", func(t *testing.T) {
|
|
// Create a post with file attachments
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
// Create some file infos and associate them with the post
|
|
fileInfo1 := &model.FileInfo{
|
|
Id: model.NewId(),
|
|
PostId: post.Id,
|
|
CreatorId: post.UserId,
|
|
Path: "test/file1.txt",
|
|
Name: "file1.txt",
|
|
Size: 100,
|
|
}
|
|
fileInfo2 := &model.FileInfo{
|
|
Id: model.NewId(),
|
|
PostId: post.Id,
|
|
CreatorId: post.UserId,
|
|
Path: "test/file2.txt",
|
|
Name: "file2.txt",
|
|
Size: 200,
|
|
}
|
|
|
|
_, err := th.App.Srv().Store().FileInfo().Save(th.Context, fileInfo1)
|
|
require.NoError(t, err)
|
|
_, err = th.App.Srv().Store().FileInfo().Save(th.Context, fileInfo2)
|
|
require.NoError(t, err)
|
|
|
|
// Update post to include file IDs
|
|
post.FileIds = []string{fileInfo1.Id, fileInfo2.Id}
|
|
_, _, appErr := th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{})
|
|
require.Nil(t, appErr)
|
|
|
|
// Flag the post
|
|
flagData := model.FlagContentRequest{
|
|
Reason: "spam",
|
|
Comment: "This is spam content",
|
|
}
|
|
|
|
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
|
require.Nil(t, appErr)
|
|
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: "Post with files needs removal",
|
|
}
|
|
|
|
require.Eventually(t, func() bool {
|
|
appErr = th.App.PermanentDeleteFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, post)
|
|
require.Nil(t, appErr)
|
|
return appErr == nil
|
|
}, 5*time.Second, 200*time.Millisecond)
|
|
|
|
// Verify post was deleted and status updated
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, `"`+model.ContentFlaggingStatusRemoved+`"`, string(statusValue.Value))
|
|
|
|
// Verify file infos were also deleted
|
|
files, err := th.App.Srv().Store().FileInfo().GetByIds([]string{fileInfo1.Id, fileInfo2.Id}, true, false)
|
|
require.NoError(t, err)
|
|
require.Empty(t, files)
|
|
})
|
|
|
|
t.Run("should handle post with edit history", func(t *testing.T) {
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
// Create edit history for the post
|
|
editedPost := post.Clone()
|
|
editedPost.Message = "Edited message"
|
|
editedPost.EditAt = model.GetMillis()
|
|
|
|
_, _, appErr := th.App.UpdatePost(th.Context, editedPost, &model.UpdatePostOptions{})
|
|
require.Nil(t, appErr)
|
|
|
|
// Flag the post
|
|
flagData := model.FlagContentRequest{
|
|
Reason: "inappropriate",
|
|
Comment: "This post is inappropriate",
|
|
}
|
|
|
|
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
|
require.Nil(t, appErr)
|
|
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: "Post with edit history needs removal",
|
|
}
|
|
|
|
require.Eventually(t, func() bool {
|
|
appErr = th.App.PermanentDeleteFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, editedPost)
|
|
require.Nil(t, appErr)
|
|
return appErr == nil
|
|
}, 5*time.Second, 200*time.Millisecond)
|
|
|
|
// Verify status was updated
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(editedPost.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify statusValue.Value is a string
|
|
var stringValue string
|
|
err := json.Unmarshal(statusValue.Value, &stringValue)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, model.ContentFlaggingStatusRemoved, stringValue)
|
|
})
|
|
|
|
t.Run("should handle post that was already deleted", func(t *testing.T) {
|
|
config := getBaseConfig(th)
|
|
config.AdditionalSettings.HideFlaggedContent = model.NewPointer(true)
|
|
appErr := th.App.SaveContentFlaggingConfig(config)
|
|
require.Nil(t, appErr)
|
|
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
flagData := model.FlagContentRequest{
|
|
Reason: "spam",
|
|
Comment: "This is spam content",
|
|
}
|
|
|
|
// Flag the post (this will delete it due to HideFlaggedContent setting)
|
|
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
|
require.Nil(t, appErr)
|
|
|
|
var deletedPost *model.Post
|
|
|
|
require.Eventually(t, func() bool {
|
|
// Get the updated post (should be deleted)
|
|
deletedPost, appErr = th.App.GetSinglePost(th.Context, post.Id, true)
|
|
require.Nil(t, appErr)
|
|
require.True(t, deletedPost.DeleteAt > 0)
|
|
return appErr == nil
|
|
}, 5*time.Second, 200*time.Millisecond)
|
|
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: "Permanently delete already hidden post",
|
|
}
|
|
|
|
appErr = th.App.PermanentDeleteFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, deletedPost)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify status was updated to removed
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(deletedPost.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify statusValue.Value is a string
|
|
var stringValue string
|
|
err := json.Unmarshal(statusValue.Value, &stringValue)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, `"`+model.ContentFlaggingStatusRemoved+`"`, string(statusValue.Value))
|
|
})
|
|
}
|
|
|
|
func TestKeepFlaggedPost(t *testing.T) {
|
|
mainHelper.Parallel(t)
|
|
th := Setup(t).InitBasic(t)
|
|
|
|
th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterpriseAdvanced))
|
|
require.Nil(t, setBaseConfig(th))
|
|
|
|
t.Run("should successfully keep pending flagged post", func(t *testing.T) {
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: "This post is acceptable after review",
|
|
}
|
|
|
|
appErr := th.App.KeepFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, post)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify post still exists and is not deleted
|
|
updatedPost, appErr := th.App.GetSinglePost(th.Context, post.Id, false)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, int64(0), updatedPost.DeleteAt)
|
|
|
|
// Verify status was updated to retained
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, `"`+model.ContentFlaggingStatusRetained+`"`, string(statusValue.Value))
|
|
|
|
// Verify actor properties were created
|
|
// Check actor user property
|
|
actorValues := searchPropertyValue(t, th, post.Id, contentFlaggingPropertyNameActorUserID)
|
|
require.Len(t, actorValues, 1)
|
|
require.Equal(t, `"`+th.SystemAdminUser.Id+`"`, string(actorValues[0].Value))
|
|
|
|
// Check actor comment property
|
|
commentValues := searchPropertyValue(t, th, post.Id, contentFlaggingPropertyNameActorComment)
|
|
require.Len(t, commentValues, 1)
|
|
require.Equal(t, `"This post is acceptable after review"`, string(commentValues[0].Value))
|
|
|
|
// Check action time property
|
|
timeValues := searchPropertyValue(t, th, post.Id, contentFlaggingPropertyNameActionTime)
|
|
require.Len(t, timeValues, 1)
|
|
|
|
var actionTime int64
|
|
err := json.Unmarshal(timeValues[0].Value, &actionTime)
|
|
require.NoError(t, err)
|
|
require.True(t, actionTime > 0)
|
|
})
|
|
|
|
t.Run("should successfully keep and restore hidden flagged post", func(t *testing.T) {
|
|
baseConfig := getBaseConfig(th)
|
|
baseConfig.AdditionalSettings.HideFlaggedContent = model.NewPointer(true)
|
|
appErr := th.App.SaveContentFlaggingConfig(baseConfig)
|
|
require.Nil(t, appErr)
|
|
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
flagData := model.FlagContentRequest{
|
|
Reason: "spam",
|
|
Comment: "This is spam content",
|
|
}
|
|
|
|
// Flag the post (this will hide/delete it due to HideFlaggedContent setting)
|
|
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
|
require.Nil(t, appErr)
|
|
|
|
var hiddenPost *model.Post
|
|
|
|
require.Eventually(t, func() bool {
|
|
// Get the updated post (should be deleted/hidden)
|
|
hiddenPost, appErr = th.App.GetSinglePost(th.Context, post.Id, true)
|
|
require.Nil(t, appErr)
|
|
return hiddenPost.DeleteAt > 0
|
|
}, 5*time.Second, 200*time.Millisecond)
|
|
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: "Restoring this post after review",
|
|
}
|
|
|
|
appErr = th.App.KeepFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, hiddenPost)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify post was restored (DeleteAt should be 0)
|
|
restoredPost, appErr := th.App.GetSinglePost(th.Context, post.Id, false)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, int64(0), restoredPost.DeleteAt)
|
|
|
|
// Verify status was updated to retained
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, `"`+model.ContentFlaggingStatusRetained+`"`, string(statusValue.Value))
|
|
})
|
|
|
|
t.Run("should successfully keep assigned flagged post", func(t *testing.T) {
|
|
// Reset config to not hide flagged content
|
|
require.Nil(t, setBaseConfig(th))
|
|
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
// Assign the post to a reviewer first
|
|
appErr := th.App.AssignFlaggedPostReviewer(th.Context, post.Id, th.BasicChannel.TeamId, th.BasicUser.Id, th.SystemAdminUser.Id)
|
|
require.Nil(t, appErr)
|
|
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: "Assigned post is acceptable",
|
|
}
|
|
|
|
appErr = th.App.KeepFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, post)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify status was updated to retained
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, `"`+model.ContentFlaggingStatusRetained+`"`, string(statusValue.Value))
|
|
})
|
|
|
|
t.Run("should fail when trying to keep already removed post", func(t *testing.T) {
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
// Set status to removed
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
|
|
statusValue.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusRemoved))
|
|
_, err := th.App.Srv().propertyAccessService.UpdatePropertyValue("", groupId, statusValue)
|
|
require.NoError(t, err)
|
|
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: "Trying to keep already removed post",
|
|
}
|
|
|
|
appErr = th.App.KeepFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, post)
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, "api.data_spillage.error.post_not_in_progress", appErr.Id)
|
|
require.Equal(t, http.StatusBadRequest, appErr.StatusCode)
|
|
})
|
|
|
|
t.Run("should fail when trying to keep already retained post", func(t *testing.T) {
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
// Set status to retained
|
|
groupId, appErr := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, appErr)
|
|
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
|
|
statusValue.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusRetained))
|
|
_, err := th.App.Srv().propertyAccessService.UpdatePropertyValue("", groupId, statusValue)
|
|
require.NoError(t, err)
|
|
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: "Trying to keep already retained post",
|
|
}
|
|
|
|
appErr = th.App.KeepFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, post)
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, "api.data_spillage.error.post_not_in_progress", appErr.Id)
|
|
require.Equal(t, http.StatusBadRequest, appErr.StatusCode)
|
|
})
|
|
|
|
t.Run("should fail when trying to keep non-flagged post", func(t *testing.T) {
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: "Trying to keep non-flagged post",
|
|
}
|
|
|
|
appErr := th.App.KeepFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, post)
|
|
require.NotNil(t, appErr)
|
|
require.Equal(t, http.StatusNotFound, appErr.StatusCode)
|
|
})
|
|
|
|
t.Run("should handle empty comment", func(t *testing.T) {
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: "",
|
|
}
|
|
|
|
appErr := th.App.KeepFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, post)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify empty comment was stored
|
|
commentValues := searchPropertyValue(t, th, post.Id, contentFlaggingPropertyNameActorComment)
|
|
require.Len(t, commentValues, 1)
|
|
require.Equal(t, `""`, string(commentValues[0].Value))
|
|
})
|
|
|
|
t.Run("should handle special characters in comment", func(t *testing.T) {
|
|
post := setupFlaggedPost(t, th)
|
|
|
|
specialComment := "Comment with @mentions #channels ~teams & <script>alert('xss')</script>"
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: specialComment,
|
|
}
|
|
|
|
appErr := th.App.KeepFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, post)
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify special characters were properly escaped and stored
|
|
commentValues := searchPropertyValue(t, th, post.Id, contentFlaggingPropertyNameActorComment)
|
|
require.Len(t, commentValues, 1)
|
|
|
|
var storedComment string
|
|
err := json.Unmarshal(commentValues[0].Value, &storedComment)
|
|
require.NoError(t, err)
|
|
require.Equal(t, specialComment, storedComment)
|
|
})
|
|
|
|
t.Run("should preserve file attachments when keeping flagged post", func(t *testing.T) {
|
|
// Create a post with file attachments
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
// Create some file infos and associate them with the post
|
|
fileInfo1 := &model.FileInfo{
|
|
Id: model.NewId(),
|
|
PostId: post.Id,
|
|
CreatorId: post.UserId,
|
|
Path: "test/file1.txt",
|
|
Name: "file1.txt",
|
|
Size: 100,
|
|
}
|
|
fileInfo2 := &model.FileInfo{
|
|
Id: model.NewId(),
|
|
PostId: post.Id,
|
|
CreatorId: post.UserId,
|
|
Path: "test/file2.txt",
|
|
Name: "file2.txt",
|
|
Size: 200,
|
|
}
|
|
|
|
_, err := th.App.Srv().Store().FileInfo().Save(th.Context, fileInfo1)
|
|
require.NoError(t, err)
|
|
_, err = th.App.Srv().Store().FileInfo().Save(th.Context, fileInfo2)
|
|
require.NoError(t, err)
|
|
|
|
// Update post to include file IDs
|
|
post.FileIds = []string{fileInfo1.Id, fileInfo2.Id}
|
|
_, _, appErr := th.App.UpdatePost(th.Context, post, &model.UpdatePostOptions{})
|
|
require.Nil(t, appErr)
|
|
|
|
// Flag the post
|
|
flagData := model.FlagContentRequest{
|
|
Reason: "spam",
|
|
Comment: "This is spam content",
|
|
}
|
|
|
|
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
|
require.Nil(t, appErr)
|
|
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: "Post with files is acceptable",
|
|
}
|
|
|
|
require.Eventually(t, func() bool {
|
|
appErr = th.App.KeepFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, post)
|
|
require.Nil(t, appErr)
|
|
return appErr == nil
|
|
}, 5*time.Second, 200*time.Millisecond)
|
|
|
|
// Verify post was retained
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
require.Equal(t, `"`+model.ContentFlaggingStatusRetained+`"`, string(statusValue.Value))
|
|
|
|
// Verify file infos are still present (not deleted)
|
|
files, err := th.App.Srv().Store().FileInfo().GetByIds([]string{fileInfo1.Id, fileInfo2.Id}, false, false)
|
|
require.NoError(t, err)
|
|
require.Len(t, files, 2, "File attachments should be preserved when keeping flagged post")
|
|
})
|
|
|
|
t.Run("should preserve edit history when keeping flagged post", func(t *testing.T) {
|
|
post := th.CreatePost(t, th.BasicChannel)
|
|
|
|
// Create edit history for the post
|
|
editedPost := post.Clone()
|
|
editedPost.Message = "Edited message"
|
|
editedPost.EditAt = model.GetMillis()
|
|
|
|
_, _, appErr := th.App.UpdatePost(th.Context, editedPost, &model.UpdatePostOptions{})
|
|
require.Nil(t, appErr)
|
|
|
|
// Verify edit history exists before flagging
|
|
editHistoryBefore, appErr := th.App.GetEditHistoryForPost(post.Id)
|
|
require.Nil(t, appErr)
|
|
require.NotEmpty(t, editHistoryBefore)
|
|
editHistoryPostId := editHistoryBefore[0].Id
|
|
|
|
// Flag the post
|
|
flagData := model.FlagContentRequest{
|
|
Reason: "inappropriate",
|
|
Comment: "This post is inappropriate",
|
|
}
|
|
|
|
appErr = th.App.FlagPost(th.Context, post, th.BasicTeam.Id, th.BasicUser2.Id, flagData)
|
|
require.Nil(t, appErr)
|
|
|
|
actionRequest := &model.FlagContentActionRequest{
|
|
Comment: "Post with edit history is acceptable",
|
|
}
|
|
|
|
require.Eventually(t, func() bool {
|
|
appErr = th.App.KeepFlaggedPost(th.Context, actionRequest, th.SystemAdminUser.Id, editedPost)
|
|
require.Nil(t, appErr)
|
|
return appErr == nil
|
|
}, 5*time.Second, 200*time.Millisecond)
|
|
|
|
// Verify status was updated to retained
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(editedPost.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
|
|
var stringValue string
|
|
err := json.Unmarshal(statusValue.Value, &stringValue)
|
|
require.NoError(t, err)
|
|
require.Equal(t, model.ContentFlaggingStatusRetained, stringValue)
|
|
|
|
// Verify edit history is still present (not deleted)
|
|
editHistoryAfter, appErr := th.App.GetEditHistoryForPost(post.Id)
|
|
require.Nil(t, appErr, "Edit history should be preserved when keeping flagged post")
|
|
require.NotEmpty(t, editHistoryAfter)
|
|
require.Equal(t, editHistoryPostId, editHistoryAfter[0].Id)
|
|
})
|
|
}
|
|
|
|
func TestScrubPost(t *testing.T) {
|
|
expectedMessage := "*Content deleted as part of Content Flagging review process*"
|
|
|
|
t.Run("should scrub all post content fields", func(t *testing.T) {
|
|
post := &model.Post{
|
|
Id: model.NewId(),
|
|
Message: "This is the original message with sensitive content",
|
|
MessageSource: "This is the original message source",
|
|
Hashtags: "#hashtag1 #hashtag2",
|
|
FileIds: []string{"file1", "file2", "file3"},
|
|
Metadata: &model.PostMetadata{
|
|
Embeds: []*model.PostEmbed{
|
|
{Type: "link", URL: "https://example.com"},
|
|
},
|
|
Files: []*model.FileInfo{
|
|
{Id: "file1", Name: "test.png"},
|
|
},
|
|
},
|
|
}
|
|
post.SetProps(map[string]any{
|
|
"custom_prop": "custom_value",
|
|
"another_prop": 123,
|
|
"sensitive_data": "should be removed",
|
|
})
|
|
|
|
scrubPost(post)
|
|
|
|
require.Equal(t, expectedMessage, post.Message)
|
|
require.Equal(t, expectedMessage, post.MessageSource)
|
|
require.Equal(t, "", post.Hashtags)
|
|
require.Nil(t, post.Metadata)
|
|
require.Empty(t, post.FileIds)
|
|
require.NotNil(t, post.GetProps())
|
|
require.Empty(t, post.GetProps())
|
|
})
|
|
|
|
t.Run("should handle post with empty fields", func(t *testing.T) {
|
|
post := &model.Post{
|
|
Id: model.NewId(),
|
|
Message: "",
|
|
MessageSource: "",
|
|
Hashtags: "",
|
|
FileIds: []string{},
|
|
Metadata: nil,
|
|
}
|
|
post.SetProps(make(map[string]any))
|
|
|
|
scrubPost(post)
|
|
|
|
require.Equal(t, expectedMessage, post.Message)
|
|
require.Equal(t, expectedMessage, post.MessageSource)
|
|
require.Equal(t, "", post.Hashtags)
|
|
require.Nil(t, post.Metadata)
|
|
require.Empty(t, post.FileIds)
|
|
require.NotNil(t, post.GetProps())
|
|
require.Empty(t, post.GetProps())
|
|
})
|
|
|
|
t.Run("should handle post with nil FileIds", func(t *testing.T) {
|
|
post := &model.Post{
|
|
Id: model.NewId(),
|
|
Message: "Test message",
|
|
FileIds: nil,
|
|
}
|
|
|
|
scrubPost(post)
|
|
|
|
require.Equal(t, expectedMessage, post.Message)
|
|
require.NotNil(t, post.FileIds)
|
|
require.Empty(t, post.FileIds)
|
|
})
|
|
|
|
t.Run("should handle post with nil Props", func(t *testing.T) {
|
|
post := &model.Post{
|
|
Id: model.NewId(),
|
|
Message: "Test message",
|
|
}
|
|
// Props is nil by default
|
|
|
|
scrubPost(post)
|
|
|
|
require.Equal(t, expectedMessage, post.Message)
|
|
require.NotNil(t, post.GetProps())
|
|
require.Empty(t, post.GetProps())
|
|
})
|
|
|
|
t.Run("should preserve non-content fields", func(t *testing.T) {
|
|
postId := model.NewId()
|
|
userId := model.NewId()
|
|
channelId := model.NewId()
|
|
rootId := model.NewId()
|
|
createAt := model.GetMillis()
|
|
updateAt := model.GetMillis()
|
|
editAt := model.GetMillis()
|
|
|
|
post := &model.Post{
|
|
Id: postId,
|
|
CreateAt: createAt,
|
|
UpdateAt: updateAt,
|
|
EditAt: editAt,
|
|
DeleteAt: 0,
|
|
UserId: userId,
|
|
ChannelId: channelId,
|
|
RootId: rootId,
|
|
OriginalId: "",
|
|
Message: "Original message to be scrubbed",
|
|
MessageSource: "Original source",
|
|
Type: model.PostTypeDefault,
|
|
Hashtags: "#test",
|
|
FileIds: []string{"file1"},
|
|
}
|
|
post.SetProps(map[string]any{"key": "value"})
|
|
|
|
scrubPost(post)
|
|
|
|
// Verify content fields are scrubbed
|
|
require.Equal(t, expectedMessage, post.Message)
|
|
require.Equal(t, expectedMessage, post.MessageSource)
|
|
require.Equal(t, "", post.Hashtags)
|
|
require.Nil(t, post.Metadata)
|
|
require.Empty(t, post.FileIds)
|
|
require.Empty(t, post.GetProps())
|
|
|
|
// Verify non-content fields are preserved
|
|
require.Equal(t, postId, post.Id)
|
|
require.Equal(t, createAt, post.CreateAt)
|
|
require.Equal(t, updateAt, post.UpdateAt)
|
|
require.Equal(t, editAt, post.EditAt)
|
|
require.Equal(t, userId, post.UserId)
|
|
require.Equal(t, channelId, post.ChannelId)
|
|
require.Equal(t, rootId, post.RootId)
|
|
require.Equal(t, model.PostTypeDefault, post.Type)
|
|
})
|
|
|
|
t.Run("should handle post with special characters in message", func(t *testing.T) {
|
|
post := &model.Post{
|
|
Id: model.NewId(),
|
|
Message: "Message with <script>alert('xss')</script> and @mentions #hashtags ~channels",
|
|
MessageSource: "Source with unicode: 你好世界 🎉 émojis",
|
|
Hashtags: "#特殊 #émoji",
|
|
}
|
|
|
|
scrubPost(post)
|
|
|
|
require.Equal(t, expectedMessage, post.Message)
|
|
require.Equal(t, expectedMessage, post.MessageSource)
|
|
require.Equal(t, "", post.Hashtags)
|
|
})
|
|
|
|
t.Run("should handle post with complex Metadata", func(t *testing.T) {
|
|
post := &model.Post{
|
|
Id: model.NewId(),
|
|
Message: "Test message",
|
|
Metadata: &model.PostMetadata{
|
|
Embeds: []*model.PostEmbed{
|
|
{Type: "link", URL: "https://example1.com"},
|
|
{Type: "link", URL: "https://example2.com"},
|
|
},
|
|
Emojis: []*model.Emoji{
|
|
{Id: "emoji1", Name: "custom_emoji"},
|
|
},
|
|
Files: []*model.FileInfo{
|
|
{Id: "file1", Name: "document.pdf", Size: 1024},
|
|
{Id: "file2", Name: "image.png", Size: 2048},
|
|
},
|
|
Reactions: []*model.Reaction{
|
|
{UserId: "user1", PostId: "post1", EmojiName: "thumbsup"},
|
|
},
|
|
},
|
|
}
|
|
|
|
scrubPost(post)
|
|
|
|
require.Equal(t, expectedMessage, post.Message)
|
|
require.Nil(t, post.Metadata)
|
|
})
|
|
}
|