Skip to content

Commit

Permalink
add read-only, offline, live-only modes + ResponseDeleteTLS mutator (#56
Browse files Browse the repository at this point in the history
)

* add read-only, offline and live-only modes and ResponseDeleteTLS mutator

* updated README
  • Loading branch information
seborama authored Aug 2, 2022
1 parent d3a488e commit a964e76
Show file tree
Hide file tree
Showing 11 changed files with 279 additions and 50 deletions.
42 changes: 0 additions & 42 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1480,48 +1480,6 @@ linters-settings:
- example
- main

thelper:
test:
# Check *testing.T is first param (or after context.Context) of helper function.
# Default: true
first: false
# Check *testing.T param has name t.
# Default: true
name: false
# Check t.Helper() begins helper function.
# Default: true
begin: false
benchmark:
# Check *testing.B is first param (or after context.Context) of helper function.
# Default: true
first: false
# Check *testing.B param has name b.
# Default: true
name: false
# Check b.Helper() begins helper function.
# Default: true
begin: false
tb:
# Check *testing.TB is first param (or after context.Context) of helper function.
# Default: true
first: false
# Check *testing.TB param has name tb.
# Default: true
name: false
# Check tb.Helper() begins helper function.
# Default: true
begin: false
fuzz:
# Check *testing.F is first param (or after context.Context) of helper function.
# Default: true
first: false
# Check *testing.F param has name f.
# Default: true
name: false
# Check f.Helper() begins helper function.
# Default: true
begin: false

unparam:
# Inspect exported functions.
#
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ deps:

test: deps
# to run a single test inside a stretchr suite (e.g.):
# go test -run ^TestHandlerTestSuite$ -testify.m TestRoundTrip_ReplaysResponse -v ./...
# go test -run ^TestGoVCRTestSuite$ -testify.m TestRoundTrip_ReplaysResponse -v ./...
go test -timeout 20s -cover ./...

