2025-07-22 05:27:37 -04:00
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
2025-10-02 10:54:29 -04:00
"encoding/json"
2025-10-14 23:32:14 -04:00
"fmt"
2025-10-13 23:36:23 -04:00
"net/http"
2025-07-22 05:27:37 -04:00
"testing"
2025-10-02 10:54:29 -04:00
"time"
2025-07-22 05:27:37 -04:00
"github.com/mattermost/mattermost/server/public/model"
"github.com/stretchr/testify/require"
)
2025-10-14 23:32:14 -04:00
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
}
2026-02-22 19:45:40 -05:00
func searchPropertyValue ( t * testing . T , th * TestHelper , postId , fieldName string ) [ ] * model . PropertyValue {
t . Helper ( )
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2026-02-22 19:45:40 -05:00
mappedFields , appErr := th . App . GetContentFlaggingMappedFields ( groupId )
require . Nil ( t , appErr )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
values , appErr2 := th . App . SearchPropertyValues ( th . Context , groupId , model . PropertyValueSearchOpts {
2026-02-22 19:45:40 -05:00
TargetIDs : [ ] string { postId } ,
PerPage : CONTENT_FLAGGING_MAX_PROPERTY_VALUES ,
FieldID : mappedFields [ fieldName ] . ID ,
} )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , appErr2 )
2026-02-22 19:45:40 -05:00
return values
}
2025-11-12 07:00:51 -05:00
func setupFlaggedPost ( t * testing . T , th * TestHelper ) * model . Post {
post := th . CreatePost ( t , th . BasicChannel )
2025-10-14 23:32:14 -04:00
flagData := model . FlagContentRequest {
Reason : "spam" ,
Comment : "This is spam content" ,
}
appErr := th . App . FlagPost ( th . Context , post , th . BasicTeam . Id , th . BasicUser2 . Id , flagData )
2025-11-12 07:00:51 -05:00
require . Nil ( t , appErr )
2025-10-14 23:32:14 -04:00
time . Sleep ( 2 * time . Second )
2025-11-12 07:00:51 -05:00
return post
2025-10-14 23:32:14 -04:00
}
2025-10-02 10:54:29 -04:00
func TestContentFlaggingEnabledForTeam ( t * testing . T ) {
mainHelper . Parallel ( t )
2025-10-13 02:54:01 -04:00
th := Setup ( t )
2025-10-02 10:54:29 -04:00
2025-07-22 05:27:37 -04:00
t . Run ( "should return true for common reviewers" , func ( t * testing . T ) {
2025-10-13 02:54:01 -04:00
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 )
2025-07-22 05:27:37 -04:00
2025-10-13 02:54:01 -04:00
status , appErr := th . App . ContentFlaggingEnabledForTeam ( "team1" )
require . Nil ( t , appErr )
2025-07-22 05:27:37 -04:00
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 ) {
2025-10-13 02:54:01 -04:00
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" } ,
} ,
} ,
} ,
2025-07-22 05:27:37 -04:00
} ,
}
2025-10-13 02:54:01 -04:00
config . SetDefaults ( )
appErr := th . App . SaveContentFlaggingConfig ( config )
require . Nil ( t , appErr )
2025-07-22 05:27:37 -04:00
2025-10-13 02:54:01 -04:00
status , appErr := th . App . ContentFlaggingEnabledForTeam ( "team1" )
require . Nil ( t , appErr )
2025-07-22 05:27:37 -04:00
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 ) {
2025-10-13 02:54:01 -04:00
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 ) ,
} ,
} ,
} ,
2025-07-22 05:27:37 -04:00
} ,
}
2025-10-13 02:54:01 -04:00
config . SetDefaults ( )
2025-07-22 05:27:37 -04:00
2025-10-13 02:54:01 -04:00
appErr := th . App . SaveContentFlaggingConfig ( config )
require . Nil ( t , appErr )
status , appErr := th . App . ContentFlaggingEnabledForTeam ( "team1" )
require . Nil ( t , appErr )
2025-07-22 05:27:37 -04:00
require . True ( t , status )
2025-10-13 02:54:01 -04:00
config . ReviewerSettings . TeamAdminsAsReviewers = model . NewPointer ( false )
config . ReviewerSettings . SystemAdminsAsReviewers = model . NewPointer ( true )
appErr = th . App . SaveContentFlaggingConfig ( config )
require . Nil ( t , appErr )
2025-07-22 05:27:37 -04:00
2025-10-13 02:54:01 -04:00
status , appErr = th . App . ContentFlaggingEnabledForTeam ( "team1" )
require . Nil ( t , appErr )
2025-07-22 05:27:37 -04:00
require . True ( t , status )
2025-10-13 02:54:01 -04:00
config . ReviewerSettings . TeamAdminsAsReviewers = model . NewPointer ( true )
config . ReviewerSettings . SystemAdminsAsReviewers = model . NewPointer ( true )
appErr = th . App . SaveContentFlaggingConfig ( config )
require . Nil ( t , appErr )
2025-07-22 05:27:37 -04:00
2025-10-13 02:54:01 -04:00
status , appErr = th . App . ContentFlaggingEnabledForTeam ( "team1" )
require . Nil ( t , appErr )
2025-07-22 05:27:37 -04:00
require . True ( t , status )
} )
2025-10-15 02:37:30 -04:00
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" )
} )
2025-07-22 05:27:37 -04:00
}
2025-10-02 10:54:29 -04:00
2025-10-13 23:36:23 -04:00
func TestAssignFlaggedPostReviewer ( t * testing . T ) {
mainHelper . Parallel ( t )
2025-11-12 07:00:51 -05:00
th := Setup ( t ) . InitBasic ( t )
2025-10-13 23:36:23 -04:00
th . App . Srv ( ) . SetLicense ( model . NewTestLicenseSKU ( model . LicenseShortSkuEnterpriseAdvanced ) )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
rctx := RequestContextWithCallerID ( th . Context , anonymousCallerId )
2025-10-13 23:36:23 -04:00
2025-10-14 23:32:14 -04:00
t . Run ( "should successfully assign reviewer to pending flagged post" , func ( t * testing . T ) {
require . Nil ( t , setBaseConfig ( th ) )
2025-10-13 23:36:23 -04:00
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-10-13 23:36:23 -04:00
2025-11-12 07:00:51 -05:00
appErr := th . App . AssignFlaggedPostReviewer ( th . Context , post . Id , th . BasicChannel . TeamId , th . BasicUser . Id , th . SystemAdminUser . Id )
2025-10-13 23:36:23 -04:00
require . Nil ( t , appErr )
// Verify status was updated to assigned
2025-11-19 23:39:36 -05:00
statusValue , appErr := th . App . GetPostContentFlaggingPropertyValue ( post . Id , ContentFlaggingPropertyNameStatus )
2025-10-13 23:36:23 -04:00
require . Nil ( t , appErr )
require . Equal ( t , ` " ` + model . ContentFlaggingStatusAssigned + ` " ` , string ( statusValue . Value ) )
// Verify reviewer property was created
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-13 23:36:23 -04:00
mappedFields , appErr := th . App . GetContentFlaggingMappedFields ( groupId )
require . Nil ( t , appErr )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
reviewerValues , err := th . App . SearchPropertyValues ( rctx , groupId , model . PropertyValueSearchOpts {
2025-10-13 23:36:23 -04:00
TargetIDs : [ ] string { post . Id } ,
PerPage : CONTENT_FLAGGING_MAX_PROPERTY_VALUES ,
FieldID : mappedFields [ contentFlaggingPropertyNameReviewerUserID ] . ID ,
} )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-13 23:36:23 -04:00
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 ) {
2025-10-14 23:32:14 -04:00
require . Nil ( t , setBaseConfig ( th ) )
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-10-13 23:36:23 -04:00
// First assignment
2025-11-12 07:00:51 -05:00
appErr := th . App . AssignFlaggedPostReviewer ( th . Context , post . Id , th . BasicChannel . TeamId , th . BasicUser . Id , th . SystemAdminUser . Id )
2025-10-13 23:36:23 -04:00
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
2025-11-19 23:39:36 -05:00
statusValue , appErr := th . App . GetPostContentFlaggingPropertyValue ( post . Id , ContentFlaggingPropertyNameStatus )
2025-10-13 23:36:23 -04:00
require . Nil ( t , appErr )
require . Equal ( t , ` " ` + model . ContentFlaggingStatusAssigned + ` " ` , string ( statusValue . Value ) )
// Verify reviewer property was updated
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-13 23:36:23 -04:00
mappedFields , appErr := th . App . GetContentFlaggingMappedFields ( groupId )
require . Nil ( t , appErr )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
reviewerValues , err := th . App . SearchPropertyValues ( rctx , groupId , model . PropertyValueSearchOpts {
2025-10-13 23:36:23 -04:00
TargetIDs : [ ] string { post . Id } ,
PerPage : CONTENT_FLAGGING_MAX_PROPERTY_VALUES ,
FieldID : mappedFields [ contentFlaggingPropertyNameReviewerUserID ] . ID ,
} )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-13 23:36:23 -04:00
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 ) {
2025-10-14 23:32:14 -04:00
require . Nil ( t , setBaseConfig ( th ) )
2025-11-12 07:00:51 -05:00
post := th . CreatePost ( t , th . BasicChannel )
2025-10-13 23:36:23 -04:00
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 ) {
2025-10-14 23:32:14 -04:00
require . Nil ( t , setBaseConfig ( th ) )
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-10-13 23:36:23 -04:00
// Assign reviewer
2025-11-12 07:00:51 -05:00
appErr := th . App . AssignFlaggedPostReviewer ( th . Context , post . Id , th . BasicChannel . TeamId , th . BasicUser . Id , th . SystemAdminUser . Id )
2025-10-13 23:36:23 -04:00
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
2025-11-19 23:39:36 -05:00
statusValue , appErr := th . App . GetPostContentFlaggingPropertyValue ( post . Id , ContentFlaggingPropertyNameStatus )
2025-10-13 23:36:23 -04:00
require . Nil ( t , appErr )
require . Equal ( t , ` " ` + model . ContentFlaggingStatusAssigned + ` " ` , string ( statusValue . Value ) )
// Verify reviewer property still exists with correct value
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-13 23:36:23 -04:00
mappedFields , appErr := th . App . GetContentFlaggingMappedFields ( groupId )
require . Nil ( t , appErr )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
reviewerValues , err := th . App . SearchPropertyValues ( rctx , groupId , model . PropertyValueSearchOpts {
2025-10-13 23:36:23 -04:00
TargetIDs : [ ] string { post . Id } ,
PerPage : CONTENT_FLAGGING_MAX_PROPERTY_VALUES ,
FieldID : mappedFields [ contentFlaggingPropertyNameReviewerUserID ] . ID ,
} )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-13 23:36:23 -04:00
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 ) {
2025-10-14 23:32:14 -04:00
require . Nil ( t , setBaseConfig ( th ) )
2025-10-13 23:36:23 -04:00
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-10-14 23:32:14 -04:00
2025-11-12 07:00:51 -05:00
appErr := th . App . AssignFlaggedPostReviewer ( th . Context , post . Id , th . BasicChannel . TeamId , "" , th . SystemAdminUser . Id )
2025-10-13 23:36:23 -04:00
require . Nil ( t , appErr )
// Verify status was updated to assigned
2025-11-19 23:39:36 -05:00
statusValue , appErr := th . App . GetPostContentFlaggingPropertyValue ( post . Id , ContentFlaggingPropertyNameStatus )
2025-10-13 23:36:23 -04:00
require . Nil ( t , appErr )
require . Equal ( t , ` " ` + model . ContentFlaggingStatusAssigned + ` " ` , string ( statusValue . Value ) )
// Verify reviewer property was created with empty value
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-13 23:36:23 -04:00
mappedFields , appErr := th . App . GetContentFlaggingMappedFields ( groupId )
require . Nil ( t , appErr )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
reviewerValues , err := th . App . SearchPropertyValues ( rctx , groupId , model . PropertyValueSearchOpts {
2025-10-13 23:36:23 -04:00
TargetIDs : [ ] string { post . Id } ,
PerPage : CONTENT_FLAGGING_MAX_PROPERTY_VALUES ,
FieldID : mappedFields [ contentFlaggingPropertyNameReviewerUserID ] . ID ,
} )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-13 23:36:23 -04:00
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 ) {
2025-10-14 23:32:14 -04:00
require . Nil ( t , setBaseConfig ( th ) )
2025-10-13 23:36:23 -04:00
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 )
} )
2025-11-03 04:07:05 -05:00
t . Run ( "should allow assigning reviewer at all stages" , func ( t * testing . T ) {
require . Nil ( t , setBaseConfig ( th ) )
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-11-03 04:07:05 -05:00
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-11-03 04:07:05 -05:00
2025-11-19 23:39:36 -05:00
statusValue , appErr := th . App . GetPostContentFlaggingPropertyValue ( post . Id , ContentFlaggingPropertyNameStatus )
2025-11-03 04:07:05 -05:00
require . Nil ( t , appErr )
// Set the status to Assigned
statusValue . Value = json . RawMessage ( fmt . Sprintf ( ` "%s" ` , model . ContentFlaggingStatusAssigned ) )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
_ , err = th . App . UpdatePropertyValue ( rctx , groupId , statusValue )
require . Nil ( t , err )
2025-11-03 04:07:05 -05:00
appErr = th . App . AssignFlaggedPostReviewer ( th . Context , post . Id , th . BasicChannel . TeamId , th . BasicUser . Id , th . SystemAdminUser . Id )
require . Nil ( t , appErr )
2025-11-19 23:39:36 -05:00
statusValue , appErr = th . App . GetPostContentFlaggingPropertyValue ( post . Id , ContentFlaggingPropertyNameStatus )
2025-11-03 04:07:05 -05:00
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 ) )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
_ , err = th . App . UpdatePropertyValue ( rctx , groupId , statusValue )
require . Nil ( t , err )
2025-11-03 04:07:05 -05:00
appErr = th . App . AssignFlaggedPostReviewer ( th . Context , post . Id , th . BasicChannel . TeamId , th . BasicUser . Id , th . SystemAdminUser . Id )
require . Nil ( t , appErr )
2025-11-19 23:39:36 -05:00
statusValue , appErr = th . App . GetPostContentFlaggingPropertyValue ( post . Id , ContentFlaggingPropertyNameStatus )
2025-11-03 04:07:05 -05:00
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 ) )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
_ , err = th . App . UpdatePropertyValue ( rctx , groupId , statusValue )
require . Nil ( t , err )
2025-11-03 04:07:05 -05:00
appErr = th . App . AssignFlaggedPostReviewer ( th . Context , post . Id , th . BasicChannel . TeamId , th . BasicUser . Id , th . SystemAdminUser . Id )
require . Nil ( t , appErr )
2025-11-19 23:39:36 -05:00
statusValue , appErr = th . App . GetPostContentFlaggingPropertyValue ( post . Id , ContentFlaggingPropertyNameStatus )
2025-11-03 04:07:05 -05:00
require . Nil ( t , appErr )
require . Equal ( t , ` " ` + model . ContentFlaggingStatusRetained + ` " ` , string ( statusValue . Value ) )
} )
2025-10-13 23:36:23 -04:00
}
2025-10-13 02:54:01 -04:00
func TestSaveContentFlaggingConfig ( t * testing . T ) {
mainHelper . Parallel ( t )
2025-11-12 07:00:51 -05:00
th := Setup ( t ) . InitBasic ( t )
2025-10-13 02:54:01 -04:00
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 )
2025-11-12 07:00:51 -05:00
th := Setup ( t ) . InitBasic ( t )
2025-10-13 02:54:01 -04:00
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 )
}
} )
}
2025-10-02 10:54:29 -04:00
func TestGetContentReviewChannels ( t * testing . T ) {
mainHelper . Parallel ( t )
2025-11-12 07:00:51 -05:00
th := Setup ( t ) . InitBasic ( t )
2025-10-02 10:54:29 -04:00
th . App . Srv ( ) . SetLicense ( model . NewTestLicenseSKU ( model . LicenseShortSkuEnterpriseAdvanced ) )
2025-10-13 02:54:01 -04:00
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
}
2025-10-02 10:54:29 -04:00
t . Run ( "should return channels for common reviewers" , func ( t * testing . T ) {
2025-10-13 02:54:01 -04:00
appErr := th . App . SaveContentFlaggingConfig ( getBaseConfig ( ) )
require . Nil ( t , appErr )
2025-10-02 10:54:29 -04:00
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 ) {
2025-10-13 02:54:01 -04:00
config := getBaseConfig ( )
config . ReviewerSettings . SystemAdminsAsReviewers = model . NewPointer ( true )
appErr := th . App . SaveContentFlaggingConfig ( config )
require . Nil ( t , appErr )
2025-10-02 10:54:29 -04:00
// Sysadmin explicitly need to be a team member to be returned as reviewer
2025-10-13 02:54:01 -04:00
_ , _ , appErr = th . App . AddUserToTeam ( th . Context , th . BasicTeam . Id , th . SystemAdminUser . Id , "" )
2025-10-02 10:54:29 -04:00
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 ) {
2025-10-13 02:54:01 -04:00
config := getBaseConfig ( )
config . ReviewerSettings . TeamAdminsAsReviewers = model . NewPointer ( true )
appErr := th . App . SaveContentFlaggingConfig ( config )
require . Nil ( t , appErr )
2025-10-02 10:54:29 -04:00
// Create a new user and make them team admin
2025-11-12 07:00:51 -05:00
teamAdmin := th . CreateUser ( t )
2025-10-02 10:54:29 -04:00
defer func ( ) {
_ = th . App . PermanentDeleteUser ( th . Context , teamAdmin )
} ( )
2025-10-13 02:54:01 -04:00
_ , _ , appErr = th . App . AddUserToTeam ( th . Context , th . BasicTeam . Id , teamAdmin . Id , "" )
2025-10-02 10:54:29 -04:00
require . Nil ( t , appErr )
2026-01-16 09:59:07 -05:00
_ , appErr = th . App . UpdateTeamMemberRoles ( th . Context , th . BasicTeam . Id , teamAdmin . Id , model . TeamUserRoleId + " " + model . TeamAdminRoleId )
2025-10-02 10:54:29 -04:00
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 ) {
2025-10-13 02:54:01 -04:00
config := getBaseConfig ( )
config . ReviewerSettings . CommonReviewers = model . NewPointer ( false )
config . ReviewerSettings . CommonReviewerIds = [ ] string { th . BasicUser . Id }
2025-10-02 10:54:29 -04:00
2025-10-13 02:54:01 -04:00
config . ReviewerSettings . TeamAdminsAsReviewers = model . NewPointer ( false )
config . ReviewerSettings . SystemAdminsAsReviewers = model . NewPointer ( false )
2025-10-02 10:54:29 -04:00
2025-10-13 02:54:01 -04:00
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 )
2025-10-02 10:54:29 -04:00
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 ) {
2025-10-13 02:54:01 -04:00
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 )
2025-10-02 10:54:29 -04:00
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 ) {
2025-10-13 02:54:01 -04:00
config := getBaseConfig ( )
config . ReviewerSettings . SystemAdminsAsReviewers = model . NewPointer ( true )
config . ReviewerSettings . TeamAdminsAsReviewers = model . NewPointer ( true )
config . ReviewerSettings . CommonReviewers = model . NewPointer ( false )
2025-10-02 10:54:29 -04:00
2025-10-13 02:54:01 -04:00
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 )
2025-10-02 10:54:29 -04:00
2025-10-13 02:54:01 -04:00
_ , _ , appErr = th . App . AddUserToTeam ( th . Context , th . BasicTeam . Id , th . SystemAdminUser . Id , "" )
2025-10-02 10:54:29 -04:00
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 )
2025-11-12 07:00:51 -05:00
th := Setup ( t ) . InitBasic ( t )
2025-10-02 10:54:29 -04:00
th . App . Srv ( ) . SetLicense ( model . NewTestLicenseSKU ( model . LicenseShortSkuEnterpriseAdvanced ) )
t . Run ( "should return common reviewers" , func ( t * testing . T ) {
2025-10-13 02:54:01 -04:00
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 )
2025-10-02 10:54:29 -04:00
2025-10-13 02:54:01 -04:00
appErr := th . App . SaveContentFlaggingConfig ( * config )
require . Nil ( t , appErr )
2025-10-02 10:54:29 -04:00
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 ) {
2025-10-13 02:54:01 -04:00
config := & model . ContentFlaggingSettingsRequest { }
config . SetDefaults ( )
config . ReviewerSettings . CommonReviewers = model . NewPointer ( true )
config . ReviewerSettings . CommonReviewerIds = [ ] string { th . BasicUser . Id }
config . ReviewerSettings . SystemAdminsAsReviewers = model . NewPointer ( true )
2025-10-02 10:54:29 -04:00
2025-10-13 02:54:01 -04:00
appErr := th . App . SaveContentFlaggingConfig ( * config )
require . Nil ( t , appErr )
2025-10-02 10:54:29 -04:00
// Sysadmin explicitly need to be a team member to be returned as reviewer
2025-10-13 02:54:01 -04:00
_ , _ , appErr = th . App . AddUserToTeam ( th . Context , th . BasicTeam . Id , th . SystemAdminUser . Id , "" )
2025-10-02 10:54:29 -04:00
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 )
2025-10-13 02:54:01 -04:00
config . ReviewerSettings . CommonReviewerIds = [ ] string { }
appErr = th . App . SaveContentFlaggingConfig ( * config )
require . Nil ( t , appErr )
2025-10-02 10:54:29 -04:00
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 )
2025-10-13 02:54:01 -04:00
config . ReviewerSettings . CommonReviewerIds = [ ] string { th . BasicUser . Id }
appErr = th . App . SaveContentFlaggingConfig ( * config )
require . Nil ( t , appErr )
2025-10-02 10:54:29 -04:00
// 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 ) {
2025-10-13 02:54:01 -04:00
config := & model . ContentFlaggingSettingsRequest { }
config . SetDefaults ( )
config . ReviewerSettings . CommonReviewers = model . NewPointer ( true )
config . ReviewerSettings . CommonReviewerIds = [ ] string { th . BasicUser . Id }
config . ReviewerSettings . SystemAdminsAsReviewers = model . NewPointer ( true )
2025-10-02 10:54:29 -04:00
2025-10-13 02:54:01 -04:00
appErr := th . App . SaveContentFlaggingConfig ( * config )
require . Nil ( t , appErr )
2025-10-02 10:54:29 -04:00
// Create a new user and make them team admin
2025-11-12 07:00:51 -05:00
teamAdmin := th . CreateUser ( t )
2025-10-02 10:54:29 -04:00
defer func ( ) {
_ = th . App . PermanentDeleteUser ( th . Context , teamAdmin )
} ( )
2025-10-13 02:54:01 -04:00
_ , _ , appErr = th . App . AddUserToTeam ( th . Context , th . BasicTeam . Id , teamAdmin . Id , "" )
2025-10-02 10:54:29 -04:00
require . Nil ( t , appErr )
2026-01-16 09:59:07 -05:00
_ , appErr = th . App . UpdateTeamMemberRoles ( th . Context , th . BasicTeam . Id , teamAdmin . Id , model . TeamUserRoleId + " " + model . TeamAdminRoleId )
2025-10-02 10:54:29 -04:00
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 )
2025-10-13 02:54:01 -04:00
config . ReviewerSettings . CommonReviewerIds = [ ] string { }
appErr = th . App . SaveContentFlaggingConfig ( * config )
require . Nil ( t , appErr )
2025-10-02 10:54:29 -04:00
reviewers , appErr = th . App . getReviewersForTeam ( th . BasicTeam . Id , true )
require . Nil ( t , appErr )
require . Len ( t , reviewers , 1 )
require . Contains ( t , reviewers , teamAdmin . Id )
2025-10-13 02:54:01 -04:00
config . ReviewerSettings . CommonReviewerIds = [ ] string { th . BasicUser . Id }
appErr = th . App . SaveContentFlaggingConfig ( * config )
require . Nil ( t , appErr )
2025-10-02 10:54:29 -04:00
// 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 ) {
2025-11-12 07:00:51 -05:00
team2 := th . CreateTeam ( t )
2025-10-13 02:54:01 -04:00
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 )
2025-10-02 10:54:29 -04:00
// 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 ) {
2025-10-13 02:54:01 -04:00
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 )
2025-10-02 10:54:29 -04:00
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 ) {
2025-10-13 02:54:01 -04:00
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 )
2025-10-02 10:54:29 -04:00
2025-10-13 02:54:01 -04:00
_ , _ , appErr = th . App . AddUserToTeam ( th . Context , th . BasicTeam . Id , th . SystemAdminUser . Id , "" )
2025-10-02 10:54:29 -04:00
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 ) {
2025-10-13 02:54:01 -04:00
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 )
2025-10-02 10:54:29 -04:00
2025-10-13 02:54:01 -04:00
_ , _ , appErr = th . App . AddUserToTeam ( th . Context , th . BasicTeam . Id , th . SystemAdminUser . Id , "" )
2025-10-02 10:54:29 -04:00
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 )
2025-11-12 07:00:51 -05:00
th := Setup ( t ) . InitBasic ( t )
2025-10-02 10:54:29 -04:00
th . App . Srv ( ) . SetLicense ( model . NewTestLicenseSKU ( model . LicenseShortSkuEnterpriseAdvanced ) )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
rctx := RequestContextWithCallerID ( th . Context , anonymousCallerId )
2025-10-02 10:54:29 -04:00
t . Run ( "should be able to flag post which has not already been flagged" , func ( t * testing . T ) {
2025-11-12 07:00:51 -05:00
post := th . CreatePost ( t , th . BasicChannel )
2025-10-02 10:54:29 -04:00
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-02 10:54:29 -04:00
2026-03-16 17:03:43 -04:00
appErr := th . App . canFlagPost ( groupId , post . Id , "en" )
2025-10-02 10:54:29 -04:00
require . Nil ( t , appErr )
} )
t . Run ( "should not be able to flag post which has already been flagged" , func ( t * testing . T ) {
2025-11-12 07:00:51 -05:00
post := th . CreatePost ( t , th . BasicChannel )
2025-10-02 10:54:29 -04:00
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-02 10:54:29 -04:00
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
statusField , err := th . App . GetPropertyFieldByName ( rctx , groupId , "" , ContentFlaggingPropertyNameStatus )
require . Nil ( t , err )
2025-10-02 10:54:29 -04:00
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
propertyValue , err := th . App . CreatePropertyValue ( rctx , & model . PropertyValue {
2025-10-02 10:54:29 -04:00
TargetID : post . Id ,
GroupID : groupId ,
FieldID : statusField . ID ,
TargetType : "post" ,
Value : json . RawMessage ( ` " ` + model . ContentFlaggingStatusPending + ` " ` ) ,
} )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-02 10:54:29 -04:00
// Can't fleg when post already flagged in pending status
2026-03-16 17:03:43 -04:00
appErr := th . App . canFlagPost ( groupId , post . Id , "en" )
2025-10-02 10:54:29 -04:00
require . NotNil ( t , appErr )
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
require . Equal ( t , "Cannot quarantine this post as it is already quarantined for review." , appErr . Id )
2025-10-02 10:54:29 -04:00
// Can't fleg when post already flagged in assigned status
propertyValue . Value = json . RawMessage ( ` " ` + model . ContentFlaggingStatusAssigned + ` " ` )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
_ , err = th . App . UpdatePropertyValue ( rctx , groupId , propertyValue )
require . Nil ( t , err )
2025-10-02 10:54:29 -04:00
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 + ` " ` )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
_ , err = th . App . UpdatePropertyValue ( rctx , groupId , propertyValue )
require . Nil ( t , err )
2025-10-02 10:54:29 -04:00
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 + ` " ` )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
_ , err = th . App . UpdatePropertyValue ( rctx , groupId , propertyValue )
require . Nil ( t , err )
2025-10-02 10:54:29 -04:00
appErr = th . App . canFlagPost ( groupId , post . Id , "en" )
require . NotNil ( t , appErr )
} )
}
func TestFlagPost ( t * testing . T ) {
mainHelper . Parallel ( t )
2025-11-12 07:00:51 -05:00
th := Setup ( t ) . InitBasic ( t )
2025-10-02 10:54:29 -04:00
th . App . Srv ( ) . SetLicense ( model . NewTestLicenseSKU ( model . LicenseShortSkuEnterpriseAdvanced ) )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
rctx := RequestContextWithCallerID ( th . Context , anonymousCallerId )
2025-10-13 02:54:01 -04:00
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
}
2025-10-02 10:54:29 -04:00
t . Run ( "should successfully flag a post with valid data" , func ( t * testing . T ) {
2025-10-13 02:54:01 -04:00
appErr := th . App . SaveContentFlaggingConfig ( getBaseConfig ( ) )
require . Nil ( t , appErr )
2025-11-12 07:00:51 -05:00
post := th . CreatePost ( t , th . BasicChannel )
2025-10-02 10:54:29 -04:00
flagData := model . FlagContentRequest {
Reason : "spam" ,
Comment : "This is spam content" ,
}
2025-10-13 02:54:01 -04:00
appErr = th . App . FlagPost ( th . Context , post , th . BasicTeam . Id , th . BasicUser2 . Id , flagData )
2025-10-02 10:54:29 -04:00
require . Nil ( t , appErr )
// Verify property values were created
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-02 10:54:29 -04:00
mappedFields , appErr := th . App . GetContentFlaggingMappedFields ( groupId )
require . Nil ( t , appErr )
// Check status property
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
statusValues , err := th . App . SearchPropertyValues ( rctx , groupId , model . PropertyValueSearchOpts {
2025-10-02 10:54:29 -04:00
TargetIDs : [ ] string { post . Id } ,
PerPage : CONTENT_FLAGGING_MAX_PROPERTY_VALUES ,
2025-11-19 23:39:36 -05:00
FieldID : mappedFields [ ContentFlaggingPropertyNameStatus ] . ID ,
2025-10-02 10:54:29 -04:00
} )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-02 10:54:29 -04:00
require . Len ( t , statusValues , 1 )
require . Equal ( t , ` " ` + model . ContentFlaggingStatusPending + ` " ` , string ( statusValues [ 0 ] . Value ) )
// Check reporting user property
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
userValues , err := th . App . SearchPropertyValues ( rctx , groupId , model . PropertyValueSearchOpts {
2025-10-02 10:54:29 -04:00
TargetIDs : [ ] string { post . Id } ,
PerPage : CONTENT_FLAGGING_MAX_PROPERTY_VALUES ,
FieldID : mappedFields [ contentFlaggingPropertyNameReportingUserID ] . ID ,
} )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-02 10:54:29 -04:00
require . Len ( t , userValues , 1 )
require . Equal ( t , ` " ` + th . BasicUser2 . Id + ` " ` , string ( userValues [ 0 ] . Value ) )
// Check reason property
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
reasonValues , err := th . App . SearchPropertyValues ( rctx , groupId , model . PropertyValueSearchOpts {
2025-10-02 10:54:29 -04:00
TargetIDs : [ ] string { post . Id } ,
PerPage : CONTENT_FLAGGING_MAX_PROPERTY_VALUES ,
FieldID : mappedFields [ contentFlaggingPropertyNameReportingReason ] . ID ,
} )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-02 10:54:29 -04:00
require . Len ( t , reasonValues , 1 )
require . Equal ( t , ` "spam" ` , string ( reasonValues [ 0 ] . Value ) )
// Check comment property
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
commentValues , err := th . App . SearchPropertyValues ( rctx , groupId , model . PropertyValueSearchOpts {
2025-10-02 10:54:29 -04:00
TargetIDs : [ ] string { post . Id } ,
PerPage : CONTENT_FLAGGING_MAX_PROPERTY_VALUES ,
FieldID : mappedFields [ contentFlaggingPropertyNameReportingComment ] . ID ,
} )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-02 10:54:29 -04:00
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 ) {
2025-10-13 02:54:01 -04:00
appErr := th . App . SaveContentFlaggingConfig ( getBaseConfig ( ) )
require . Nil ( t , appErr )
2025-11-12 07:00:51 -05:00
post := th . CreatePost ( t , th . BasicChannel )
2025-10-02 10:54:29 -04:00
flagData := model . FlagContentRequest {
Reason : "invalid_reason" ,
Comment : "This is spam content" ,
}
2025-10-13 02:54:01 -04:00
appErr = th . App . FlagPost ( th . Context , post , th . BasicTeam . Id , th . BasicUser2 . Id , flagData )
2025-10-02 10:54:29 -04:00
require . NotNil ( t , appErr )
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
require . Equal ( t , "api.data_spillage.error.reason_invalid" , appErr . Id )
2025-10-02 10:54:29 -04:00
} )
t . Run ( "should fail when comment is required but not provided" , func ( t * testing . T ) {
2025-10-14 23:32:14 -04:00
config := getBaseConfig ( )
config . AdditionalSettings . ReporterCommentRequired = model . NewPointer ( true )
appErr := th . App . SaveContentFlaggingConfig ( config )
2025-10-13 02:54:01 -04:00
require . Nil ( t , appErr )
2025-11-12 07:00:51 -05:00
post := th . CreatePost ( t , th . BasicChannel )
2025-10-02 10:54:29 -04:00
flagData := model . FlagContentRequest {
Reason : "spam" ,
Comment : "" ,
}
2025-10-13 02:54:01 -04:00
appErr = th . App . FlagPost ( th . Context , post , th . BasicTeam . Id , th . BasicUser2 . Id , flagData )
2025-10-02 10:54:29 -04:00
require . NotNil ( t , appErr )
} )
t . Run ( "should fail when trying to flag already flagged post" , func ( t * testing . T ) {
2025-10-13 02:54:01 -04:00
appErr := th . App . SaveContentFlaggingConfig ( getBaseConfig ( ) )
require . Nil ( t , appErr )
2025-11-12 07:00:51 -05:00
post := th . CreatePost ( t , th . BasicChannel )
2025-10-02 10:54:29 -04:00
flagData := model . FlagContentRequest {
Reason : "spam" ,
Comment : "\"This is spam content\"" ,
}
// Flag the post first time
2025-10-13 02:54:01 -04:00
appErr = th . App . FlagPost ( th . Context , post , th . BasicTeam . Id , th . BasicUser2 . Id , flagData )
2025-10-02 10:54:29 -04:00
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 )
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
require . Equal ( t , "Cannot quarantine this post as it is already quarantined for review." , appErr . Id )
2025-10-02 10:54:29 -04:00
} )
t . Run ( "should hide flagged content when configured" , func ( t * testing . T ) {
2025-10-14 23:32:14 -04:00
config := getBaseConfig ( )
config . AdditionalSettings . HideFlaggedContent = model . NewPointer ( true )
appErr := th . App . SaveContentFlaggingConfig ( config )
2025-10-13 02:54:01 -04:00
require . Nil ( t , appErr )
2025-11-12 07:00:51 -05:00
post := th . CreatePost ( t , th . BasicChannel )
2025-10-02 10:54:29 -04:00
flagData := model . FlagContentRequest {
Reason : "spam" ,
Comment : "\"This is spam content\"" ,
}
2025-10-13 02:54:01 -04:00
appErr = th . App . FlagPost ( th . Context , post , th . BasicTeam . Id , th . BasicUser2 . Id , flagData )
2025-10-02 10:54:29 -04:00
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 ) {
2025-10-13 02:54:01 -04:00
appErr := th . App . SaveContentFlaggingConfig ( getBaseConfig ( ) )
require . Nil ( t , appErr )
2025-11-12 07:00:51 -05:00
post := th . CreatePost ( t , th . BasicChannel )
2025-10-02 10:54:29 -04:00
flagData := model . FlagContentRequest {
Reason : "harassment" ,
Comment : "\"This is harassment\"" ,
}
2025-10-13 02:54:01 -04:00
appErr = th . App . FlagPost ( th . Context , post , th . BasicTeam . Id , th . BasicUser2 . Id , flagData )
2025-10-02 10:54:29 -04:00
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 ) {
2025-10-13 02:54:01 -04:00
appErr := th . App . SaveContentFlaggingConfig ( getBaseConfig ( ) )
require . Nil ( t , appErr )
2025-11-12 07:00:51 -05:00
post := th . CreatePost ( t , th . BasicChannel )
2025-10-02 10:54:29 -04:00
flagData := model . FlagContentRequest {
Reason : "inappropriate" ,
Comment : "" ,
}
2025-10-13 02:54:01 -04:00
appErr = th . App . FlagPost ( th . Context , post , th . BasicTeam . Id , th . BasicUser2 . Id , flagData )
2025-10-02 10:54:29 -04:00
require . Nil ( t , appErr )
// Verify property values were created with empty comment
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-02 10:54:29 -04:00
mappedFields , appErr := th . App . GetContentFlaggingMappedFields ( groupId )
require . Nil ( t , appErr )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
commentValues , err := th . App . SearchPropertyValues ( rctx , groupId , model . PropertyValueSearchOpts {
2025-10-02 10:54:29 -04:00
TargetIDs : [ ] string { post . Id } ,
PerPage : CONTENT_FLAGGING_MAX_PROPERTY_VALUES ,
FieldID : mappedFields [ contentFlaggingPropertyNameReportingComment ] . ID ,
} )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-02 10:54:29 -04:00
require . Len ( t , commentValues , 1 )
require . Equal ( t , ` "" ` , string ( commentValues [ 0 ] . Value ) )
} )
t . Run ( "should set reporting time property" , func ( t * testing . T ) {
2025-10-13 02:54:01 -04:00
appErr := th . App . SaveContentFlaggingConfig ( getBaseConfig ( ) )
require . Nil ( t , appErr )
2025-11-12 07:00:51 -05:00
post := th . CreatePost ( t , th . BasicChannel )
2025-10-02 10:54:29 -04:00
flagData := model . FlagContentRequest {
Reason : "spam" ,
Comment : "\"Test comment\"" ,
}
beforeTime := model . GetMillis ( )
2025-10-13 02:54:01 -04:00
appErr = th . App . FlagPost ( th . Context , post , th . BasicTeam . Id , th . BasicUser2 . Id , flagData )
2025-10-02 10:54:29 -04:00
afterTime := model . GetMillis ( )
require . Nil ( t , appErr )
// Verify reporting time property was set
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-02 10:54:29 -04:00
mappedFields , appErr := th . App . GetContentFlaggingMappedFields ( groupId )
require . Nil ( t , appErr )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
timeValues , err := th . App . SearchPropertyValues ( rctx , groupId , model . PropertyValueSearchOpts {
2025-10-02 10:54:29 -04:00
TargetIDs : [ ] string { post . Id } ,
PerPage : CONTENT_FLAGGING_MAX_PROPERTY_VALUES ,
FieldID : mappedFields [ contentFlaggingPropertyNameReportingTime ] . ID ,
} )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-02 10:54:29 -04:00
require . Len ( t , timeValues , 1 )
var reportingTime int64
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
unmarshalErr := json . Unmarshal ( timeValues [ 0 ] . Value , & reportingTime )
require . NoError ( t , unmarshalErr )
2025-10-02 10:54:29 -04:00
require . True ( t , reportingTime >= beforeTime && reportingTime <= afterTime )
} )
}
2025-10-13 23:36:23 -04:00
func TestSearchReviewers ( t * testing . T ) {
mainHelper . Parallel ( t )
2025-11-12 07:00:51 -05:00
th := Setup ( t ) . InitBasic ( t )
2025-10-13 23:36:23 -04:00
th . App . Srv ( ) . SetLicense ( model . NewTestLicenseSKU ( model . LicenseShortSkuEnterpriseAdvanced ) )
getBaseConfig := func ( ) model . ContentFlaggingSettingsRequest {
config := model . ContentFlaggingSettingsRequest { }
config . SetDefaults ( )
return config
}
2025-11-03 00:36:13 -05:00
allProfilesSanitized := func ( reviewers [ ] * model . User ) {
for _ , reviewer := range reviewers {
require . Empty ( t , reviewer . LastPasswordUpdate )
require . Empty ( t , reviewer . NotifyProps )
}
}
2025-10-13 23:36:23 -04:00
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 )
2025-11-03 00:36:13 -05:00
allProfilesSanitized ( reviewers )
2025-10-13 23:36:23 -04:00
// 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 )
2025-11-03 00:36:13 -05:00
allProfilesSanitized ( reviewers )
2025-10-13 23:36:23 -04:00
// 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 )
2025-11-03 00:36:13 -05:00
allProfilesSanitized ( reviewers )
2025-10-13 23:36:23 -04:00
} )
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
2025-11-12 07:00:51 -05:00
teamAdmin := th . CreateUser ( t )
2025-10-13 23:36:23 -04:00
defer func ( ) {
_ = th . App . PermanentDeleteUser ( th . Context , teamAdmin )
} ( )
_ , _ , appErr = th . App . AddUserToTeam ( th . Context , th . BasicTeam . Id , teamAdmin . Id , "" )
require . Nil ( t , appErr )
2026-01-16 09:59:07 -05:00
_ , appErr = th . App . UpdateTeamMemberRoles ( th . Context , th . BasicTeam . Id , teamAdmin . Id , model . TeamUserRoleId + " " + model . TeamAdminRoleId )
2025-10-13 23:36:23 -04:00
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 )
2025-11-03 00:36:13 -05:00
allProfilesSanitized ( reviewers )
2025-10-13 23:36:23 -04:00
} )
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
2025-11-12 07:00:51 -05:00
teamAdmin := th . CreateUser ( t )
2025-10-13 23:36:23 -04:00
defer func ( ) {
_ = th . App . PermanentDeleteUser ( th . Context , teamAdmin )
} ( )
_ , _ , appErr = th . App . AddUserToTeam ( th . Context , th . BasicTeam . Id , teamAdmin . Id , "" )
require . Nil ( t , appErr )
2026-01-16 09:59:07 -05:00
_ , appErr = th . App . UpdateTeamMemberRoles ( th . Context , th . BasicTeam . Id , teamAdmin . Id , model . TeamUserRoleId + " " + model . TeamAdminRoleId )
2025-10-13 23:36:23 -04:00
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 )
2025-11-03 00:36:13 -05:00
require . Equal ( t , 3 , len ( reviewers ) )
allProfilesSanitized ( reviewers )
2025-10-13 23:36:23 -04:00
// 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 )
2025-11-03 00:36:13 -05:00
allProfilesSanitized ( reviewers )
2025-10-13 23:36:23 -04:00
} )
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 )
2025-11-03 00:36:13 -05:00
allProfilesSanitized ( reviewers )
2025-10-13 23:36:23 -04:00
} )
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 )
2025-11-03 00:36:13 -05:00
allProfilesSanitized ( reviewers )
2025-10-13 23:36:23 -04:00
} )
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 )
2025-11-03 00:36:13 -05:00
allProfilesSanitized ( reviewers )
2025-10-13 23:36:23 -04:00
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 )
2025-11-12 07:00:51 -05:00
th := Setup ( t ) . InitBasic ( t )
2025-10-13 23:36:23 -04:00
th . App . Srv ( ) . SetLicense ( model . NewTestLicenseSKU ( model . LicenseShortSkuEnterpriseAdvanced ) )
t . Run ( "should return reviewer posts for flagged post" , func ( t * testing . T ) {
2025-10-14 23:32:14 -04:00
require . Nil ( t , setBaseConfig ( th ) )
2025-10-13 23:36:23 -04:00
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-10-13 23:36:23 -04:00
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-13 23:36:23 -04:00
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 ) {
2025-10-14 23:32:14 -04:00
require . Nil ( t , setBaseConfig ( th ) )
2025-11-12 07:00:51 -05:00
post := th . CreatePost ( t , th . BasicChannel )
2025-10-13 23:36:23 -04:00
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-13 23:36:23 -04:00
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
2025-10-14 23:32:14 -04:00
config := getBaseConfig ( th )
2025-10-13 23:36:23 -04:00
config . ReviewerSettings . CommonReviewerIds = [ ] string { th . BasicUser . Id , th . BasicUser2 . Id }
appErr := th . App . SaveContentFlaggingConfig ( config )
require . Nil ( t , appErr )
2025-11-12 07:00:51 -05:00
post := th . CreatePost ( t , th . BasicChannel )
2025-10-13 23:36:23 -04:00
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 )
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-13 23:36:23 -04:00
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 ) {
2025-10-14 23:32:14 -04:00
require . Nil ( t , setBaseConfig ( th ) )
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-13 23:36:23 -04:00
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 )
2025-11-12 07:00:51 -05:00
th := Setup ( t ) . InitBasic ( t )
2025-10-13 23:36:23 -04:00
th . App . Srv ( ) . SetLicense ( model . NewTestLicenseSKU ( model . LicenseShortSkuEnterpriseAdvanced ) )
t . Run ( "should post reviewer message to thread" , func ( t * testing . T ) {
2025-10-14 23:32:14 -04:00
require . Nil ( t , setBaseConfig ( th ) )
2025-10-13 23:36:23 -04:00
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-10-13 23:36:23 -04:00
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-13 23:36:23 -04:00
testMessage := "Test reviewer message"
2026-03-16 17:03:43 -04:00
_ , appErr := th . App . postReviewerMessage ( th . Context , testMessage , groupId , post . Id )
2025-10-13 23:36:23 -04:00
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
2025-10-14 23:32:14 -04:00
config := getBaseConfig ( th )
2025-10-13 23:36:23 -04:00
config . ReviewerSettings . CommonReviewerIds = [ ] string { th . BasicUser . Id , th . BasicUser2 . Id }
appErr := th . App . SaveContentFlaggingConfig ( config )
require . Nil ( t , appErr )
2025-11-12 07:00:51 -05:00
post := th . CreatePost ( t , th . BasicChannel )
2025-10-13 23:36:23 -04:00
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 )
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-13 23:36:23 -04:00
testMessage := "Test message for multiple reviewers"
2025-10-14 23:32:14 -04:00
_ , appErr = th . App . postReviewerMessage ( th . Context , testMessage , groupId , post . Id )
2025-10-13 23:36:23 -04:00
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 ) {
2025-10-14 23:32:14 -04:00
require . Nil ( t , setBaseConfig ( th ) )
2025-11-12 07:00:51 -05:00
post := th . CreatePost ( t , th . BasicChannel )
2025-10-13 23:36:23 -04:00
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-13 23:36:23 -04:00
testMessage := "Test message for non-flagged post"
2026-03-16 17:03:43 -04:00
_ , appErr := th . App . postReviewerMessage ( th . Context , testMessage , groupId , post . Id )
2025-10-13 23:36:23 -04:00
require . Nil ( t , appErr )
} )
t . Run ( "should handle message with special characters" , func ( t * testing . T ) {
2025-10-14 23:32:14 -04:00
require . Nil ( t , setBaseConfig ( th ) )
2025-10-13 23:36:23 -04:00
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-10-13 23:36:23 -04:00
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-13 23:36:23 -04:00
testMessage := "Test message with special chars: @user #channel ~team & <script>alert('xss')</script>"
2026-03-16 17:03:43 -04:00
_ , appErr := th . App . postReviewerMessage ( th . Context , testMessage , groupId , post . Id )
2025-10-13 23:36:23 -04:00
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 )
} )
}
2025-10-14 23:32:14 -04:00
// 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 )
2025-11-12 07:00:51 -05:00
th := Setup ( t ) . InitBasic ( t )
2025-10-14 23:32:14 -04:00
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 )
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-10-14 23:32:14 -04:00
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-14 23:32:14 -04:00
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
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
reviewerMessage := fmt . Sprintf ( "The quarantined message was removed by @%s\n\nWith comment:\n\n> %s" , th . SystemAdminUser . Username , actorComment )
2025-10-14 23:32:14 -04:00
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
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
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 )
2025-10-14 23:32:14 -04:00
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
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
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 )
2025-10-14 23:32:14 -04:00
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 )
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-10-14 23:32:14 -04:00
// Setup notification config for only author
appErr = setupNotificationConfig ( th , map [ model . ContentFlaggingEvent ] [ ] model . NotificationTarget {
model . EventContentRemoved : { model . TargetReviewers } ,
} )
require . Nil ( t , appErr )
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-14 23:32:14 -04:00
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 )
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
expectedMessage := fmt . Sprintf ( "The quarantined message was removed by @%s\n\nWith comment:\n\n> %s" , th . SystemAdminUser . Username , "Test comment" )
2025-10-14 23:32:14 -04:00
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 )
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-10-14 23:32:14 -04:00
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-14 23:32:14 -04:00
createdPosts := th . App . sendFlaggedPostRemovalNotification ( th . Context , post , th . SystemAdminUser . Id , "" , groupId )
require . Len ( t , createdPosts , 1 )
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
expectedMessage := fmt . Sprintf ( "The quarantined message was removed by @%s" , th . SystemAdminUser . Username )
2025-10-14 23:32:14 -04:00
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 )
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-10-14 23:32:14 -04:00
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-14 23:32:14 -04:00
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 )
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
expectedMessage := fmt . Sprintf ( "The quarantined message was removed by @%s\n\nWith comment:\n\n> %s" , th . SystemAdminUser . Username , specialComment )
2025-10-14 23:32:14 -04:00
verifyNotificationPost ( t , createdPosts [ 0 ] , expectedMessage , createdPosts [ 0 ] . UserId , createdPosts [ 0 ] . ChannelId )
} )
}
func TestSendKeepFlaggedPostNotification ( t * testing . T ) {
mainHelper . Parallel ( t )
2025-11-12 07:00:51 -05:00
th := Setup ( t ) . InitBasic ( t )
2025-10-14 23:32:14 -04:00
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 )
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-10-14 23:32:14 -04:00
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-14 23:32:14 -04:00
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
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
reviewerMessage := fmt . Sprintf ( "The quarantined message was retained by @%s\n\nWith comment:\n\n> %s" , th . SystemAdminUser . Username , actorComment )
2025-10-14 23:32:14 -04:00
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
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
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 )
2025-10-14 23:32:14 -04:00
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
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
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 )
2025-10-14 23:32:14 -04:00
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 )
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-10-14 23:32:14 -04:00
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-14 23:32:14 -04:00
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 )
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
expectedMessage := fmt . Sprintf ( "The quarantined message was retained by @%s\n\nWith comment:\n\n> %s" , th . SystemAdminUser . Username , comment )
2025-10-14 23:32:14 -04:00
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 )
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-10-14 23:32:14 -04:00
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-14 23:32:14 -04:00
createdPosts := th . App . sendKeepFlaggedPostNotification ( th . Context , post , th . SystemAdminUser . Id , "" , groupId )
require . Len ( t , createdPosts , 1 )
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
expectedMessage := fmt . Sprintf ( "The quarantined message was retained by @%s" , th . SystemAdminUser . Username )
2025-10-14 23:32:14 -04:00
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 )
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-10-14 23:32:14 -04:00
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-14 23:32:14 -04:00
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 )
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
expectedMessage := fmt . Sprintf ( "The quarantined message was retained by @%s\n\nWith comment:\n\n> %s" , th . SystemAdminUser . Username , specialComment )
2025-10-14 23:32:14 -04:00
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 )
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-10-14 23:32:14 -04:00
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-10-14 23:32:14 -04:00
// 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 )
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
expectedMessage := fmt . Sprintf ( "The quarantined message was retained by @%s\n\nWith comment:\n\n> %s" , th . BasicUser . Username , "Reviewed by different user" )
2025-10-14 23:32:14 -04:00
verifyNotificationPost ( t , createdPosts [ 0 ] , expectedMessage , createdPosts [ 0 ] . UserId , createdPosts [ 0 ] . ChannelId )
} )
}
2025-11-06 12:41:34 -05:00
func TestPermanentDeleteFlaggedPost ( t * testing . T ) {
mainHelper . Parallel ( t )
2025-11-12 07:00:51 -05:00
th := Setup ( t ) . InitBasic ( t )
2025-11-06 12:41:34 -05:00
th . App . Srv ( ) . SetLicense ( model . NewTestLicenseSKU ( model . LicenseShortSkuEnterpriseAdvanced ) )
require . Nil ( t , setBaseConfig ( th ) )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
rctx := RequestContextWithCallerID ( th . Context , anonymousCallerId )
2025-11-06 12:41:34 -05:00
t . Run ( "should successfully permanently delete pending flagged post" , func ( t * testing . T ) {
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-11-06 12:41:34 -05:00
actionRequest := & model . FlagContentActionRequest {
Comment : "This post violates community guidelines" ,
}
2025-11-12 07:00:51 -05:00
appErr := th . App . PermanentDeleteFlaggedPost ( th . Context , actionRequest , th . SystemAdminUser . Id , post )
2025-11-06 12:41:34 -05:00
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
2025-11-19 23:39:36 -05:00
statusValue , appErr := th . App . GetPostContentFlaggingPropertyValue ( post . Id , ContentFlaggingPropertyNameStatus )
2025-11-06 12:41:34 -05:00
require . Nil ( t , appErr )
require . Equal ( t , ` " ` + model . ContentFlaggingStatusRemoved + ` " ` , string ( statusValue . Value ) )
// Verify actor properties were created
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-11-06 12:41:34 -05:00
mappedFields , appErr := th . App . GetContentFlaggingMappedFields ( groupId )
require . Nil ( t , appErr )
// Check actor user property
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
actorValues , err := th . App . SearchPropertyValues ( rctx , groupId , model . PropertyValueSearchOpts {
2025-11-06 12:41:34 -05:00
TargetIDs : [ ] string { post . Id } ,
PerPage : CONTENT_FLAGGING_MAX_PROPERTY_VALUES ,
FieldID : mappedFields [ contentFlaggingPropertyNameActorUserID ] . ID ,
} )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-11-06 12:41:34 -05:00
require . Len ( t , actorValues , 1 )
require . Equal ( t , ` " ` + th . SystemAdminUser . Id + ` " ` , string ( actorValues [ 0 ] . Value ) )
// Check actor comment property
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
commentValues , err := th . App . SearchPropertyValues ( rctx , groupId , model . PropertyValueSearchOpts {
2025-11-06 12:41:34 -05:00
TargetIDs : [ ] string { post . Id } ,
PerPage : CONTENT_FLAGGING_MAX_PROPERTY_VALUES ,
FieldID : mappedFields [ contentFlaggingPropertyNameActorComment ] . ID ,
} )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-11-06 12:41:34 -05:00
require . Len ( t , commentValues , 1 )
require . Equal ( t , ` "This post violates community guidelines" ` , string ( commentValues [ 0 ] . Value ) )
// Check action time property
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
timeValues , err := th . App . SearchPropertyValues ( rctx , groupId , model . PropertyValueSearchOpts {
2025-11-06 12:41:34 -05:00
TargetIDs : [ ] string { post . Id } ,
PerPage : CONTENT_FLAGGING_MAX_PROPERTY_VALUES ,
FieldID : mappedFields [ contentFlaggingPropertyNameActionTime ] . ID ,
} )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-11-06 12:41:34 -05:00
require . Len ( t , timeValues , 1 )
var actionTime int64
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
unmarshalErr := json . Unmarshal ( timeValues [ 0 ] . Value , & actionTime )
require . NoError ( t , unmarshalErr )
2025-11-06 12:41:34 -05:00
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 )
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-11-06 12:41:34 -05:00
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 ) {
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-11-06 12:41:34 -05:00
// Assign the post to a reviewer first
2025-11-12 07:00:51 -05:00
appErr := th . App . AssignFlaggedPostReviewer ( th . Context , post . Id , th . BasicChannel . TeamId , th . BasicUser . Id , th . SystemAdminUser . Id )
2025-11-06 12:41:34 -05:00
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
2025-11-19 23:39:36 -05:00
statusValue , appErr := th . App . GetPostContentFlaggingPropertyValue ( post . Id , ContentFlaggingPropertyNameStatus )
2025-11-06 12:41:34 -05:00
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 ) {
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-11-06 12:41:34 -05:00
// Set status to removed
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-11-06 12:41:34 -05:00
2025-11-19 23:39:36 -05:00
statusValue , appErr := th . App . GetPostContentFlaggingPropertyValue ( post . Id , ContentFlaggingPropertyNameStatus )
2025-11-06 12:41:34 -05:00
require . Nil ( t , appErr )
statusValue . Value = json . RawMessage ( fmt . Sprintf ( ` "%s" ` , model . ContentFlaggingStatusRemoved ) )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
_ , err = th . App . UpdatePropertyValue ( rctx , groupId , statusValue )
require . Nil ( t , err )
2025-11-06 12:41:34 -05:00
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 )
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
require . Equal ( t , "api.data_spillage.error.post_not_in_progress" , appErr . Id )
2025-11-06 12:41:34 -05:00
require . Equal ( t , http . StatusBadRequest , appErr . StatusCode )
} )
t . Run ( "should fail when trying to delete already retained post" , func ( t * testing . T ) {
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-11-06 12:41:34 -05:00
// Set status to retained
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-11-06 12:41:34 -05:00
2025-11-19 23:39:36 -05:00
statusValue , appErr := th . App . GetPostContentFlaggingPropertyValue ( post . Id , ContentFlaggingPropertyNameStatus )
2025-11-06 12:41:34 -05:00
require . Nil ( t , appErr )
statusValue . Value = json . RawMessage ( fmt . Sprintf ( ` "%s" ` , model . ContentFlaggingStatusRetained ) )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
_ , err = th . App . UpdatePropertyValue ( rctx , groupId , statusValue )
require . Nil ( t , err )
2025-11-06 12:41:34 -05:00
actionRequest := & model . FlagContentActionRequest {
Comment : "Trying to delete retained post" ,
}
appErr = th . App . PermanentDeleteFlaggedPost ( th . Context , actionRequest , th . SystemAdminUser . Id , post )
require . NotNil ( t , appErr )
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
require . Equal ( t , "api.data_spillage.error.post_not_in_progress" , appErr . Id )
2025-11-06 12:41:34 -05:00
require . Equal ( t , http . StatusBadRequest , appErr . StatusCode )
} )
t . Run ( "should fail when trying to delete non-flagged post" , func ( t * testing . T ) {
2025-11-12 07:00:51 -05:00
post := th . CreatePost ( t , th . BasicChannel )
2025-11-06 12:41:34 -05:00
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 ) {
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-11-06 12:41:34 -05:00
actionRequest := & model . FlagContentActionRequest {
Comment : "" ,
}
2025-11-12 07:00:51 -05:00
appErr := th . App . PermanentDeleteFlaggedPost ( th . Context , actionRequest , th . SystemAdminUser . Id , post )
2025-11-06 12:41:34 -05:00
require . Nil ( t , appErr )
// Verify empty comment was stored
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-11-06 12:41:34 -05:00
mappedFields , appErr := th . App . GetContentFlaggingMappedFields ( groupId )
require . Nil ( t , appErr )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
commentValues , err := th . App . SearchPropertyValues ( rctx , groupId , model . PropertyValueSearchOpts {
2025-11-06 12:41:34 -05:00
TargetIDs : [ ] string { post . Id } ,
PerPage : CONTENT_FLAGGING_MAX_PROPERTY_VALUES ,
FieldID : mappedFields [ contentFlaggingPropertyNameActorComment ] . ID ,
} )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-11-06 12:41:34 -05:00
require . Len ( t , commentValues , 1 )
require . Equal ( t , ` "" ` , string ( commentValues [ 0 ] . Value ) )
} )
t . Run ( "should handle special characters in comment" , func ( t * testing . T ) {
2025-11-12 07:00:51 -05:00
post := setupFlaggedPost ( t , th )
2025-11-06 12:41:34 -05:00
specialComment := "Comment with @mentions #channels ~teams & <script>alert('xss')</script>"
actionRequest := & model . FlagContentActionRequest {
Comment : specialComment ,
}
2025-11-12 07:00:51 -05:00
appErr := th . App . PermanentDeleteFlaggedPost ( th . Context , actionRequest , th . SystemAdminUser . Id , post )
2025-11-06 12:41:34 -05:00
require . Nil ( t , appErr )
// Verify special characters were properly escaped and stored
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-11-06 12:41:34 -05:00
mappedFields , appErr := th . App . GetContentFlaggingMappedFields ( groupId )
require . Nil ( t , appErr )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
commentValues , err := th . App . SearchPropertyValues ( rctx , groupId , model . PropertyValueSearchOpts {
2025-11-06 12:41:34 -05:00
TargetIDs : [ ] string { post . Id } ,
PerPage : CONTENT_FLAGGING_MAX_PROPERTY_VALUES ,
FieldID : mappedFields [ contentFlaggingPropertyNameActorComment ] . ID ,
} )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2025-11-06 12:41:34 -05:00
require . Len ( t , commentValues , 1 )
var storedComment string
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
unmarshalErr := json . Unmarshal ( commentValues [ 0 ] . Value , & storedComment )
require . NoError ( t , unmarshalErr )
2025-11-06 12:41:34 -05:00
require . Equal ( t , specialComment , storedComment )
} )
t . Run ( "should handle post with file attachments" , func ( t * testing . T ) {
// Create a post with file attachments
2025-11-12 07:00:51 -05:00
post := th . CreatePost ( t , th . BasicChannel )
2025-11-06 12:41:34 -05:00
// 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 }
2026-01-20 04:38:27 -05:00
_ , _ , appErr := th . App . UpdatePost ( th . Context , post , & model . UpdatePostOptions { } )
2025-11-06 12:41:34 -05:00
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 )
2026-02-22 19:45:40 -05:00
return appErr == nil
2025-11-06 12:41:34 -05:00
} , 5 * time . Second , 200 * time . Millisecond )
// Verify post was deleted and status updated
2025-11-19 23:39:36 -05:00
statusValue , appErr := th . App . GetPostContentFlaggingPropertyValue ( post . Id , ContentFlaggingPropertyNameStatus )
2025-11-06 12:41:34 -05:00
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 ) {
2025-11-12 07:00:51 -05:00
post := th . CreatePost ( t , th . BasicChannel )
2025-11-06 12:41:34 -05:00
// Create edit history for the post
editedPost := post . Clone ( )
editedPost . Message = "Edited message"
editedPost . EditAt = model . GetMillis ( )
2026-01-20 04:38:27 -05:00
_ , _ , appErr := th . App . UpdatePost ( th . Context , editedPost , & model . UpdatePostOptions { } )
2025-11-06 12:41:34 -05:00
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 )
2026-02-22 19:45:40 -05:00
return appErr == nil
2025-11-06 12:41:34 -05:00
} , 5 * time . Second , 200 * time . Millisecond )
// Verify status was updated
2025-11-19 23:39:36 -05:00
statusValue , appErr := th . App . GetPostContentFlaggingPropertyValue ( editedPost . Id , ContentFlaggingPropertyNameStatus )
2025-11-06 12:41:34 -05:00
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 )
2025-11-12 07:00:51 -05:00
post := th . CreatePost ( t , th . BasicChannel )
2025-11-06 12:41:34 -05:00
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 )
2026-02-22 19:45:40 -05:00
return appErr == nil
2025-11-06 12:41:34 -05:00
} , 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
2025-11-19 23:39:36 -05:00
statusValue , appErr := th . App . GetPostContentFlaggingPropertyValue ( deletedPost . Id , ContentFlaggingPropertyNameStatus )
2025-11-06 12:41:34 -05:00
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 ) )
} )
}
2026-02-22 19:45:40 -05:00
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
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2026-02-22 19:45:40 -05:00
statusValue , appErr := th . App . GetPostContentFlaggingPropertyValue ( post . Id , ContentFlaggingPropertyNameStatus )
require . Nil ( t , appErr )
statusValue . Value = json . RawMessage ( fmt . Sprintf ( ` "%s" ` , model . ContentFlaggingStatusRemoved ) )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
_ , appErr = th . App . UpdatePropertyValue ( th . Context , groupId , statusValue )
require . Nil ( t , appErr )
2026-02-22 19:45:40 -05:00
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 )
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
require . Equal ( t , "api.data_spillage.error.post_not_in_progress" , appErr . Id )
2026-02-22 19:45:40 -05:00
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
2026-03-16 17:03:43 -04:00
groupId , err := th . App . ContentFlaggingGroupId ( )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
require . Nil ( t , err )
2026-02-22 19:45:40 -05:00
statusValue , appErr := th . App . GetPostContentFlaggingPropertyValue ( post . Id , ContentFlaggingPropertyNameStatus )
require . Nil ( t , appErr )
statusValue . Value = json . RawMessage ( fmt . Sprintf ( ` "%s" ` , model . ContentFlaggingStatusRetained ) )
Add property system app layer architecture (#35157)
* 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>
2026-03-26 03:54:50 -04:00
_ , appErr = th . App . UpdatePropertyValue ( th . Context , groupId , statusValue )
require . Nil ( t , appErr )
2026-02-22 19:45:40 -05:00
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 )
Rename Content Flagging to Data Spillage Handling (#35407)
* Rename Content Flagging to Data Spillage Handling
Update all user-facing text to use "Data Spillage Handling" and
"Quarantine for Review" terminology. Rename i18n keys that referenced
content flagging. Auto-patch bot display name on pre-existing servers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fixed searchable stringgs
* Revert unintended package-lock.json changes
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix i18n extract check: correct typo and key ordering
Fix "posed" -> "posted" typo in keep/remove quarantine modal
defaultMessages. Move admin.contentFlagging.title to correct
alphabetical position in en.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix webapp tests for Data Spillage Handling rename
Update test assertions to match renamed i18n strings:
notification settings, content reviewers, and additional
settings tests now expect the new quarantine terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Use translatable i18n strings for notification messages
Replace hardcoded "flagged for review" notification templates with
i18n.T() calls using "quarantined for review" terminology. Add six
new server i18n keys for author, reporter, and reviewer notifications.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix server i18n key mismatches and update test assertions
Rename remaining app.content_flagging.* keys to app.data_spillage.*
in server/i18n/en.json to match Go code references. Fix the
quarantine_post_confirmation key name. Update test assertions to
match new "quarantined for review" terminology.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix gofmt formatting in content_flagging.go
Co-authored-by: Cursor <cursoragent@cursor.com>
* Prevent nil bot on PatchBot failure in getContentReviewBot
Use a separate variable for PatchBot result so the original bot
is preserved if the display name update fails.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Reorder server i18n keys after extract
Run mmgotool i18n extract to sort entries into correct
alphabetical order.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Replace i18n.T() with fmt.Sprintf for notification messages and fix test assertions
Use direct string formatting for bot notification messages instead of
i18n translation keys, which were being removed by mmgotool i18n extract
due to indirect key references. Also update test expectations for renamed
error keys (content_flagging -> data_spillage).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Update default quarantine reasons to DISC-aligned terminology
Replace generic content moderation reasons with defense/intelligence
sector terminology: Classification mismatch, Need-to-know violation,
PII exposure, OPSEC concern, CUI violation, Unauthorized disclosure,
and Other. Updated across model, API tests, webapp tests, and e2e tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Adding a string missing from bad merge
* Update remaining flagged terminology and icon for data spillage rename
- Change post menu icon from flag-outline to alert-outline
- Update reviewer notification: "quarantined" -> "submitted" a message
- Update action notifications: "flagged message" -> "quarantined message"
- Update modal errors: "flagging" -> "quarantining" this message
- Update report title: "flagged" -> "submitted" a message for review
- Update e2e page object locator for renamed menu item
Made-with: Cursor
* Fix tests
* Fix quarantine icon alignment in post dot menu
Use AlertOutlineIcon React component with size={18} instead of raw
<i> tag to match the sizing of all other menu item icons.
Made-with: Cursor
* Fixed E2E tests
* Missing test fix
* Fix E2E tests
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
2026-03-06 21:15:01 -05:00
require . Equal ( t , "api.data_spillage.error.post_not_in_progress" , appErr . Id )
2026-02-22 19:45:40 -05:00
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 )
} )
}