Files
team/server/cmd/mmctl/commands/post_e2e_test.go
fb7c9b9d7a Add direct message support to mmctl post create (#37159)
* Add direct message support to mmctl post create

Co-authored-by: mattermost-code <[email protected]>

* Regenerate mmctl docs for post create direct messages

Co-authored-by: mattermost-code <[email protected]>

* Strengthen mmctl post create direct message test coverage

Co-authored-by: mattermost-code <[email protected]>

* Document why local mmctl client cannot send direct messages

Add a test comment explaining that local mode lacks the post-create,
direct-channel, and current-user API endpoints required by post create.

Co-authored-by: mattermost-code <[email protected]>

* Add local mode API support for mmctl post create

Implement local mode handlers for POST /posts and POST /channels/direct,
and add --user flag to mmctl post create for specifying the acting user
in local mode instead of relying on GET /users/me.

Co-authored-by: mattermost-code <[email protected]>

* Address CodeRabbit feedback and regenerate mmctl docs

- Defer WriteHeader in localCreatePost until after burn-on-read handling
- Preserve prior viper local state in post create tests
- Regenerate mmctl docs for --user flag

Co-authored-by: mattermost-code <[email protected]>

* Address PR feedback: 2 answered, 3 resolved, 0 declined

* Reset mmctl unit test config state

* Address PR feedback: 3 answered, 1 resolved, 0 declined

* Address PR feedback: 1 answered, 1 resolved, 0 declined

* Retrigger CI after transient Enterprise FIPS proxy.golang.org failure

Co-authored-by: mattermost-code <[email protected]>

* Merge local-mode API tests into their parent test functions

Fold TestLocalCreateDirectChannel into TestCreateDirectChannel and
TestLocalCreatePost into TestCreatePost as local-mode subtests, per
reviewer feedback to consolidate the local-mode coverage with the
existing create tests.

* Revert local mode support for mmctl post create

Reverts 20a2b1c per reviewer feedback: remove the local-mode POST /posts
and POST /channels/direct API handlers, the --user flag, and the associated
audit events. mmctl post create now errors out when run with --local instead
of attempting to create posts in local mode.

* Retrigger CI after transient Enterprise FIPS npm cache failure

The Enterprise CI/tests failure on 8d04b5c was caused by an npm
EEXIST cache error during webapp node_modules install — unrelated to
this PR's mmctl changes.

Co-authored-by: mattermost-code <[email protected]>

---------

Co-authored-by: mattermost-code <[email protected]>
Co-authored-by: Mattermost Build <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-06-30 23:04:07 +02:00

294 lines
9.1 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package commands
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/client"
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/printer"
"github.com/mattermost/mattermost/server/public/model"
)
func (s *MmctlE2ETestSuite) TestPostListCmd() {
s.SetupTestHelper().InitBasic(s.T())
var createNewChannelAndPosts = func() (string, *model.Post, *model.Post) {
channelName := model.NewRandomString(10)
channelDisplayName := "channelDisplayName"
channel, err := s.th.App.CreateChannel(s.th.Context, &model.Channel{Name: channelName, DisplayName: channelDisplayName, Type: model.ChannelTypePrivate, TeamId: s.th.BasicTeam.Id}, false)
s.Require().Nil(err)
post1, _, err := s.th.App.CreatePost(s.th.Context, &model.Post{Message: model.NewRandomString(15), UserId: s.th.BasicUser.Id, ChannelId: channel.Id}, channel, model.CreatePostFlags{})
s.Require().Nil(err)
post2, _, err := s.th.App.CreatePost(s.th.Context, &model.Post{Message: model.NewRandomString(15), UserId: s.th.BasicUser.Id, ChannelId: channel.Id}, channel, model.CreatePostFlags{})
s.Require().Nil(err)
return channelName, post1, post2
}
s.RunForSystemAdminAndLocal("List all posts for a channel", func(c client.Client) {
printer.Clean()
teamName := s.th.BasicTeam.Name
channelName, post1, post2 := createNewChannelAndPosts()
cmd := &cobra.Command{}
cmd.Flags().Int("number", 2, "")
err := postListCmdF(c, cmd, []string{teamName + ":" + channelName})
s.Require().Nil(err)
s.Equal(2, len(printer.GetLines()))
printedPost1, ok := printer.GetLines()[0].(*model.Post)
s.Require().True(ok)
s.Require().Equal(printedPost1.Message, post1.Message)
printedPost2, ok := printer.GetLines()[1].(*model.Post)
s.Require().True(ok)
s.Require().Equal(printedPost2.Message, post2.Message)
s.Len(printer.GetErrorLines(), 0)
})
s.Run("List all posts for a channel without permissions", func() {
printer.Clean()
teamName := s.th.BasicTeam.Name
channelName, _, _ := createNewChannelAndPosts()
cmd := &cobra.Command{}
cmd.Flags().Int("number", 2, "")
err := postListCmdF(s.th.Client, cmd, []string{teamName + ":" + channelName})
s.Require().NotNil(err)
//s.Require().Contains(err.Error(), "You do not have the appropriate permissions.")
})
s.RunForSystemAdminAndLocal("List all posts for a channel with since flag", func(c client.Client) {
printer.Clean()
ISO8601ValidString := "2006-01-02T15:04:05-07:00"
teamName := s.th.BasicTeam.Name
channelName, post1, post2 := createNewChannelAndPosts()
cmd := &cobra.Command{}
cmd.Flags().String("since", ISO8601ValidString, "")
err := postListCmdF(c, cmd, []string{teamName + ":" + channelName})
s.Require().Nil(err)
s.Equal(2, len(printer.GetLines()))
printedPost1, ok := printer.GetLines()[0].(*model.Post)
s.Require().True(ok)
s.Require().Equal(printedPost1.Message, post1.Message)
printedPost2, ok := printer.GetLines()[1].(*model.Post)
s.Require().True(ok)
s.Require().Equal(printedPost2.Message, post2.Message)
s.Len(printer.GetErrorLines(), 0)
})
s.Run("List all posts for a channel with since flag without permissions", func() {
printer.Clean()
ISO8601ValidString := "2006-01-02T15:04:05-07:00"
teamName := s.th.BasicTeam.Name
channelName, _, _ := createNewChannelAndPosts()
cmd := &cobra.Command{}
cmd.Flags().String("since", ISO8601ValidString, "")
err := postListCmdF(s.th.Client, cmd, []string{teamName + ":" + channelName})
s.Require().NotNil(err)
//s.Require().Contains(err.Error(), "You do not have the appropriate permissions.")
})
}
func (s *MmctlE2ETestSuite) TestPostCreateCmd() {
s.SetupTestHelper().InitBasic(s.T())
s.Run("Create a post for System Admin Client", func() {
printer.Clean()
msgArg := "some text"
cmd := &cobra.Command{}
cmd.Flags().String("message", msgArg, "")
err := postCreateCmdF(s.th.SystemAdminClient, cmd, []string{s.th.BasicTeam.Name + ":" + s.th.BasicChannel.Name})
s.Require().Nil(err)
s.Len(printer.GetErrorLines(), 0)
})
s.Run("Create a post for Client", func() {
printer.Clean()
msgArg := "some text"
cmd := &cobra.Command{}
cmd.Flags().String("message", msgArg, "")
err := postCreateCmdF(s.th.Client, cmd, []string{s.th.BasicTeam.Name + ":" + s.th.BasicChannel.Name})
s.Require().Nil(err)
s.Len(printer.GetErrorLines(), 0)
})
s.Run("Create a post for Local Client should fail", func() {
printer.Clean()
msgArg := "some text"
cmd := &cobra.Command{}
cmd.Flags().String("message", msgArg, "")
prevLocal := viper.GetBool("local")
viper.Set("local", true)
defer viper.Set("local", prevLocal)
err := postCreateCmdF(s.th.LocalClient, cmd, []string{s.th.BasicTeam.Name + ":" + s.th.BasicChannel.Name})
s.Require().NotNil(err)
s.Require().Contains(err.Error(), "creating posts is not supported in local mode")
s.Len(printer.GetErrorLines(), 0)
})
s.Run("Send a direct message for System Admin Client", func() {
printer.Clean()
msgArg := model.NewRandomString(15)
cmd := &cobra.Command{}
cmd.Flags().String("message", msgArg, "")
err := postCreateCmdF(s.th.SystemAdminClient, cmd, []string{"@" + s.th.BasicUser2.Username})
s.Require().Nil(err)
s.Len(printer.GetErrorLines(), 0)
dmChannel, appErr := s.th.App.GetOrCreateDirectChannel(s.th.Context, s.th.SystemAdminUser.Id, s.th.BasicUser2.Id)
s.Require().Nil(appErr)
posts, appErr := s.th.App.GetPosts(s.th.Context, dmChannel.Id, 0, 10)
s.Require().Nil(appErr)
var matched []*model.Post
for _, post := range posts.Posts {
if post.Message == msgArg {
matched = append(matched, post)
}
}
s.Require().Len(matched, 1, "expected exactly one direct message with the sent text")
s.Require().Equal(s.th.SystemAdminUser.Id, matched[0].UserId)
s.Require().Equal(dmChannel.Id, matched[0].ChannelId)
})
s.Run("Send a direct message for Client", func() {
printer.Clean()
msgArg := model.NewRandomString(15)
cmd := &cobra.Command{}
cmd.Flags().String("message", msgArg, "")
err := postCreateCmdF(s.th.Client, cmd, []string{"@" + s.th.BasicUser2.Username})
s.Require().Nil(err)
s.Len(printer.GetErrorLines(), 0)
dmChannel, appErr := s.th.App.GetOrCreateDirectChannel(s.th.Context, s.th.BasicUser.Id, s.th.BasicUser2.Id)
s.Require().Nil(appErr)
posts, appErr := s.th.App.GetPosts(s.th.Context, dmChannel.Id, 0, 10)
s.Require().Nil(appErr)
var matched []*model.Post
for _, post := range posts.Posts {
if post.Message == msgArg {
matched = append(matched, post)
}
}
s.Require().Len(matched, 1, "expected exactly one direct message with the sent text")
s.Require().Equal(s.th.BasicUser.Id, matched[0].UserId)
s.Require().Equal(dmChannel.Id, matched[0].ChannelId)
})
s.Run("Send a direct message for Local Client should fail", func() {
printer.Clean()
msgArg := model.NewRandomString(15)
cmd := &cobra.Command{}
cmd.Flags().String("message", msgArg, "")
prevLocal := viper.GetBool("local")
viper.Set("local", true)
defer viper.Set("local", prevLocal)
err := postCreateCmdF(s.th.LocalClient, cmd, []string{"@" + s.th.BasicUser2.Username})
s.Require().NotNil(err)
s.Require().Contains(err.Error(), "creating posts is not supported in local mode")
s.Len(printer.GetErrorLines(), 0)
})
s.Run("Send a direct message to a non-existing user should fail", func() {
printer.Clean()
msgArg := model.NewRandomString(15)
missingUsername := model.NewUsername()
cmd := &cobra.Command{}
cmd.Flags().String("message", msgArg, "")
err := postCreateCmdF(s.th.SystemAdminClient, cmd, []string{"@" + missingUsername})
s.Require().NotNil(err)
s.Require().Contains(err.Error(), missingUsername)
s.Len(printer.GetErrorLines(), 0)
})
s.Run("Reply to a an existing post for System Admin Client", func() {
printer.Clean()
msgArg := "some text"
cmd := &cobra.Command{}
cmd.Flags().String("message", msgArg, "")
cmd.Flags().String("reply-to", s.th.BasicPost.Id, "")
err := postCreateCmdF(s.th.SystemAdminClient, cmd, []string{s.th.BasicTeam.Name + ":" + s.th.BasicChannel.Name})
s.Require().Nil(err)
s.Len(printer.GetErrorLines(), 0)
})
s.Run("Reply to a an existing post for Client", func() {
printer.Clean()
msgArg := "some text"
cmd := &cobra.Command{}
cmd.Flags().String("message", msgArg, "")
cmd.Flags().String("reply-to", s.th.BasicPost.Id, "")
err := postCreateCmdF(s.th.Client, cmd, []string{s.th.BasicTeam.Name + ":" + s.th.BasicChannel.Name})
s.Require().Nil(err)
s.Len(printer.GetErrorLines(), 0)
})
s.Run("Reply to a an existing post for Local Client should fail", func() {
printer.Clean()
msgArg := "some text"
cmd := &cobra.Command{}
cmd.Flags().String("message", msgArg, "")
cmd.Flags().String("reply-to", s.th.BasicPost.Id, "")
prevLocal := viper.GetBool("local")
viper.Set("local", true)
defer viper.Set("local", prevLocal)
err := postCreateCmdF(s.th.LocalClient, cmd, []string{s.th.BasicTeam.Name + ":" + s.th.BasicChannel.Name})
s.Require().NotNil(err)
s.Require().Contains(err.Error(), "creating posts is not supported in local mode")
s.Len(printer.GetErrorLines(), 0)
})
}