diff --git a/README.md b/README.md index 07753aa..d947cd5 100644 --- a/README.md +++ b/README.md @@ -81,13 +81,14 @@ In some cases, attributes in a JSON response can by dynamic (e.g unique id's, da ```json { "defaults": { + "Etag": "default-etag-value", "updated_at": 0, "foo": "foobar" } } ``` -When used with `AssertHTTPResponse`, for any response with `Content-Type: application/json`, the key-value pairs in `defaults` will be used to override the JSON response, allowing for consistent snapshot testing. +When used with `AssertHTTPResponse`, for any response with `Content-Type: application/json`, the key-value pairs in `defaults` will be used to override the JSON response, allowing for consistent snapshot testing. Any HTTP headers will also be override for key matches in `defaults`. ## Using custom `__snapshot__` directory diff --git a/abide.go b/abide.go index bdf40fd..009c69c 100644 --- a/abide.go +++ b/abide.go @@ -99,7 +99,7 @@ func (s snapshots) save() error { return err } - err = ioutil.WriteFile(path, data, os.ModePerm) + err = ioutil.WriteFile(path, data, 0666) if err != nil { return err } diff --git a/assert.go b/assert.go index 4132935..ab3c637 100644 --- a/assert.go +++ b/assert.go @@ -27,23 +27,63 @@ func Assert(t *testing.T, id string, a Assertable) { // AssertHTTPResponse asserts the value of an http.Response. func AssertHTTPResponse(t *testing.T, id string, w *http.Response) { - config, err := getConfig() + body, err := httputil.DumpResponse(w, true) if err != nil { t.Fatal(err) } - body, err := httputil.DumpResponse(w, true) + assertHTTP(t, id, body, contentTypeIsJSON(w.Header.Get("Content-Type"))) +} + +// AssertHTTPRequestOut asserts the value of an http.Request. +// Intended for use when testing outgoing client requests +// See https://golang.org/pkg/net/http/httputil/#DumpRequestOut for more +func AssertHTTPRequestOut(t *testing.T, id string, r *http.Request) { + body, err := httputil.DumpRequestOut(r, true) + if err != nil { + t.Fatal(err) + } + + assertHTTP(t, id, body, contentTypeIsJSON(r.Header.Get("Content-Type"))) +} + +// AssertHTTPRequest asserts the value of an http.Request. +// Intended for use when testing incoming client requests +// See https://golang.org/pkg/net/http/httputil/#DumpRequest for more +func AssertHTTPRequest(t *testing.T, id string, r *http.Request) { + body, err := httputil.DumpRequest(r, true) + if err != nil { + t.Fatal(err) + } + + assertHTTP(t, id, body, contentTypeIsJSON(r.Header.Get("Content-Type"))) +} + +func assertHTTP(t *testing.T, id string, body []byte, isJSON bool) { + config, err := getConfig() if err != nil { t.Fatal(err) } data := string(body) + lines := strings.Split(strings.TrimSpace(data), "\n") - contentType := w.Header.Get("Content-Type") + if config != nil { + // empty line identifies the end of the HTTP header + for i, line := range lines { + if line == "" { + break + } + + headerItem := strings.Split(line, ":") + if def, ok := config.Defaults[headerItem[0]]; ok { + lines[i] = fmt.Sprintf("%s: %s", headerItem[0], def) + } + } + } // If the response body is JSON, indent. - if contentTypeIsJSON(contentType) { - lines := strings.Split(strings.TrimSpace(data), "\n") + if isJSON { jsonStr := lines[len(lines)-1] var jsonIface map[string]interface{} @@ -64,9 +104,9 @@ func AssertHTTPResponse(t *testing.T, id string, w *http.Response) { t.Fatal(err) } lines[len(lines)-1] = string(out) - data = strings.Join(lines, "\n") } + data = strings.Join(lines, "\n") createOrUpdateSnapshot(t, id, data) } diff --git a/example/__snapshots__/example.snapshot b/example/__snapshots__/example.snapshot index 3106d80..cd96eaa 100755 --- a/example/__snapshots__/example.snapshot +++ b/example/__snapshots__/example.snapshot @@ -8,11 +8,36 @@ string to be asserted HTTP/1.1 200 OK Connection: close Content-Type: application/json +Etag: default-etag-value { "foo": "foobar" } +/* snapshot: http client request */ +GET / HTTP/1.1 +Host: example.com +User-Agent: Go-http-client/1.1 +Content-Length: 31 +Content-Type: application/json +X-Expected-Header: expected header value +Accept-Encoding: gzip + +{ + "message": "expected message" +} + +/* snapshot: http server request */ +POST / HTTP/1.1 +Accept-Encoding: gzip +Content-Length: 31 +Content-Type: application/json +User-Agent: Go-http-client/1.1 + +{ + "message": "expected message" +} + /* snapshot: reader */ Hello World. @@ -20,6 +45,7 @@ Hello World. HTTP/1.1 200 OK Connection: close Content-Type: application/json +Etag: default-etag-value { "post": { diff --git a/example/abide.json b/example/abide.json index 4c7fa09..4b428ff 100644 --- a/example/abide.json +++ b/example/abide.json @@ -1,5 +1,6 @@ { "defaults": { + "Etag": "default-etag-value", "updated_at": 0, "foo": "foobar" } diff --git a/example/main.go b/example/main.go index c579635..c3f76eb 100644 --- a/example/main.go +++ b/example/main.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "net/http" + "strconv" "time" "github.com/beme/abide/example/models" @@ -20,6 +21,7 @@ func firstHandler(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/json") + w.Header().Set("Etag", strconv.FormatInt(time.Now().UnixNano(), 10)) w.Write(body) } @@ -47,6 +49,7 @@ func secondHandler(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/json") + w.Header().Set("Etag", strconv.FormatInt(time.Now().UnixNano(), 10)) w.Write(body) } diff --git a/example/main_test.go b/example/main_test.go index f747cda..6430df6 100644 --- a/example/main_test.go +++ b/example/main_test.go @@ -1,8 +1,10 @@ package main import ( + "net/http" "net/http/httptest" "os" + "strings" "testing" "github.com/beme/abide" @@ -42,6 +44,23 @@ func TestReader(t *testing.T) { abide.AssertReader(t, "reader", res.Body) } +func TestAssertHTTPRequestOut(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "http://example.com", strings.NewReader(`{"message": "expected message"}`)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Expected-Header", "expected header value") + + abide.AssertHTTPRequestOut(t, "http client request", req) +} + +func TestAssertHTTPRequest(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + r.Host = "" // httptest servers are spawned on random ports, prevent that from being in the snapshot. + abide.AssertHTTPRequest(t, "http server request", r) + })) + + http.Post(server.URL, "application/json", strings.NewReader(`{"message": "expected message"}`)) +} + func TestAssertableString(t *testing.T) { abide.Assert(t, "assertable string", abide.String("string to be asserted")) } @@ -59,5 +78,4 @@ func TestAssertableInterface(t *testing.T) { true, "string4", } - abide.Assert(t, "assertable interface", abide.Interface(myStruct)) -} + abide.Assert(t, "assertable interface", abide.Interface(myStruct)) \ No newline at end of file