Mm 67896 manual cherry pick onto release 10.11 (#35989)
* improves time limit checks * consistently check for presence of patch fields * fix variable shadowing in test * allow idempotent pinning operations with time limit expired * new utility function for post limit time check * fix style issue * Add missing E2E CI files and delivery-platform migration for release-10.11 - Add calculate-playwright-results and calculate-cypress-results GitHub Actions (referenced by e2e-tests-playwright-template.yml and e2e-tests-cypress-template.yml but never backported to release-10.11) - Add e2e-tests/playwright/merge.config.mjs (required by merge-reports step) - Add run-specs Makefile target and server.run_specs.sh (required by run-failed-tests job) - Fix merge-shard-results step: pin @playwright/test version and add fallback for when no blob reports exist (json reporter output used directly) - Remove pull_request trigger from e2e-tests-ci.yml (delivery-platform migration) - Remove dead e2e-fulltests-ci.yml and e2e-tests-ci-template.yml Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
e092af7a33
Коммит
b21ef30202
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -977,6 +978,14 @@ func searchPosts(c *Context, w http.ResponseWriter, r *http.Request, teamId stri
|
||||
}
|
||||
}
|
||||
|
||||
func postEditTimeLimitExpired(cfg *model.Config, post *model.Post) bool {
|
||||
limit := *cfg.ServiceSettings.PostEditTimeLimit
|
||||
if limit == -1 {
|
||||
return false
|
||||
}
|
||||
return model.GetMillis() > post.CreateAt+int64(limit)*1000
|
||||
}
|
||||
|
||||
func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequirePostId()
|
||||
if c.Err != nil {
|
||||
@@ -1035,6 +1044,21 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
post.FileIds = originalPost.FileIds
|
||||
}
|
||||
|
||||
// passing nil props should not have any effect on a post's props
|
||||
// so, we restore the original props in this case
|
||||
if post.Props == nil {
|
||||
post.Props = originalPost.Props
|
||||
}
|
||||
|
||||
if postEditTimeLimitExpired(c.App.Config(), originalPost) &&
|
||||
(post.Message != originalPost.Message ||
|
||||
!slices.Equal(post.FileIds, originalPost.FileIds) ||
|
||||
model.StringInterfaceToJSON(post.GetProps()) != model.StringInterfaceToJSON(originalPost.GetProps()) ||
|
||||
post.IsPinned != originalPost.IsPinned) {
|
||||
c.Err = model.NewAppError("UpdatePost", "api.post.update_post.permissions_time_limit.app_error", map[string]any{"timeLimit": *c.App.Config().ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Check upload_file permission only if update is adding NEW files (not just keeping existing ones)
|
||||
checkUploadFilePermissionForNewFiles(c, post.FileIds, originalPost)
|
||||
if c.Err != nil {
|
||||
@@ -1051,11 +1075,6 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
post.Id = c.Params.PostId
|
||||
|
||||
if *c.App.Config().ServiceSettings.PostEditTimeLimit != -1 && model.GetMillis() > originalPost.CreateAt+int64(*c.App.Config().ServiceSettings.PostEditTimeLimit*1000) && post.Message != originalPost.Message {
|
||||
c.Err = model.NewAppError("UpdatePost", "api.post.update_post.permissions_time_limit.app_error", map[string]any{"timeLimit": *c.App.Config().ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
rpost, isMemberForPreviews, err := c.App.UpdatePost(c.AppContext, c.App.PostWithProxyRemovedFromImageURLs(&post), &model.UpdatePostOptions{SafeUpdate: false})
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
@@ -1104,7 +1123,7 @@ func patchPost(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
isMember := postPatchChecks(c, auditRec, post.Message)
|
||||
isMember := postPatchChecks(c, auditRec, &post)
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
@@ -1140,7 +1159,7 @@ func patchPost(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func postPatchChecks(c *Context, auditRec *model.AuditRecord, message *string) bool {
|
||||
func postPatchChecks(c *Context, auditRec *model.AuditRecord, patch *model.PostPatch) bool {
|
||||
originalPost, err := c.App.GetSinglePost(c.AppContext, c.Params.PostId, false)
|
||||
if err != nil {
|
||||
c.SetPermissionError(model.PermissionEditPost)
|
||||
@@ -1169,7 +1188,7 @@ func postPatchChecks(c *Context, auditRec *model.AuditRecord, message *string) b
|
||||
return false
|
||||
}
|
||||
|
||||
if *c.App.Config().ServiceSettings.PostEditTimeLimit != -1 && model.GetMillis() > originalPost.CreateAt+int64(*c.App.Config().ServiceSettings.PostEditTimeLimit*1000) && message != nil {
|
||||
if postEditTimeLimitExpired(c.App.Config(), originalPost) && !patch.IsEmpty() {
|
||||
c.Err = model.NewAppError("patchPost", "api.post.update_post.permissions_time_limit.app_error", map[string]any{"timeLimit": *c.App.Config().ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest)
|
||||
return isMember
|
||||
}
|
||||
@@ -1264,6 +1283,18 @@ func saveIsPinnedPost(c *Context, w http.ResponseWriter, isPinned bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// Allow no-op requests (e.g. pinning an already-pinned post) regardless of age.
|
||||
if post.IsPinned == isPinned {
|
||||
auditRec.Success()
|
||||
ReturnStatusOK(w)
|
||||
return
|
||||
}
|
||||
|
||||
if postEditTimeLimitExpired(c.App.Config(), post) {
|
||||
c.Err = model.NewAppError("saveIsPinnedPost", "api.post.update_post.permissions_time_limit.app_error", map[string]any{"timeLimit": *c.App.Config().ServiceSettings.PostEditTimeLimit}, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
patch := &model.PostPatch{}
|
||||
patch.IsPinned = model.NewPointer(isPinned)
|
||||
|
||||
@@ -1557,7 +1588,7 @@ func restorePostVersion(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
isMember := postPatchChecks(c, auditRec, &toRestorePost.Message)
|
||||
isMember := postPatchChecks(c, auditRec, &model.PostPatch{Message: &toRestorePost.Message, FileIds: &toRestorePost.FileIds})
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1565,6 +1565,94 @@ func TestUpdatePost(t *testing.T) {
|
||||
CheckBadRequestStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("change file ids but not message, post too old", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = 1
|
||||
})
|
||||
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = -1
|
||||
})
|
||||
|
||||
fileResp, _, err := client.UploadFile(context.Background(), data, channel.Id, "test.png")
|
||||
require.NoError(t, err)
|
||||
newFileId := fileResp.FileInfos[0].Id
|
||||
|
||||
oldPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{
|
||||
ChannelId: channel.Id,
|
||||
Message: "original message",
|
||||
UserId: th.BasicUser.Id,
|
||||
CreateAt: model.GetMillis() - 2000,
|
||||
}, channel, model.CreatePostFlags{SetOnline: true})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
up := &model.Post{
|
||||
Id: oldPost.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: oldPost.Message,
|
||||
FileIds: model.StringArray{newFileId},
|
||||
}
|
||||
_, resp, err := client.UpdatePost(context.Background(), oldPost.Id, up)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Equal(t, "api.post.update_post.permissions_time_limit.app_error", err.(*model.AppError).Id)
|
||||
})
|
||||
|
||||
t.Run("change props but not message, post too old", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = 1
|
||||
})
|
||||
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = -1
|
||||
})
|
||||
|
||||
oldPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{
|
||||
ChannelId: channel.Id,
|
||||
Message: "original message",
|
||||
UserId: th.BasicUser.Id,
|
||||
CreateAt: model.GetMillis() - 2000,
|
||||
}, channel, model.CreatePostFlags{SetOnline: true})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
up := &model.Post{
|
||||
Id: oldPost.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: oldPost.Message,
|
||||
Props: model.StringInterface{"channel_header": "injected"},
|
||||
}
|
||||
_, resp, err := client.UpdatePost(context.Background(), oldPost.Id, up)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Equal(t, "api.post.update_post.permissions_time_limit.app_error", err.(*model.AppError).Id)
|
||||
})
|
||||
|
||||
t.Run("change is_pinned but not message, post too old", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = 1
|
||||
})
|
||||
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = -1
|
||||
})
|
||||
|
||||
oldPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{
|
||||
ChannelId: channel.Id,
|
||||
Message: "original message",
|
||||
UserId: th.BasicUser.Id,
|
||||
CreateAt: model.GetMillis() - 2000,
|
||||
}, channel, model.CreatePostFlags{SetOnline: true})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
up := &model.Post{
|
||||
Id: oldPost.Id,
|
||||
ChannelId: channel.Id,
|
||||
Message: oldPost.Message,
|
||||
IsPinned: true,
|
||||
}
|
||||
_, resp, err := client.UpdatePost(context.Background(), oldPost.Id, up)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Equal(t, "api.post.update_post.permissions_time_limit.app_error", err.(*model.AppError).Id)
|
||||
})
|
||||
|
||||
t.Run("err with integrations-reserved props", func(t *testing.T) {
|
||||
originalHardenedModeSetting := *th.App.Config().ServiceSettings.ExperimentalEnableHardenedMode
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
@@ -2096,6 +2184,110 @@ func TestPatchPost(t *testing.T) {
|
||||
require.Equal(t, "api.post.update_post.permissions_time_limit.app_error", err.(*model.AppError).Id, "should be time limit error")
|
||||
})
|
||||
|
||||
t.Run("patch file ids only, time limit expired", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = 1
|
||||
})
|
||||
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = -1
|
||||
})
|
||||
|
||||
fileResp, _, err := th.SystemAdminClient.UploadFile(context.Background(), data, channel.Id, "test.png")
|
||||
require.NoError(t, err)
|
||||
newFileId := fileResp.FileInfos[0].Id
|
||||
|
||||
oldPost := &model.Post{
|
||||
ChannelId: channel.Id,
|
||||
Message: "original message",
|
||||
CreateAt: model.GetMillis() - 2000,
|
||||
}
|
||||
oldPost, _, err = th.SystemAdminClient.CreatePost(context.Background(), oldPost)
|
||||
require.NoError(t, err)
|
||||
|
||||
patch := &model.PostPatch{
|
||||
FileIds: &model.StringArray{newFileId},
|
||||
}
|
||||
_, resp, err := th.SystemAdminClient.PatchPost(context.Background(), oldPost.Id, patch)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Equal(t, "api.post.update_post.permissions_time_limit.app_error", err.(*model.AppError).Id)
|
||||
})
|
||||
|
||||
t.Run("patch props only, time limit expired", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = 1
|
||||
})
|
||||
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = -1
|
||||
})
|
||||
|
||||
oldPost := &model.Post{
|
||||
ChannelId: channel.Id,
|
||||
Message: "original message",
|
||||
CreateAt: model.GetMillis() - 2000,
|
||||
}
|
||||
oldPost, _, err := th.SystemAdminClient.CreatePost(context.Background(), oldPost)
|
||||
require.NoError(t, err)
|
||||
|
||||
patch := &model.PostPatch{
|
||||
Props: &model.StringInterface{"channel_header": "injected"},
|
||||
}
|
||||
_, resp, err := th.SystemAdminClient.PatchPost(context.Background(), oldPost.Id, patch)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Equal(t, "api.post.update_post.permissions_time_limit.app_error", err.(*model.AppError).Id)
|
||||
})
|
||||
|
||||
t.Run("patch is_pinned only, time limit expired", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = 1
|
||||
})
|
||||
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = -1
|
||||
})
|
||||
|
||||
oldPost := &model.Post{
|
||||
ChannelId: channel.Id,
|
||||
Message: "original message",
|
||||
CreateAt: model.GetMillis() - 2000,
|
||||
}
|
||||
oldPost, _, err := th.SystemAdminClient.CreatePost(context.Background(), oldPost)
|
||||
require.NoError(t, err)
|
||||
|
||||
patch := &model.PostPatch{
|
||||
IsPinned: model.NewPointer(true),
|
||||
}
|
||||
_, resp, err := th.SystemAdminClient.PatchPost(context.Background(), oldPost.Id, patch)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Equal(t, "api.post.update_post.permissions_time_limit.app_error", err.(*model.AppError).Id)
|
||||
})
|
||||
|
||||
t.Run("patch has_reactions only, time limit expired", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = 1
|
||||
})
|
||||
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = -1
|
||||
})
|
||||
|
||||
oldPost := &model.Post{
|
||||
ChannelId: channel.Id,
|
||||
Message: "original message",
|
||||
CreateAt: model.GetMillis() - 2000,
|
||||
}
|
||||
oldPost, _, err := th.SystemAdminClient.CreatePost(context.Background(), oldPost)
|
||||
require.NoError(t, err)
|
||||
|
||||
patch := &model.PostPatch{
|
||||
HasReactions: model.NewPointer(true),
|
||||
}
|
||||
_, resp, err := th.SystemAdminClient.PatchPost(context.Background(), oldPost.Id, patch)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Equal(t, "api.post.update_post.permissions_time_limit.app_error", err.(*model.AppError).Id)
|
||||
})
|
||||
|
||||
t.Run("err with integrations-reserved props", func(t *testing.T) {
|
||||
originalHardenedModeSetting := *th.App.Config().ServiceSettings.ExperimentalEnableHardenedMode
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
@@ -2259,6 +2451,61 @@ func TestPinPost(t *testing.T) {
|
||||
defer th.TearDown()
|
||||
client := th.Client
|
||||
|
||||
t.Run("pin post after time limit", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = 1
|
||||
})
|
||||
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = -1
|
||||
})
|
||||
|
||||
oldPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
Message: "original message",
|
||||
UserId: th.BasicUser.Id,
|
||||
CreateAt: model.GetMillis() - 2000,
|
||||
}, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
resp, err := client.PinPost(context.Background(), oldPost.Id)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
})
|
||||
|
||||
t.Run("idempotent pin/unpin after time limit", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = 1
|
||||
})
|
||||
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = -1
|
||||
})
|
||||
|
||||
pinnedPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
Message: "original message",
|
||||
UserId: th.BasicUser.Id,
|
||||
IsPinned: true,
|
||||
CreateAt: model.GetMillis() - 2000,
|
||||
}, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
unpinnedPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
Message: "original message",
|
||||
UserId: th.BasicUser.Id,
|
||||
IsPinned: false,
|
||||
CreateAt: model.GetMillis() - 2000,
|
||||
}, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Both are no-ops and must succeed regardless of age.
|
||||
_, err := client.PinPost(context.Background(), pinnedPost.Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = client.UnpinPost(context.Background(), unpinnedPost.Id)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
post := th.BasicPost
|
||||
_, err := client.PinPost(context.Background(), post.Id)
|
||||
require.NoError(t, err)
|
||||
@@ -2292,6 +2539,28 @@ func TestUnpinPost(t *testing.T) {
|
||||
defer th.TearDown()
|
||||
client := th.Client
|
||||
|
||||
t.Run("unpin post after time limit", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = 1
|
||||
})
|
||||
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = -1
|
||||
})
|
||||
|
||||
oldPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
Message: "original message",
|
||||
UserId: th.BasicUser.Id,
|
||||
IsPinned: true,
|
||||
CreateAt: model.GetMillis() - 2000,
|
||||
}, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
resp, err := client.UnpinPost(context.Background(), oldPost.Id)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
})
|
||||
|
||||
pinnedPost := th.CreatePinnedPost()
|
||||
_, err := client.UnpinPost(context.Background(), pinnedPost.Id)
|
||||
require.NoError(t, err)
|
||||
@@ -5700,4 +5969,85 @@ func TestRestorePostVersion(t *testing.T) {
|
||||
CheckForbiddenStatus(t, response)
|
||||
require.Nil(t, restoredPost)
|
||||
})
|
||||
|
||||
t.Run("restore post version blocked when time limit expired", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = 1
|
||||
})
|
||||
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = -1
|
||||
})
|
||||
|
||||
// Create post in the past via app layer (bypasses API time limit)
|
||||
oldPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
Message: "original message",
|
||||
UserId: th.BasicUser.Id,
|
||||
CreateAt: model.GetMillis() - 2000,
|
||||
}, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Patch via app layer to create edit history (bypasses API time limit check)
|
||||
_, _, appErr = th.App.PatchPost(th.Context, oldPost.Id, &model.PostPatch{
|
||||
Message: model.NewPointer("edited message"),
|
||||
}, &model.UpdatePostOptions{})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Get edit history
|
||||
editHistory, response, err := client.GetEditHistoryForPost(context.Background(), oldPost.Id)
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, response)
|
||||
require.Equal(t, 1, len(editHistory))
|
||||
|
||||
// Restore should be blocked by time limit
|
||||
_, resp, err := client.RestorePostVersion(context.Background(), oldPost.Id, editHistory[0].Id)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Equal(t, "api.post.update_post.permissions_time_limit.app_error", err.(*model.AppError).Id)
|
||||
})
|
||||
|
||||
t.Run("restore post version with file change blocked when time limit expired", func(t *testing.T) {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = 1
|
||||
})
|
||||
defer th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.PostEditTimeLimit = -1
|
||||
})
|
||||
|
||||
// Upload a file for the original post
|
||||
fileResp, _, err := client.UploadFile(context.Background(), []byte("data"), th.BasicChannel.Id, "test")
|
||||
require.NoError(t, err)
|
||||
fileId := fileResp.FileInfos[0].Id
|
||||
|
||||
// Create post in the past with file attached
|
||||
oldPost, _, appErr := th.App.CreatePost(th.Context, &model.Post{
|
||||
ChannelId: th.BasicChannel.Id,
|
||||
Message: "original message",
|
||||
UserId: th.BasicUser.Id,
|
||||
FileIds: model.StringArray{fileId},
|
||||
CreateAt: model.GetMillis() - 2000,
|
||||
}, th.BasicChannel, model.CreatePostFlags{SetOnline: true})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Remove file via app layer to create history entry that has the file
|
||||
emptyFiles := model.StringArray{}
|
||||
_, _, appErr = th.App.PatchPost(th.Context, oldPost.Id, &model.PostPatch{
|
||||
Message: model.NewPointer("edited message"),
|
||||
FileIds: &emptyFiles,
|
||||
}, &model.UpdatePostOptions{})
|
||||
require.Nil(t, appErr)
|
||||
|
||||
// Get edit history (should have the entry with the original file)
|
||||
editHistory, response, err := client.GetEditHistoryForPost(context.Background(), oldPost.Id)
|
||||
require.NoError(t, err)
|
||||
CheckOKStatus(t, response)
|
||||
require.Equal(t, 1, len(editHistory))
|
||||
require.Equal(t, 1, len(editHistory[0].FileIds))
|
||||
|
||||
// Restore (which would re-attach the file) should be blocked by time limit
|
||||
_, resp, err := client.RestorePostVersion(context.Background(), oldPost.Id, editHistory[0].Id)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Equal(t, "api.post.update_post.permissions_time_limit.app_error", err.(*model.AppError).Id)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -168,6 +168,10 @@ type PostPatch struct {
|
||||
HasReactions *bool `json:"has_reactions"`
|
||||
}
|
||||
|
||||
func (o *PostPatch) IsEmpty() bool {
|
||||
return o.IsPinned == nil && o.Message == nil && o.Props == nil && o.FileIds == nil && o.HasReactions == nil
|
||||
}
|
||||
|
||||
type PostReminder struct {
|
||||
TargetTime int64 `json:"target_time"`
|
||||
// These fields are only used internally for interacting with DB.
|
||||
|
||||
Ссылка в новой задаче
Block a user