Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions core/cmd/hoverfly/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
hvc "github.com/SpectoLabs/hoverfly/core/certs"
cs "github.com/SpectoLabs/hoverfly/core/cors"
"github.com/SpectoLabs/hoverfly/core/handlers"
v2 "github.com/SpectoLabs/hoverfly/core/handlers/v2"
"github.com/SpectoLabs/hoverfly/core/matching"
mw "github.com/SpectoLabs/hoverfly/core/middleware"
"github.com/SpectoLabs/hoverfly/core/modes"
Expand Down Expand Up @@ -77,6 +78,7 @@ var (
synthesize = flag.Bool("synthesize", false, "Start Hoverfly in synthesize mode (middleware is required)")
modify = flag.Bool("modify", false, "Start Hoverfly in modify mode - applies middleware (required) to both outgoing and incoming HTTP traffic")
spy = flag.Bool("spy", false, "Start Hoverfly in spy mode, similar to simulate but calls real server when cache miss")
captureOnMiss = flag.Bool("capture-on-miss", false, "Capture requests that don't match a simulation when in spy mode")
diff = flag.Bool("diff", false, "Start Hoverfly in diff mode - calls real server and compares the actual response with the expected simulation config if present")
middleware = flag.String("middleware", "", "Set middleware by passing the name of the binary and the path of the middleware script separated by space. (i.e. '-middleware \"python script.py\"')")
proxyPort = flag.String("pp", "", "Proxy port - run proxy on another port (i.e. '-pp 9999' to run proxy on port 9999)")
Expand Down Expand Up @@ -678,6 +680,19 @@ func main() {
hoverfly.CacheMatcher.PreloadCache(hoverfly.Simulation)
}

if *captureOnMiss && !*spy {
log.Fatal("-capture-on-miss can only be used with -spy mode")
}

if *spy && *captureOnMiss {
if err := hoverfly.SetModeWithArguments(v2.ModeView{
Mode: modes.Spy,
Arguments: v2.ModeArgumentsView{CaptureOnMiss: true},
}); err != nil {
log.WithError(err).Fatal("Failed to set spy mode with captureOnMiss")
}
}

// start metrics registry flush
if *metrics {
hoverfly.Counter.Init()
Expand Down
16 changes: 16 additions & 0 deletions core/hoverfly_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,22 @@ func Test_Hoverfly_SetModeWithArguments_OverwriteDuplicate(t *testing.T) {
Expect(storedMode.Arguments.OverwriteDuplicate).To(BeTrue())
}

func Test_Hoverfly_SetModeWithArguments_SpyCaptureOnMiss(t *testing.T) {
RegisterTestingT(t)

unit := NewHoverflyWithConfiguration(&Configuration{})

Expect(unit.SetModeWithArguments(v2.ModeView{
Mode: "spy",
Arguments: v2.ModeArgumentsView{
CaptureOnMiss: true,
},
})).To(Succeed())

storedMode := unit.modeMap[modes.Spy].View()
Expect(storedMode.Arguments.CaptureOnMiss).To(BeTrue())
}

func Test_Hoverfly_AddDiff_AddEntry(t *testing.T) {
RegisterTestingT(t)

Expand Down
79 changes: 70 additions & 9 deletions core/modes/spy_mode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ import (
. "github.com/onsi/gomega"
)

type hoverflySpyStub struct{}
type hoverflySpyStub struct {
savedRequest *models.RequestDetails
savedResponse *models.ResponseDetails
savedArguments *modes.ModeArguments
}

// DoRequest - Stub implementation of modes.HoverflySpy interface
func (this hoverflySpyStub) DoRequest(request *http.Request) (*http.Response, *time.Duration, error) {
func (this *hoverflySpyStub) DoRequest(request *http.Request) (*http.Response, *time.Duration, error) {
response := &http.Response{}
if request.Host == "error.com" {
return nil, nil, fmt.Errorf("Could not reach error.com")
Expand All @@ -30,7 +34,7 @@ func (this hoverflySpyStub) DoRequest(request *http.Request) (*http.Response, *t
return response, &duration, nil
}

func (this hoverflySpyStub) GetResponse(requestDetails models.RequestDetails) (*models.ResponseDetails, *errors.HoverflyError) {
func (this *hoverflySpyStub) GetResponse(requestDetails models.RequestDetails) (*models.ResponseDetails, *errors.HoverflyError) {
if requestDetails.Destination == "positive-match.com" {
return &models.ResponseDetails{
Status: 200,
Expand All @@ -42,22 +46,25 @@ func (this hoverflySpyStub) GetResponse(requestDetails models.RequestDetails) (*
}
}

func (this hoverflySpyStub) ApplyMiddleware(pair models.RequestResponsePair) (models.RequestResponsePair, error) {
func (this *hoverflySpyStub) ApplyMiddleware(pair models.RequestResponsePair) (models.RequestResponsePair, error) {
if pair.Request.Path == "middleware-error" {
return pair, fmt.Errorf("middleware-error")
}
return pair, nil
}

func (this hoverflySpyStub) Save(request *models.RequestDetails, response *models.ResponseDetails, arguments *modes.ModeArguments) error {
func (this *hoverflySpyStub) Save(request *models.RequestDetails, response *models.ResponseDetails, arguments *modes.ModeArguments) error {
this.savedRequest = request
this.savedResponse = response
this.savedArguments = arguments
return nil
}

func Test_SpyMode_WhenGivenAMatchingRequestItReturnsTheCorrectResponse(t *testing.T) {
RegisterTestingT(t)

unit := &modes.SpyMode{
Hoverfly: hoverflySpyStub{},
Hoverfly: &hoverflySpyStub{},
}

request := models.RequestDetails{
Expand All @@ -74,7 +81,7 @@ func Test_SpyMode_WhenGivenANonMatchingRequestItWillMakeTheRequestAndReturnIt(t
RegisterTestingT(t)

unit := &modes.SpyMode{
Hoverfly: hoverflySpyStub{},
Hoverfly: &hoverflySpyStub{},
}

requestDetails := models.RequestDetails{
Expand All @@ -100,7 +107,7 @@ func Test_SpyMode_WhenGivenAMatchingRequesAndMiddlewareFaislItReturnsAnError(t *
RegisterTestingT(t)

unit := &modes.SpyMode{
Hoverfly: hoverflySpyStub{},
Hoverfly: &hoverflySpyStub{},
}

request := models.RequestDetails{
Expand All @@ -124,7 +131,7 @@ func Test_SpyMode_ShouldReturnErrorOnRemoteServiceCall(t *testing.T) {
RegisterTestingT(t)

unit := &modes.SpyMode{
Hoverfly: hoverflySpyStub{},
Hoverfly: &hoverflySpyStub{},
}

requestDetails := models.RequestDetails{
Expand All @@ -147,3 +154,57 @@ func Test_SpyMode_ShouldReturnErrorOnRemoteServiceCall(t *testing.T) {
Expect(string(responseBody)).To(ContainSubstring("Could not reach error.com"))

}

func Test_SpyMode_OnCacheMiss_WhenCaptureOnMissEnabled_SavesRequestAndResponse(t *testing.T) {
RegisterTestingT(t)

stub := &hoverflySpyStub{}
unit := &modes.SpyMode{
Hoverfly: stub,
Arguments: modes.ModeArguments{CaptureOnMiss: true},
}

requestDetails := models.RequestDetails{
Scheme: "http",
Destination: "negative-match.com",
}

request, err := http.NewRequest("GET", "http://negative-match.com", nil)
Expect(err).To(BeNil())

result, err := unit.Process(request, requestDetails)
Expect(err).To(BeNil())
Expect(result.Response.StatusCode).To(Equal(200))

Expect(stub.savedRequest).ToNot(BeNil())
Expect(stub.savedResponse).ToNot(BeNil())
Expect(stub.savedResponse.Status).To(Equal(200))
Expect(stub.savedResponse.Body).To(Equal("test"))
Expect(stub.savedArguments).ToNot(BeNil())
Expect(stub.savedArguments.CaptureOnMiss).To(BeTrue())
}

func Test_SpyMode_OnCacheMiss_WhenCaptureOnMissDisabled_DoesNotSave(t *testing.T) {
RegisterTestingT(t)

stub := &hoverflySpyStub{}
unit := &modes.SpyMode{
Hoverfly: stub,
Arguments: modes.ModeArguments{CaptureOnMiss: false},
}

requestDetails := models.RequestDetails{
Scheme: "http",
Destination: "negative-match.com",
}

request, err := http.NewRequest("GET", "http://negative-match.com", nil)
Expect(err).To(BeNil())

result, err := unit.Process(request, requestDetails)
Expect(err).To(BeNil())
Expect(result.Response.StatusCode).To(Equal(200))

Expect(stub.savedRequest).To(BeNil())
Expect(stub.savedResponse).To(BeNil())
}
61 changes: 61 additions & 0 deletions functional-tests/core/ft_modes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os"
"strings"

"github.com/SpectoLabs/hoverfly/core/handlers/v2"
"github.com/SpectoLabs/hoverfly/functional-tests"
"github.com/dghubble/sling"
. "github.com/onsi/ginkgo"
Expand Down Expand Up @@ -127,6 +128,66 @@ var _ = Describe("Running Hoverfly in various modes", func() {
Expect(string(body)).To(Equal("Simulated"))
})
})

Context("With capture-on-miss enabled", func() {

var fakeServer *httptest.Server

BeforeEach(func() {
hoverfly.Start()

fakeServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte("Real response"))
}))

hoverfly.SetModeWithArgs("spy", v2.ModeArgumentsView{CaptureOnMiss: true})
hoverfly.ImportSimulation(`{
"data": {
"pairs": [
{
"request": {
"headers": {
"X-API-TEST": [ { "value": "test", "matcher": "exact" } ]
}
},
"response": {
"status": 200,
"body": "Simulated"
}
}
]
},
"meta": { "schemaVersion": "v5" }
}`)
})

AfterEach(func() {
fakeServer.Close()
})

It("Should save the request/response pair when there is a cache miss", func() {
resp := hoverfly.Proxy(sling.New().Get(fakeServer.URL))
Expect(resp.StatusCode).To(Equal(http.StatusOK))
body, err := ioutil.ReadAll(resp.Body)
Expect(err).To(BeNil())
Expect(string(body)).To(Equal("Real response"))

simulation := hoverfly.ExportSimulation()
Expect(simulation.RequestResponsePairs).To(HaveLen(2))
})

It("Should not save the request/response pair when there is a cache hit", func() {
resp := hoverfly.Proxy(sling.New().Get(fakeServer.URL).Set("X-API-TEST", "test"))
Expect(resp.StatusCode).To(Equal(http.StatusOK))
body, err := ioutil.ReadAll(resp.Body)
Expect(err).To(BeNil())
Expect(string(body)).To(Equal("Simulated"))

simulation := hoverfly.ExportSimulation()
Expect(simulation.RequestResponsePairs).To(HaveLen(1))
})
})
})

Context("When running in synthesise mode", func() {
Expand Down
Loading