From b12b6ebfc3d9285f8b38fab5d9413ac52b237e8b Mon Sep 17 00:00:00 2001 From: Kapish Malik Date: Mon, 6 May 2024 13:00:00 +0530 Subject: [PATCH] Add option for fallback post serve action on passing empty action name (#1127) * add option for fallback post serve action * removing fallback option and from view - review comment changes --- core/action/action.go | 12 +--- core/action/action_test.go | 20 +++---- core/action/postserveactiondetails.go | 22 ++++--- core/action/postserveactiondetails_test.go | 17 +++++- .../v2/postserveactiondetails_views.go | 2 +- core/hoverfly.go | 4 ++ core/hoverfly_service.go | 9 ++- core/hoverfly_service_test.go | 59 +++++++++++++++++++ core/import.go | 2 +- .../core/ft_postserveaction_test.go | 20 +++++++ .../hoverctl/postserveaction_test.go | 17 ++++++ hoverctl/cmd/postserveaction.go | 15 +++-- 12 files changed, 162 insertions(+), 37 deletions(-) diff --git a/core/action/action.go b/core/action/action.go index cb7648f4f..7c4803446 100644 --- a/core/action/action.go +++ b/core/action/action.go @@ -10,7 +10,6 @@ import ( "os" "os/exec" "path" - "strings" "time" v2 "github.com/SpectoLabs/hoverfly/core/handlers/v2" @@ -27,12 +26,9 @@ type Action struct { DelayInMs int } -func NewLocalAction(actionName, binary, scriptContent string, delayInMs int) (*Action, error) { +func NewLocalAction(binary, scriptContent string, delayInMs int) (*Action, error) { scriptInfo := &Action{} - if strings.TrimSpace(actionName) == "" { - return nil, errors.New("empty action name passed") - } scriptInfo.DelayInMs = delayInMs @@ -46,11 +42,7 @@ func NewLocalAction(actionName, binary, scriptContent string, delayInMs int) (*A return scriptInfo, nil } -func NewRemoteAction(actionName, host string, delayInMs int) (*Action, error) { - - if strings.TrimSpace(actionName) == "" { - return nil, errors.New("empty action name passed") - } +func NewRemoteAction(host string, delayInMs int) (*Action, error) { if !isValidURL(host) { return nil, errors.New("remote host is invalid") diff --git a/core/action/action_test.go b/core/action/action_test.go index dc962efcd..9853c8fe5 100644 --- a/core/action/action_test.go +++ b/core/action/action_test.go @@ -17,7 +17,7 @@ const pythonBasicScript = "import sys\nprint(sys.stdin.readlines()[0])" func Test_NewLocalActionMethod(t *testing.T) { RegisterTestingT(t) - newAction, err := action.NewLocalAction("test-callback", "python3", "dummy-script", 1800) + newAction, err := action.NewLocalAction("python3", "dummy-script", 1800) Expect(err).To(BeNil()) Expect(newAction).NotTo(BeNil()) @@ -33,7 +33,7 @@ func Test_NewLocalActionMethod(t *testing.T) { func Test_NewRemoteActionMethodWithEmptyHost(t *testing.T) { RegisterTestingT(t) - newAction, err := action.NewRemoteAction("test-callback", "", 1800) + newAction, err := action.NewRemoteAction("", 1800) Expect(err).NotTo(BeNil()) Expect(newAction).To(BeNil()) @@ -42,7 +42,7 @@ func Test_NewRemoteActionMethodWithEmptyHost(t *testing.T) { func Test_NewRemoteActionMethodWithInvalidHost(t *testing.T) { RegisterTestingT(t) - newAction, err := action.NewRemoteAction("test-callback", "testing", 1800) + newAction, err := action.NewRemoteAction("testing", 1800) Expect(err).NotTo(BeNil()) Expect(err.Error()).To(Equal("remote host is invalid")) @@ -52,7 +52,7 @@ func Test_NewRemoteActionMethodWithInvalidHost(t *testing.T) { func Test_NewRemoteActionMethodWithHttpHost(t *testing.T) { RegisterTestingT(t) - newAction, err := action.NewRemoteAction("test-callback", "http://localhost", 1800) + newAction, err := action.NewRemoteAction("http://localhost", 1800) Expect(err).To(BeNil()) Expect(newAction).NotTo(BeNil()) @@ -63,7 +63,7 @@ func Test_NewRemoteActionMethodWithHttpHost(t *testing.T) { func Test_NewRemoteActionMethodWithHttpsHost(t *testing.T) { RegisterTestingT(t) - newAction, err := action.NewRemoteAction("test-callback", "https://test.com", 1800) + newAction, err := action.NewRemoteAction("https://test.com", 1800) Expect(err).To(BeNil()) Expect(newAction).NotTo(BeNil()) @@ -74,7 +74,7 @@ func Test_NewRemoteActionMethodWithHttpsHost(t *testing.T) { func Test_GetLocalActionViewMethod(t *testing.T) { RegisterTestingT(t) - newAction, err := action.NewLocalAction("test-callback", "python3", "dummy-script", 1800) + newAction, err := action.NewLocalAction("python3", "dummy-script", 1800) Expect(err).To(BeNil()) actionView := newAction.GetActionView("test-callback") @@ -88,7 +88,7 @@ func Test_GetLocalActionViewMethod(t *testing.T) { func Test_GetRemoteActionViewMethod(t *testing.T) { RegisterTestingT(t) - newAction, err := action.NewRemoteAction("test-callback", "http://localhost:8000", 1800) + newAction, err := action.NewRemoteAction("http://localhost:8000", 1800) Expect(err).To(BeNil()) actionView := newAction.GetActionView("test-callback") @@ -102,7 +102,7 @@ func Test_GetRemoteActionViewMethod(t *testing.T) { func Test_ExecuteLocalPostServeAction(t *testing.T) { RegisterTestingT(t) - newAction, err := action.NewLocalAction("test-callback", "python3", pythonBasicScript, 0) + newAction, err := action.NewLocalAction("python3", pythonBasicScript, 0) Expect(err).To(BeNil()) @@ -136,7 +136,7 @@ func Test_ExecuteRemotePostServeAction(t *testing.T) { journalIDChannel := make(chan string, 1) newJournal := journal.NewJournal() journalIDChannel <- "1" - newAction, err := action.NewRemoteAction("test-callback", server.URL+"/process", 0) + newAction, err := action.NewRemoteAction(server.URL+"/process", 0) close(journalIDChannel) Expect(err).To(BeNil()) err = newAction.Execute(&originalPair, journalIDChannel, newJournal) @@ -150,7 +150,7 @@ func Test_ExecuteRemotePostServeAction_WithUnReachableHost(t *testing.T) { }, } - newAction, err := action.NewRemoteAction("test-callback", "http://test", 0) + newAction, err := action.NewRemoteAction("http://test", 0) Expect(err).To(BeNil()) //not adding entry as update journal method will be tested in its file diff --git a/core/action/postserveactiondetails.go b/core/action/postserveactiondetails.go index 583329f68..62f9b51e7 100644 --- a/core/action/postserveactiondetails.go +++ b/core/action/postserveactiondetails.go @@ -2,12 +2,14 @@ package action import ( "fmt" + "strings" "sync" ) type PostServeActionDetails struct { - Actions map[string]Action - RWMutex sync.RWMutex + Actions map[string]Action + FallbackAction *Action + RWMutex sync.RWMutex } func NewPostServeActionDetails() *PostServeActionDetails { @@ -20,12 +22,18 @@ func NewPostServeActionDetails() *PostServeActionDetails { func (postServeActionDetails *PostServeActionDetails) SetAction(actionName string, newAction *Action) error { postServeActionDetails.RWMutex.Lock() - //cleanup - if existingAction, ok := postServeActionDetails.Actions[actionName]; ok { - existingAction.DeleteScript() - delete(postServeActionDetails.Actions, actionName) + if strings.TrimSpace(actionName) == "" { + if postServeActionDetails.FallbackAction != nil { + postServeActionDetails.FallbackAction.DeleteScript() + } + postServeActionDetails.FallbackAction = newAction + } else { + if existingAction, ok := postServeActionDetails.Actions[actionName]; ok { + existingAction.DeleteScript() + delete(postServeActionDetails.Actions, actionName) + } + postServeActionDetails.Actions[actionName] = *newAction } - postServeActionDetails.Actions[actionName] = *newAction postServeActionDetails.RWMutex.Unlock() return nil } diff --git a/core/action/postserveactiondetails_test.go b/core/action/postserveactiondetails_test.go index 2daaf36a0..5968435d6 100644 --- a/core/action/postserveactiondetails_test.go +++ b/core/action/postserveactiondetails_test.go @@ -10,7 +10,7 @@ import ( func Test_SetPostServeActionMethod(t *testing.T) { RegisterTestingT(t) - newAction, err := action.NewLocalAction("test-callback", "python3", "dummy script", 1800) + newAction, err := action.NewLocalAction("python3", "dummy script", 1800) Expect(err).To(BeNil()) unit := action.NewPostServeActionDetails() @@ -22,10 +22,23 @@ func Test_SetPostServeActionMethod(t *testing.T) { Expect(unit.Actions["test-callback"].DelayInMs).To(Equal(1800)) } +func Test_SetPostServeActionMethod_WithEmptyActionName(t *testing.T) { + RegisterTestingT(t) + + newAction, err := action.NewLocalAction("python3", "dummy script", 1800) + Expect(err).To(BeNil()) + + unit := action.NewPostServeActionDetails() + err = unit.SetAction("", newAction) + + Expect(err).To(BeNil()) + Expect(unit.FallbackAction).NotTo(BeNil()) +} + func Test_DeletePostServeActionMethod(t *testing.T) { RegisterTestingT(t) - newAction, err := action.NewLocalAction("test-callback", "python3", "dummy script", 1800) + newAction, err := action.NewLocalAction("python3", "dummy script", 1800) Expect(err).To(BeNil()) unit := action.NewPostServeActionDetails() diff --git a/core/handlers/v2/postserveactiondetails_views.go b/core/handlers/v2/postserveactiondetails_views.go index 565fde5a5..ba2558a0d 100644 --- a/core/handlers/v2/postserveactiondetails_views.go +++ b/core/handlers/v2/postserveactiondetails_views.go @@ -5,7 +5,7 @@ type PostServeActionDetailsView struct { } type ActionView struct { - ActionName string `json:"actionName"` + ActionName string `json:"actionName,omitempty"` Binary string `json:"binary,omitempty"` ScriptContent string `json:"script,omitempty"` Remote string `json:"remote,omitempty"` diff --git a/core/hoverfly.go b/core/hoverfly.go index 8529634b2..f95f8f057 100644 --- a/core/hoverfly.go +++ b/core/hoverfly.go @@ -224,6 +224,10 @@ func (hf *Hoverfly) processRequest(req *http.Request) (*http.Response, chan stri journalIDChannel := make(chan string, 1) go postServeAction.Execute(result.PostServeActionInputDetails.Pair, journalIDChannel, hf.Journal) return result.Response, journalIDChannel + } else if hf.PostServeActionDetails.FallbackAction != nil { + journalIDChannel := make(chan string, 1) + go hf.PostServeActionDetails.FallbackAction.Execute(result.PostServeActionInputDetails.Pair, journalIDChannel, hf.Journal) + return result.Response, journalIDChannel } } diff --git a/core/hoverfly_service.go b/core/hoverfly_service.go index 84285857d..cd9b33c67 100644 --- a/core/hoverfly_service.go +++ b/core/hoverfly_service.go @@ -467,6 +467,11 @@ func (hf *Hoverfly) GetAllPostServeActions() v2.PostServeActionDetailsView { for actionName, action := range hf.PostServeActionDetails.Actions { actions = append(actions, action.GetActionView(actionName)) } + + if hf.PostServeActionDetails.FallbackAction != nil { + actions = append(actions, hf.PostServeActionDetails.FallbackAction.GetActionView("")) + } + return v2.PostServeActionDetailsView{ Actions: actions, } @@ -474,7 +479,7 @@ func (hf *Hoverfly) GetAllPostServeActions() v2.PostServeActionDetailsView { func (hf *Hoverfly) SetLocalPostServeAction(actionName string, binary string, scriptContent string, delayInMs int) error { - action, err := action.NewLocalAction(actionName, binary, scriptContent, delayInMs) + action, err := action.NewLocalAction(binary, scriptContent, delayInMs) if err != nil { return err } @@ -488,7 +493,7 @@ func (hf *Hoverfly) SetLocalPostServeAction(actionName string, binary string, sc func (hf *Hoverfly) SetRemotePostServeAction(actionName, remote string, delayInMs int) error { - action, err := action.NewRemoteAction(actionName, remote, delayInMs) + action, err := action.NewRemoteAction(remote, delayInMs) if err != nil { return err } diff --git a/core/hoverfly_service_test.go b/core/hoverfly_service_test.go index a422b7506..7e8765e3f 100644 --- a/core/hoverfly_service_test.go +++ b/core/hoverfly_service_test.go @@ -1379,6 +1379,36 @@ func TestHoverfly_GetPostServeActions(t *testing.T) { Expect(postServeActions.Actions[1].DelayInMs).To(Equal(1800)) } +func TestHoverfly_GetPostServeActions_WithFallback(t *testing.T) { + + RegisterTestingT(t) + + unit := NewHoverflyWithConfiguration(&Configuration{}) + localActionDetails := action.Action{Binary: "python3", DelayInMs: 1900} + remoteActionDetails := action.Action{Remote: "http://localhost", DelayInMs: 1800} + fallbackActionDetails := action.Action{Remote: "http://localhost:8081", DelayInMs: 1800} + actionMap := map[string]action.Action{ + "test-local-callback": localActionDetails, + "test-remote-callback": remoteActionDetails, + } + + unit.PostServeActionDetails.Actions = actionMap + unit.PostServeActionDetails.FallbackAction = &fallbackActionDetails + postServeActions := unit.GetAllPostServeActions() + + Expect(postServeActions).NotTo(BeNil()) + Expect(postServeActions.Actions).To(HaveLen(3)) + Expect(postServeActions.Actions[0].ActionName).To(Equal("test-local-callback")) + Expect(postServeActions.Actions[0].Binary).To(Equal("python3")) + Expect(postServeActions.Actions[0].DelayInMs).To(Equal(1900)) + Expect(postServeActions.Actions[1].ActionName).To(Equal("test-remote-callback")) + Expect(postServeActions.Actions[1].Remote).To(Equal("http://localhost")) + Expect(postServeActions.Actions[1].DelayInMs).To(Equal(1800)) + Expect(postServeActions.Actions[2]).NotTo(BeNil()) + Expect(postServeActions.Actions[2].Remote).To(Equal("http://localhost:8081")) + Expect(postServeActions.Actions[2].DelayInMs).To(Equal(1800)) +} + func TestHoverfly_SetLocalPostServeAction(t *testing.T) { RegisterTestingT(t) @@ -1409,6 +1439,35 @@ func TestHoverfly_SetRemotePostServeAction(t *testing.T) { Expect(unit.PostServeActionDetails.Actions["test-callback"].DelayInMs).To(Equal(1800)) } +func TestHoverfly_SetFallbackLocalPostServeAction(t *testing.T) { + + RegisterTestingT(t) + + unit := NewHoverflyWithConfiguration(&Configuration{}) + + err := unit.SetLocalPostServeAction("", "script", "dummy script", 1800) + + Expect(err).To(BeNil()) + Expect(unit.PostServeActionDetails.FallbackAction).NotTo(BeNil()) + Expect(unit.PostServeActionDetails.FallbackAction.Binary).To(Equal("script")) + Expect(unit.PostServeActionDetails.FallbackAction.DelayInMs).To(Equal(1800)) + +} + +func TestHoverfly_SetFallbackRemotePostServeAction(t *testing.T) { + + RegisterTestingT(t) + + unit := NewHoverflyWithConfiguration(&Configuration{}) + + err := unit.SetRemotePostServeAction("", "http://localhost:8080", 1800) + + Expect(err).To(BeNil()) + Expect(unit.PostServeActionDetails.FallbackAction).NotTo(BeNil()) + Expect(unit.PostServeActionDetails.FallbackAction.Remote).To(Equal("http://localhost:8080")) + Expect(unit.PostServeActionDetails.FallbackAction.DelayInMs).To(Equal(1800)) +} + func TestHoverfly_DeleteLocalPostServeAction(t *testing.T) { RegisterTestingT(t) diff --git a/core/import.go b/core/import.go index efaa1af9e..8fde2ae92 100644 --- a/core/import.go +++ b/core/import.go @@ -123,7 +123,7 @@ func (hf *Hoverfly) importRequestResponsePairViewsWithCustomData(pairViews []v2. failed := 0 for i, pairView := range pairViews { - if _, ok := hf.PostServeActionDetails.Actions[pairView.Response.PostServeAction]; pairView.Response.PostServeAction != "" && !ok { + if _, ok := hf.PostServeActionDetails.Actions[pairView.Response.PostServeAction]; pairView.Response.PostServeAction != "" && !ok && hf.PostServeActionDetails.FallbackAction == nil { importResult.SetError(fmt.Errorf("invalid post server action name provided")) break } diff --git a/functional-tests/core/ft_postserveaction_test.go b/functional-tests/core/ft_postserveaction_test.go index d0d11c7ed..aa1e2f0f7 100644 --- a/functional-tests/core/ft_postserveaction_test.go +++ b/functional-tests/core/ft_postserveaction_test.go @@ -103,6 +103,26 @@ var _ = Describe("Manage post serve actions in hoverfly", func() { Expect(postServeActionDetails.Actions[0].DelayInMs).To(Equal(1400)) }) }) + + Context("start hoverfly and set fallback remote post serve action", func() { + + BeforeEach(func() { + hoverfly.Start() + }) + + AfterEach(func() { + hoverfly.Stop() + }) + + It("Should set post serve action", func() { + postServeActionDetails := hoverfly.SetRemotePostServeAction("", "http://localhost:8080", 1600) + Expect(postServeActionDetails).NotTo(BeNil()) + Expect(postServeActionDetails.Actions).NotTo(BeNil()) + Expect(postServeActionDetails.Actions[0].ActionName).To(Equal("")) + Expect(postServeActionDetails.Actions[0].Remote).To(Equal("http://localhost:8080")) + Expect(postServeActionDetails.Actions[0].DelayInMs).To(Equal(1600)) + }) + }) }) Context("delete post serve action", func() { diff --git a/functional-tests/hoverctl/postserveaction_test.go b/functional-tests/hoverctl/postserveaction_test.go index 188bcdef7..621b27842 100644 --- a/functional-tests/hoverctl/postserveaction_test.go +++ b/functional-tests/hoverctl/postserveaction_test.go @@ -36,6 +36,12 @@ var _ = Describe("When I use hoverctl", func() { Expect(output).To(ContainSubstring("Success")) }) + It("should return success on setting fallback remote post-serve-action", func() { + output := functional_tests.Run(hoverctlBinary, "post-serve-action", "set", "--remote", "http://localhost", "--delay", "1500") + + Expect(output).To(ContainSubstring("Success")) + }) + }) Describe("delete post-serve-action", func() { @@ -106,5 +112,16 @@ var _ = Describe("When I use hoverctl", func() { Expect(output).To(ContainSubstring("1700")) }) + It("should return remote default post-serve-action", func() { + output := functional_tests.Run(hoverctlBinary, "post-serve-action", "set", "--remote", "http://localhost", "--delay", "1700") + + Expect(output).To(ContainSubstring("Success")) + + output = functional_tests.Run(hoverctlBinary, "post-serve-action", "get-all") + Expect(output).To(ContainSubstring("fallback")) + Expect(output).To(ContainSubstring("http://localhost")) + Expect(output).To(ContainSubstring("1700")) + }) + }) }) diff --git a/hoverctl/cmd/postserveaction.go b/hoverctl/cmd/postserveaction.go index a207a703c..176ce0539 100644 --- a/hoverctl/cmd/postserveaction.go +++ b/hoverctl/cmd/postserveaction.go @@ -48,18 +48,18 @@ Hoverfly Remote PostServeAction can be set using the following flags: `, Run: func(cmd *cobra.Command, args []string) { checkTargetAndExit(target) - if remote != "" && actionNameToBeSet != "" { + if remote != "" { err := wrapper.SetRemotePostServeAction(actionNameToBeSet, remote, delayInMs, *target) handleIfError(err) fmt.Println("Success") - } else if binary != "" && scriptPath != "" && actionNameToBeSet != "" { + } else if binary != "" && scriptPath != "" { script, err := configuration.ReadFile(scriptPath) handleIfError(err) err = wrapper.SetLocalPostServeAction(actionNameToBeSet, binary, string(script), delayInMs, *target) handleIfError(err) fmt.Println("Success") } else { - fmt.Println("(Binary and script path/remote) and action name are compulsory to set post serve action") + fmt.Println("(Binary and script path/remote) are compulsory to set post serve action") } }, } @@ -109,9 +109,16 @@ func getPostServeActionsTabularData(postServeActions v2.PostServeActionDetailsVi actionData := []string{action.ActionName, action.Binary, getContentShorthand(action.ScriptContent), fmt.Sprint(action.DelayInMs)} localPostServeActionsData = append(localPostServeActionsData, actionData) } else { - actionData := []string{action.ActionName, action.Remote, fmt.Sprint(action.DelayInMs)} + var actionName string + if action.ActionName == "" { + actionName = "fallback" + } else { + actionName = action.ActionName + } + actionData := []string{actionName, action.Remote, fmt.Sprint(action.DelayInMs)} remotePostServeActionData = append(remotePostServeActionData, actionData) } } + return localPostServeActionsData, remotePostServeActionData }