diff --git a/core/handlers/v2/views.go b/core/handlers/v2/views.go index 5326021a3..c7feb994e 100644 --- a/core/handlers/v2/views.go +++ b/core/handlers/v2/views.go @@ -38,6 +38,7 @@ type ModeArgumentsView struct { MatchingStrategy *string `json:"matchingStrategy,omitempty"` Stateful bool `json:"stateful,omitempty"` OverwriteDuplicate bool `json:"overwriteDuplicate,omitempty"` + CaptureOnMiss bool `json:"captureOnMiss,omitempty"` } type IsWebServerView struct { diff --git a/core/hoverfly_funcs.go b/core/hoverfly_funcs.go index f8549b719..2cb36654a 100644 --- a/core/hoverfly_funcs.go +++ b/core/hoverfly_funcs.go @@ -402,6 +402,10 @@ func (hf *Hoverfly) Save(request *models.RequestDetails, response *models.Respon hf.Simulation.AddPair(&pair) } + if hf.Cfg.GetMode() == modes.Spy { + _, _ = hf.CacheMatcher.SaveRequestMatcherResponsePair(*request, &pair, nil) + } + return nil } diff --git a/core/hoverfly_service.go b/core/hoverfly_service.go index 6c2b12563..84285857d 100644 --- a/core/hoverfly_service.go +++ b/core/hoverfly_service.go @@ -108,6 +108,7 @@ func (hf *Hoverfly) SetModeWithArguments(modeView v2.ModeView) error { MatchingStrategy: matchingStrategy, Stateful: modeView.Arguments.Stateful, OverwriteDuplicate: modeView.Arguments.OverwriteDuplicate, + CaptureOnMiss: modeView.Arguments.CaptureOnMiss, } hf.modeMap[hf.Cfg.GetMode()].SetArguments(modeArguments) diff --git a/core/modes/modes.go b/core/modes/modes.go index 56226ea7a..36468cf84 100644 --- a/core/modes/modes.go +++ b/core/modes/modes.go @@ -45,6 +45,7 @@ type ModeArguments struct { MatchingStrategy *string Stateful bool OverwriteDuplicate bool + CaptureOnMiss bool } type ProcessResult struct { diff --git a/core/modes/spy_mode.go b/core/modes/spy_mode.go index 7894b6dea..18166fc06 100644 --- a/core/modes/spy_mode.go +++ b/core/modes/spy_mode.go @@ -5,6 +5,7 @@ import ( "github.com/SpectoLabs/hoverfly/core/errors" v2 "github.com/SpectoLabs/hoverfly/core/handlers/v2" + "github.com/SpectoLabs/hoverfly/core/util" log "github.com/sirupsen/logrus" @@ -15,27 +16,40 @@ type HoverflySpy interface { GetResponse(models.RequestDetails) (*models.ResponseDetails, *errors.HoverflyError) ApplyMiddleware(models.RequestResponsePair) (models.RequestResponsePair, error) DoRequest(*http.Request) (*http.Response, error) + Save(*models.RequestDetails, *models.ResponseDetails, *ModeArguments) error } type SpyMode struct { - Hoverfly HoverflySpy - MatchingStrategy string + Hoverfly HoverflySpy + Arguments ModeArguments } func (this *SpyMode) View() v2.ModeView { return v2.ModeView{ Mode: Spy, Arguments: v2.ModeArgumentsView{ - MatchingStrategy: &this.MatchingStrategy, + MatchingStrategy: this.Arguments.MatchingStrategy, + CaptureOnMiss: this.Arguments.CaptureOnMiss, + Stateful: this.Arguments.Stateful, + Headers: this.Arguments.Headers, + OverwriteDuplicate: this.Arguments.OverwriteDuplicate, }, } } func (this *SpyMode) SetArguments(arguments ModeArguments) { - if arguments.MatchingStrategy == nil { - this.MatchingStrategy = "strongest" + var matchingStrategy string + if arguments.MatchingStrategy == nil || *arguments.MatchingStrategy == "" { + matchingStrategy = "strongest" } else { - this.MatchingStrategy = *arguments.MatchingStrategy + matchingStrategy = *arguments.MatchingStrategy + } + this.Arguments = ModeArguments{ + MatchingStrategy: &matchingStrategy, + Headers: arguments.Headers, + Stateful: arguments.Stateful, + OverwriteDuplicate: arguments.OverwriteDuplicate, + CaptureOnMiss: arguments.CaptureOnMiss, } } @@ -55,6 +69,24 @@ func (this SpyMode) Process(request *http.Request, details models.RequestDetails } response, err := this.Hoverfly.DoRequest(modifiedRequest) if err == nil { + + if this.Arguments.CaptureOnMiss { + respBody, _ := util.GetResponseBody(response) + respHeaders := util.GetResponseHeaders(response) + + responseObj := &models.ResponseDetails{ + Status: response.StatusCode, + Body: respBody, + Headers: respHeaders, + } + if this.Arguments.Headers == nil { + this.Arguments.Headers = []string{} + } + err = this.Hoverfly.Save(&pair.Request, responseObj, &this.Arguments) + if err != nil { + return ReturnErrorAndLog(request, err, &pair, "There was an error when saving request and response", Spy) + } + } log.Info("Going to return response from real server") return newProcessResult(response, 0, nil), nil } else { @@ -69,13 +101,9 @@ func (this SpyMode) Process(request *http.Request, details models.RequestDetails return ReturnErrorAndLog(request, err, &pair, "There was an error when executing middleware", Spy) } - return newProcessResultWithPostServeActionInputDetails( + return newProcessResult( ReconstructResponse(request, pair), pair.Response.FixedDelay, pair.Response.LogNormalDelay, - &PostServeActionInputDetails{ - PostServeAction: pair.Response.PostServeAction, - Pair: &pair, - }, ), nil } diff --git a/core/modes/spy_mode_test.go b/core/modes/spy_mode_test.go index c3bf8c3d1..b43ff614f 100644 --- a/core/modes/spy_mode_test.go +++ b/core/modes/spy_mode_test.go @@ -47,6 +47,10 @@ func (this hoverflySpyStub) ApplyMiddleware(pair models.RequestResponsePair) (mo return pair, nil } +func (this hoverflySpyStub) Save(request *models.RequestDetails, response *models.ResponseDetails, arguments *modes.ModeArguments) error { + return nil +} + func Test_SpyMode_WhenGivenAMatchingRequestItReturnsTheCorrectResponse(t *testing.T) { RegisterTestingT(t) diff --git a/hoverctl/cmd/mode.go b/hoverctl/cmd/mode.go index ec47bf819..5795c184e 100644 --- a/hoverctl/cmd/mode.go +++ b/hoverctl/cmd/mode.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/SpectoLabs/hoverfly/core/handlers/v2" + v2 "github.com/SpectoLabs/hoverfly/core/handlers/v2" "github.com/SpectoLabs/hoverfly/core/modes" "github.com/SpectoLabs/hoverfly/hoverctl/wrapper" "github.com/spf13/cobra" @@ -15,6 +15,7 @@ var allHeaders bool var stateful bool var overwriteDuplicate bool var matchingStrategy string +var captureOnMiss bool var modeCmd = &cobra.Command{ Use: "mode [capture|diff|simulate|spy|modify|synthesize (optional)]", @@ -55,6 +56,13 @@ mode is shown. case modes.Diff: setHeaderArgument(modeView) break + case modes.Spy: + modeView.Arguments.MatchingStrategy = &matchingStrategy + modeView.Arguments.Stateful = stateful + modeView.Arguments.OverwriteDuplicate = overwriteDuplicate + modeView.Arguments.CaptureOnMiss = captureOnMiss + setHeaderArgument(modeView) + break } mode, err := wrapper.SetModeWithArguments(*target, modeView) @@ -100,6 +108,18 @@ func getExtraInfo(mode *v2.ModeView) string { } } break + case modes.Spy: + if mode.Arguments.CaptureOnMiss { + extraInfo = "and will capture on not finding the match" + } + if len(mode.Arguments.Headers) > 0 { + if len(mode.Arguments.Headers) == 1 && mode.Arguments.Headers[0] == "*" { + extraInfo = "and also will capture all request headers" + } else { + extraInfo = fmt.Sprintf("and also will capture the following request headers: %s", mode.Arguments.Headers) + } + } + break } return extraInfo @@ -111,11 +131,12 @@ func init() { modeCmd.PersistentFlags().StringVar(&specificHeaders, "headers", "", "A comma separated list of request headers to record (for capture mode) or response headers to ignore (for diff mode) `Content-Type,Authorization`") modeCmd.PersistentFlags().BoolVar(&allHeaders, "all-headers", false, - "Record all request headers (for capture mode) or ignore all response headers (for diff mode)") + "Record all request headers (for capture/spy mode) or ignore all response headers (for diff mode)") modeCmd.PersistentFlags().StringVar(&matchingStrategy, "matching-strategy", "strongest", "Sets the matching strategy - 'strongest | first'") modeCmd.PersistentFlags().BoolVar(&stateful, "stateful", false, "Record stateful responses as a sequence in capture mode") modeCmd.PersistentFlags().BoolVar(&overwriteDuplicate, "overwrite-duplicate", false, "Overwrite duplicate requests in capture mode") + modeCmd.PersistentFlags().BoolVar(&captureOnMiss, "capture-on-miss", false, "Capture the request on miss in spy mode") }