mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
* Add create, show, and cancel subcommands to mmctl job The `mmctl job` command previously only supported `list` and `update`. This adds three subcommands backed by existing client methods: - `job create [type] [--data k=v]` triggers a new job (CreateJob) - `job show [job]` displays a single job (GetJob) - `job cancel [job]` requests cancellation via the dedicated cancel endpoint (CancelJob), which uses the create-job permission and emits its own audit event, unlike `update [job] cancel_requested` Also fixes the leftover "import jobs" wording in the `list` flag help and regenerates the mmctl docs. * Exercise --data flag parsing in mmctl job create test The create-job test passed the Data map as the flag's default value, so GetStringToString returned the default without ever exercising pflag's stringToString parser. Declare the flag empty and Set the value explicitly so the test validates the real parse path. * Don't gate mmctl job create on model.AllJobTypes model.AllJobTypes (which IsValidJobType checks against) is a curated subset that omits creatable job types such as access_control_sync, so validating the type client-side wrongly rejected jobs the server can schedule. Drop the client-side check and let the server validate the type per its create-job permissions; it returns a clear error for anything unschedulable. Tab-completion still suggests AllJobTypes as an advisory hint. * Remove explanatory AllJobTypes comments from mmctl job * Add e2e tests for mmctl job create, show, and cancel Cover the no-permission, not-found, and success paths for the new generic job subcommands against a running server, mirroring the existing export job e2e tests. --------- Co-authored-by: Hanzo Dev <[email protected]>
351 lines
8.7 KiB
Go
351 lines
8.7 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package commands
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
|
|
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/printer"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func (s *MmctlUnitTestSuite) TestListJobsCmdF() {
|
|
s.Run("no jobs found", func() {
|
|
printer.Clean()
|
|
var mockJobs []*model.Job
|
|
|
|
cmd := &cobra.Command{}
|
|
perPage := 10
|
|
cmd.Flags().Int("page", 0, "")
|
|
cmd.Flags().Int("per-page", perPage, "")
|
|
cmd.Flags().Bool("all", false, "")
|
|
cmd.Flags().StringSlice("ids", []string{}, "")
|
|
cmd.Flags().String("status", "", "")
|
|
cmd.Flags().String("type", "", "")
|
|
|
|
s.client.
|
|
EXPECT().
|
|
GetJobs(context.TODO(), "", "", 0, perPage).
|
|
Return(mockJobs, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
err := listJobsCmdF(s.client, cmd, nil)
|
|
s.Require().Nil(err)
|
|
s.Len(printer.GetLines(), 1)
|
|
s.Empty(printer.GetErrorLines())
|
|
s.Equal("No jobs found", printer.GetLines()[0])
|
|
})
|
|
|
|
s.Run("3 jobs found", func() {
|
|
printer.Clean()
|
|
mockJobs := []*model.Job{
|
|
{
|
|
Id: model.NewId(),
|
|
},
|
|
{
|
|
Id: model.NewId(),
|
|
},
|
|
{
|
|
Id: model.NewId(),
|
|
},
|
|
}
|
|
|
|
cmd := &cobra.Command{}
|
|
perPage := 3
|
|
cmd.Flags().Int("page", 0, "")
|
|
cmd.Flags().Int("per-page", perPage, "")
|
|
cmd.Flags().Bool("all", false, "")
|
|
cmd.Flags().StringSlice("ids", []string{}, "")
|
|
cmd.Flags().String("status", "", "")
|
|
cmd.Flags().String("type", "", "")
|
|
|
|
s.client.
|
|
EXPECT().
|
|
GetJobs(context.TODO(), "", "", 0, perPage).
|
|
Return(mockJobs, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
err := listJobsCmdF(s.client, cmd, nil)
|
|
s.Require().Nil(err)
|
|
s.Len(printer.GetLines(), len(mockJobs))
|
|
s.Empty(printer.GetErrorLines())
|
|
for i, line := range printer.GetLines() {
|
|
s.Equal(mockJobs[i], line.(*model.Job))
|
|
}
|
|
})
|
|
|
|
s.Run("return 1 job using ids flag", func() {
|
|
printer.Clean()
|
|
id := model.NewId()
|
|
mockJob := &model.Job{
|
|
Id: id,
|
|
}
|
|
|
|
cmd := &cobra.Command{}
|
|
perPage := 3
|
|
cmd.Flags().Int("page", 0, "")
|
|
cmd.Flags().Int("per-page", perPage, "")
|
|
cmd.Flags().Bool("all", false, "")
|
|
cmd.Flags().StringSlice("ids", []string{id}, "")
|
|
cmd.Flags().String("status", "", "")
|
|
cmd.Flags().String("type", "", "")
|
|
|
|
s.client.
|
|
EXPECT().
|
|
GetJob(context.TODO(), id).
|
|
Return(mockJob, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
err := listJobsCmdF(s.client, cmd, nil)
|
|
s.Require().Nil(err)
|
|
s.Len(printer.GetLines(), 1)
|
|
s.Empty(printer.GetErrorLines())
|
|
for _, line := range printer.GetLines() {
|
|
s.Equal(mockJob, line.(*model.Job))
|
|
}
|
|
})
|
|
|
|
s.Run("return 2 jobs by status", func() {
|
|
printer.Clean()
|
|
mockJobs := []*model.Job{
|
|
{
|
|
Id: model.NewId(),
|
|
Status: model.JobStatusSuccess,
|
|
},
|
|
{
|
|
Id: model.NewId(),
|
|
Status: model.JobStatusSuccess,
|
|
},
|
|
}
|
|
|
|
cmd := &cobra.Command{}
|
|
perPage := 2
|
|
cmd.Flags().Int("page", 0, "")
|
|
cmd.Flags().Int("per-page", perPage, "")
|
|
cmd.Flags().Bool("all", false, "")
|
|
cmd.Flags().String("status", model.JobStatusSuccess, "")
|
|
cmd.Flags().StringSlice("ids", []string{}, "")
|
|
cmd.Flags().String("type", "", "")
|
|
|
|
s.client.
|
|
EXPECT().
|
|
GetJobs(context.TODO(), "", model.JobStatusSuccess, 0, perPage).
|
|
Return(mockJobs, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
err := listJobsCmdF(s.client, cmd, nil)
|
|
s.Require().Nil(err)
|
|
s.Len(printer.GetLines(), len(mockJobs))
|
|
s.Empty(printer.GetErrorLines())
|
|
for i, line := range printer.GetLines() {
|
|
s.Equal(mockJobs[i], line.(*model.Job))
|
|
}
|
|
})
|
|
|
|
s.Run("return 2 jobs by type", func() {
|
|
printer.Clean()
|
|
mockJobs := []*model.Job{
|
|
{
|
|
Id: model.NewId(),
|
|
Type: model.JobTypeDataRetention,
|
|
},
|
|
{
|
|
Id: model.NewId(),
|
|
Type: model.JobTypeDataRetention,
|
|
},
|
|
}
|
|
|
|
cmd := &cobra.Command{}
|
|
perPage := 2
|
|
cmd.Flags().Int("page", 0, "")
|
|
cmd.Flags().Int("per-page", perPage, "")
|
|
cmd.Flags().Bool("all", false, "")
|
|
cmd.Flags().String("type", model.JobTypeDataRetention, "")
|
|
cmd.Flags().StringSlice("ids", []string{}, "")
|
|
cmd.Flags().String("status", "", "")
|
|
|
|
s.client.
|
|
EXPECT().
|
|
GetJobs(context.TODO(), model.JobTypeDataRetention, "", 0, perPage).
|
|
Return(mockJobs, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
err := listJobsCmdF(s.client, cmd, nil)
|
|
s.Require().Nil(err)
|
|
s.Len(printer.GetLines(), len(mockJobs))
|
|
s.Empty(printer.GetErrorLines())
|
|
for i, line := range printer.GetLines() {
|
|
s.Equal(mockJobs[i], line.(*model.Job))
|
|
}
|
|
})
|
|
}
|
|
|
|
func (s *MmctlUnitTestSuite) TestUpdateJobCmdF() {
|
|
s.Run("update job status", func() {
|
|
printer.Clean()
|
|
id := model.NewId()
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("force", true, "")
|
|
|
|
s.client.
|
|
EXPECT().
|
|
UpdateJobStatus(context.TODO(), id, model.JobStatusPending, true).
|
|
Return(&model.Response{}, nil).
|
|
Times(1)
|
|
|
|
err := updateJobCmdF(s.client, cmd, []string{id, model.JobStatusPending})
|
|
s.Require().Nil(err)
|
|
})
|
|
}
|
|
|
|
func (s *MmctlUnitTestSuite) TestCreateJobCmdF() {
|
|
s.Run("create job with data", func() {
|
|
printer.Clean()
|
|
data := map[string]string{"batch_size": "1000"}
|
|
mockJob := &model.Job{
|
|
Id: model.NewId(),
|
|
Type: model.JobTypeMessageExport,
|
|
Data: data,
|
|
}
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().StringToString("data", nil, "")
|
|
err := cmd.Flags().Set("data", "batch_size=1000")
|
|
s.Require().NoError(err)
|
|
|
|
s.client.
|
|
EXPECT().
|
|
CreateJob(context.TODO(), &model.Job{Type: model.JobTypeMessageExport, Data: data}).
|
|
Return(mockJob, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
err = createJobCmdF(s.client, cmd, []string{model.JobTypeMessageExport})
|
|
s.Require().Nil(err)
|
|
s.Len(printer.GetLines(), 1)
|
|
s.Empty(printer.GetErrorLines())
|
|
s.Equal(mockJob, printer.GetLines()[0].(*model.Job))
|
|
})
|
|
|
|
s.Run("forwards type not in AllJobTypes to the server", func() {
|
|
printer.Clean()
|
|
mockJob := &model.Job{Id: model.NewId(), Type: model.JobTypeAccessControlSync}
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().StringToString("data", nil, "")
|
|
|
|
s.client.
|
|
EXPECT().
|
|
CreateJob(context.TODO(), &model.Job{Type: model.JobTypeAccessControlSync, Data: map[string]string{}}).
|
|
Return(mockJob, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
err := createJobCmdF(s.client, cmd, []string{model.JobTypeAccessControlSync})
|
|
s.Require().Nil(err)
|
|
s.Len(printer.GetLines(), 1)
|
|
s.Empty(printer.GetErrorLines())
|
|
})
|
|
|
|
s.Run("create job returns server error", func() {
|
|
printer.Clean()
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().StringToString("data", nil, "")
|
|
|
|
s.client.
|
|
EXPECT().
|
|
CreateJob(context.TODO(), &model.Job{Type: model.JobTypeLdapSync, Data: map[string]string{}}).
|
|
Return(nil, &model.Response{}, errors.New("some-error")).
|
|
Times(1)
|
|
|
|
err := createJobCmdF(s.client, cmd, []string{model.JobTypeLdapSync})
|
|
s.Require().NotNil(err)
|
|
s.Empty(printer.GetLines())
|
|
})
|
|
}
|
|
|
|
func (s *MmctlUnitTestSuite) TestJobTypeCompletionF() {
|
|
s.Run("suggests all job types for the first argument", func() {
|
|
suggestions, directive := jobTypeCompletionF(&cobra.Command{}, []string{}, "")
|
|
s.Equal(cobra.ShellCompDirectiveNoFileComp, directive)
|
|
s.ElementsMatch(model.AllJobTypes[:], suggestions)
|
|
})
|
|
|
|
s.Run("suggests nothing once the type is provided", func() {
|
|
suggestions, directive := jobTypeCompletionF(&cobra.Command{}, []string{model.JobTypeLdapSync}, "")
|
|
s.Equal(cobra.ShellCompDirectiveNoFileComp, directive)
|
|
s.Empty(suggestions)
|
|
})
|
|
}
|
|
|
|
func (s *MmctlUnitTestSuite) TestShowJobCmdF() {
|
|
s.Run("show job", func() {
|
|
printer.Clean()
|
|
id := model.NewId()
|
|
mockJob := &model.Job{Id: id}
|
|
|
|
s.client.
|
|
EXPECT().
|
|
GetJob(context.TODO(), id).
|
|
Return(mockJob, &model.Response{}, nil).
|
|
Times(1)
|
|
|
|
err := showJobCmdF(s.client, &cobra.Command{}, []string{id})
|
|
s.Require().Nil(err)
|
|
s.Len(printer.GetLines(), 1)
|
|
s.Empty(printer.GetErrorLines())
|
|
s.Equal(mockJob, printer.GetLines()[0].(*model.Job))
|
|
})
|
|
|
|
s.Run("show job with invalid ID", func() {
|
|
printer.Clean()
|
|
|
|
err := showJobCmdF(s.client, &cobra.Command{}, []string{"invalid-id"})
|
|
s.Require().NotNil(err)
|
|
s.Empty(printer.GetLines())
|
|
})
|
|
}
|
|
|
|
func (s *MmctlUnitTestSuite) TestCancelJobCmdF() {
|
|
s.Run("cancel job", func() {
|
|
printer.Clean()
|
|
id := model.NewId()
|
|
|
|
s.client.
|
|
EXPECT().
|
|
CancelJob(context.TODO(), id).
|
|
Return(&model.Response{}, nil).
|
|
Times(1)
|
|
|
|
err := cancelJobCmdF(s.client, &cobra.Command{}, []string{id})
|
|
s.Require().Nil(err)
|
|
s.Empty(printer.GetErrorLines())
|
|
})
|
|
|
|
s.Run("cancel job with invalid ID", func() {
|
|
printer.Clean()
|
|
|
|
err := cancelJobCmdF(s.client, &cobra.Command{}, []string{"invalid-id"})
|
|
s.Require().NotNil(err)
|
|
})
|
|
|
|
s.Run("cancel job returns server error", func() {
|
|
printer.Clean()
|
|
id := model.NewId()
|
|
|
|
s.client.
|
|
EXPECT().
|
|
CancelJob(context.TODO(), id).
|
|
Return(&model.Response{}, errors.New("some-error")).
|
|
Times(1)
|
|
|
|
err := cancelJobCmdF(s.client, &cobra.Command{}, []string{id})
|
|
s.Require().NotNil(err)
|
|
})
|
|
}
|