# note: -race significantly degrades performance hence a high "timeout" value and reduced parallelism
Expand Down
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,69 @@ func TestExample2() {
}
```

### Recipe: Remove Response TLS from cassette or from track playback

Use the provided mutator `track.ResponseDeleteTLS`.

Remove Response.TLS from the cassette recording:

```go
vcr := govcr.NewVCR(
govcr.WithCassette(exampleCassetteName2),
govcr.WithTrackRecordingMutators(track.ResponseDeleteTLS),
)
```

Remove Response.TLS from the track at playback time:

```go
vcr := govcr.NewVCR(
govcr.WithCassette(exampleCassetteName2),
govcr.WithTrackReplayingMutators(track.ResponseDeleteTLS),
)
```

### Recipe: Change the playback mode of the VCR

**govcr** support operation modes:

- Live only: never replay from the cassette.
- Read only: normal behaviour except that recording to cassette is disabled.
- Offline: playback from cassette only, return a transport error if no track matches.

#### Live only

```go
vcr := govcr.NewVCR(
govcr.WithCassette(exampleCassetteName2),
govcr.WithLiveOnlyMode(),
)
// or equally:
vcr.SetLiveOnlyMode(true) // `false` to disable option
```

#### Read only

```go
vcr := govcr.NewVCR(
govcr.WithCassette(exampleCassetteName2),
govcr.WithReadOnlyMode(),
)
// or equally:
vcr.SetReadOnlyMode(true) // `false` to disable option
```

#### Offline

```go
vcr := govcr.NewVCR(
govcr.WithCassette(exampleCassetteName2),
govcr.WithOfflineMode(),
)
// or equally:
vcr.SetOfflineMode(true) // `false` to disable option
```

### Recipe: VCR with a RequestFilter

**TODO: THIS EXAMPLE FOR v4 NOT v5**
Expand Down
9 changes: 9 additions & 0 deletions cassette/track/mutator.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,15 @@ func ResponseChangeBody(fn func(b []byte) []byte) Mutator {
}
}

// ResponseDeleteTLS removes TLS data from the response.
func ResponseDeleteTLS(key, value string) Mutator {
return func(trk *Track) {
if trk != nil {
trk.Response.TLS = nil
}
}
}

// Mutators is a collection of Track Mutator's.
type Mutators []Mutator

Expand Down
15 changes: 15 additions & 0 deletions controlpanel.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,21 @@ func (controlPanel *ControlPanel) SetRequestMatcher(requestMatcher RequestMatche
controlPanel.vcrTransport().SetRequestMatcher(requestMatcher)
}

// SetReadOnlyMode sets the VCR to read-only mode (true) or to normal read-write (false).
func (controlPanel *ControlPanel) SetReadOnlyMode(state bool) {
controlPanel.vcrTransport().SetReadOnlyMode(state)
}

// SetOfflineMode sets the VCR to offline mode (true) or to normal live/replay (false).
func (controlPanel *ControlPanel) SetOfflineMode(state bool) {
controlPanel.vcrTransport().SetOfflineMode(state)
}

// SetLiveOnlyMode sets the VCR to live-only mode (true) or to normal live/replay (false).
func (controlPanel *ControlPanel) SetLiveOnlyMode(state bool) {
controlPanel.vcrTransport().SetLiveOnlyMode(state)
}

// AddRecordingMutators adds a set of recording Track Mutator's to the VCR.
func (controlPanel *ControlPanel) AddRecordingMutators(trackMutators ...track.Mutator) {
controlPanel.vcrTransport().AddRecordingMutators(trackMutators...)
Expand Down
3 changes: 3 additions & 0 deletions govcr.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ func NewVCR(settings ...Setting) *ControlPanel {
requestMatcher: vcrSettings.requestMatcher,
trackRecordingMutators: vcrSettings.trackRecordingMutators,
trackReplayingMutators: vcrSettings.trackReplayingMutators,
liveOnly: vcrSettings.liveOnly,
readOnly: vcrSettings.readOnly,
offlineMode: vcrSettings.offlineMode,
},
cassette: vcrSettings.cassette,
transport: vcrSettings.client.Transport,
Expand Down
108 changes: 104 additions & 4 deletions govcr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func removeUnreadableCassette(t *testing.T, name string) {
require.NoError(t, err)
}

func TestVCRControlPanel_Player(t *testing.T) {
func TestVCRControlPanel_HTTPClient(t *testing.T) {
vcr := govcr.NewVCR()
unit := vcr.HTTPClient()
assert.IsType(t, (*http.Client)(nil), unit)
Expand All @@ -112,7 +112,7 @@ type GoVCRTestSuite struct {
cassetteName string
}

func TestHandlerTestSuite(t *testing.T) {
func TestGoVCRTestSuite(t *testing.T) {
suite.Run(t, new(GoVCRTestSuite))
}

Expand All @@ -132,14 +132,114 @@ func (suite *GoVCRTestSuite) SetupTest() {
testServerClient := suite.testServer.Client()
testServerClient.Timeout = 3 * time.Second
suite.vcr = govcr.NewVCR(govcr.WithClient(testServerClient))
suite.cassetteName = "test-fixtures/TestRecordsTrack.cassette.json"
suite.cassetteName = "temp-fixtures/TestGoVCRTestSuite.cassette.json"
_ = os.Remove(suite.cassetteName)
}

func (suite *GoVCRTestSuite) TearDownTest() {
_ = os.Remove(suite.cassetteName)
}

func (suite *GoVCRTestSuite) TestVCR_ReadOnlyMode() {
suite.vcr.SetReadOnlyMode(true)

err := suite.vcr.LoadCassette(suite.cassetteName)
suite.Require().NoError(err)

resp, err := suite.vcr.HTTPClient().Get(suite.testServer.URL)
suite.Require().NoError(err)
suite.Require().NotNil(resp)

actualStats := *suite.vcr.Stats()
suite.vcr.EjectCassette()
suite.EqualValues(0, suite.vcr.NumberOfTracks())

expectedStats := stats.Stats{
TotalTracks: 0,
TracksLoaded: 0,
TracksRecorded: 0,
TracksPlayed: 0,
}
suite.EqualValues(expectedStats, actualStats)
}

func (suite *GoVCRTestSuite) TestVCR_LiveOnlyMode() {
suite.vcr.SetLiveOnlyMode(true)
suite.vcr.SetRequestMatcher(govcr.NewBlankRequestMatcher()) // ensure always matching

// 1st execution of set of calls
err := suite.vcr.LoadCassette(suite.cassetteName)
suite.Require().NoError(err)

actualStats := suite.makeHTTPCalls_WithSuccess(0)
expectedStats := stats.Stats{
TotalTracks: 2,
TracksLoaded: 0,
TracksRecorded: 2,
TracksPlayed: 0,
}
suite.EqualValues(expectedStats, actualStats)
suite.Require().FileExists(suite.cassetteName)
suite.vcr.EjectCassette()

// 2nd execution of set of calls
err = suite.vcr.LoadCassette(suite.cassetteName)
suite.Require().NoError(err)

actualStats = suite.makeHTTPCalls_WithSuccess(2) // as we're making live requests, the sever keeps on increasing the counter
expectedStats = stats.Stats{
TotalTracks: 4,
TracksLoaded: 2,
TracksRecorded: 2,
TracksPlayed: 0,
}
suite.EqualValues(expectedStats, actualStats)
}

func (suite *GoVCRTestSuite) TestVCR_OfflineMode() {
suite.vcr.SetRequestMatcher(govcr.NewBlankRequestMatcher()) // ensure always matching

// 1st execution of set of calls - populate cassette
suite.vcr.SetOfflineMode(false) // get data in the cassette
err := suite.vcr.LoadCassette(suite.cassetteName)
suite.Require().NoError(err)

actualStats := suite.makeHTTPCalls_WithSuccess(0)
expectedStats := stats.Stats{
TotalTracks: 2,
TracksLoaded: 0,
TracksRecorded: 2,
TracksPlayed: 0,
}
suite.EqualValues(expectedStats, actualStats)
suite.Require().FileExists(suite.cassetteName)
suite.vcr.EjectCassette()

// 2nd execution of set of calls -- offline only
suite.vcr.SetOfflineMode(true)

err = suite.vcr.LoadCassette(suite.cassetteName)
suite.Require().NoError(err)

actualStats = suite.makeHTTPCalls_WithSuccess(0)
expectedStats = stats.Stats{
TotalTracks: 2,
TracksLoaded: 2,
TracksRecorded: 0,
TracksPlayed: 2,
}
suite.EqualValues(expectedStats, actualStats)

// 3rd execution of set of calls -- still offline only
// we've run out of tracks and we're in offline mode so we expect a transport error
req, err := http.NewRequest(http.MethodGet, suite.testServer.URL, nil) // +fmt.Sprintf("?i=%d", i), nil)
suite.Require().NoError(err)
resp, err := suite.vcr.HTTPClient().Do(req)
suite.Require().Error(err)
suite.Assert().Contains(err.Error(), "no track matched on cassette and offline mode is active")
suite.Assert().Nil(resp)
}

func (suite *GoVCRTestSuite) TestRoundTrip_ReplaysError() {
tt := []struct {
name string
Expand Down Expand Up @@ -252,7 +352,7 @@ func (suite *GoVCRTestSuite) TestRoundTrip_ReplaysPlainResponse() {
suite.EqualValues(expectedStats, actualStats)

// 3rd execution of set of calls (replayed without cassette reload)
actualStats = suite.makeHTTPCalls_WithSuccess(int(expectedStats.TotalTracks))
actualStats = suite.makeHTTPCalls_WithSuccess(int(expectedStats.TotalTracks)) // as we're making live requests, the sever keeps on increasing the counter
expectedStats = stats.Stats{
TotalTracks: 4,
TracksLoaded: 2,
Expand Down
2 changes: 1 addition & 1 deletion govcr_wb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type GoVCRWBTestSuite struct {
cassetteName string
}

func TestHandlerTestSuite(t *testing.T) {
func TestGoVCRWBTestSuite(t *testing.T) {
suite.Run(t, new(GoVCRWBTestSuite))
}

Expand Down
30 changes: 30 additions & 0 deletions pcb.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,24 @@ type PrintedCircuitBoard struct {
// could be mutated, it will have no effect.
// However, the Request data can be referenced as part of mutating the Response.
trackReplayingMutators track.Mutators

// Make live calls only, do not replay from cassette even if a track would exist.
// Perhaps more useful when used in combination with 'readOnly' to by-pass govcr entirely.
liveOnly bool

// Replay tracks from cassette, if present, or make live calls but do not records new tracks.
readOnly bool

// Replay tracks from cassette, if present, but do not make live calls.
// govcr will return a transport error if no track was found.
offlineMode bool
}

func (pcb *PrintedCircuitBoard) seekTrack(k7 *cassette.Cassette, httpRequest *http.Request) (*track.Track, error) {
if pcb.liveOnly {
return nil, nil
}

request := track.ToRequest(httpRequest)

numberOfTracksInCassette := k7.NumberOfTracks()
Expand Down Expand Up @@ -57,6 +72,21 @@ func (pcb *PrintedCircuitBoard) SetRequestMatcher(requestMatcher RequestMatcher)
pcb.requestMatcher = requestMatcher
}

// SetReadOnlyMode sets the VCR to read-only mode (true) or to normal read-write (false).
func (pcb *PrintedCircuitBoard) SetReadOnlyMode(state bool) {
pcb.readOnly = state
}

// SetOfflineMode sets the VCR to offline mode (true) or to normal live/replay (false).
func (pcb *PrintedCircuitBoard) SetOfflineMode(state bool) {
pcb.offlineMode = state
}

// SetLiveOnlyMode sets the VCR to live-only mode (true) or to normal live/replay (false).
func (pcb *PrintedCircuitBoard) SetLiveOnlyMode(state bool) {
pcb.liveOnly = state
}

// AddRecordingMutators adds a collection of recording TrackMutator's.
func (pcb *PrintedCircuitBoard) AddRecordingMutators(mutators ...track.Mutator) {
pcb.trackRecordingMutators = pcb.trackRecordingMutators.Add(mutators...)
Expand Down
Loading

0 comments on commit a964e76

Please sign in to comment.