Skip to content

Commit

Permalink
Added TestRoundTrip_DoesNotChangeLiveRequestOrResponse (#57)
Browse files Browse the repository at this point in the history
* Added TestRoundTrip_DoesNotChangeLiveRequestOrResponse

Also, added:
SetRecordingMutators
ClearRecordingMutators
SetReplayingMutators
ClearReplayingMutators

* added github flow
  • Loading branch information
seborama authored Aug 2, 2022
1 parent a964e76 commit f0ad341
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 13 deletions.
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Test and Build

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:

lint:
name: Linting
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2

- name: Lint and Vet
uses: golangci/golangci-lint-action@v2
with:
version: latest
args: --timeout=3m

test:
name: Test
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2

- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17

- name: Test
run: go test -count 1 -parallel 2 ./...
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ test: deps
go test -timeout 20s -cover ./...

# note: -race significantly degrades performance hence a high "timeout" value and reduced parallelism
go test -timeout 60s -cover $(GORACE) -parallel 2 ./...
go test -timeout 120s -cover $(GORACE) -parallel 2 ./...

lint: deps
./golangci-lint.sh || :
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,7 @@ Settings are populated via `With*` options:
- `WithCassette` loads the specified cassette.\
Note that it is also possible to call `LoadCassette` from the vcr instance.
- See `vcrsettings.go` for more options such as `WithRequestMatcher`, `WithTrackRecordingMutators`, `WithTrackReplayingMutators`, ...
- TODO in v5: `WithDisableRecording` disables track recording (but will replay matching tracks)
- TODO in v5: `WithLogging` enables logging to help understand what govcr is doing internally.
- TODO in v5: `WithSaveTLS` enables saving TLS in the track response.\
Note: this doesn't work well because of limitations in Go's json package and unspecified `any` in the PublicKey certificate struct.

## Match a request to a cassette track

Expand Down
15 changes: 10 additions & 5 deletions cassette/cassette.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"sync"
"sync/atomic"

"github.com/google/uuid"
"github.com/pkg/errors"

"github.com/seborama/govcr/v6/cassette/track"
Expand Down Expand Up @@ -105,11 +106,15 @@ func (k7 *Cassette) ReplayTrack(trackNumber int32) (*track.Track, error) {
// AddTrack to cassette.
// Note that the Track does not receive mutations here, it must be mutated
// before passed to the cassette for recording.
func (k7 *Cassette) AddTrack(track *track.Track) {
func (k7 *Cassette) AddTrack(trk *track.Track) {
k7.trackSliceMutex.Lock()
defer k7.trackSliceMutex.Unlock()

k7.Tracks = append(k7.Tracks, *track)
if trk.UUID == "" {
trk.UUID = uuid.NewString()
}

k7.Tracks = append(k7.Tracks, *trk)
}

// IsLongPlay returns true if the cassette content is compressed.
Expand Down Expand Up @@ -194,12 +199,12 @@ func transformInterfacesInJSON(jsonString []byte) []byte {
}

// AddTrackToCassette saves a new track using the specified details to a cassette.
func AddTrackToCassette(cassette *Cassette, aTrack *track.Track) error {
func AddTrackToCassette(cassette *Cassette, trk *track.Track) error {
// mark track as replayed since it's coming from a live Request!
aTrack.SetReplayed(true)
trk.SetReplayed(true)

// add track to cassette
cassette.AddTrack(aTrack)
cassette.AddTrack(trk)

// save cassette
return cassette.save()
Expand Down
20 changes: 20 additions & 0 deletions controlpanel.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ func (controlPanel *ControlPanel) AddRecordingMutators(trackMutators ...track.Mu
controlPanel.vcrTransport().AddRecordingMutators(trackMutators...)
}

// SetRecordingMutators replaces the set of recording Track Mutator's in the VCR.
func (controlPanel *ControlPanel) SetRecordingMutators(trackMutators ...track.Mutator) {
controlPanel.vcrTransport().SetRecordingMutators(trackMutators...)
}

// ClearRecordingMutators clears the set of recording Track Mutator's from the VCR.
func (controlPanel *ControlPanel) ClearRecordingMutators() {
controlPanel.vcrTransport().ClearRecordingMutators()
}

// AddReplayingMutators adds a set of replaying Track Mutator's to the VCR.
// Replaying happens AFTER the request has been matched. As such, while the track's Request
// could be mutated, it will have no effect.
Expand All @@ -57,6 +67,16 @@ func (controlPanel *ControlPanel) AddReplayingMutators(trackMutators ...track.Mu
controlPanel.vcrTransport().AddReplayingMutators(trackMutators...)
}

// SetReplayingMutators replaces the set of replaying Track Mutator's in the VCR.
func (controlPanel *ControlPanel) SetReplayingMutators(trackMutators ...track.Mutator) {
controlPanel.vcrTransport().SetReplayingMutators(trackMutators...)
}

// ClearReplayingMutators clears the set of replaying Track Mutator's from the VCR.
func (controlPanel *ControlPanel) ClearReplayingMutators() {
controlPanel.vcrTransport().ClearReplayingMutators()
}

// HTTPClient returns the http.Client that contains the VCR.
func (controlPanel *ControlPanel) HTTPClient() *http.Client {
return controlPanel.client
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/seborama/govcr/v6
go 1.17

require (
github.com/google/uuid v1.3.0
github.com/jinzhu/copier v0.3.5
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.8.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
Expand Down
2 changes: 1 addition & 1 deletion govcr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ func (suite *GoVCRTestSuite) TestVCR_OfflineMode() {

// 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)
req, err := http.NewRequest(http.MethodGet, suite.testServer.URL, nil)
suite.Require().NoError(err)
resp, err := suite.vcr.HTTPClient().Do(req)
suite.Require().Error(err)
Expand Down
140 changes: 137 additions & 3 deletions govcr_wb_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package govcr

import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"mime/multipart"
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"time"
Expand Down Expand Up @@ -71,10 +75,140 @@ func (suite *GoVCRWBTestSuite) TearDownTest() {
}

func (suite *GoVCRWBTestSuite) TestRoundTrip_DoesNotChangeLiveRequestOrResponse() {
suite.vcr.SetLiveOnlyMode(true) // ensure we always make live calls for this test

trackDestroyerMutator := track.Mutator(
func(trk *track.Track) {
newURL := url.URL{
Scheme: "x",
Opaque: "y",
User: &url.Userinfo{},
Host: "z",
Path: "a",
RawPath: "b",
ForceQuery: false,
RawQuery: "c",
Fragment: "d",
RawFragment: "e",
}

trk.Request = track.Request{
Method: "m",
URL: &newURL,
Proto: "p",
ProtoMajor: 2,
ProtoMinor: 3,
Header: map[string][]string{"h": {"v"}},
Body: []byte("body bytes"),
ContentLength: 200,
TransferEncoding: []string{"x"},
Close: false,
Host: "y",
Form: map[string][]string{"i": {"n"}},
PostForm: map[string][]string{"j": {"m"}},
MultipartForm: &multipart.Form{
Value: map[string][]string{"p": {"u"}},
File: map[string][]*multipart.FileHeader{
"s": {{
Filename: "f",
Header: map[string][]string{"e": {"d"}},
Size: 7,
}},
},
},
Trailer: map[string][]string{"k": {"l"}},
RemoteAddr: "b",
RequestURI: "c",
}

trk.Response = &track.Response{
Status: "s1",
StatusCode: 3,
Proto: "p1",
ProtoMajor: 7,
ProtoMinor: 8,
Header: map[string][]string{"ch": {"cv"}},
Body: []byte("resp body"),
ContentLength: 123,
TransferEncoding: []string{"t blah"},
Trailer: map[string][]string{"h54": {"34v"}},
TLS: &tls.ConnectionState{
Version: 120,
HandshakeComplete: false,
DidResume: false,
CipherSuite: 7,
NegotiatedProtocol: "sfd",
NegotiatedProtocolIsMutual: false,
ServerName: "4dc",
PeerCertificates: []*x509.Certificate{{}},
VerifiedChains: [][]*x509.Certificate{{{}}},
SignedCertificateTimestamps: [][]byte{[]byte("asd")},
OCSPResponse: []byte("asdad"),
TLSUnique: []byte("fyjyt"),
},
}

trk.ErrMsg = func(s string) *string { return &s }("err msg")
trk.ErrType = func(s string) *string { return &s }("err type")
})

suite.vcr.SetRequestMatcher(NewBlankRequestMatcher())
suite.Fail("implement me")
// TODO: create a VCR with WithTrackRecordingMutators and WithTrackReplayingMutators
// and confirm that both the live request and response remain un-mutated.
suite.vcr.SetRecordingMutators(trackDestroyerMutator) // replace all existing mutators with this one
suite.vcr.ClearReplayingMutators() // remove mutators

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

// 1st call
req, err := http.NewRequest(http.MethodGet, suite.testServer.URL, nil)
suite.Require().NoError(err)

preRoundTripReq := track.CloneHTTPRequest(req)

resp, err := suite.vcr.HTTPClient().Do(req)
suite.Require().NoError(err)

expectedStats := &stats.Stats{
TotalTracks: 1,
TracksLoaded: 0,
TracksRecorded: 1,
TracksPlayed: 0,
}
suite.Require().EqualValues(expectedStats, suite.vcr.Stats())

postRoundTripReq := track.CloneHTTPRequest(req)
suite.Require().EqualValues(preRoundTripReq, postRoundTripReq)

// 2nd call
suite.vcr.ClearRecordingMutators() // remove mutators
suite.vcr.ClearReplayingMutators() // remove mutators

req, err = http.NewRequest(http.MethodGet, suite.testServer.URL, nil)
suite.Require().NoError(err)

resp2, err := suite.vcr.HTTPClient().Do(req)
suite.Require().NoError(err)

expectedStats = &stats.Stats{
TotalTracks: 2,
TracksLoaded: 0,
TracksRecorded: 2, // we haven't ejected the cassette
TracksPlayed: 0,
}
suite.Require().EqualValues(expectedStats, suite.vcr.Stats())

// for simplification, we're using our own track.Response
// we'll make the assumption that if that's well, the rest ought to be too.
vcrResp := track.ToResponse(resp)
suite.Assert().Equal([]byte("Hello, server responds '1' to query ''"), vcrResp.Body)

vcrResp2 := track.ToResponse(resp2)
suite.Assert().Equal([]byte("Hello, server responds '2' to query ''"), vcrResp2.Body)

vcrResp, vcrResp2 = nil, nil

suite.Require().EqualValues(vcrResp, vcrResp2)

}

func (suite *GoVCRWBTestSuite) TestRoundTrip_WithRecordingAndReplayingMutations() {
Expand Down
20 changes: 20 additions & 0 deletions pcb.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ func (pcb *PrintedCircuitBoard) AddRecordingMutators(mutators ...track.Mutator)
pcb.trackRecordingMutators = pcb.trackRecordingMutators.Add(mutators...)
}

// SetRecordingMutators replaces the set of recording Track Mutator's in the VCR.
func (pcb *PrintedCircuitBoard) SetRecordingMutators(trackMutators ...track.Mutator) {
pcb.trackRecordingMutators = trackMutators
}

// ClearRecordingMutators clears the set of recording Track Mutator's from the VCR.
func (pcb *PrintedCircuitBoard) ClearRecordingMutators() {
pcb.trackRecordingMutators = nil
}

// AddReplayingMutators adds a collection of replaying TrackMutator's.
// Replaying happens AFTER the request has been matched. As such, while the track's Request
// could be mutated, it will have no effect.
Expand All @@ -100,6 +110,16 @@ func (pcb *PrintedCircuitBoard) AddReplayingMutators(mutators ...track.Mutator)
pcb.trackReplayingMutators = pcb.trackReplayingMutators.Add(mutators...)
}

// SetReplayingMutators replaces the set of replaying Track Mutator's in the VCR.
func (pcb *PrintedCircuitBoard) SetReplayingMutators(trackMutators ...track.Mutator) {
pcb.trackReplayingMutators = trackMutators
}

// ClearReplayingMutators clears the set of replaying Track Mutator's from the VCR.
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.
// TODO: there could be a case to have RequestMatchers (plural) that would work akin to track.Mutators.
Expand Down
20 changes: 20 additions & 0 deletions vcrtransport.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,16 @@ func (t *vcrTransport) AddRecordingMutators(mutators ...track.Mutator) {
t.pcb.AddRecordingMutators(mutators...)
}

// SetRecordingMutators replaces the set of recording Track Mutator's in the VCR.
func (t *vcrTransport) SetRecordingMutators(trackMutators ...track.Mutator) {
t.pcb.SetRecordingMutators(trackMutators...)
}

// ClearRecordingMutators clears the set of recording Track Mutator's from the VCR.
func (t *vcrTransport) ClearRecordingMutators() {
t.pcb.ClearRecordingMutators()
}

// AddReplayingMutators adds a set of replaying Track Mutator's to the VCR.
// Replaying happens AFTER the request has been matched. As such, while the track's Request
// could be mutated, it will have no effect.
Expand All @@ -118,6 +128,16 @@ func (t *vcrTransport) AddReplayingMutators(mutators ...track.Mutator) {
t.pcb.AddReplayingMutators(mutators...)
}

// SetReplayingMutators replaces the set of replaying Track Mutator's in the VCR.
func (t *vcrTransport) SetReplayingMutators(trackMutators ...track.Mutator) {
t.pcb.SetReplayingMutators(trackMutators...)
}

// ClearReplayingMutators clears the set of replaying Track Mutator's from the VCR.
func (t *vcrTransport) ClearReplayingMutators() {
t.pcb.ClearReplayingMutators()
}

func (t *vcrTransport) stats() *stats.Stats {
return t.cassette.Stats()
}

0 comments on commit f0ad341

Please sign in to comment.