Import upstream version 1.1.0
Debian Janitor
2 years ago
0 | 0 | language: go |
1 | 1 | |
2 | 2 | go: |
3 | - 1.15 | |
4 | - 1.14 | |
5 | - 1.13 | |
6 | - 1.12 | |
7 | - 1.11 | |
8 | - 1.10 | |
3 | 9 | - 1.9 |
4 | 10 | - 1.8 |
5 | 11 | - 1.7 |
3 | 3 | [![GoDoc](https://godoc.org/github.com/dnaeon/go-vcr?status.svg)](https://godoc.org/github.com/dnaeon/go-vcr) |
4 | 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/dnaeon/go-vcr)](https://goreportcard.com/report/github.com/dnaeon/go-vcr) |
5 | 5 | [![codecov](https://codecov.io/gh/dnaeon/go-vcr/branch/master/graph/badge.svg)](https://codecov.io/gh/dnaeon/go-vcr) |
6 | ||
7 | 6 | |
8 | 7 | `go-vcr` simplifies testing by recording your HTTP interactions and |
9 | 8 | replaying them in future runs in order to provide fast, deterministic |
75 | 74 | ``` |
76 | 75 | |
77 | 76 | ## Custom Request Matching |
77 | ||
78 | 78 | During replay mode, You can customize the way incoming requests are |
79 | 79 | matched against the recorded request/response pairs by defining a |
80 | 80 | Matcher function. For example, the following matcher will match on |
88 | 88 | defer r.Stop() // Make sure recorder is stopped once done with it |
89 | 89 | |
90 | 90 | r.SetMatcher(func(r *http.Request, i cassette.Request) bool { |
91 | var b bytes.Buffer | |
91 | if r.Body == nil { | |
92 | return cassette.DefaultMatcher(r, i) | |
93 | } | |
94 | var b bytes.Buffer | |
92 | 95 | if _, err := b.ReadFrom(r.Body); err != nil { |
93 | 96 | return false |
94 | 97 | } |
97 | 100 | }) |
98 | 101 | ``` |
99 | 102 | |
103 | ## Protecting Sensitive Data | |
104 | ||
105 | You often provide sensitive data, such as API credentials, when making | |
106 | requests against a service. | |
107 | By default, this data will be stored in the recorded data but you probably | |
108 | don't want this. | |
109 | Removing or replacing data before it is stored can be done by adding one or | |
110 | more `Filter`s to your `Recorder`. | |
111 | Here is an example that removes the `Authorization` header from all requests: | |
112 | ||
113 | ```go | |
114 | r, err := recorder.New("fixtures/filters") | |
115 | if err != nil { | |
116 | log.Fatal(err) | |
117 | } | |
118 | defer r.Stop() // Make sure recorder is stopped once done with it | |
119 | ||
120 | // Add a filter which removes Authorization headers from all requests: | |
121 | r.AddFilter(func(i *cassette.Interaction) error { | |
122 | delete(i.Request.Headers, "Authorization") | |
123 | return nil | |
124 | }) | |
125 | ``` | |
126 | ||
127 | ### Sensitive data in responses | |
128 | ||
129 | Filters added using `*Recorder.AddFilter` are applied within VCR's custom `http.Transport`. This means that if you edit a response in such a filter then subsequent test code will see the edited response. This may not be desirable in all cases. For instance, if a response body contains an OAuth access token that is needed for subsequent requests, then redact the access token in `SaveFilter` will result in authorization failures. | |
130 | ||
131 | Another way to edit recorded interactions is to use `*Recorder.AddSaveFilter`. Filters added with this method are applied just before interactions are saved when `*Recorder.Stop` is called. | |
132 | ||
133 | ```go | |
134 | r, err := recorder.New("fixtures/filters") | |
135 | if err != nil { | |
136 | log.Fatal(err) | |
137 | } | |
138 | defer r.Stop() // Make sure recorder is stopped once done with it | |
139 | ||
140 | // Your test code will continue to see the real access token and | |
141 | // it is redacted before the recorded interactions are saved | |
142 | r.AddSaveFilter(func(i *cassette.Interaction) error { | |
143 | if strings.Contains(i.URL, "/oauth/token") { | |
144 | i.Response.Body = `{"access_token": "[REDACTED]"}` | |
145 | } | |
146 | ||
147 | return nil | |
148 | }) | |
149 | ``` | |
150 | ||
151 | ## Passing Through Requests | |
152 | ||
153 | Sometimes you want to allow specific requests to pass through to the remote | |
154 | server without recording anything. | |
155 | ||
156 | Globally, you can use `ModeDisabled` for this, but if you want to disable the | |
157 | recorder for individual requests, you can add `Passthrough` functions to the | |
158 | recorder. The function takes a pointer to the original request, and returns a | |
159 | boolean, indicating if the request should pass through to the remote server. | |
160 | ||
161 | Here's an example to pass through requests to a specific endpoint: | |
162 | ||
163 | ```go | |
164 | // Pass through the request to the remote server if the path matches "/login". | |
165 | r.AddPassthrough(func(req *http.Request) bool { | |
166 | return req.URL.Path == "/login" | |
167 | }) | |
168 | ``` | |
169 | ||
100 | 170 | ## License |
101 | 171 | |
102 | 172 | `go-vcr` is Open Source and licensed under the |
83 | 83 | |
84 | 84 | // Response duration (something like "100ms" or "10s") |
85 | 85 | Duration string `yaml:"duration"` |
86 | ||
87 | replayed bool | |
86 | 88 | } |
87 | 89 | |
88 | 90 | // Interaction type contains a pair of request/response for a |
103 | 105 | return r.Method == i.Method && r.URL.String() == i.URL |
104 | 106 | } |
105 | 107 | |
108 | // Filter function allows modification of an interaction before saving. | |
109 | type Filter func(*Interaction) error | |
110 | ||
106 | 111 | // Cassette type |
107 | 112 | type Cassette struct { |
108 | 113 | // Name of the cassette |
122 | 127 | |
123 | 128 | // Matches actual request with interaction requests. |
124 | 129 | Matcher Matcher `yaml:"-"` |
130 | ||
131 | // Filters interactions before when they are captured. | |
132 | Filters []Filter `yaml:"-"` | |
133 | ||
134 | // SaveFilters are applied to interactions just before they are saved. | |
135 | SaveFilters []Filter `yaml:"-"` | |
125 | 136 | } |
126 | 137 | |
127 | 138 | // New creates a new empty cassette |
132 | 143 | Version: cassetteFormatV1, |
133 | 144 | Interactions: make([]*Interaction, 0), |
134 | 145 | Matcher: DefaultMatcher, |
146 | Filters: make([]Filter, 0), | |
147 | SaveFilters: make([]Filter, 0), | |
135 | 148 | } |
136 | 149 | |
137 | 150 | return c |
159 | 172 | |
160 | 173 | // GetInteraction retrieves a recorded request/response interaction |
161 | 174 | func (c *Cassette) GetInteraction(r *http.Request) (*Interaction, error) { |
162 | c.Mu.RLock() | |
163 | defer c.Mu.RUnlock() | |
175 | c.Mu.Lock() | |
176 | defer c.Mu.Unlock() | |
164 | 177 | for _, i := range c.Interactions { |
165 | if c.Matcher(r, i.Request) { | |
178 | if !i.replayed && c.Matcher(r, i.Request) { | |
179 | i.replayed = true | |
166 | 180 | return i, nil |
167 | 181 | } |
168 | 182 | } |
179 | 193 | return nil |
180 | 194 | } |
181 | 195 | |
196 | for _, interaction := range c.Interactions { | |
197 | for _, filter := range c.SaveFilters { | |
198 | if err := filter(interaction); err != nil { | |
199 | return err | |
200 | } | |
201 | } | |
202 | } | |
203 | ||
182 | 204 | // Create directory for cassette if missing |
183 | 205 | cassetteDir := filepath.Dir(c.File) |
184 | 206 | if _, err := os.Stat(cassetteDir); os.IsNotExist(err) { |
0 | module github.com/dnaeon/go-vcr | |
1 | ||
2 | go 1.15 | |
3 | ||
4 | require ( | |
5 | github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5 | |
6 | gopkg.in/yaml.v2 v2.2.1 | |
7 | ) |
0 | github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5 h1:8Q0qkMVC/MmWkpIdlvZgcv2o2jrlF6zqVOh7W5YHdMA= | |
1 | github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= | |
2 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | |
3 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= | |
4 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
0 | // Copyright (c) 2015-2016 Marin Atanasov Nikolov <dnaeon@gmail.com> | |
1 | // Copyright (c) 2016 David Jack <davars@gmail.com> | |
2 | // All rights reserved. | |
3 | // | |
4 | // Redistribution and use in source and binary forms, with or without | |
5 | // modification, are permitted provided that the following conditions | |
6 | // are met: | |
7 | // 1. Redistributions of source code must retain the above copyright | |
8 | // notice, this list of conditions and the following disclaimer | |
9 | // in this position and unchanged. | |
10 | // 2. Redistributions in binary form must reproduce the above copyright | |
11 | // notice, this list of conditions and the following disclaimer in the | |
12 | // documentation and/or other materials provided with the distribution. | |
13 | // | |
14 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR | |
15 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
16 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
17 | // IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, | |
18 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |
19 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
20 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
21 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
22 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
23 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
24 | // | |
25 | // +build !go1.8 | |
26 | ||
27 | package recorder | |
28 | ||
29 | import ( | |
30 | "io" | |
31 | ) | |
32 | ||
33 | // isNoBody returns true iff r is an http.NoBody. | |
34 | // http.NoBody didn't exist before Go 1.7, so the version in this file | |
35 | // always returns false. | |
36 | func isNoBody(r io.ReadCloser) bool { return false } |
0 | // Copyright (c) 2015-2016 Marin Atanasov Nikolov <dnaeon@gmail.com> | |
1 | // Copyright (c) 2016 David Jack <davars@gmail.com> | |
2 | // All rights reserved. | |
3 | // | |
4 | // Redistribution and use in source and binary forms, with or without | |
5 | // modification, are permitted provided that the following conditions | |
6 | // are met: | |
7 | // 1. Redistributions of source code must retain the above copyright | |
8 | // notice, this list of conditions and the following disclaimer | |
9 | // in this position and unchanged. | |
10 | // 2. Redistributions in binary form must reproduce the above copyright | |
11 | // notice, this list of conditions and the following disclaimer in the | |
12 | // documentation and/or other materials provided with the distribution. | |
13 | // | |
14 | // THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR | |
15 | // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
16 | // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
17 | // IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, | |
18 | // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |
19 | // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
20 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
21 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
22 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
23 | // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
24 | // | |
25 | // +build go1.8 | |
26 | ||
27 | package recorder | |
28 | ||
29 | import ( | |
30 | "io" | |
31 | "net/http" | |
32 | ) | |
33 | ||
34 | // isNoBody returns true iff r is an http.NoBody. | |
35 | func isNoBody(r io.ReadCloser) bool { return r == http.NoBody } |
60 | 60 | |
61 | 61 | // realTransport is the underlying http.RoundTripper to make real requests |
62 | 62 | realTransport http.RoundTripper |
63 | } | |
63 | ||
64 | // Pass through requests. | |
65 | Passthroughs []Passthrough | |
66 | } | |
67 | ||
68 | // Passthrough function allows ignoring certain requests. | |
69 | type Passthrough func(*http.Request) bool | |
64 | 70 | |
65 | 71 | // SetTransport can be used to configure the behavior of the 'real' client used in record-mode |
66 | 72 | func (r *Recorder) SetTransport(t http.RoundTripper) { |
71 | 77 | func requestHandler(r *http.Request, c *cassette.Cassette, mode Mode, realTransport http.RoundTripper) (*cassette.Interaction, error) { |
72 | 78 | // Return interaction from cassette if in replay mode |
73 | 79 | if mode == ModeReplaying { |
80 | if err := r.Context().Err(); err != nil { | |
81 | return nil, err | |
82 | } | |
74 | 83 | return c.GetInteraction(r) |
75 | 84 | } |
76 | 85 | |
92 | 101 | } |
93 | 102 | |
94 | 103 | reqBody := &bytes.Buffer{} |
95 | if r.Body != nil { | |
104 | if r.Body != nil && !isNoBody(r.Body) { | |
96 | 105 | // Record the request body so we can add it to the cassette |
97 | 106 | r.Body = ioutil.NopCloser(io.TeeReader(r.Body, reqBody)) |
98 | 107 | } |
103 | 112 | if err != nil { |
104 | 113 | return nil, err |
105 | 114 | } |
115 | defer resp.Body.Close() | |
106 | 116 | |
107 | 117 | respBody, err := ioutil.ReadAll(resp.Body) |
108 | 118 | if err != nil { |
125 | 135 | Code: resp.StatusCode, |
126 | 136 | }, |
127 | 137 | } |
138 | for _, filter := range c.Filters { | |
139 | err = filter(interaction) | |
140 | if err != nil { | |
141 | return nil, err | |
142 | } | |
143 | } | |
128 | 144 | c.AddInteraction(interaction) |
129 | 145 | |
130 | 146 | return interaction, nil |
187 | 203 | if r.mode == ModeDisabled { |
188 | 204 | return r.realTransport.RoundTrip(req) |
189 | 205 | } |
206 | for _, passthrough := range r.Passthroughs { | |
207 | if passthrough(req) { | |
208 | return r.realTransport.RoundTrip(req) | |
209 | } | |
210 | } | |
211 | ||
190 | 212 | // Pass cassette and mode to handler, so that interactions can be |
191 | 213 | // retrieved or recorded depending on the current recorder mode |
192 | 214 | interaction, err := requestHandler(req, r.cassette, r.mode, r.realTransport) |
253 | 275 | r.cassette.Matcher = matcher |
254 | 276 | } |
255 | 277 | } |
278 | ||
279 | // AddPassthrough appends a hook to determine if a request should be ignored by the | |
280 | // recorder. | |
281 | func (r *Recorder) AddPassthrough(pass Passthrough) { | |
282 | r.Passthroughs = append(r.Passthroughs, pass) | |
283 | } | |
284 | ||
285 | // AddFilter appends a hook to modify a request before it is recorded. | |
286 | // | |
287 | // Filters are useful for filtering out sensitive parameters from the recorded data. | |
288 | func (r *Recorder) AddFilter(filter cassette.Filter) { | |
289 | if r.cassette != nil { | |
290 | r.cassette.Filters = append(r.cassette.Filters, filter) | |
291 | } | |
292 | } | |
293 | ||
294 | // AddSaveFilter appends a hook to modify a request before it is saved. | |
295 | // | |
296 | // This filter is suitable for treating recorded responses to remove sensitive data. Altering responses using a regular | |
297 | // AddFilter can have unintended consequences on code that is consuming responses. | |
298 | func (r *Recorder) AddSaveFilter(filter cassette.Filter) { | |
299 | if r.cassette != nil { | |
300 | r.cassette.SaveFilters = append(r.cassette.SaveFilters, filter) | |
301 | } | |
302 | } | |
303 | ||
304 | // Mode returns recorder state | |
305 | func (r *Recorder) Mode() Mode { | |
306 | return r.mode | |
307 | } |
80 | 80 | func TestRecord(t *testing.T) { |
81 | 81 | runID, cassPath, tests := setupTests(t, "record_test") |
82 | 82 | |
83 | serverURL := httpRecorderTest(t, runID, tests, cassPath, recorder.ModeRecording) | |
83 | r, serverURL := httpRecorderTest(t, runID, tests, cassPath, recorder.ModeRecording) | |
84 | 84 | |
85 | 85 | c, err := cassette.Load(cassPath) |
86 | 86 | if err != nil { |
87 | 87 | t.Fatal(err) |
88 | } | |
89 | ||
90 | if m := r.Mode(); m != recorder.ModeRecording { | |
91 | t.Fatalf("Expected recording mode, got %v", m) | |
88 | 92 | } |
89 | 93 | |
90 | 94 | for i, test := range tests { |
95 | 99 | } |
96 | 100 | |
97 | 101 | // Re-run without the actual server |
98 | r, err := recorder.New(cassPath) | |
102 | r, err = recorder.New(cassPath) | |
99 | 103 | if err != nil { |
100 | 104 | t.Fatal(err) |
101 | 105 | } |
102 | 106 | defer r.Stop() |
107 | ||
108 | if m := r.Mode(); m != recorder.ModeReplaying { | |
109 | t.Fatalf("Expected replaying mode, got %v", m) | |
110 | } | |
103 | 111 | |
104 | 112 | // Use a custom matcher that includes matching on request body |
105 | 113 | r.SetMatcher(func(r *http.Request, i cassette.Request) bool { |
121 | 129 | func TestModeContextTimeout(t *testing.T) { |
122 | 130 | // Record initial requests |
123 | 131 | runID, cassPath, tests := setupTests(t, "record_playback_timeout") |
124 | serverURL := httpRecorderTest(t, runID, tests, cassPath, recorder.ModeReplaying) | |
132 | _, serverURL := httpRecorderTest(t, runID, tests, cassPath, recorder.ModeReplaying) | |
125 | 133 | |
126 | 134 | // Re-run without the actual server |
127 | 135 | r, err := recorder.New(cassPath) |
134 | 142 | ctx, cancelFn := context.WithCancel(context.Background()) |
135 | 143 | cancelFn() |
136 | 144 | _, err := test.performReq(t, ctx, serverURL, r) |
137 | if err == nil { | |
138 | t.Fatal("Expected cancellation error") | |
145 | if err == nil || err == cassette.ErrInteractionNotFound { | |
146 | t.Fatalf("Expected cancellation error, got %v", err) | |
139 | 147 | } |
140 | 148 | } |
141 | 149 | } |
173 | 181 | func TestModeDisabled(t *testing.T) { |
174 | 182 | runID, cassPath, tests := setupTests(t, "record_disabled_test") |
175 | 183 | |
176 | httpRecorderTest(t, runID, tests, cassPath, recorder.ModeDisabled) | |
184 | r, _ := httpRecorderTest(t, runID, tests, cassPath, recorder.ModeDisabled) | |
185 | ||
186 | if m := r.Mode(); m != recorder.ModeDisabled { | |
187 | t.Fatalf("Expected disabled mode, got %v", m) | |
188 | } | |
177 | 189 | |
178 | 190 | _, err := cassette.Load(cassPath) |
179 | 191 | // Expect the file to not exist if record is disabled |
180 | 192 | if !os.IsNotExist(err) { |
181 | 193 | t.Fatal(err) |
194 | } | |
195 | } | |
196 | ||
197 | func TestPassthrough(t *testing.T) { | |
198 | runID, cassPath, tests := setupTests(t, "test_passthrough") | |
199 | recorder, server := httpRecorderTestSetup(t, runID, cassPath, recorder.ModeRecording) | |
200 | serverURL := server.URL | |
201 | ||
202 | // Add a passthrough configuration which does not record any requests with | |
203 | // a specific body. | |
204 | recorder.AddPassthrough(func(r *http.Request) bool { | |
205 | if r.Body == nil { | |
206 | return false | |
207 | } | |
208 | var b bytes.Buffer | |
209 | if _, err := b.ReadFrom(r.Body); err != nil { | |
210 | return false | |
211 | } | |
212 | r.Body = ioutil.NopCloser(&b) | |
213 | ||
214 | return b.String() == "alt body" | |
215 | }) | |
216 | ||
217 | t.Log("make http requests") | |
218 | for _, test := range tests { | |
219 | test.perform(t, serverURL, recorder) | |
220 | } | |
221 | ||
222 | // Make sure recorder is stopped once done with it | |
223 | server.Close() | |
224 | t.Log("server shut down") | |
225 | ||
226 | recorder.Stop() | |
227 | t.Log("recorder stopped") | |
228 | ||
229 | // Load the cassette we just stored: | |
230 | c, err := cassette.Load(cassPath) | |
231 | if err != nil { | |
232 | t.Fatal(err) | |
233 | } | |
234 | ||
235 | // Assert that no body exists matching our pass through test | |
236 | for _, i := range c.Interactions { | |
237 | body := i.Request.Body | |
238 | if body == "alt body" { | |
239 | t.Fatalf("unexpected recording:\t%s", body) | |
240 | } | |
241 | } | |
242 | } | |
243 | ||
244 | func TestFilter(t *testing.T) { | |
245 | dummyBody := "[REDACTED]" | |
246 | ||
247 | runID, cassPath, tests := setupTests(t, "test_filter") | |
248 | recorder, server := httpRecorderTestSetup(t, runID, cassPath, recorder.ModeRecording) | |
249 | serverURL := server.URL | |
250 | ||
251 | // Add a filter which replaces each request body in the stored cassette: | |
252 | recorder.AddFilter(func(i *cassette.Interaction) error { | |
253 | i.Request.Body = dummyBody | |
254 | return nil | |
255 | }) | |
256 | ||
257 | t.Log("make http requests") | |
258 | for _, test := range tests { | |
259 | test.perform(t, serverURL, recorder) | |
260 | } | |
261 | ||
262 | // Make sure recorder is stopped once done with it | |
263 | server.Close() | |
264 | t.Log("server shut down") | |
265 | ||
266 | recorder.Stop() | |
267 | t.Log("recorder stopped") | |
268 | ||
269 | // Load the cassette we just stored: | |
270 | c, err := cassette.Load(cassPath) | |
271 | if err != nil { | |
272 | t.Fatal(err) | |
273 | } | |
274 | ||
275 | // Assert that each body has been set to our dummy value | |
276 | for i := range tests { | |
277 | body := c.Interactions[i].Request.Body | |
278 | if body != dummyBody { | |
279 | t.Fatalf("got:\t%s\n\twant:\t%s", string(body), string(dummyBody)) | |
280 | } | |
281 | } | |
282 | } | |
283 | ||
284 | func TestSaveFilter(t *testing.T) { | |
285 | dummyBody := "[REDACTED]" | |
286 | ||
287 | runID, cassPath, tests := setupTests(t, "test_save_filter") | |
288 | recorder, server := httpRecorderTestSetup(t, runID, cassPath, recorder.ModeRecording) | |
289 | serverURL := server.URL | |
290 | ||
291 | // Add a filter which replaces each request body in the stored cassette: | |
292 | recorder.AddSaveFilter(func(i *cassette.Interaction) error { | |
293 | i.Response.Body = dummyBody | |
294 | return nil | |
295 | }) | |
296 | ||
297 | t.Log("make http requests") | |
298 | for _, test := range tests { | |
299 | test.perform(t, serverURL, recorder) | |
300 | } | |
301 | ||
302 | // Make sure recorder is stopped once done with it | |
303 | server.Close() | |
304 | t.Log("server shut down") | |
305 | ||
306 | recorder.Stop() | |
307 | t.Log("recorder stopped") | |
308 | ||
309 | // Load the cassette we just stored: | |
310 | c, err := cassette.Load(cassPath) | |
311 | if err != nil { | |
312 | t.Fatal(err) | |
313 | } | |
314 | ||
315 | // Assert that each body has been set to our dummy value | |
316 | for i := range tests { | |
317 | body := c.Interactions[i].Response.Body | |
318 | if body != dummyBody { | |
319 | t.Fatalf("got:\t%s\n\twant:\t%s", string(body), string(dummyBody)) | |
320 | } | |
182 | 321 | } |
183 | 322 | } |
184 | 323 | |
201 | 340 | return recorder, server |
202 | 341 | } |
203 | 342 | |
204 | func httpRecorderTest(t *testing.T, runID string, tests []recordTest, cassPath string, mode recorder.Mode) string { | |
343 | func httpRecorderTest(t *testing.T, runID string, tests []recordTest, cassPath string, mode recorder.Mode) (*recorder.Recorder, string) { | |
205 | 344 | recorder, server := httpRecorderTestSetup(t, runID, cassPath, mode) |
206 | 345 | serverURL := server.URL |
207 | 346 | |
217 | 356 | recorder.Stop() |
218 | 357 | t.Log("recorder stopped") |
219 | 358 | |
220 | return serverURL | |
359 | return recorder, serverURL | |
221 | 360 | } |
222 | 361 | |
223 | 362 | func (test recordTest) perform(t *testing.T, url string, r *recorder.Recorder) { |