From 8114b07302a1d897d7421366736828ba4f557720 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Sun, 1 Jan 2023 01:32:08 +0000 Subject: [PATCH] fix(proxy): empty response body should skip writing a response file. --- proxy/recorder.go | 41 +++++++++++----- proxy/recorder_test.go | 105 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 13 deletions(-) create mode 100644 proxy/recorder_test.go diff --git a/proxy/recorder.go b/proxy/recorder.go index 202de39..9bda24b 100644 --- a/proxy/recorder.go +++ b/proxy/recorder.go @@ -60,11 +60,11 @@ func StartRecorder(upstream string, dir string, options RecorderOptions) (chan H var responseFilePrefix string requestHash := getRequestHash(exchange.Request) if stringutil.Contains(requestHashes, requestHash) { - responseFilePrefix = uuid.New().String() + "-" if options.IgnoreDuplicateRequests { logger.Debugf("skipping recording of duplicate request %s %v", exchange.Request.Method, exchange.Request.URL) continue } + responseFilePrefix = uuid.New().String() + "-" } else { responseFilePrefix = "" } @@ -106,7 +106,14 @@ func formatUpstreamHostPort(upstream string) (string, error) { } } -func record(upstreamHost string, dir string, responseHashes *map[string]string, prefix string, exchange HttpExchange, options RecorderOptions) (resource *impostermodel.Resource, err error) { +func record( + upstreamHost string, + dir string, + responseHashes *map[string]string, + prefix string, + exchange HttpExchange, + options RecorderOptions, +) (resource *impostermodel.Resource, err error) { respFile, err := getResponseFile(upstreamHost, dir, options, exchange, responseHashes, prefix) if err != nil { return nil, err @@ -118,8 +125,9 @@ func record(upstreamHost string, dir string, responseHashes *map[string]string, return &r, nil } -// getResponseFile checks the map for the hash of the response body to see if it has already been -// written. If not, a new file is written and its hash stored in the map. +// getResponseFile checks if there is a response body. If not, an empty string is returned. +// If a body is not empty, the file hashes are checked for the hash of the response body to +// see if it has already been written. If not, a new file is written and its hash stored in the map. func getResponseFile( upstreamHost string, dir string, @@ -130,6 +138,10 @@ func getResponseFile( ) (string, error) { req := exchange.Request respBody := *exchange.ResponseBody + if len(respBody) == 0 { + logger.Debugf("empty response body for %s %v", req.Method, req.URL) + return "", nil + } bodyHash := stringutil.Sha1hash(respBody) if existing := (*fileHashes)[bodyHash]; existing != "" { @@ -152,17 +164,20 @@ func getResponseFile( func buildResource(dir string, options RecorderOptions, exchange HttpExchange, respFile string) (impostermodel.Resource, error) { req := *exchange.Request - relResponseFile, err := filepath.Rel(dir, respFile) - if err != nil { - return impostermodel.Resource{}, fmt.Errorf("failed to get relative path for response file: %s: %v", respFile, err) + response := &impostermodel.ResponseConfig{ + StatusCode: exchange.StatusCode, + } + if len(respFile) > 0 { + relResponseFile, err := filepath.Rel(dir, respFile) + if err != nil { + return impostermodel.Resource{}, fmt.Errorf("failed to get relative path for response file: %s: %v", respFile, err) + } + response.StaticFile = relResponseFile } resource := impostermodel.Resource{ - Path: req.URL.Path, - Method: req.Method, - Response: &impostermodel.ResponseConfig{ - StatusCode: exchange.StatusCode, - StaticFile: relResponseFile, - }, + Path: req.URL.Path, + Method: req.Method, + Response: response, } if len(req.URL.Query()) > 0 { queryParams := make(map[string]string) diff --git a/proxy/recorder_test.go b/proxy/recorder_test.go new file mode 100644 index 0000000..523366e --- /dev/null +++ b/proxy/recorder_test.go @@ -0,0 +1,105 @@ +package proxy + +import ( + "fmt" + "net/http" + "net/url" + "os" + "path" + "testing" +) + +func Test_getResponseFile(t *testing.T) { + outputDir, err := os.MkdirTemp(os.TempDir(), "imposter-cli") + if err != nil { + panic(err) + } + rootUrl, _ := url.Parse("https://example.com") + + responseBody := []byte("test") + bodyHash := "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3" + + type args struct { + upstreamHost string + dir string + options RecorderOptions + exchange HttpExchange + fileHashes *map[string]string + prefix string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "empty response body", + args: args{ + upstreamHost: "example.com", + dir: outputDir, + options: RecorderOptions{FlatResponseFileStructure: false}, + exchange: HttpExchange{ + Request: &http.Request{Method: "GET", URL: rootUrl}, + ResponseBody: &[]byte{}, + }, + fileHashes: buildMap(outputDir, []string{}), + }, + want: "", + wantErr: false, + }, + { + name: "existing response body", + args: args{ + upstreamHost: "example.com", + dir: outputDir, + options: RecorderOptions{FlatResponseFileStructure: false}, + exchange: HttpExchange{ + Request: &http.Request{Method: "GET", URL: rootUrl}, + ResponseBody: &responseBody, + ResponseHeaders: &http.Header{}, + }, + fileHashes: buildMap(outputDir, []string{bodyHash}), + }, + want: path.Join(outputDir, "existing-file-0.txt"), + wantErr: false, + }, + { + name: "new response body", + args: args{ + upstreamHost: "example.com", + dir: outputDir, + options: RecorderOptions{FlatResponseFileStructure: false}, + exchange: HttpExchange{ + Request: &http.Request{Method: "GET", URL: rootUrl}, + ResponseBody: &responseBody, + ResponseHeaders: &http.Header{}, + }, + fileHashes: buildMap(outputDir, []string{}), + }, + want: path.Join(outputDir, "GET-index.txt"), + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getResponseFile(tt.args.upstreamHost, tt.args.dir, tt.args.options, tt.args.exchange, tt.args.fileHashes, tt.args.prefix) + if (err != nil) != tt.wantErr { + t.Errorf("getResponseFile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("getResponseFile() got = %v, want %v", got, tt.want) + } + }) + } +} + +func buildMap(dir string, hashes []string) *map[string]string { + m := make(map[string]string) + for i, hash := range hashes { + filename := fmt.Sprintf("existing-file-%d.txt", i) + m[hash] = path.Join(dir, filename) + } + return &m +}