mirror of
https://github.com/mattermost/mattermost.git
synced 2026-04-13 13:08:56 -04:00
Some checks are pending
API / build (push) Waiting to run
Server CI / Compute Go Version (push) Waiting to run
Server CI / Check mocks (push) Blocked by required conditions
Server CI / Check go mod tidy (push) Blocked by required conditions
Server CI / check-style (push) Blocked by required conditions
Server CI / Check serialization methods for hot structs (push) Blocked by required conditions
Server CI / Vet API (push) Blocked by required conditions
Server CI / Check migration files (push) Blocked by required conditions
Server CI / Generate email templates (push) Blocked by required conditions
Server CI / Check store layers (push) Blocked by required conditions
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres with binary parameters (push) Blocked by required conditions
Server CI / Postgres (push) Blocked by required conditions
Server CI / Postgres (FIPS) (push) Blocked by required conditions
Server CI / Generate Test Coverage (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-external-links (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions
* Refactor property system with app layer routing and access control separation Establish the app layer as the primary entry point for property operations with intelligent routing based on group type. This architecture separates access-controlled operations (CPA groups) from standard operations, improving performance and code clarity. Architecture Changes: - App layer now routes operations based on group type: - CPA groups -> PropertyAccessService (enforces access control) - Non-CPA groups -> PropertyService (direct, no access control) - PropertyAccessService simplified to handle only CPA operations - Eliminated redundant group type checks throughout the codebase * Move access control routing into PropertyService This change makes the PropertyService the main entrypoint for property related operations, and adds a routing mechanism to decide if extra behaviors or checks should run for each operation, in this case, the property access service logic. To add specific payloads that pluggable checks and operations may need, we use the request context. When the request comes from the API, the endpoints are in charge of adding the caller ID to the payload, and in the case of the plugin API, on receiving a request, the server automatically tags the context with the plugin ID so the property service can react accordingly. Finally, the new design enforces all these checks migrating the actual property logic to internal, non-exposed methods, so any caller from the App layer needs to go through the service checks that decide if pluggable logic is needed, avoiding any possibility of a bypass. * Fix i18n * Fix bad error string * Added nil guards to property methods * Add check for multiple group IDs on value operations * Add nil guard to the plugin checker * Fix build error * Update value tests * Fix linter * Adds early return when content flaggin a thread with no replies * Fix mocks * Clean the state of plugin property tests before each run * Do not wrap appErr on API response and fix i18n * Fix create property field test * Remove the need to cache cpaGroupID as part of the property service * Split the property.go file into multiple * Not found group doesn't bypass access control check * Unexport SetPluginCheckerForTests * Rename plugin context getter to be more PSA specific --------- Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>
3095 lines
110 KiB
Go
3095 lines
110 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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
values, appErr2 := th.App.SearchPropertyValues(th.Context, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{postId},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[fieldName].ID,
|
|
})
|
|
require.Nil(t, appErr2)
|
|
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))
|
|
rctx := RequestContextWithCallerID(th.Context, anonymousCallerId)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
reviewerValues, err := th.App.SearchPropertyValues(rctx, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameReviewerUserID].ID,
|
|
})
|
|
require.Nil(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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
reviewerValues, err := th.App.SearchPropertyValues(rctx, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameReviewerUserID].ID,
|
|
})
|
|
require.Nil(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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
reviewerValues, err := th.App.SearchPropertyValues(rctx, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameReviewerUserID].ID,
|
|
})
|
|
require.Nil(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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
reviewerValues, err := th.App.SearchPropertyValues(rctx, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameReviewerUserID].ID,
|
|
})
|
|
require.Nil(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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
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.UpdatePropertyValue(rctx, groupId, statusValue)
|
|
require.Nil(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.UpdatePropertyValue(rctx, groupId, statusValue)
|
|
require.Nil(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.UpdatePropertyValue(rctx, groupId, statusValue)
|
|
require.Nil(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))
|
|
rctx := RequestContextWithCallerID(th.Context, anonymousCallerId)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
statusField, err := th.App.GetPropertyFieldByName(rctx, groupId, "", ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, err)
|
|
|
|
propertyValue, err := th.App.CreatePropertyValue(rctx, &model.PropertyValue{
|
|
TargetID: post.Id,
|
|
GroupID: groupId,
|
|
FieldID: statusField.ID,
|
|
TargetType: "post",
|
|
Value: json.RawMessage(`"` + model.ContentFlaggingStatusPending + `"`),
|
|
})
|
|
require.Nil(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.App.UpdatePropertyValue(rctx, groupId, propertyValue)
|
|
require.Nil(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.App.UpdatePropertyValue(rctx, groupId, propertyValue)
|
|
require.Nil(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.App.UpdatePropertyValue(rctx, groupId, propertyValue)
|
|
require.Nil(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))
|
|
rctx := RequestContextWithCallerID(th.Context, anonymousCallerId)
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
// Check status property
|
|
statusValues, err := th.App.SearchPropertyValues(rctx, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[ContentFlaggingPropertyNameStatus].ID,
|
|
})
|
|
require.Nil(t, err)
|
|
require.Len(t, statusValues, 1)
|
|
require.Equal(t, `"`+model.ContentFlaggingStatusPending+`"`, string(statusValues[0].Value))
|
|
|
|
// Check reporting user property
|
|
userValues, err := th.App.SearchPropertyValues(rctx, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameReportingUserID].ID,
|
|
})
|
|
require.Nil(t, err)
|
|
require.Len(t, userValues, 1)
|
|
require.Equal(t, `"`+th.BasicUser2.Id+`"`, string(userValues[0].Value))
|
|
|
|
// Check reason property
|
|
reasonValues, err := th.App.SearchPropertyValues(rctx, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameReportingReason].ID,
|
|
})
|
|
require.Nil(t, err)
|
|
require.Len(t, reasonValues, 1)
|
|
require.Equal(t, `"spam"`, string(reasonValues[0].Value))
|
|
|
|
// Check comment property
|
|
commentValues, err := th.App.SearchPropertyValues(rctx, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameReportingComment].ID,
|
|
})
|
|
require.Nil(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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
commentValues, err := th.App.SearchPropertyValues(rctx, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameReportingComment].ID,
|
|
})
|
|
require.Nil(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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
timeValues, err := th.App.SearchPropertyValues(rctx, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameReportingTime].ID,
|
|
})
|
|
require.Nil(t, err)
|
|
require.Len(t, timeValues, 1)
|
|
|
|
var reportingTime int64
|
|
unmarshalErr := json.Unmarshal(timeValues[0].Value, &reportingTime)
|
|
require.NoError(t, unmarshalErr)
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
// 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))
|
|
rctx := RequestContextWithCallerID(th.Context, anonymousCallerId)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
// Check actor user property
|
|
actorValues, err := th.App.SearchPropertyValues(rctx, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameActorUserID].ID,
|
|
})
|
|
require.Nil(t, err)
|
|
require.Len(t, actorValues, 1)
|
|
require.Equal(t, `"`+th.SystemAdminUser.Id+`"`, string(actorValues[0].Value))
|
|
|
|
// Check actor comment property
|
|
commentValues, err := th.App.SearchPropertyValues(rctx, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameActorComment].ID,
|
|
})
|
|
require.Nil(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.App.SearchPropertyValues(rctx, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameActionTime].ID,
|
|
})
|
|
require.Nil(t, err)
|
|
require.Len(t, timeValues, 1)
|
|
|
|
var actionTime int64
|
|
unmarshalErr := json.Unmarshal(timeValues[0].Value, &actionTime)
|
|
require.NoError(t, unmarshalErr)
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
|
|
statusValue.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusRemoved))
|
|
_, err = th.App.UpdatePropertyValue(rctx, groupId, statusValue)
|
|
require.Nil(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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
|
|
statusValue.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusRetained))
|
|
_, err = th.App.UpdatePropertyValue(rctx, groupId, statusValue)
|
|
require.Nil(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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
commentValues, err := th.App.SearchPropertyValues(rctx, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameActorComment].ID,
|
|
})
|
|
require.Nil(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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
mappedFields, appErr := th.App.GetContentFlaggingMappedFields(groupId)
|
|
require.Nil(t, appErr)
|
|
|
|
commentValues, err := th.App.SearchPropertyValues(rctx, groupId, model.PropertyValueSearchOpts{
|
|
TargetIDs: []string{post.Id},
|
|
PerPage: CONTENT_FLAGGING_MAX_PROPERTY_VALUES,
|
|
FieldID: mappedFields[contentFlaggingPropertyNameActorComment].ID,
|
|
})
|
|
require.Nil(t, err)
|
|
require.Len(t, commentValues, 1)
|
|
|
|
var storedComment string
|
|
unmarshalErr := json.Unmarshal(commentValues[0].Value, &storedComment)
|
|
require.NoError(t, unmarshalErr)
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
|
|
statusValue.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusRemoved))
|
|
_, appErr = th.App.UpdatePropertyValue(th.Context, groupId, statusValue)
|
|
require.Nil(t, appErr)
|
|
|
|
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, err := th.App.ContentFlaggingGroupId()
|
|
require.Nil(t, err)
|
|
|
|
statusValue, appErr := th.App.GetPostContentFlaggingPropertyValue(post.Id, ContentFlaggingPropertyNameStatus)
|
|
require.Nil(t, appErr)
|
|
|
|
statusValue.Value = json.RawMessage(fmt.Sprintf(`"%s"`, model.ContentFlaggingStatusRetained))
|
|
_, appErr = th.App.UpdatePropertyValue(th.Context, groupId, statusValue)
|
|
require.Nil(t, appErr)
|
|
|
|
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)
|
|
})
|
|
}
|