From 35d3c21174bcef0f292fd0ca60295a74db8e5555 Mon Sep 17 00:00:00 2001 From: Lee Spottiswood Date: Thu, 17 Jun 2021 11:19:43 +0100 Subject: [PATCH] add pss request feedback command, update output to serialize pss models (#106) --- cmd/pss/output.go | 53 +-------- cmd/pss/pss_request.go | 1 + cmd/pss/pss_request_feedback.go | 133 +++++++++++++++++++++++ cmd/pss/pss_request_feedback_test.go | 156 +++++++++++++++++++++++++++ go.mod | 2 +- go.sum | 4 +- test/mocks/mock_pssservice.go | 30 ++++++ 7 files changed, 328 insertions(+), 51 deletions(-) create mode 100644 cmd/pss/pss_request_feedback.go create mode 100644 cmd/pss/pss_request_feedback_test.go diff --git a/cmd/pss/output.go b/cmd/pss/output.go index 6e66530..57fc6b9 100644 --- a/cmd/pss/output.go +++ b/cmd/pss/output.go @@ -1,61 +1,18 @@ package pss import ( - "strconv" - "github.com/ukfast/cli/internal/pkg/output" "github.com/ukfast/sdk-go/pkg/service/pss" ) func OutputPSSRequestsProvider(requests []pss.Request) output.OutputHandlerDataProvider { - return output.NewGenericOutputHandlerDataProvider( - output.WithData(requests), - output.WithFieldDataFunc(func() ([]*output.OrderedFields, error) { - var data []*output.OrderedFields - for _, request := range requests { - fields := output.NewOrderedFields() - fields.Set("id", output.NewFieldValue(strconv.Itoa(request.ID), true)) - fields.Set("author_id", output.NewFieldValue(strconv.Itoa(request.Author.ID), false)) - fields.Set("author_name", output.NewFieldValue(request.Author.Name, true)) - fields.Set("type", output.NewFieldValue(request.Type, true)) - fields.Set("secure", output.NewFieldValue(strconv.FormatBool(request.Secure), false)) - fields.Set("subject", output.NewFieldValue(request.Subject, true)) - fields.Set("created_at", output.NewFieldValue(request.CreatedAt.String(), true)) - fields.Set("priority", output.NewFieldValue(request.Priority.String(), false)) - fields.Set("archived", output.NewFieldValue(strconv.FormatBool(request.Archived), false)) - fields.Set("status", output.NewFieldValue(request.Status.String(), true)) - fields.Set("request_sms", output.NewFieldValue(strconv.FormatBool(request.RequestSMS), false)) - fields.Set("version", output.NewFieldValue(strconv.Itoa(request.Version), false)) - fields.Set("customer_reference", output.NewFieldValue(request.CustomerReference, false)) - fields.Set("last_replied_at", output.NewFieldValue(request.LastRepliedAt.String(), true)) - fields.Set("product_id", output.NewFieldValue(strconv.Itoa(request.Product.ID), false)) - fields.Set("product_name", output.NewFieldValue(request.Product.Name, false)) - fields.Set("product_type", output.NewFieldValue(request.Product.Type, false)) - - data = append(data, fields) - } - - return data, nil - }), - ) + return output.NewSerializedOutputHandlerDataProvider(requests).WithDefaultFields([]string{"id", "author_name", "type", "subject", "status", "last_replied_at", "created_at"}) } func OutputPSSRepliesProvider(replies []pss.Reply) output.OutputHandlerDataProvider { - return output.NewGenericOutputHandlerDataProvider( - output.WithData(replies), - output.WithFieldDataFunc(func() ([]*output.OrderedFields, error) { - var data []*output.OrderedFields - for _, reply := range replies { - fields := output.NewOrderedFields() - fields.Set("id", output.NewFieldValue(reply.ID, true)) - fields.Set("author_name", output.NewFieldValue(reply.Author.Name, true)) - fields.Set("description", output.NewFieldValue(reply.Description, false)) - fields.Set("created_at", output.NewFieldValue(reply.CreatedAt.String(), true)) - - data = append(data, fields) - } + return output.NewSerializedOutputHandlerDataProvider(replies).WithDefaultFields([]string{"id", "author_name", "created_at"}) +} - return data, nil - }), - ) +func OutputPSSFeedbackProvider(feedback []pss.Feedback) output.OutputHandlerDataProvider { + return output.NewSerializedOutputHandlerDataProvider(feedback).WithDefaultFields([]string{"id", "score", "created_at"}) } diff --git a/cmd/pss/pss_request.go b/cmd/pss/pss_request.go index 4c0d0c1..d9c251b 100644 --- a/cmd/pss/pss_request.go +++ b/cmd/pss/pss_request.go @@ -28,6 +28,7 @@ func pssRequestRootCmd(f factory.ClientFactory) *cobra.Command { // Child root commands cmd.AddCommand(pssRequestReplyRootCmd(f)) + cmd.AddCommand(pssRequestFeedbackRootCmd(f)) return cmd } diff --git a/cmd/pss/pss_request_feedback.go b/cmd/pss/pss_request_feedback.go new file mode 100644 index 0000000..1a6d6ed --- /dev/null +++ b/cmd/pss/pss_request_feedback.go @@ -0,0 +1,133 @@ +package pss + +import ( + "errors" + "fmt" + "strconv" + + "github.com/spf13/cobra" + "github.com/ukfast/cli/internal/pkg/factory" + "github.com/ukfast/cli/internal/pkg/output" + "github.com/ukfast/sdk-go/pkg/service/pss" +) + +func pssRequestFeedbackRootCmd(f factory.ClientFactory) *cobra.Command { + cmd := &cobra.Command{ + Use: "feedback", + Short: "sub-commands relating to request feedback", + } + + // Child commands + cmd.AddCommand(pssRequestFeedbackShowCmd(f)) + cmd.AddCommand(pssRequestFeedbackCreateCmd(f)) + + return cmd +} + +func pssRequestFeedbackShowCmd(f factory.ClientFactory) *cobra.Command { + return &cobra.Command{ + Use: "show ...", + Short: "Shows feedback for a request", + Long: "This command shows feedback for one or more requests", + Example: "ukfast pss request feedback show 123", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("Missing request") + } + + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + c, err := f.NewClient() + if err != nil { + return err + } + + return pssRequestFeedbackShow(c.PSSService(), cmd, args) + }, + } +} + +func pssRequestFeedbackShow(service pss.PSSService, cmd *cobra.Command, args []string) error { + var feedbacks []pss.Feedback + for _, arg := range args { + requestID, err := strconv.Atoi(arg) + if err != nil { + output.OutputWithErrorLevelf("Invalid request ID [%s]", arg) + continue + } + + feedback, err := service.GetRequestFeedback(requestID) + if err != nil { + output.OutputWithErrorLevelf("Error retrieving feedback for request [%s]: %s", arg, err) + continue + } + + feedbacks = append(feedbacks, feedback) + } + + return output.CommandOutput(cmd, OutputPSSFeedbackProvider(feedbacks)) +} + +func pssRequestFeedbackCreateCmd(f factory.ClientFactory) *cobra.Command { + cmd := &cobra.Command{ + Use: "create ", + Short: "Creates feedback for a request", + Long: "This command creates feedback for a request", + Example: "ukfast pss request feedback create 123", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("Missing request") + } + + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + c, err := f.NewClient() + if err != nil { + return err + } + + return pssRequestFeedbackCreate(c.PSSService(), cmd, args) + }, + } + + cmd.Flags().Int("contact", 0, "Specifies contact ID") + cmd.MarkFlagRequired("contact") + cmd.Flags().Int("score", 0, "Specifies feedback score") + cmd.Flags().String("comment", "", "Specifies feedback comment") + cmd.Flags().Int("speed-resolved", 0, "Specifies feedback speed resolved score") + cmd.Flags().Int("quality", 0, "Specifies feedback quality") + cmd.Flags().Int("nps-score", 0, "Specifies feedback NPS score") + cmd.Flags().Bool("thirdparty-consent", false, "Specifies feedback third party consent") + + return cmd +} + +func pssRequestFeedbackCreate(service pss.PSSService, cmd *cobra.Command, args []string) error { + requestID, err := strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("Invalid request ID [%s]", args[0]) + } + + createRequest := pss.CreateFeedbackRequest{} + createRequest.ContactID, _ = cmd.Flags().GetInt("contact") + createRequest.Score, _ = cmd.Flags().GetInt("score") + createRequest.Comment, _ = cmd.Flags().GetString("comment") + createRequest.SpeedResolved, _ = cmd.Flags().GetInt("speed-resolved") + createRequest.Quality, _ = cmd.Flags().GetInt("quality") + createRequest.NPSScore, _ = cmd.Flags().GetInt("nps-score") + createRequest.ThirdPartyConsent, _ = cmd.Flags().GetBool("thirdparty-consent") + + _, err = service.CreateRequestFeedback(requestID, createRequest) + if err != nil { + return fmt.Errorf("Error creating feedback for request: %s", err) + } + + feedback, err := service.GetRequestFeedback(requestID) + if err != nil { + return fmt.Errorf("Error retrieving new feedback for request: %s", err) + } + + return output.CommandOutput(cmd, OutputPSSFeedbackProvider([]pss.Feedback{feedback})) +} diff --git a/cmd/pss/pss_request_feedback_test.go b/cmd/pss/pss_request_feedback_test.go new file mode 100644 index 0000000..8a099c2 --- /dev/null +++ b/cmd/pss/pss_request_feedback_test.go @@ -0,0 +1,156 @@ +package pss + +import ( + "errors" + "testing" + + gomock "github.com/golang/mock/gomock" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/ukfast/cli/test/mocks" + "github.com/ukfast/cli/test/test_output" + "github.com/ukfast/sdk-go/pkg/service/pss" +) + +func Test_pssRequestFeedbackShowCmd_Args(t *testing.T) { + t.Run("ValidArgs_NoError", func(t *testing.T) { + err := pssRequestFeedbackShowCmd(nil).Args(nil, []string{"123"}) + + assert.Nil(t, err) + }) + + t.Run("InvalidArgs_Error", func(t *testing.T) { + err := pssRequestFeedbackShowCmd(nil).Args(nil, []string{}) + + assert.NotNil(t, err) + assert.Equal(t, "Missing request", err.Error()) + }) +} + +func Test_pssRequestFeedbackShow(t *testing.T) { + t.Run("SingleRequest", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + service := mocks.NewMockPSSService(mockCtrl) + + service.EXPECT().GetRequestFeedback(123).Return(pss.Feedback{}, nil).Times(1) + + pssRequestFeedbackShow(service, &cobra.Command{}, []string{"123"}) + }) + + t.Run("MultipleRequests", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + service := mocks.NewMockPSSService(mockCtrl) + + gomock.InOrder( + service.EXPECT().GetRequestFeedback(123).Return(pss.Feedback{}, nil), + service.EXPECT().GetRequestFeedback(456).Return(pss.Feedback{}, nil), + ) + + pssRequestFeedbackShow(service, &cobra.Command{}, []string{"123", "456"}) + }) + + t.Run("GetRequestID_OutputsError", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + service := mocks.NewMockPSSService(mockCtrl) + + test_output.AssertErrorOutput(t, "Invalid request ID [abc]\n", func() { + pssRequestFeedbackShow(service, &cobra.Command{}, []string{"abc"}) + }) + }) + + t.Run("GetRequestError_OutputsError", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + service := mocks.NewMockPSSService(mockCtrl) + + service.EXPECT().GetRequestFeedback(123).Return(pss.Feedback{}, errors.New("test error")) + + test_output.AssertErrorOutput(t, "Error retrieving feedback for request [123]: test error\n", func() { + pssRequestFeedbackShow(service, &cobra.Command{}, []string{"123"}) + }) + }) +} + +func Test_pssRequestFeedbackCreateCmd_Args(t *testing.T) { + t.Run("ValidArgs_NoError", func(t *testing.T) { + err := pssRequestFeedbackCreateCmd(nil).Args(nil, []string{"123"}) + + assert.Nil(t, err) + }) + + t.Run("InvalidArgs_Error", func(t *testing.T) { + err := pssRequestFeedbackCreateCmd(nil).Args(nil, []string{}) + + assert.NotNil(t, err) + assert.Equal(t, "Missing request", err.Error()) + }) +} + +func Test_pssRequestFeedbackCreate(t *testing.T) { + t.Run("DefaultCreate", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + service := mocks.NewMockPSSService(mockCtrl) + cmd := pssRequestFeedbackCreateCmd(nil) + cmd.ParseFlags([]string{"--contact=1"}) + + expectedRequest := pss.CreateFeedbackRequest{ + ContactID: 1, + } + + gomock.InOrder( + service.EXPECT().CreateRequestFeedback(123, gomock.Eq(expectedRequest)).Return(123, nil), + service.EXPECT().GetRequestFeedback(123).Return(pss.Feedback{}, nil), + ) + + pssRequestFeedbackCreate(service, cmd, []string{"123"}) + }) + + t.Run("InvalidRequestID_ReturnsError", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + service := mocks.NewMockPSSService(mockCtrl) + cmd := pssRequestFeedbackCreateCmd(nil) + + err := pssRequestFeedbackCreate(service, cmd, []string{"invalid"}) + assert.Contains(t, err.Error(), "Invalid request ID [invalid]") + }) + + t.Run("CreateRequestFeedbackError_ReturnsError", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + service := mocks.NewMockPSSService(mockCtrl) + cmd := pssRequestFeedbackCreateCmd(nil) + + service.EXPECT().CreateRequestFeedback(123, gomock.Any()).Return(0, errors.New("test error")).Times(1) + + err := pssRequestFeedbackCreate(service, cmd, []string{"123"}) + assert.Equal(t, "Error creating feedback for request: test error", err.Error()) + }) + + t.Run("GetRequestError_ReturnsError", func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + service := mocks.NewMockPSSService(mockCtrl) + cmd := pssRequestFeedbackCreateCmd(nil) + + gomock.InOrder( + service.EXPECT().CreateRequestFeedback(123, gomock.Any()).Return(123, nil), + service.EXPECT().GetRequestFeedback(123).Return(pss.Feedback{}, errors.New("test error")), + ) + + err := pssRequestFeedbackCreate(service, cmd, []string{"123"}) + assert.Equal(t, "Error retrieving new feedback for request: test error", err.Error()) + }) +} diff --git a/go.mod b/go.mod index a9d19e5..b7674a3 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/spf13/cobra v0.0.5 github.com/spf13/viper v1.3.2 github.com/stretchr/testify v1.6.1 - github.com/ukfast/sdk-go v1.4.4 + github.com/ukfast/sdk-go v1.4.5 golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b // indirect gopkg.in/go-playground/assert.v1 v1.2.1 k8s.io/client-go v11.0.0+incompatible diff --git a/go.sum b/go.sum index 4cca345..20e5192 100644 --- a/go.sum +++ b/go.sum @@ -99,8 +99,8 @@ github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4U github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ukfast/go-durationstring v1.0.0 h1:kgPuA7XjLjgLDfkG8j0MpolxcZh/eMdiVoOIFD/uc5I= github.com/ukfast/go-durationstring v1.0.0/go.mod h1:Ci81n51kfxlKUIaLY9cINIKRO94VTqV+iCGbOMTb0V8= -github.com/ukfast/sdk-go v1.4.4 h1:z/B6mM3RezDi9+BWQuZ4LXrhvWPta1vsa+KSTXtuCIo= -github.com/ukfast/sdk-go v1.4.4/go.mod h1:tspweEP77MHhVEYgEEieKAKGITFgwkYl1q5fLh4HZAo= +github.com/ukfast/sdk-go v1.4.5 h1:O2VvIMulQ1P3KL3xzg7INpmwuCWtLB4NBdqDf14gn00= +github.com/ukfast/sdk-go v1.4.5/go.mod h1:tspweEP77MHhVEYgEEieKAKGITFgwkYl1q5fLh4HZAo= github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok= github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= diff --git a/test/mocks/mock_pssservice.go b/test/mocks/mock_pssservice.go index d0c8202..1affcdd 100644 --- a/test/mocks/mock_pssservice.go +++ b/test/mocks/mock_pssservice.go @@ -50,6 +50,21 @@ func (mr *MockPSSServiceMockRecorder) CreateRequest(arg0 interface{}) *gomock.Ca return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateRequest", reflect.TypeOf((*MockPSSService)(nil).CreateRequest), arg0) } +// CreateRequestFeedback mocks base method +func (m *MockPSSService) CreateRequestFeedback(arg0 int, arg1 pss.CreateFeedbackRequest) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateRequestFeedback", arg0, arg1) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateRequestFeedback indicates an expected call of CreateRequestFeedback +func (mr *MockPSSServiceMockRecorder) CreateRequestFeedback(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateRequestFeedback", reflect.TypeOf((*MockPSSService)(nil).CreateRequestFeedback), arg0, arg1) +} + // CreateRequestReply mocks base method func (m *MockPSSService) CreateRequestReply(arg0 int, arg1 pss.CreateReplyRequest) (string, error) { m.ctrl.T.Helper() @@ -154,6 +169,21 @@ func (mr *MockPSSServiceMockRecorder) GetRequestConversationPaginated(arg0, arg1 return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRequestConversationPaginated", reflect.TypeOf((*MockPSSService)(nil).GetRequestConversationPaginated), arg0, arg1) } +// GetRequestFeedback mocks base method +func (m *MockPSSService) GetRequestFeedback(arg0 int) (pss.Feedback, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRequestFeedback", arg0) + ret0, _ := ret[0].(pss.Feedback) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetRequestFeedback indicates an expected call of GetRequestFeedback +func (mr *MockPSSServiceMockRecorder) GetRequestFeedback(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRequestFeedback", reflect.TypeOf((*MockPSSService)(nil).GetRequestFeedback), arg0) +} + // GetRequestReplies mocks base method func (m *MockPSSService) GetRequestReplies(arg0 int, arg1 connection.APIRequestParameters) ([]pss.Reply, error) { m.ctrl.T.Helper()