From 3d1c95cb75f6f32fcdbe82a7b27996feb714919c Mon Sep 17 00:00:00 2001 From: Seb C Date: Sun, 28 Aug 2022 23:58:22 +0100 Subject: [PATCH] Change RequestMatchers to work like Mutators (#104) BREAKING CHANGE: Change RequestMatchers to work like Mutators --- README.md | 48 +++++++------- cassette/cassette.go | 10 +-- cassette/cassette_test.go | 6 +- cassette/cassette_wb_test.go | 2 +- cassette/track/http_test.go | 2 +- cassette/track/mutator.go | 3 +- cassette/track/mutator_test.go | 2 +- cassette/track/track.go | 2 +- cassette/track/track_test.go | 2 +- cmd/govcr/main.go | 4 +- concurrency_test.go | 4 +- controlpanel.go | 15 +++-- controlpanel_wb_test.go | 2 +- encryption/.study/rsa.go | 2 +- encryption/aesgcm.go | 2 +- encryption/aesgcm_test.go | 2 +- encryption/chacha20poly1305_test.go | 2 +- examples/Example1_test.go | 9 +-- examples/Example2_test.go | 2 +- examples/Example3_test.go | 6 +- examples/Example4_test.go | 8 +-- go.mod | 2 +- govcr.go | 10 +-- govcr_test.go | 19 +++--- govcr_wb_test.go | 20 +++--- matchers.go | 98 +++++++++-------------------- matchers_test.go | 4 +- pcb.go | 25 ++++---- pcb_wb_test.go | 70 ++++++++++----------- vcrsettings.go | 27 +++----- vcrtransport.go | 25 +++++--- 31 files changed, 197 insertions(+), 238 deletions(-) diff --git a/README.md b/README.md index 0c388b3..746ed2a 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,12 @@ govcr - + govcr - - govcr + + govcr

@@ -77,7 +77,7 @@ This project is an adaptation for Google's Go / Golang programming language. func TestExample1() { vcr := govcr.NewVCR( govcr.NewCassetteLoader("MyCassette1.json"), - govcr.WithRequestMatcher(govcr.NewMethodURLRequestMatcher()), // use a "relaxed" request matcher + govcr.WithRequestMatchers(govcr.NewMethodURLRequestMatchers()...), // use a "relaxed" request matcher ) vcr.Client.Get("http://example.com/foo") @@ -97,7 +97,7 @@ We use a "relaxed" request matcher because `example.com` injects an "`Age`" head ## Install ```bash -go get github.com/seborama/govcr/v12@latest +go get github.com/seborama/govcr/v13@latest ``` For all available releases, please check the [releases](https://github.com/seborama/govcr/releases) tab on github. @@ -105,7 +105,7 @@ For all available releases, please check the [releases](https://github.com/sebor And your source code would use this import: ```go -import "github.com/seborama/govcr/v12" +import "github.com/seborama/govcr/v13" ``` For versions of **govcr** before v5 (which don't use go.mod), use a dependency manager to lock the version you wish to use (perhaps v4)! @@ -135,7 +135,7 @@ go get gopkg.in/seborama/govcr.v4 **govcr** is a wrapper around the Go `http.Client`. It can record live HTTP traffic to files (called "**cassettes**") and later replay HTTP requests ("**tracks**") from them instead of live HTTP calls. -The code documentation can be found on [godoc](https://pkg.go.dev/github.com/seborama/govcr/v12). +The code documentation can be found on [godoc](https://pkg.go.dev/github.com/seborama/govcr/v13). When using **govcr**'s `http.Client`, the request is matched against the **tracks** on the '**cassette**': @@ -161,7 +161,7 @@ This structure contains parameters for configuring your **govcr** recorder. Settings are populated via `With*` options: - Use `WithClient` to provide a custom http.Client otherwise the default Go http.Client will be used. -- See `vcrsettings.go` for more options such as `WithRequestMatcher`, `WithTrackRecordingMutators`, `WithTrackReplayingMutators`, ... +- See `vcrsettings.go` for more options such as `WithRequestMatchers`, `WithTrackRecordingMutators`, `WithTrackReplayingMutators`, ... - TODO: `WithLogging` enables logging to help understand what **govcr** is doing internally. [(toc)](#table-of-content) @@ -178,7 +178,7 @@ This may be the case when the request contains a timestamp or a dynamically chan You can create your own matcher on any part of the request and in any manner (like ignoring or modifying some headers, etc). -The input parameters received by a `RequestMatcherFunc` are scoped to the `RequestMatcher`. It is safe to modify them as needed. This affects the other `RequestMatcherFunc`'s in the `RequestMatcher`. But it does **not** permeate to the original incoming HTTP request or the tracks read from or written to the cassette. +The input parameters received by a `RequestMatcher` are scoped to the `RequestMatchers`. This affects the other `RequestMatcher`'s. But it does **not** permeate throughout the VCR to the original incoming HTTP request or the tracks read from or written to the cassette. [(toc)](#table-of-content) @@ -437,7 +437,7 @@ vcr := govcr.NewVCR( The command is located in the `cmd/govcr` folder, to install it: ```bash -go install github.com/seborama/govcr/v12/cmd/govcr@latest +go install github.com/seborama/govcr/v13/cmd/govcr@latest ``` Example usage: @@ -473,20 +473,18 @@ This example shows how to handle situations where a header in the request needs This could be necessary because the header value is not predictable or changes for each request. ```go -vcr.SetRequestMatcher( - govcr.NewRequestMatcherCollection( - govcr.DefaultMethodMatcher, - govcr.DefaultURLMatcher, - func(httpRequest, trackRequest *track.Request) bool { - // we can safely mutate our inputs: - // mutations affect other RequestMatcherFunc's but _not_ the - // original HTTP request or the cassette Tracks. - httpRequest.Header.Del("X-Custom-Timestamp") - trackRequest.Header.Del("X-Custom-Timestamp") - - return govcr.DefaultHeaderMatcher(httpRequest, trackRequest) - }, - ), +vcr.SetRequestMatchers( + govcr.DefaultMethodMatcher, + govcr.DefaultURLMatcher, + func(httpRequest, trackRequest *track.Request) bool { + // we can safely mutate our inputs: + // mutations affect other RequestMatcher's but _not_ the + // original HTTP request or the cassette Tracks. + httpRequest.Header.Del("X-Custom-Timestamp") + trackRequest.Header.Del("X-Custom-Timestamp") + + return govcr.DefaultHeaderMatcher(httpRequest, trackRequest) + }, ) ``` @@ -508,7 +506,7 @@ How you specifically tackle this in practice really depends on how the API you a // See TestExample3 in tests for fully working example. vcr := govcr.NewVCR( govcr.NewCassetteLoader(exampleCassetteName3), - govcr.WithRequestMatcherFuncs( + govcr.WithRequestMatchers( func(httpRequest, trackRequest *track.Request) bool { // Remove the header from comparison. // Note: this removal is only scoped to the request matcher, it does not affect the original HTTP request diff --git a/cassette/cassette.go b/cassette/cassette.go index 7ddead2..d285a2b 100644 --- a/cassette/cassette.go +++ b/cassette/cassette.go @@ -14,11 +14,11 @@ import ( "github.com/google/uuid" "github.com/pkg/errors" - "github.com/seborama/govcr/v12/cassette/track" - "github.com/seborama/govcr/v12/compression" - cryptoerr "github.com/seborama/govcr/v12/encryption/errors" - govcrerr "github.com/seborama/govcr/v12/errors" - "github.com/seborama/govcr/v12/stats" + "github.com/seborama/govcr/v13/cassette/track" + "github.com/seborama/govcr/v13/compression" + cryptoerr "github.com/seborama/govcr/v13/encryption/errors" + govcrerr "github.com/seborama/govcr/v13/errors" + "github.com/seborama/govcr/v13/stats" ) // Cassette contains a set of tracks. diff --git a/cassette/cassette_test.go b/cassette/cassette_test.go index 05be3c6..ed57da8 100644 --- a/cassette/cassette_test.go +++ b/cassette/cassette_test.go @@ -8,9 +8,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/seborama/govcr/v12/cassette" - "github.com/seborama/govcr/v12/cassette/track" - "github.com/seborama/govcr/v12/encryption" + "github.com/seborama/govcr/v13/cassette" + "github.com/seborama/govcr/v13/cassette/track" + "github.com/seborama/govcr/v13/encryption" ) func Test_cassette_GzipFilter(t *testing.T) { diff --git a/cassette/cassette_wb_test.go b/cassette/cassette_wb_test.go index f0f9121..c4e48a4 100644 --- a/cassette/cassette_wb_test.go +++ b/cassette/cassette_wb_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/seborama/govcr/v12/stats" + "github.com/seborama/govcr/v13/stats" ) func Test_cassette_NumberOfTracks_PanicsWhenNoCassette(t *testing.T) { diff --git a/cassette/track/http_test.go b/cassette/track/http_test.go index 805cccb..f096622 100644 --- a/cassette/track/http_test.go +++ b/cassette/track/http_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/seborama/govcr/v12/cassette/track" + "github.com/seborama/govcr/v13/cassette/track" ) func TestRequest_Clone(t *testing.T) { diff --git a/cassette/track/mutator.go b/cassette/track/mutator.go index 5e6f92d..a6b37c6 100644 --- a/cassette/track/mutator.go +++ b/cassette/track/mutator.go @@ -5,8 +5,9 @@ import ( "regexp" ) +// Predicate is a function signature that takes a Track and returns a boolean. // It is used to construct conditional mutators. -type Predicate func(trk *Track) bool +type Predicate func(*Track) bool // Any accepts one or more predicates and returns a new predicate that will evaluate // to true when any the supplied predicate is true, otherwise false. diff --git a/cassette/track/mutator_test.go b/cassette/track/mutator_test.go index 3dad958..c5f7ff9 100644 --- a/cassette/track/mutator_test.go +++ b/cassette/track/mutator_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/seborama/govcr/v12/cassette/track" + "github.com/seborama/govcr/v13/cassette/track" ) func Test_Mutator_On(t *testing.T) { diff --git a/cassette/track/track.go b/cassette/track/track.go index 5fd7643..8c4a284 100644 --- a/cassette/track/track.go +++ b/cassette/track/track.go @@ -9,7 +9,7 @@ import ( "github.com/pkg/errors" - trkerr "github.com/seborama/govcr/v12/cassette/track/errors" + trkerr "github.com/seborama/govcr/v13/cassette/track/errors" ) // Track is a recording (HTTP Request + Response) in a cassette. diff --git a/cassette/track/track_test.go b/cassette/track/track_test.go index 5367ffd..b277175 100644 --- a/cassette/track/track_test.go +++ b/cassette/track/track_test.go @@ -13,7 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/seborama/govcr/v12/cassette/track" + "github.com/seborama/govcr/v13/cassette/track" ) func TestTrack_ToHTTPResponse(t *testing.T) { diff --git a/cmd/govcr/main.go b/cmd/govcr/main.go index 2028e85..c6e46b6 100644 --- a/cmd/govcr/main.go +++ b/cmd/govcr/main.go @@ -7,8 +7,8 @@ import ( "github.com/pkg/errors" - "github.com/seborama/govcr/v12/cassette" - "github.com/seborama/govcr/v12/encryption" + "github.com/seborama/govcr/v13/cassette" + "github.com/seborama/govcr/v13/encryption" ) func main() { diff --git a/concurrency_test.go b/concurrency_test.go index 06e2513..cd4e5cd 100644 --- a/concurrency_test.go +++ b/concurrency_test.go @@ -15,8 +15,8 @@ import ( "github.com/pkg/errors" "github.com/stretchr/testify/require" - "github.com/seborama/govcr/v12" - "github.com/seborama/govcr/v12/stats" + "github.com/seborama/govcr/v13" + "github.com/seborama/govcr/v13/stats" ) func TestConcurrencySafety(t *testing.T) { diff --git a/controlpanel.go b/controlpanel.go index 1fca0f5..1d9ad95 100644 --- a/controlpanel.go +++ b/controlpanel.go @@ -3,8 +3,8 @@ package govcr import ( "net/http" - "github.com/seborama/govcr/v12/cassette/track" - "github.com/seborama/govcr/v12/stats" + "github.com/seborama/govcr/v13/cassette/track" + "github.com/seborama/govcr/v13/stats" ) // ControlPanel holds the parts of a VCR that can be interacted with. @@ -18,9 +18,14 @@ func (controlPanel *ControlPanel) Stats() *stats.Stats { return controlPanel.vcrTransport().stats() } -// SetRequestMatcher sets a new RequestMatcher to the VCR. -func (controlPanel *ControlPanel) SetRequestMatcher(requestMatcher RequestMatcher) { - controlPanel.vcrTransport().SetRequestMatcher(requestMatcher) +// SetRequestMatchers sets a new set of RequestMatcher's to the VCR. +func (controlPanel *ControlPanel) SetRequestMatchers(requestMatcher ...RequestMatcher) { + controlPanel.vcrTransport().SetRequestMatchers(requestMatcher...) +} + +// AddRequestMatchers sets a new set of RequestMatcher's to the VCR. +func (controlPanel *ControlPanel) AddRequestMatchers(requestMatcher ...RequestMatcher) { + controlPanel.vcrTransport().AddRequestMatchers(requestMatcher...) } // SetReadOnlyMode sets the VCR to read-only mode (true) or to normal read-write (false). diff --git a/controlpanel_wb_test.go b/controlpanel_wb_test.go index 9b7e5d7..997081a 100644 --- a/controlpanel_wb_test.go +++ b/controlpanel_wb_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/seborama/govcr/v12/cassette/track" + "github.com/seborama/govcr/v13/cassette/track" ) func TestControlPanel_SetRecordingMutators(t *testing.T) { diff --git a/encryption/.study/rsa.go b/encryption/.study/rsa.go index 4f75769..1a4fc9b 100644 --- a/encryption/.study/rsa.go +++ b/encryption/.study/rsa.go @@ -11,7 +11,7 @@ import ( "github.com/pkg/errors" "golang.org/x/crypto/ssh" - cryptoerr "github.com/seborama/govcr/v12/encryption/errors" + cryptoerr "github.com/seborama/govcr/v13/encryption/errors" ) // nolint: deadcode diff --git a/encryption/aesgcm.go b/encryption/aesgcm.go index 4e13dbb..6a6965a 100644 --- a/encryption/aesgcm.go +++ b/encryption/aesgcm.go @@ -6,7 +6,7 @@ import ( "github.com/pkg/errors" - cryptoerr "github.com/seborama/govcr/v12/encryption/errors" + cryptoerr "github.com/seborama/govcr/v13/encryption/errors" ) // NewAESGCMWithRandomNonceGenerator creates a new Cryptor initialised with an diff --git a/encryption/aesgcm_test.go b/encryption/aesgcm_test.go index 13323e2..72258a6 100644 --- a/encryption/aesgcm_test.go +++ b/encryption/aesgcm_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/seborama/govcr/v12/encryption" + "github.com/seborama/govcr/v13/encryption" ) func TestCryptor_AESGCM(t *testing.T) { diff --git a/encryption/chacha20poly1305_test.go b/encryption/chacha20poly1305_test.go index 9313829..772ccb2 100644 --- a/encryption/chacha20poly1305_test.go +++ b/encryption/chacha20poly1305_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/seborama/govcr/v12/encryption" + "github.com/seborama/govcr/v13/encryption" ) func TestCryptor_ChaCha20Poly1305(t *testing.T) { diff --git a/examples/Example1_test.go b/examples/Example1_test.go index 94fd91c..488e04b 100644 --- a/examples/Example1_test.go +++ b/examples/Example1_test.go @@ -4,9 +4,10 @@ import ( "os" "testing" - "github.com/seborama/govcr/v12" - "github.com/seborama/govcr/v12/stats" "github.com/stretchr/testify/assert" + + "github.com/seborama/govcr/v13" + "github.com/seborama/govcr/v13/stats" ) const exampleCassetteName1 = "temp-fixtures/TestExample1.cassette.json" @@ -17,7 +18,7 @@ func TestExample1(t *testing.T) { vcr := govcr.NewVCR( govcr.NewCassetteLoader(exampleCassetteName1), - govcr.WithRequestMatcher(govcr.NewMethodURLRequestMatcher()), // use a "relaxed" request matcher + govcr.WithRequestMatchers(govcr.NewMethodURLRequestMatchers()...), // use a "relaxed" request matcher ) // The first request will be live and transparently recorded by govcr since the cassette is empty @@ -37,7 +38,7 @@ func TestExample1(t *testing.T) { // No live HTTP request is placed to the live server vcr = govcr.NewVCR( govcr.NewCassetteLoader(exampleCassetteName1), - govcr.WithRequestMatcher(govcr.NewMethodURLRequestMatcher()), // use a "relaxed" request matcher + govcr.WithRequestMatchers(govcr.NewMethodURLRequestMatchers()...), // use a "relaxed" request matcher ) vcr.HTTPClient().Get("http://example.com/foo") diff --git a/examples/Example2_test.go b/examples/Example2_test.go index 19ac912..c340279 100644 --- a/examples/Example2_test.go +++ b/examples/Example2_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/seborama/govcr/v12" + "github.com/seborama/govcr/v13" ) const exampleCassetteName2 = "temp-fixtures/TestExample2.cassette.json" diff --git a/examples/Example3_test.go b/examples/Example3_test.go index d026bb4..54fd786 100644 --- a/examples/Example3_test.go +++ b/examples/Example3_test.go @@ -7,8 +7,8 @@ import ( "testing" "github.com/google/uuid" - "github.com/seborama/govcr/v12" - "github.com/seborama/govcr/v12/cassette/track" + "github.com/seborama/govcr/v13" + "github.com/seborama/govcr/v13/cassette/track" "github.com/stretchr/testify/require" ) @@ -21,7 +21,7 @@ func TestExample3(t *testing.T) { // Instantiate VCR. vcr := govcr.NewVCR( govcr.NewCassetteLoader(exampleCassetteName3), - govcr.WithRequestMatcherFuncs( + govcr.WithRequestMatchers( func(httpRequest, trackRequest *track.Request) bool { // Remove the header from comparison. // Note: this removal is only scoped to the request matcher, it does not affect the original HTTP request diff --git a/examples/Example4_test.go b/examples/Example4_test.go index 127cbb6..f88e500 100644 --- a/examples/Example4_test.go +++ b/examples/Example4_test.go @@ -4,9 +4,9 @@ import ( "os" "testing" - "github.com/seborama/govcr/v12" - "github.com/seborama/govcr/v12/encryption" - "github.com/seborama/govcr/v12/stats" + "github.com/seborama/govcr/v13" + "github.com/seborama/govcr/v13/encryption" + "github.com/seborama/govcr/v13/stats" "github.com/stretchr/testify/assert" ) @@ -22,7 +22,7 @@ func TestExample4(t *testing.T) { WithCipher( encryption.NewChaCha20Poly1305WithRandomNonceGenerator, "test-fixtures/TestExample4.unsafe.key"), - govcr.WithRequestMatcher(govcr.NewMethodURLRequestMatcher()), // use a "relaxed" request matcher + govcr.WithRequestMatchers(govcr.NewMethodURLRequestMatchers()...), // use a "relaxed" request matcher ) // The first request will be live and transparently recorded by govcr since the cassette is empty diff --git a/go.mod b/go.mod index ddb08dd..f50bdd0 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/seborama/govcr/v12 +module github.com/seborama/govcr/v13 go 1.17 diff --git a/govcr.go b/govcr.go index fbd9037..99774dd 100644 --- a/govcr.go +++ b/govcr.go @@ -7,8 +7,8 @@ import ( "github.com/pkg/errors" - "github.com/seborama/govcr/v12/cassette" - "github.com/seborama/govcr/v12/encryption" + "github.com/seborama/govcr/v13/cassette" + "github.com/seborama/govcr/v13/encryption" ) // CrypterProvider is the signature of a cipher provider function with default nonce generator. @@ -106,15 +106,15 @@ func NewVCR(cassetteLoader *CassetteLoader, settings ...Setting) *ControlPanel { } // use a default RequestMatcher if none provided - if vcrSettings.requestMatcher == nil { - vcrSettings.requestMatcher = NewStrictRequestMatcher() + if vcrSettings.requestMatchers == nil { + vcrSettings.requestMatchers = NewStrictRequestMatchers() } // create VCR's HTTP client vcrClient := &http.Client{ Transport: &vcrTransport{ pcb: &PrintedCircuitBoard{ - requestMatcher: vcrSettings.requestMatcher, + requestMatchers: vcrSettings.requestMatchers, trackRecordingMutators: vcrSettings.trackRecordingMutators, trackReplayingMutators: vcrSettings.trackReplayingMutators, httpMode: vcrSettings.httpMode, diff --git a/govcr_test.go b/govcr_test.go index 6bca17c..6122273 100644 --- a/govcr_test.go +++ b/govcr_test.go @@ -16,9 +16,10 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "github.com/seborama/govcr/v12" - "github.com/seborama/govcr/v12/encryption" - "github.com/seborama/govcr/v12/stats" + "github.com/seborama/govcr/v13" + "github.com/seborama/govcr/v13/cassette/track" + "github.com/seborama/govcr/v13/encryption" + "github.com/seborama/govcr/v13/stats" ) func TestNewVCR(t *testing.T) { @@ -231,7 +232,7 @@ func (ts *GoVCRTestSuite) TestVCR_LiveOnlyMode() { // 1st execution of set of calls vcr := ts.newVCR(k7Name, actionDeleteCassette) vcr.SetLiveOnlyMode() - vcr.SetRequestMatcher(govcr.NewRequestMatcherCollection()) // ensure always matching + vcr.SetRequestMatchers(alwaysMatchRequest) // ensure always matching ts.makeHTTPCalls_WithSuccess(vcr.HTTPClient(), 0) expectedStats := &stats.Stats{ @@ -246,7 +247,7 @@ func (ts *GoVCRTestSuite) TestVCR_LiveOnlyMode() { // 2nd execution of set of calls vcr = ts.newVCR(k7Name, actionKeepCassette) vcr.SetLiveOnlyMode() - vcr.SetRequestMatcher(govcr.NewRequestMatcherCollection()) // ensure always matching + vcr.SetRequestMatchers(alwaysMatchRequest) // ensure always matching ts.makeHTTPCalls_WithSuccess(vcr.HTTPClient(), 2) // as we're making live requests, the sever keeps on increasing the counter expectedStats = &stats.Stats{ @@ -263,8 +264,8 @@ func (ts *GoVCRTestSuite) TestVCR_OfflineMode() { // 1st execution of set of calls - populate cassette vcr := ts.newVCR(k7Name, actionDeleteCassette) - vcr.SetRequestMatcher(govcr.NewRequestMatcherCollection()) // ensure always matching - vcr.SetNormalMode() // get data in the cassette + vcr.SetRequestMatchers(alwaysMatchRequest) // ensure always matching + vcr.SetNormalMode() // get data in the cassette ts.makeHTTPCalls_WithSuccess(vcr.HTTPClient(), 0) expectedStats := &stats.Stats{ @@ -436,3 +437,7 @@ func (ts *GoVCRTestSuite) makeHTTPCalls_WithSuccess(httpClient *http.Client, ser ts.NotNil(resp.TLS) } } + +func alwaysMatchRequest(httpRequest, trackRequest *track.Request) bool { + return true +} diff --git a/govcr_wb_test.go b/govcr_wb_test.go index 33da94e..a73405b 100644 --- a/govcr_wb_test.go +++ b/govcr_wb_test.go @@ -12,8 +12,8 @@ import ( "github.com/stretchr/testify/suite" - "github.com/seborama/govcr/v12/cassette/track" - "github.com/seborama/govcr/v12/stats" + "github.com/seborama/govcr/v13/cassette/track" + "github.com/seborama/govcr/v13/stats" ) type GoVCRWBTestSuite struct { @@ -77,8 +77,8 @@ func (ts *GoVCRWBTestSuite) TestRoundTrip_RequestMatcherDoesNotMutateState() { requestMatcherCount := 0 - reqMatcher := func(outcome bool) *RequestMatcherCollection { - return NewRequestMatcherCollection( + reqMatchers := func(outcome bool) RequestMatchers { + return RequestMatchers{ // attempt to mutate state func(httpRequest, trackRequest *track.Request) bool { requestMatcherCount++ @@ -96,13 +96,13 @@ func (ts *GoVCRWBTestSuite) TestRoundTrip_RequestMatcherDoesNotMutateState() { return outcome }, - ) + } } // 1st call - live vcr := ts.newVCR(cassetteName, actionDeleteCassette) - vcr.SetLiveOnlyMode() // ensure we record one track so we can have a request matcher execution later (no track on cassette = no request matching) - vcr.SetRequestMatcher(reqMatcher(false)) // false: we want to attempt but not satisfy request matching so to check if the live request was altered + vcr.SetLiveOnlyMode() // ensure we record one track so we can have a request matcher execution later (no track on cassette = no request matching) + vcr.SetRequestMatchers(reqMatchers(false)...) // false: we want to attempt but not satisfy request matching so to check if the live request was altered req, err := http.NewRequest(http.MethodGet, ts.testServer.URL, nil) ts.Require().NoError(err) @@ -134,8 +134,8 @@ func (ts *GoVCRWBTestSuite) TestRoundTrip_RequestMatcherDoesNotMutateState() { // 2nd call - live vcr = ts.newVCR(cassetteName, actionKeepCassette) - vcr.SetNormalMode() // ensure we attempt request matching - vcr.SetRequestMatcher(reqMatcher(false)) // false: we want to attempt but not satisfy request matching so to check if the live request was altered + vcr.SetNormalMode() // ensure we attempt request matching + vcr.SetRequestMatchers(reqMatchers(false)...) // false: we want to attempt but not satisfy request matching so to check if the live request was altered req, err = http.NewRequest(http.MethodGet, ts.testServer.URL, nil) ts.Require().NoError(err) @@ -168,7 +168,7 @@ func (ts *GoVCRWBTestSuite) TestRoundTrip_RequestMatcherDoesNotMutateState() { vcr.SetOfflineMode() requestMatcherCount = 0 - vcr.SetRequestMatcher(reqMatcher(true)) // true: xssatisfy request matching and force replay from track to ensure no mutation + vcr.SetRequestMatchers(reqMatchers(true)...) // true: satisfy request matching and force replay from track to ensure no mutation req, err = http.NewRequest(http.MethodGet, ts.testServer.URL, nil) ts.Require().NoError(err) diff --git a/matchers.go b/matchers.go index 45f37bf..519dd28 100644 --- a/matchers.go +++ b/matchers.go @@ -4,102 +4,64 @@ import ( "net/http" "net/url" - "github.com/seborama/govcr/v12/cassette/track" + "github.com/seborama/govcr/v13/cassette/track" ) -// RequestMatcherFunc is a function that performs request comparison. -type RequestMatcherFunc func(httpRequest, trackRequest *track.Request) bool +// RequestMatcher is a function that performs request comparison. +// request comparison involves the HTTP request and the track request recorded on cassette. +type RequestMatcher func(httpRequest, trackRequest *track.Request) bool -// HeaderMatcher is a function that performs header comparison. -type HeaderMatcher func(httpHeaders, trackHeaders http.Header) bool +// RequestMatchers is a collection of RequestMatcher's. +type RequestMatchers []RequestMatcher -// MethodMatcher is a function that performs method name comparison. -type MethodMatcher func(httpMethod, trackMethod string) bool - -// URLMatcher is a function that performs URL comparison. -type URLMatcher func(httpURL, trackURL *url.URL) bool - -// BodyMatcher is a function that performs body comparison. -type BodyMatcher func(httpBody, trackBody []byte) bool - -// TrailerMatcher is a function that performs trailer comparison. -type TrailerMatcher func(httpTrailers, trackTrailers http.Header) bool - -// RequestMatcherCollection is an implementation of RequestMatcher which combines -// multiple RequestMatcherFunc's with a logical 'AND'. -type RequestMatcherCollection struct { - matchers []RequestMatcherFunc +// Add a set of RequestMatcher's to this RequestMatchers collection. +func (rm RequestMatchers) Add(reqMatchers ...RequestMatcher) RequestMatchers { + return append(rm, reqMatchers...) } -// Match is the default implementation of RequestMatcher. -func (rm *RequestMatcherCollection) Match(httpRequest, trackRequest *track.Request) bool { - for _, matcher := range rm.matchers { +// Match returns true if all RequestMatcher's in RequestMatchers return true, thereby indicating that +// the trackRequest matches the httpRequest. +// When no matchers are supplied, Match returns false. +func (rm RequestMatchers) Match(httpRequest, trackRequest *track.Request) bool { + for _, matcher := range rm { if !matcher(httpRequest, trackRequest) { return false } } - return true -} - -// NewRequestMatcherCollection creates a new RequestMatcherCollection. -// If no requestMatcherFuncs are supplied, it will always match any and all requests -// to a cassette track. -// You should pass specific RequestMatcherFunc to customise its behaviour. -// -// Alternately to manually creating a RequestMatcherCollection, you can also use one -// of the predefined matchers such as those provided by NewStrictRequestMatcher() -// or NewMethodURLRequestMatcher(). -func NewRequestMatcherCollection(requestMatcherFuncs ...RequestMatcherFunc) *RequestMatcherCollection { - return &RequestMatcherCollection{ - matchers: requestMatcherFuncs, - } + return len(rm) != 0 } -// NewStrictRequestMatcher creates a new default implementation of RequestMatcher. -func NewStrictRequestMatcher() *RequestMatcherCollection { - drm := RequestMatcherCollection{ - matchers: []RequestMatcherFunc{ - DefaultHeaderMatcher, - DefaultMethodMatcher, - DefaultURLMatcher, - DefaultBodyMatcher, - DefaultTrailerMatcher, - }, +// NewStrictRequestMatchers creates a new default sets of RequestMatcher's. +func NewStrictRequestMatchers() RequestMatchers { + return RequestMatchers{ + DefaultHeaderMatcher, + DefaultMethodMatcher, + DefaultURLMatcher, + DefaultBodyMatcher, + DefaultTrailerMatcher, } - - return &drm } -// NewMethodURLRequestMatcher creates a new implementation of RequestMatcher based on Method and URL. -func NewMethodURLRequestMatcher() *RequestMatcherCollection { - drm := RequestMatcherCollection{ - matchers: []RequestMatcherFunc{ - DefaultMethodMatcher, - DefaultURLMatcher, - }, +// NewMethodURLRequestMatchers creates a new default set of RequestMatcher's based on Method and URL. +func NewMethodURLRequestMatchers() RequestMatchers { + return RequestMatchers{ + DefaultMethodMatcher, + DefaultURLMatcher, } - - return &drm } // DefaultHeaderMatcher is the default implementation of HeaderMatcher. -// Because this function is meant to be called from RequestMatcher.Match(), -// it doesn't check for either argument to be nil. Match() takes care of it. func DefaultHeaderMatcher(httpRequest, trackRequest *track.Request) bool { return areHTTPHeadersEqual(httpRequest.Header, trackRequest.Header) } // DefaultMethodMatcher is the default implementation of MethodMatcher. -// Because this function is meant to be called from RequestMatcherCollection.Match(), -// it doesn't check for either argument to be nil. Match() takes care of it. func DefaultMethodMatcher(httpRequest, trackRequest *track.Request) bool { return httpRequest.Method == trackRequest.Method } // DefaultURLMatcher is the default implementation of URLMatcher. -// Because this function is meant to be called from RequestMatcherCollection.Match(), -// it doesn't check for either argument to be nil. Match() takes care of it. // nolint: gocyclo,gocognit func DefaultURLMatcher(httpRequest, trackRequest *track.Request) bool { httpURL := httpRequest.URL @@ -124,15 +86,11 @@ func DefaultURLMatcher(httpRequest, trackRequest *track.Request) bool { } // DefaultBodyMatcher is the default implementation of BodyMatcher. -// Because this function is meant to be called from RequestMatcherCollection.Match(), -// it doesn't check for either argument to be nil. Match() takes care of it. func DefaultBodyMatcher(httpRequest, trackRequest *track.Request) bool { return string(httpRequest.Body) == string(trackRequest.Body) } // DefaultTrailerMatcher is the default implementation of TrailerMatcher. -// Because this function is meant to be called from RequestMatcherCollection.Match(), -// it doesn't check for either argument to be nil. Match() takes care of it. func DefaultTrailerMatcher(httpRequest, trackRequest *track.Request) bool { return areHTTPHeadersEqual(httpRequest.Trailer, trackRequest.Trailer) } diff --git a/matchers_test.go b/matchers_test.go index b0da374..cfd1cd4 100644 --- a/matchers_test.go +++ b/matchers_test.go @@ -7,8 +7,8 @@ import ( "github.com/stretchr/testify/assert" - "github.com/seborama/govcr/v12" - "github.com/seborama/govcr/v12/cassette/track" + "github.com/seborama/govcr/v13" + "github.com/seborama/govcr/v13/cassette/track" ) func Test_DefaultHeaderMatcher(t *testing.T) { diff --git a/pcb.go b/pcb.go index c5da6cb..c0750be 100644 --- a/pcb.go +++ b/pcb.go @@ -3,8 +3,8 @@ package govcr import ( "net/http" - "github.com/seborama/govcr/v12/cassette" - "github.com/seborama/govcr/v12/cassette/track" + "github.com/seborama/govcr/v13/cassette" + "github.com/seborama/govcr/v13/cassette/track" ) // HTTPMode defines govcr's mode for HTTP requests. @@ -25,7 +25,7 @@ const ( // PrintedCircuitBoard is a structure that holds some facilities that are passed to // the VCR machine to influence its internal behaviour. type PrintedCircuitBoard struct { - requestMatcher RequestMatcher + requestMatchers RequestMatchers // These mutators are applied before saving a track to a cassette. trackRecordingMutators track.Mutators @@ -67,7 +67,7 @@ func (pcb *PrintedCircuitBoard) trackMatches(k7 *cassette.Cassette, trackNumber httpRequestClone := httpRequest.Clone() trackReqClone := trk.Request.Clone() - return !trk.IsReplayed() && pcb.requestMatcher.Match(httpRequestClone, trackReqClone) + return !trk.IsReplayed() && pcb.requestMatchers.Match(httpRequestClone, trackReqClone) } func (pcb *PrintedCircuitBoard) replayTrack(k7 *cassette.Cassette, trackNumber int32, httpRequest *track.Request) (*track.Track, error) { @@ -95,9 +95,14 @@ func (pcb *PrintedCircuitBoard) mutateTrackReplaying(t *track.Track) { pcb.trackReplayingMutators.Mutate(t) } -// SetRequestMatcher sets a new RequestMatcher to the VCR. -func (pcb *PrintedCircuitBoard) SetRequestMatcher(requestMatcher RequestMatcher) { - pcb.requestMatcher = requestMatcher +// SetRequestMatchers sets a collection of RequestMatcher's. +func (pcb *PrintedCircuitBoard) SetRequestMatchers(requestMatchers ...RequestMatcher) { + pcb.requestMatchers = requestMatchers +} + +// AddRequestMatchers adds a collection of RequestMatcher's. +func (pcb *PrintedCircuitBoard) AddRequestMatchers(requestMatchers ...RequestMatcher) { + pcb.requestMatchers = pcb.requestMatchers.Add(requestMatchers...) } // SetReadOnlyMode sets the VCR to read-only mode (true) or to normal read-write (false). @@ -152,9 +157,3 @@ func (pcb *PrintedCircuitBoard) SetReplayingMutators(trackMutators ...track.Muta func (pcb *PrintedCircuitBoard) ClearReplayingMutators() { pcb.trackReplayingMutators = nil } - -// RequestMatcher is an interface that exposes the method to perform request comparison. -// request comparison involves the HTTP request and the track request recorded on cassette. -type RequestMatcher interface { - Match(httpRequest, trackRequest *track.Request) bool -} diff --git a/pcb_wb_test.go b/pcb_wb_test.go index 78484aa..b327b97 100644 --- a/pcb_wb_test.go +++ b/pcb_wb_test.go @@ -11,54 +11,52 @@ import ( "github.com/stretchr/testify/require" - "github.com/seborama/govcr/v12/cassette" - "github.com/seborama/govcr/v12/cassette/track" + "github.com/seborama/govcr/v13/cassette" + "github.com/seborama/govcr/v13/cassette/track" ) func TestPrintedCircuitBoard_trackMatches(t *testing.T) { // This test is in addition toTestRoundTrip_RequestMatcherDoesNotMutateState pcb := &PrintedCircuitBoard{} - pcb.SetRequestMatcher( - NewRequestMatcherCollection( - func(httpRequest, trackRequest *track.Request) bool { - for k := range httpRequest.Header { - httpRequest.Header.Set(k, "nil") - } - for i := range httpRequest.Body { - httpRequest.Body[i] = 'X' - } - httpRequest.ContentLength = -1 - for k := range httpRequest.MultipartForm.File { - for k2 := range httpRequest.MultipartForm.File[k] { - httpRequest.MultipartForm.File[k][k2].Filename = "nil" - for k3 := range httpRequest.MultipartForm.File[k][k2].Header { - httpRequest.MultipartForm.File[k][k2].Header.Set(k3, "nil") - } + pcb.SetRequestMatchers( + func(httpRequest, trackRequest *track.Request) bool { + for k := range httpRequest.Header { + httpRequest.Header.Set(k, "nil") + } + for i := range httpRequest.Body { + httpRequest.Body[i] = 'X' + } + httpRequest.ContentLength = -1 + for k := range httpRequest.MultipartForm.File { + for k2 := range httpRequest.MultipartForm.File[k] { + httpRequest.MultipartForm.File[k][k2].Filename = "nil" + for k3 := range httpRequest.MultipartForm.File[k][k2].Header { + httpRequest.MultipartForm.File[k][k2].Header.Set(k3, "nil") } } + } - for k := range trackRequest.Header { - trackRequest.Header.Set(k, "nil") - } - for i := range trackRequest.Body { - trackRequest.Body[i] = 'X' - } - trackRequest.ContentLength = -1 - for k := range trackRequest.MultipartForm.File { - for k2 := range trackRequest.MultipartForm.File[k] { - trackRequest.MultipartForm.File[k][k2].Filename = "nil" - for k3 := range trackRequest.MultipartForm.File[k][k2].Header { - trackRequest.MultipartForm.File[k][k2].Header.Set(k3, "nil") - } + for k := range trackRequest.Header { + trackRequest.Header.Set(k, "nil") + } + for i := range trackRequest.Body { + trackRequest.Body[i] = 'X' + } + trackRequest.ContentLength = -1 + for k := range trackRequest.MultipartForm.File { + for k2 := range trackRequest.MultipartForm.File[k] { + trackRequest.MultipartForm.File[k][k2].Filename = "nil" + for k3 := range trackRequest.MultipartForm.File[k][k2].Header { + trackRequest.MultipartForm.File[k][k2].Header.Set(k3, "nil") } } + } - httpRequest = nil - trackRequest = nil + httpRequest = nil + trackRequest = nil - return true - }, - ), + return true + }, ) k7 := &cassette.Cassette{ diff --git a/vcrsettings.go b/vcrsettings.go index 299b318..402983b 100644 --- a/vcrsettings.go +++ b/vcrsettings.go @@ -3,8 +3,8 @@ package govcr import ( "net/http" - "github.com/seborama/govcr/v12/cassette" - "github.com/seborama/govcr/v12/cassette/track" + "github.com/seborama/govcr/v13/cassette" + "github.com/seborama/govcr/v13/cassette/track" ) // Setting defines an optional functional parameter as received by NewVCR(). @@ -18,23 +18,12 @@ func WithClient(httpClient *http.Client) Setting { } } -// WithRequestMatcher is an optional functional parameter to provide a VCR with -// a RequestMatcher applied when matching an HTTP/S request to an existing track -// on a cassette. -func WithRequestMatcher(matcher RequestMatcher) Setting { +// WithRequestMatchers is an optional functional parameter to provide a VCR with a +// set of RequestMatcher's applied when matching an HTTP/S request to an existing +// track on a cassette. +func WithRequestMatchers(reqMatchers ...RequestMatcher) Setting { return func(vcrSettings *VCRSettings) { - vcrSettings.requestMatcher = matcher - } -} - -// WithRequestMatcherFuncs is syntactic sugar for -// WithRequestMatcher(NewRequestMatcherCollection(...)). -// It allows to add a RequestMatcher straight from a collection of RequestMatcherFunc. -// -// See also: WithRequestMatcher and NewRequestMatcherCollection. -func WithRequestMatcherFuncs(matcherFuncs ...RequestMatcherFunc) Setting { - return func(vcrSettings *VCRSettings) { - vcrSettings.requestMatcher = NewRequestMatcherCollection(matcherFuncs...) + vcrSettings.requestMatchers = vcrSettings.requestMatchers.Add(reqMatchers...) } } @@ -87,7 +76,7 @@ func WithOfflineMode() Setting { type VCRSettings struct { client *http.Client cassette *cassette.Cassette - requestMatcher RequestMatcher + requestMatchers RequestMatchers trackRecordingMutators track.Mutators trackReplayingMutators track.Mutators httpMode HTTPMode diff --git a/vcrtransport.go b/vcrtransport.go index 5477846..0afc7c3 100644 --- a/vcrtransport.go +++ b/vcrtransport.go @@ -5,11 +5,11 @@ import ( "github.com/pkg/errors" - "github.com/seborama/govcr/v12/cassette" - "github.com/seborama/govcr/v12/cassette/track" - "github.com/seborama/govcr/v12/encryption" - govcrerr "github.com/seborama/govcr/v12/errors" - "github.com/seborama/govcr/v12/stats" + "github.com/seborama/govcr/v13/cassette" + "github.com/seborama/govcr/v13/cassette/track" + "github.com/seborama/govcr/v13/encryption" + govcrerr "github.com/seborama/govcr/v13/errors" + "github.com/seborama/govcr/v13/stats" ) // vcrTransport is the heart of VCR. It implements @@ -71,9 +71,14 @@ func (t *vcrTransport) NumberOfTracks() int32 { return t.cassette.NumberOfTracks() } -// SetRequestMatcher sets a new RequestMatcher to the VCR. -func (t *vcrTransport) SetRequestMatcher(requestMatcher RequestMatcher) { - t.pcb.SetRequestMatcher(requestMatcher) +// SetRequestMatchers sets a new collection of RequestMatcher's to the VCR. +func (t *vcrTransport) SetRequestMatchers(reqMatchers ...RequestMatcher) { + t.pcb.SetRequestMatchers(reqMatchers...) +} + +// AddRequestMatchers sets a new collection of RequestMatcher's to the VCR. +func (t *vcrTransport) AddRequestMatchers(reqMatchers ...RequestMatcher) { + t.pcb.AddRequestMatchers(reqMatchers...) } // SetReadOnlyMode sets the VCR to read-only mode (true) or to normal read-write (false). @@ -123,8 +128,8 @@ func (t *vcrTransport) SetCipherCustomNonce(crypter CrypterNonceProvider, keyFil } // AddRecordingMutators adds a set of recording Track Mutator's to the VCR. -func (t *vcrTransport) AddRecordingMutators(mutators ...track.Mutator) { - t.pcb.AddRecordingMutators(mutators...) +func (t *vcrTransport) AddRecordingMutators(trackMutators ...track.Mutator) { + t.pcb.AddRecordingMutators(trackMutators...) } // SetRecordingMutators replaces the set of recording Track Mutator's in the VCR.