Skip to content

Commit

Permalink
feat: HaveHTTPHeaderWithValue() matcher (#463)
Browse files Browse the repository at this point in the history
Co-authored-by: Onsi Fakhouri <onsijoe@gmail.com>
  • Loading branch information
blgm and onsi authored Aug 19, 2021
1 parent 504e1f2 commit dd83a96
Show file tree
Hide file tree
Showing 3 changed files with 253 additions and 0 deletions.
11 changes: 11 additions & 0 deletions matchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,17 @@ func HaveHTTPStatus(expected interface{}) types.GomegaMatcher {
return &matchers.HaveHTTPStatusMatcher{Expected: expected}
}

// HaveHTTPHeaderWithValue succeeds if the header is found and the value matches.
// Actual must be either a *http.Response or *httptest.ResponseRecorder.
// Expected must be a string header name, followed by a header value which
// can be a string, or another matcher.
func HaveHTTPHeaderWithValue(header string, value interface{}) types.GomegaMatcher {
return &matchers.HaveHTTPHeaderWithValueMatcher{
Header: header,
Value: value,
}
}

// HaveHTTPBody matches if the body matches.
// Actual must be either a *http.Response or *httptest.ResponseRecorder.
// Expected must be either a string, []byte, or other matcher
Expand Down
81 changes: 81 additions & 0 deletions matchers/have_http_header_with_value_matcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package matchers

import (
"fmt"
"net/http"
"net/http/httptest"

"github.com/onsi/gomega/format"
"github.com/onsi/gomega/types"
)

type HaveHTTPHeaderWithValueMatcher struct {
Header string
Value interface{}
}

func (matcher *HaveHTTPHeaderWithValueMatcher) Match(actual interface{}) (success bool, err error) {
headerValue, err := matcher.extractHeader(actual)
if err != nil {
return false, err
}

headerMatcher, err := matcher.getSubMatcher()
if err != nil {
return false, err
}

return headerMatcher.Match(headerValue)
}

func (matcher *HaveHTTPHeaderWithValueMatcher) FailureMessage(actual interface{}) string {
headerValue, err := matcher.extractHeader(actual)
if err != nil {
panic(err) // protected by Match()
}

headerMatcher, err := matcher.getSubMatcher()
if err != nil {
panic(err) // protected by Match()
}

diff := format.IndentString(headerMatcher.FailureMessage(headerValue), 1)
return fmt.Sprintf("HTTP header %q:\n%s", matcher.Header, diff)
}

func (matcher *HaveHTTPHeaderWithValueMatcher) NegatedFailureMessage(actual interface{}) (message string) {
headerValue, err := matcher.extractHeader(actual)
if err != nil {
panic(err) // protected by Match()
}

headerMatcher, err := matcher.getSubMatcher()
if err != nil {
panic(err) // protected by Match()
}

diff := format.IndentString(headerMatcher.NegatedFailureMessage(headerValue), 1)
return fmt.Sprintf("HTTP header %q:\n%s", matcher.Header, diff)
}

func (matcher *HaveHTTPHeaderWithValueMatcher) getSubMatcher() (types.GomegaMatcher, error) {
switch m := matcher.Value.(type) {
case string:
return &EqualMatcher{Expected: matcher.Value}, nil
case types.GomegaMatcher:
return m, nil
default:
return nil, fmt.Errorf("HaveHTTPHeaderWithValue matcher must be passed a string or a GomegaMatcher. Got:\n%s", format.Object(matcher.Value, 1))
}
}

func (matcher *HaveHTTPHeaderWithValueMatcher) extractHeader(actual interface{}) (string, error) {
switch r := actual.(type) {
case *http.Response:
return r.Header.Get(matcher.Header), nil
case *httptest.ResponseRecorder:
return r.Result().Header.Get(matcher.Header), nil
default:
return "", fmt.Errorf("HaveHTTPHeaderWithValue matcher expects *http.Response or *httptest.ResponseRecorder. Got:\n%s", format.Object(actual, 1))
}
}
161 changes: 161 additions & 0 deletions matchers/have_http_header_with_value_matcher_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package matchers_test

import (
"net/http"
"net/http/httptest"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

var _ = Describe("HaveHTTPHeader", func() {
It("can match an HTTP header", func() {
resp := &http.Response{}
resp.Header = make(http.Header)
resp.Header.Add("fake-header", "fake value")
Expect(resp).To(HaveHTTPHeaderWithValue("fake-header", "fake value"))
})

It("can mismatch an HTTP header", func() {
resp := &http.Response{}
resp.Header = make(http.Header)
resp.Header.Add("fake-header", "fake value")
Expect(resp).NotTo(HaveHTTPHeaderWithValue("other-header", "fake value"))
Expect(resp).NotTo(HaveHTTPHeaderWithValue("fake-header", "other value"))
})

When("the header is set more than once", func() {
It("matches the first value and not the second", func() {
resp := &http.Response{}
resp.Header = make(http.Header)
resp.Header.Add("fake-header", "fake value1")
resp.Header.Add("fake-header", "fake value2")
Expect(resp).To(HaveHTTPHeaderWithValue("fake-header", "fake value1"))
Expect(resp).NotTo(HaveHTTPHeaderWithValue("fake-header", "fake value2"))
})
})

When("ACTUAL is *httptest.ResponseRecorder", func() {
It("can match an HTTP header", func() {
resp := &httptest.ResponseRecorder{}
resp.Header().Add("fake-header", "fake value")
Expect(resp).To(HaveHTTPHeaderWithValue("fake-header", "fake value"))
})

It("can mismatch an HTTP header", func() {
resp := &httptest.ResponseRecorder{}
resp.Header().Add("fake-header", "fake value")
Expect(resp).NotTo(HaveHTTPHeaderWithValue("other-header", "fake value"))
Expect(resp).NotTo(HaveHTTPHeaderWithValue("fake-header", "other value"))
})
})

When("ACTUAL is neither *http.Response nor *httptest.ResponseRecorder", func() {
It("errors", func() {
failures := InterceptGomegaFailures(func() {
Expect("foo").To(HaveHTTPHeaderWithValue("bar", "baz"))
})
Expect(failures).To(HaveLen(1))
Expect(failures[0]).To(Equal("HaveHTTPHeaderWithValue matcher expects *http.Response or *httptest.ResponseRecorder. Got:\n <string>: foo"))
})
})

When("EXPECTED VALUE is a matcher", func() {
It("can match an HTTP header", func() {
resp := &http.Response{}
resp.Header = make(http.Header)
resp.Header.Add("fake-header", "fake value")
Expect(resp).To(HaveHTTPHeaderWithValue("fake-header", ContainSubstring("value")))
})

It("can mismatch an HTTP header", func() {
resp := &http.Response{}
resp.Header = make(http.Header)
resp.Header.Add("fake-header", "fake value")
Expect(resp).NotTo(HaveHTTPHeaderWithValue("fake-header", ContainSubstring("foo")))
})
})

When("EXPECTED VALUE is something else", func() {
It("errors", func() {
failures := InterceptGomegaFailures(func() {
resp := &http.Response{}
Expect(resp).To(HaveHTTPHeaderWithValue("bar", 42))
})
Expect(failures).To(HaveLen(1))
Expect(failures[0]).To(Equal("HaveHTTPHeaderWithValue matcher must be passed a string or a GomegaMatcher. Got:\n <int>: 42"))
})
})

Describe("FailureMessage", func() {
When("matching a string", func() {
It("returns message", func() {
failures := InterceptGomegaFailures(func() {
resp := &http.Response{}
resp.Header = make(http.Header)
resp.Header.Add("fake-header", "fake value")
Expect(resp).To(HaveHTTPHeaderWithValue("fake-header", "other value"))
})
Expect(failures).To(HaveLen(1))
Expect(failures[0]).To(Equal(`HTTP header "fake-header":
Expected
<string>: fake value
to equal
<string>: other value`), failures[0])
})
})

When("matching a matcher", func() {
It("returns message", func() {
failures := InterceptGomegaFailures(func() {
resp := &http.Response{}
resp.Header = make(http.Header)
resp.Header.Add("fake-header", "fake value")
Expect(resp).To(HaveHTTPHeaderWithValue("fake-header", ContainSubstring("other")))
})
Expect(failures).To(HaveLen(1))
Expect(failures[0]).To(Equal(`HTTP header "fake-header":
Expected
<string>: fake value
to contain substring
<string>: other`), failures[0])
})
})
})

Describe("NegatedFailureMessage", func() {
When("matching a string", func() {
It("returns message", func() {
failures := InterceptGomegaFailures(func() {
resp := &http.Response{}
resp.Header = make(http.Header)
resp.Header.Add("fake-header", "fake value")
Expect(resp).NotTo(HaveHTTPHeaderWithValue("fake-header", "fake value"))
})
Expect(failures).To(HaveLen(1))
Expect(failures[0]).To(Equal(`HTTP header "fake-header":
Expected
<string>: fake value
not to equal
<string>: fake value`), failures[0])
})
})

When("matching a matcher", func() {
It("returns message", func() {
failures := InterceptGomegaFailures(func() {
resp := &http.Response{}
resp.Header = make(http.Header)
resp.Header.Add("fake-header", "fake value")
Expect(resp).NotTo(HaveHTTPHeaderWithValue("fake-header", ContainSubstring("value")))
})
Expect(failures).To(HaveLen(1))
Expect(failures[0]).To(Equal(`HTTP header "fake-header":
Expected
<string>: fake value
not to contain substring
<string>: value`), failures[0])
})
})
})
})

0 comments on commit dd83a96

Please sign in to comment.