MM-17412: update post incorrectly removing files (#11834)
* leverage testify * improve TestUpdatePost tests * improve TestPatchPost * assert unchanged fileIds after UpdatePost * MM-17412: retain existing FileIds on post update
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
9bb36614a6
Коммит
87c0d304c6
@@ -501,9 +501,6 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updating the file_ids of a post is not a supported operation and will be ignored
|
|
||||||
post.FileIds = nil
|
|
||||||
|
|
||||||
if !c.App.SessionHasPermissionToChannelByPost(c.App.Session, c.Params.PostId, model.PERMISSION_EDIT_POST) {
|
if !c.App.SessionHasPermissionToChannelByPost(c.App.Session, c.Params.PostId, model.PERMISSION_EDIT_POST) {
|
||||||
c.SetPermissionError(model.PERMISSION_EDIT_POST)
|
c.SetPermissionError(model.PERMISSION_EDIT_POST)
|
||||||
return
|
return
|
||||||
@@ -515,6 +512,9 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updating the file_ids of a post is not a supported operation and will be ignored
|
||||||
|
post.FileIds = originalPost.FileIds
|
||||||
|
|
||||||
if c.App.Session.UserId != originalPost.UserId {
|
if c.App.Session.UserId != originalPost.UserId {
|
||||||
if !c.App.SessionHasPermissionToChannelByPost(c.App.Session, c.Params.PostId, model.PERMISSION_EDIT_OTHERS_POSTS) {
|
if !c.App.SessionHasPermissionToChannelByPost(c.App.Session, c.Params.PostId, model.PERMISSION_EDIT_OTHERS_POSTS) {
|
||||||
c.SetPermissionError(model.PERMISSION_EDIT_OTHERS_POSTS)
|
c.SetPermissionError(model.PERMISSION_EDIT_OTHERS_POSTS)
|
||||||
|
|||||||
@@ -587,95 +587,154 @@ func TestUpdatePost(t *testing.T) {
|
|||||||
|
|
||||||
th.App.SetLicense(model.NewTestLicense())
|
th.App.SetLicense(model.NewTestLicense())
|
||||||
|
|
||||||
post := &model.Post{ChannelId: channel.Id, Message: "zz" + model.NewId() + "a"}
|
fileIds := make([]string, 3)
|
||||||
rpost, resp := Client.CreatePost(post)
|
data, err := testutils.ReadTestFile("test.png")
|
||||||
CheckNoError(t, resp)
|
require.Nil(t, err)
|
||||||
|
for i := 0; i < len(fileIds); i++ {
|
||||||
if rpost.Message != post.Message {
|
fileResp, resp := Client.UploadFile(data, channel.Id, "test.png")
|
||||||
t.Fatal("full name didn't match")
|
CheckNoError(t, resp)
|
||||||
|
fileIds[i] = fileResp.FileInfos[0].Id
|
||||||
}
|
}
|
||||||
|
|
||||||
if rpost.EditAt != 0 {
|
rpost, err := th.App.CreatePost(&model.Post{
|
||||||
t.Fatal("Newly created post shouldn't have EditAt set")
|
UserId: th.BasicUser.Id,
|
||||||
}
|
ChannelId: channel.Id,
|
||||||
|
Message: "zz" + model.NewId() + "a",
|
||||||
|
FileIds: fileIds,
|
||||||
|
}, channel, false)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
msg := "zz" + model.NewId() + " update post"
|
assert.Equal(t, rpost.Message, rpost.Message, "full name didn't match")
|
||||||
rpost.Message = msg
|
assert.EqualValues(t, 0, rpost.EditAt, "Newly created post shouldn't have EditAt set")
|
||||||
rpost.UserId = ""
|
assert.Equal(t, model.StringArray(fileIds), rpost.FileIds, "FileIds should have been set")
|
||||||
|
|
||||||
rupost, resp := Client.UpdatePost(rpost.Id, rpost)
|
t.Run("same message, fewer files", func(t *testing.T) {
|
||||||
CheckNoError(t, resp)
|
msg := "zz" + model.NewId() + " update post"
|
||||||
|
rpost.Message = msg
|
||||||
|
rpost.UserId = ""
|
||||||
|
|
||||||
if rupost.Message != msg {
|
rupost, resp := Client.UpdatePost(rpost.Id, &model.Post{
|
||||||
t.Fatal("failed to updates")
|
Id: rpost.Id,
|
||||||
}
|
Message: rpost.Message,
|
||||||
if rupost.EditAt == 0 {
|
FileIds: fileIds[0:2], // one fewer file id
|
||||||
t.Fatal("EditAt not updated for post")
|
})
|
||||||
}
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
msg1 := "#hashtag a" + model.NewId() + " update post again"
|
assert.Equal(t, rupost.Message, msg, "failed to updates")
|
||||||
rpost.Message = msg1
|
assert.NotEqual(t, 0, rupost.EditAt, "EditAt not updated for post")
|
||||||
rpost.Props[model.PROPS_ADD_CHANNEL_MEMBER] = "no good"
|
assert.Equal(t, model.StringArray(fileIds), rupost.FileIds, "FileIds should have not have been updated")
|
||||||
rrupost, resp := Client.UpdatePost(rpost.Id, rpost)
|
|
||||||
CheckNoError(t, resp)
|
|
||||||
|
|
||||||
if rrupost.Message != msg1 && rrupost.Hashtags != "#hashtag" {
|
actual, resp := Client.GetPost(rpost.Id, "")
|
||||||
t.Fatal("failed to updates")
|
CheckNoError(t, resp)
|
||||||
}
|
|
||||||
|
|
||||||
if rrupost.Props[model.PROPS_ADD_CHANNEL_MEMBER] != nil {
|
assert.Equal(t, actual.Message, msg, "failed to updates")
|
||||||
t.Fatal("failed to sanitize Props['add_channel_member'], should be nil")
|
assert.NotEqual(t, 0, actual.EditAt, "EditAt not updated for post")
|
||||||
}
|
assert.Equal(t, model.StringArray(fileIds), actual.FileIds, "FileIds should have not have been updated")
|
||||||
|
|
||||||
rpost2, err := th.App.CreatePost(&model.Post{ChannelId: channel.Id, Message: "zz" + model.NewId() + "a", Type: model.POST_JOIN_LEAVE, UserId: th.BasicUser.Id}, channel, false)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
up2 := &model.Post{Id: rpost2.Id, ChannelId: channel.Id, Message: "zz" + model.NewId() + " update post 2"}
|
|
||||||
_, resp = Client.UpdatePost(rpost2.Id, up2)
|
|
||||||
CheckBadRequestStatus(t, resp)
|
|
||||||
|
|
||||||
rpost3, err := th.App.CreatePost(&model.Post{ChannelId: channel.Id, Message: "zz" + model.NewId() + "a", UserId: th.BasicUser.Id}, channel, false)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileIds := model.StringArray{"abcdef", "geh"}
|
|
||||||
up3 := &model.Post{Id: rpost3.Id, ChannelId: channel.Id, Message: "zz" + model.NewId() + " update post 3", FileIds: fileIds}
|
|
||||||
rrupost3, resp := Client.UpdatePost(rpost3.Id, up3)
|
|
||||||
CheckNoError(t, resp)
|
|
||||||
assert.Empty(t, rrupost3.FileIds)
|
|
||||||
|
|
||||||
up4 := &model.Post{Id: rpost3.Id, ChannelId: channel.Id, Message: "zz" + model.NewId() + " update post 3"}
|
|
||||||
up4.AddProp("attachments", []model.SlackAttachment{
|
|
||||||
{
|
|
||||||
Text: "Hello World",
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
rrupost3, resp = Client.UpdatePost(rpost3.Id, up4)
|
|
||||||
CheckNoError(t, resp)
|
|
||||||
assert.NotEqual(t, rpost3.EditAt, rrupost3.EditAt)
|
|
||||||
assert.NotEqual(t, rpost3.Attachments(), rrupost3.Attachments())
|
|
||||||
|
|
||||||
Client.Logout()
|
t.Run("new message, invalid props", func(t *testing.T) {
|
||||||
_, resp = Client.UpdatePost(rpost.Id, rpost)
|
msg1 := "#hashtag a" + model.NewId() + " update post again"
|
||||||
CheckUnauthorizedStatus(t, resp)
|
rpost.Message = msg1
|
||||||
|
rpost.Props[model.PROPS_ADD_CHANNEL_MEMBER] = "no good"
|
||||||
|
rrupost, resp := Client.UpdatePost(rpost.Id, rpost)
|
||||||
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
th.LoginBasic2()
|
assert.Equal(t, msg1, rrupost.Message, "failed to update message")
|
||||||
_, resp = Client.UpdatePost(rpost.Id, rpost)
|
assert.Equal(t, "#hashtag", rrupost.Hashtags, "failed to update hashtags")
|
||||||
CheckForbiddenStatus(t, resp)
|
assert.Nil(t, rrupost.Props[model.PROPS_ADD_CHANNEL_MEMBER], "failed to sanitize Props['add_channel_member'], should be nil")
|
||||||
|
|
||||||
Client.Logout()
|
actual, resp := Client.GetPost(rpost.Id, "")
|
||||||
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
th.LoginTeamAdmin()
|
assert.Equal(t, msg1, actual.Message, "failed to update message")
|
||||||
_, resp = Client.UpdatePost(rpost.Id, rpost)
|
assert.Equal(t, "#hashtag", actual.Hashtags, "failed to update hashtags")
|
||||||
CheckForbiddenStatus(t, resp)
|
assert.Nil(t, actual.Props[model.PROPS_ADD_CHANNEL_MEMBER], "failed to sanitize Props['add_channel_member'], should be nil")
|
||||||
|
})
|
||||||
|
|
||||||
Client.Logout()
|
t.Run("join/leave post", func(t *testing.T) {
|
||||||
|
rpost2, err := th.App.CreatePost(&model.Post{
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "zz" + model.NewId() + "a",
|
||||||
|
Type: model.POST_JOIN_LEAVE,
|
||||||
|
UserId: th.BasicUser.Id,
|
||||||
|
}, channel, false)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
_, resp = th.SystemAdminClient.UpdatePost(rpost.Id, rpost)
|
up2 := &model.Post{
|
||||||
CheckNoError(t, resp)
|
Id: rpost2.Id,
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "zz" + model.NewId() + " update post 2",
|
||||||
|
}
|
||||||
|
_, resp := Client.UpdatePost(rpost2.Id, up2)
|
||||||
|
CheckBadRequestStatus(t, resp)
|
||||||
|
})
|
||||||
|
|
||||||
|
rpost3, err := th.App.CreatePost(&model.Post{
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "zz" + model.NewId() + "a",
|
||||||
|
UserId: th.BasicUser.Id,
|
||||||
|
}, channel, false)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
t.Run("new message, add files", func(t *testing.T) {
|
||||||
|
up3 := &model.Post{
|
||||||
|
Id: rpost3.Id,
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "zz" + model.NewId() + " update post 3",
|
||||||
|
FileIds: fileIds[0:2],
|
||||||
|
}
|
||||||
|
rrupost3, resp := Client.UpdatePost(rpost3.Id, up3)
|
||||||
|
CheckNoError(t, resp)
|
||||||
|
assert.Empty(t, rrupost3.FileIds)
|
||||||
|
|
||||||
|
actual, resp := Client.GetPost(rpost.Id, "")
|
||||||
|
CheckNoError(t, resp)
|
||||||
|
assert.Equal(t, model.StringArray(fileIds), actual.FileIds)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("add slack attachments", func(t *testing.T) {
|
||||||
|
up4 := &model.Post{
|
||||||
|
Id: rpost3.Id,
|
||||||
|
ChannelId: channel.Id,
|
||||||
|
Message: "zz" + model.NewId() + " update post 3",
|
||||||
|
}
|
||||||
|
up4.AddProp("attachments", []model.SlackAttachment{
|
||||||
|
{
|
||||||
|
Text: "Hello World",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
rrupost3, resp := Client.UpdatePost(rpost3.Id, up4)
|
||||||
|
CheckNoError(t, resp)
|
||||||
|
assert.NotEqual(t, rpost3.EditAt, rrupost3.EditAt)
|
||||||
|
assert.NotEqual(t, rpost3.Attachments(), rrupost3.Attachments())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("logged out", func(t *testing.T) {
|
||||||
|
Client.Logout()
|
||||||
|
_, resp := Client.UpdatePost(rpost.Id, rpost)
|
||||||
|
CheckUnauthorizedStatus(t, resp)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("different user", func(t *testing.T) {
|
||||||
|
th.LoginBasic2()
|
||||||
|
_, resp := Client.UpdatePost(rpost.Id, rpost)
|
||||||
|
CheckForbiddenStatus(t, resp)
|
||||||
|
|
||||||
|
Client.Logout()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("different user, but team admin", func(t *testing.T) {
|
||||||
|
th.LoginTeamAdmin()
|
||||||
|
_, resp := Client.UpdatePost(rpost.Id, rpost)
|
||||||
|
CheckForbiddenStatus(t, resp)
|
||||||
|
|
||||||
|
Client.Logout()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("different user, but system admin", func(t *testing.T) {
|
||||||
|
_, resp := th.SystemAdminClient.UpdatePost(rpost.Id, rpost)
|
||||||
|
CheckNoError(t, resp)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateOthersPostInDirectMessageChannel(t *testing.T) {
|
func TestUpdateOthersPostInDirectMessageChannel(t *testing.T) {
|
||||||
@@ -711,98 +770,105 @@ func TestPatchPost(t *testing.T) {
|
|||||||
|
|
||||||
th.App.SetLicense(model.NewTestLicense())
|
th.App.SetLicense(model.NewTestLicense())
|
||||||
|
|
||||||
|
fileIds := make([]string, 3)
|
||||||
|
data, err := testutils.ReadTestFile("test.png")
|
||||||
|
require.Nil(t, err)
|
||||||
|
for i := 0; i < len(fileIds); i++ {
|
||||||
|
fileResp, resp := Client.UploadFile(data, channel.Id, "test.png")
|
||||||
|
CheckNoError(t, resp)
|
||||||
|
fileIds[i] = fileResp.FileInfos[0].Id
|
||||||
|
}
|
||||||
|
|
||||||
post := &model.Post{
|
post := &model.Post{
|
||||||
ChannelId: channel.Id,
|
ChannelId: channel.Id,
|
||||||
IsPinned: true,
|
IsPinned: true,
|
||||||
Message: "#hashtag a message",
|
Message: "#hashtag a message",
|
||||||
Props: model.StringInterface{"channel_header": "old_header"},
|
Props: model.StringInterface{"channel_header": "old_header"},
|
||||||
FileIds: model.StringArray{"file1", "file2"},
|
FileIds: fileIds[0:2],
|
||||||
HasReactions: true,
|
HasReactions: true,
|
||||||
}
|
}
|
||||||
post, _ = Client.CreatePost(post)
|
post, _ = Client.CreatePost(post)
|
||||||
|
|
||||||
patch := &model.PostPatch{}
|
var rpost *model.Post
|
||||||
|
t.Run("new message, props, files, HasReactions bit", func(t *testing.T) {
|
||||||
|
patch := &model.PostPatch{}
|
||||||
|
|
||||||
patch.IsPinned = model.NewBool(false)
|
patch.IsPinned = model.NewBool(false)
|
||||||
patch.Message = model.NewString("#otherhashtag other message")
|
patch.Message = model.NewString("#otherhashtag other message")
|
||||||
patch.Props = new(model.StringInterface)
|
patch.Props = &model.StringInterface{"channel_header": "new_header"}
|
||||||
*patch.Props = model.StringInterface{"channel_header": "new_header"}
|
patchFileIds := model.StringArray(fileIds) // one extra file
|
||||||
patch.FileIds = new(model.StringArray)
|
patch.FileIds = &patchFileIds
|
||||||
*patch.FileIds = model.StringArray{"file1", "otherfile2", "otherfile3"}
|
patch.HasReactions = model.NewBool(false)
|
||||||
patch.HasReactions = model.NewBool(false)
|
|
||||||
|
|
||||||
rpost, resp := Client.PatchPost(post.Id, patch)
|
var resp *model.Response
|
||||||
CheckNoError(t, resp)
|
rpost, resp = Client.PatchPost(post.Id, patch)
|
||||||
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
if rpost.IsPinned {
|
assert.False(t, rpost.IsPinned, "IsPinned did not update properly")
|
||||||
t.Fatal("IsPinned did not update properly")
|
assert.Equal(t, "#otherhashtag other message", rpost.Message, "Message did not update properly")
|
||||||
}
|
assert.Equal(t, *patch.Props, rpost.Props, "Props did not update properly")
|
||||||
if rpost.Message != "#otherhashtag other message" {
|
assert.Equal(t, "#otherhashtag", rpost.Hashtags, "Message did not update properly")
|
||||||
t.Fatal("Message did not update properly")
|
assert.Equal(t, model.StringArray(fileIds[0:2]), rpost.FileIds, "FileIds should not update")
|
||||||
}
|
assert.False(t, rpost.HasReactions, "HasReactions did not update properly")
|
||||||
if len(rpost.Props) != 1 {
|
})
|
||||||
t.Fatal("Props did not update properly")
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(rpost.Props, *patch.Props) {
|
|
||||||
t.Fatal("Props did not update properly")
|
|
||||||
}
|
|
||||||
if rpost.Hashtags != "#otherhashtag" {
|
|
||||||
t.Fatal("Message did not update properly")
|
|
||||||
}
|
|
||||||
if len(rpost.FileIds) == 3 {
|
|
||||||
t.Fatal("FileIds should not update properly")
|
|
||||||
}
|
|
||||||
if reflect.DeepEqual(rpost.FileIds, *patch.FileIds) {
|
|
||||||
t.Fatal("FileIds should not update")
|
|
||||||
}
|
|
||||||
if rpost.HasReactions {
|
|
||||||
t.Fatal("HasReactions did not update properly")
|
|
||||||
}
|
|
||||||
|
|
||||||
patch2 := &model.PostPatch{}
|
t.Run("add slack attachments", func(t *testing.T) {
|
||||||
attachments := []model.SlackAttachment{
|
patch2 := &model.PostPatch{}
|
||||||
{
|
attachments := []model.SlackAttachment{
|
||||||
Text: "Hello World",
|
{
|
||||||
},
|
Text: "Hello World",
|
||||||
}
|
},
|
||||||
patch2.Props = new(model.StringInterface)
|
|
||||||
*patch2.Props = model.StringInterface{"attachments": attachments}
|
|
||||||
|
|
||||||
rpost2, resp := Client.PatchPost(post.Id, patch2)
|
|
||||||
CheckNoError(t, resp)
|
|
||||||
assert.NotEmpty(t, rpost2.Props["attachments"])
|
|
||||||
assert.NotEqual(t, rpost.EditAt, rpost2.EditAt)
|
|
||||||
|
|
||||||
if r, err := Client.DoApiPut("/posts/"+post.Id+"/patch", "garbage"); err == nil {
|
|
||||||
t.Fatal("should have errored")
|
|
||||||
} else {
|
|
||||||
if r.StatusCode != http.StatusBadRequest {
|
|
||||||
t.Log("actual: " + strconv.Itoa(r.StatusCode))
|
|
||||||
t.Log("expected: " + strconv.Itoa(http.StatusBadRequest))
|
|
||||||
t.Fatal("wrong status code")
|
|
||||||
}
|
}
|
||||||
}
|
patch2.Props = &model.StringInterface{"attachments": attachments}
|
||||||
|
|
||||||
_, resp = Client.PatchPost("junk", patch)
|
rpost2, resp := Client.PatchPost(post.Id, patch2)
|
||||||
CheckBadRequestStatus(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
assert.NotEmpty(t, rpost2.Props["attachments"])
|
||||||
|
assert.NotEqual(t, rpost.EditAt, rpost2.EditAt)
|
||||||
|
})
|
||||||
|
|
||||||
_, resp = Client.PatchPost(GenerateTestId(), patch)
|
t.Run("invalid requests", func(t *testing.T) {
|
||||||
CheckForbiddenStatus(t, resp)
|
r, err := Client.DoApiPut("/posts/"+post.Id+"/patch", "garbage")
|
||||||
|
require.EqualError(t, err, ": Invalid or missing post in request body, ")
|
||||||
|
require.Equal(t, http.StatusBadRequest, r.StatusCode, "wrong status code")
|
||||||
|
|
||||||
Client.Logout()
|
patch := &model.PostPatch{}
|
||||||
_, resp = Client.PatchPost(post.Id, patch)
|
_, resp := Client.PatchPost("junk", patch)
|
||||||
CheckUnauthorizedStatus(t, resp)
|
CheckBadRequestStatus(t, resp)
|
||||||
|
})
|
||||||
|
|
||||||
th.LoginBasic2()
|
t.Run("unknown post", func(t *testing.T) {
|
||||||
_, resp = Client.PatchPost(post.Id, patch)
|
patch := &model.PostPatch{}
|
||||||
CheckForbiddenStatus(t, resp)
|
_, resp := Client.PatchPost(GenerateTestId(), patch)
|
||||||
|
CheckForbiddenStatus(t, resp)
|
||||||
|
})
|
||||||
|
|
||||||
th.LoginTeamAdmin()
|
t.Run("logged out", func(t *testing.T) {
|
||||||
_, resp = Client.PatchPost(post.Id, patch)
|
Client.Logout()
|
||||||
CheckForbiddenStatus(t, resp)
|
patch := &model.PostPatch{}
|
||||||
|
_, resp := Client.PatchPost(post.Id, patch)
|
||||||
|
CheckUnauthorizedStatus(t, resp)
|
||||||
|
})
|
||||||
|
|
||||||
_, resp = th.SystemAdminClient.PatchPost(post.Id, patch)
|
t.Run("different user", func(t *testing.T) {
|
||||||
CheckNoError(t, resp)
|
th.LoginBasic2()
|
||||||
|
patch := &model.PostPatch{}
|
||||||
|
_, resp := Client.PatchPost(post.Id, patch)
|
||||||
|
CheckForbiddenStatus(t, resp)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("different user, but team admin", func(t *testing.T) {
|
||||||
|
th.LoginTeamAdmin()
|
||||||
|
patch := &model.PostPatch{}
|
||||||
|
_, resp := Client.PatchPost(post.Id, patch)
|
||||||
|
CheckForbiddenStatus(t, resp)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("different user, but system admin", func(t *testing.T) {
|
||||||
|
patch := &model.PostPatch{}
|
||||||
|
_, resp := th.SystemAdminClient.PatchPost(post.Id, patch)
|
||||||
|
CheckNoError(t, resp)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPinPost(t *testing.T) {
|
func TestPinPost(t *testing.T) {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user