diff --git a/cassette/cassette.go b/cassette/cassette.go index f2ad372..0e6c346 100644 --- a/cassette/cassette.go +++ b/cassette/cassette.go @@ -123,10 +123,23 @@ type Response struct { // Interaction type contains a pair of request/response for a // single HTTP interaction between a client and a server type Interaction struct { - ID int `yaml:"id"` - Request Request `yaml:"request"` + // ID is the id of the interaction + ID int `yaml:"id"` + + // Request is the recorded request + Request Request `yaml:"request"` + + // Response is the recorded response Response Response `yaml:"response"` - replayed bool `yaml:"-"` + + // DiscardOnSave if set to true will discard the interaction + // as a whole and it will not be part of the final + // interactions when saving the cassette on disk. + DiscardOnSave bool `yaml:"-"` + + // replayed is true when this interaction has been played + // already. + replayed bool `yaml:"-"` } // GetHTTPRequest converts the recorded interaction request to @@ -301,6 +314,20 @@ func (c *Cassette) Save() error { } } + // Filter out interactions which should be discarded. While + // discarding interactions we should also fix the interaction + // IDs, so that we don't introduce gaps in the final results. + nextId := 0 + interactions := make([]*Interaction, 0) + for _, i := range c.Interactions { + if !i.DiscardOnSave { + i.ID = nextId + interactions = append(interactions, i) + nextId += 1 + } + } + c.Interactions = interactions + // Marshal to YAML and save interactions data, err := yaml.Marshal(c) if err != nil { diff --git a/recorder/recorder_test.go b/recorder/recorder_test.go index a1cd4f9..d6d5c5b 100644 --- a/recorder/recorder_test.go +++ b/recorder/recorder_test.go @@ -1225,3 +1225,82 @@ func TestInvalidRecorderMode(t *testing.T) { t.Fatal("expected recorder to fail with invalid mode") } } + +func TestDiscardInteractionsOnSave(t *testing.T) { + tests := []testCase{ + { + method: http.MethodPost, + body: "foo", + wantBody: "POST go-vcr\nfoo", + wantStatus: http.StatusOK, + wantContentLength: 15, + path: "/api/v1/foo", + }, + { + method: http.MethodPost, + body: "bar", + wantBody: "POST go-vcr\nbar", + wantStatus: http.StatusOK, + wantContentLength: 15, + path: "/api/v1/bar", + }, + } + + server := newEchoHttpServer() + serverUrl := server.URL + + cassPath, err := newCassettePath("test_discard_interactions_on_save") + if err != nil { + t.Fatal(err) + } + + // Create recorder + rec, err := recorder.New(cassPath) + if err != nil { + t.Fatal(err) + } + + if rec.Mode() != recorder.ModeRecordOnce { + t.Fatal("recorder is not in the correct mode") + } + + if rec.IsRecording() != true { + t.Fatal("recorder is not recording") + } + + // The following hook function will be used to determine + // whether an interaction is to be discarded when saving the + // cassette on disk. + hook := func(i *cassette.Interaction) error { + if i.Request.Method == http.MethodPost && i.Request.Body == "foo" { + i.DiscardOnSave = true + } + + return nil + } + rec.AddHook(hook, recorder.AfterCaptureHook) + + ctx := context.Background() + client := rec.GetDefaultClient() + for _, test := range tests { + if err := test.run(client, ctx, serverUrl); err != nil { + t.Fatal(err) + } + } + + // Stop recorder and verify cassette + rec.Stop() + + cass, err := cassette.Load(cassPath) + if err != nil { + t.Fatal(err) + } + + // We should have one interaction less than our test cases + // when reading the cassette from disk. + wantInteractions := len(tests) - 1 + gotInteractions := len(cass.Interactions) + if wantInteractions != gotInteractions { + t.Fatalf("expected %d interactions, got %d", wantInteractions, gotInteractions) + } +}