If you are using VCR to record/playback HTTP requests for your Rails tests, you may run into problems if your cassettes use tokens to authenticate with those services.
The error
After recording a cassette, you may see this familiar error upon running your tests:
# Error
An HTTP request has been made that VCR does not know how to handle
The cause
VCR uses a RequestMatcher to determine if an outgoing request (and response) already exist in a cassette. Upon finding a match, the test will use the response pre-recorded in the cassette. Two common ways to Authenticate on an external request are:
# Token Param
https://example.com/service/data?user=123&account=johnsmith&token=qW413vsj4Sv4
# HTTP Basic Header Auth
https://johnsmith:qW413vsj4Sv4@example.com/service/data?user=123
Because tokens typically expire, cassettes recorded at different times may contain different authentication tokens. This becomes a problem when running the whole test suite. Because the request/response to get a token will also be pre-recorded, VCR will return the same response (and auth token) to all of the tests. The first test will pass, but those using cassettes recorded at a different time will experience the error mentioned above. Which test(s) pass may vary on each run because VCR uses a Seed to (randomly) order & execute the tests.
Breaking down a request
The next step is to understand which part of the request is failing to match, and what can be done about it.
# Sample Request
https://example.com/service/data?user=123&account=johnsmith#time=789
# Components
* Scheme (http://, https://, etc)
* Host (example.com)
* Path (/service/data)
* Query (?user=123&account=johnsmith)
* Fragment (#time=789)
The culprit
Depending on which authentication method is used, the culprit is in one of two places:
# Token is in the Query
https://example.com/service/data?user=123&account=johnsmith&token=qW413vsj4Sv4
# Token is in the Header
https://johnsmith:qW413vsj4Sv4@example.com/service/data?user=123
The Solution
VCR allows you to create a Custom RequestMatcher. With this, we can create a matcher that ignores the auth token (or other problematic/mutable param).
If you supply a Token Param…
VCR.configure do |config|
...
config.default_cassette_options = {
...
:match_requests_on => [:method, VCR.request_matchers.uri_without_param(:token)]
}
end
If you use a Basic Auth Header…
VCR.configure do |config|
...
config.default_cassette_options = {
...
:match_requests_on => [:method, :no_basic_auth]
}
config.register_request_matcher :no_basic_auth do |req1, req2|
(URI(req1.uri).scheme == URI(req2.uri).scheme) &&
(URI(req1.uri).host == URI(req2.uri).host) &&
(URI(req1.uri).path == URI(req2.uri).path) &&
(URI(req1.uri).query == URI(req2.uri).query)
end
end
Wrap Up
While the ability to use uri_without_param
is easy enough, creating a custom matcher is definitely a workaround at best. Unfortunately, requests for a cleaner solution to this problem seem to have been closed. Until that changes, we’ll have to keep using the custom matcher.