Skip to content

Commit

Permalink
Add option to matcher to ignore headers
Browse files Browse the repository at this point in the history
The new matcher, which is more strict now, is breaking tests where some
of the headers are skipped (like secrets) or change on each request (like tracing
spans). The cassete already supports the User-Agentt, and the Authorization has
also been added but not released yet.

In order to make it more generic and usable for people, I've added a more
generic implementation of these options, which will allow us to define which
headers we really want to ignore.

Signed-off-by: ivangonzalezacuna <[email protected]>
  • Loading branch information
ivangonzalezacuna authored and dnaeon committed Oct 10, 2024
1 parent 7f2a4c5 commit e544a7e
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 23 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ hook := func(i *cassette.Interaction) error {
opts := []recorder.Option{
recorder.WithCassette("fixtures/filters"),
recorder.WithHook(hook, recorder.AfterCaptureHook),
recorder.WithMatcher(cassette.NewDefaultMatcher(cassette.WithIgnoreAuthorization(true))),
recorder.WithMatcher(cassette.NewDefaultMatcher(cassette.WithIgnoreAuthorization())),
}

r, err := recorder.New(opts...)
Expand Down
38 changes: 20 additions & 18 deletions pkg/cassette/cassette.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,32 +202,39 @@ type MatcherFunc func(*http.Request, Request) bool
// defaultMatcher is the default matcher used to match HTTP requests with
// recorded interactions.
type defaultMatcher struct {
// If set to true, the default matcher will ignore matching on the
// User-Agent HTTP header.
ignoreUserAgent bool
// If set to true, the default matcher will ignore matching on the
// Authorization HTTP header.
ignoreAuthorization bool
// If set, the default matcher will ignore matching on any of the
// defined headers.
ignoreHeaders []string
}

// DefaultMatcherOption is a function which configures the default matcher.
type DefaultMatcherOption func(m *defaultMatcher)

// WithIgnoreUserAgent is a [DefaultMatcherOption], which configures the default
// matcher to ignore matching on the User-Agent HTTP header.
func WithIgnoreUserAgent(val bool) DefaultMatcherOption {
func WithIgnoreUserAgent() DefaultMatcherOption {
opt := func(m *defaultMatcher) {
m.ignoreUserAgent = val
m.ignoreHeaders = append(m.ignoreHeaders, "User-Agent")
}

return opt
}

// WithIgnoreAuthorization is a [DefaultMatcherOption], which configures the default
// matcher to ignore matching on the Authorization HTTP header.
func WithIgnoreAuthorization(val bool) DefaultMatcherOption {
func WithIgnoreAuthorization() DefaultMatcherOption {
opt := func(m *defaultMatcher) {
m.ignoreAuthorization = val
m.ignoreHeaders = append(m.ignoreHeaders, "Authorization")
}

return opt
}

// WithIgnoreHeaders is a [DefaultMatcherOption], which configures the default
// matcher to ignore matching on the defined HTTP headers.
func WithIgnoreHeaders(val ...string) DefaultMatcherOption {
opt := func(m *defaultMatcher) {
m.ignoreHeaders = append(m.ignoreHeaders, val...)
}

return opt
Expand Down Expand Up @@ -310,14 +317,9 @@ func (m *defaultMatcher) matcher(r *http.Request, i Request) bool {
requestHeader := r.Header.Clone()
cassetteRequestHeaders := i.Headers.Clone()

if m.ignoreUserAgent {
delete(requestHeader, "User-Agent")
delete(cassetteRequestHeaders, "User-Agent")
}

if m.ignoreAuthorization {
delete(requestHeader, "Authorization")
delete(cassetteRequestHeaders, "Authorization")
for _, header := range m.ignoreHeaders {
delete(requestHeader, header)
delete(cassetteRequestHeaders, header)
}

if !m.deepEqualContents(requestHeader, cassetteRequestHeaders) {
Expand Down
32 changes: 28 additions & 4 deletions pkg/cassette/cassette_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,8 @@ func TestMatcher(t *testing.T) {
})
})

t.Run("IgnoreUserAgent=true", func(t *testing.T) {
matcherFn := NewDefaultMatcher(WithIgnoreUserAgent(true))
t.Run("IgnoreUserAgent", func(t *testing.T) {
matcherFn := NewDefaultMatcher(WithIgnoreUserAgent())

t.Run("match", func(t *testing.T) {
r, i := getMatcherRequests(t)
Expand All @@ -251,8 +251,8 @@ func TestMatcher(t *testing.T) {
})
})

t.Run("IgnoreAuthorization=true", func(t *testing.T) {
matcherFn := NewDefaultMatcher(WithIgnoreAuthorization(true))
t.Run("IgnoreAuthorization", func(t *testing.T) {
matcherFn := NewDefaultMatcher(WithIgnoreAuthorization())

t.Run("match", func(t *testing.T) {
r, i := getMatcherRequests(t)
Expand All @@ -268,4 +268,28 @@ func TestMatcher(t *testing.T) {
}
})
})

t.Run("IgnoreHeaders", func(t *testing.T) {
matcherFn := NewDefaultMatcher(WithIgnoreHeaders("Header-One", "Header-Two"), WithIgnoreUserAgent(), WithIgnoreAuthorization())

t.Run("match", func(t *testing.T) {
r, i := getMatcherRequests(t)

r.Header = http.Header{
"Header-One": {"foo"},
"Header-Two": {"foo"},
"User-Agent": {"foo", "bar"},
}

i.Headers = http.Header{
"Header-One": {"bar"},
"Header-Two": {"bar"},
"Authorization": {"Bearer xyz"},
}

if b := matcherFn(r, i); !b {
t.Fatalf("request should have matched")
}
})
})
}

0 comments on commit e544a7e

Please sign in to comment.