forked from vcr/vcr
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a rake task to migrate the cassettes from the 1.x format to the 2…
….x format. Also, normalize nil body to a blank string. It's nice to always be able to treat the body as a string and not need to worry about a nil special case.
- Loading branch information
1 parent
d78941d
commit 26c1a8a
Showing
6 changed files
with
350 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
require 'yaml' | ||
require 'vcr/structs' | ||
require 'uri' | ||
|
||
module VCR | ||
class Cassette | ||
class Migrator | ||
def initialize(dir, out = $stdout) | ||
@dir, @out = dir, out | ||
end | ||
|
||
def migrate! | ||
@out.puts "Migrating VCR cassettes in #{@dir}..." | ||
Dir["#{@dir}/**/*.yml"].each do |cassette| | ||
migrate_cassette(cassette) | ||
end | ||
end | ||
|
||
private | ||
|
||
def migrate_cassette(cassette) | ||
unless http_interactions = load_yaml(cassette) | ||
@out.puts " - Ignored #{relative_casssette_name(cassette)} since it could not be parsed as YAML (does it have some ERB?)" | ||
return | ||
end | ||
|
||
unless valid_vcr_1_cassette?(http_interactions) | ||
@out.puts " - Ignored #{relative_casssette_name(cassette)} since it does not appear to be a valid VCR 1.x cassette" | ||
return | ||
end | ||
|
||
http_interactions.map! do |interaction| | ||
remove_unnecessary_standard_port(interaction) | ||
denormalize_http_header_keys(interaction.request) | ||
denormalize_http_header_keys(interaction.response) | ||
normalize_body(interaction.request) | ||
normalize_body(interaction.response) | ||
interaction.to_hash | ||
end | ||
|
||
File.open(cassette, 'w') { |f| f.write ::YAML.dump(http_interactions) } | ||
@out.puts " - Migrated #{relative_casssette_name(cassette)}" | ||
end | ||
|
||
def load_yaml(cassette) | ||
::YAML.load_file(cassette) | ||
rescue *yaml_load_errors | ||
return nil | ||
end | ||
|
||
def yaml_load_errors | ||
[ArgumentError].tap do |errors| | ||
errors << Psych::SyntaxError if defined?(Psych::SyntaxError) | ||
end | ||
end | ||
|
||
def relative_casssette_name(cassette) | ||
cassette.gsub(%r|\A#{Regexp.escape(@dir)}/?|, '') | ||
end | ||
|
||
def valid_vcr_1_cassette?(content) | ||
content.is_a?(Array) && | ||
content.map(&:class).uniq == [HTTPInteraction] | ||
end | ||
|
||
def remove_unnecessary_standard_port(interaction) | ||
uri = URI(interaction.request.uri) | ||
if uri.scheme == 'http' && uri.port == 80 || | ||
uri.scheme == 'https' && uri.port == 443 | ||
uri.port = nil | ||
interaction.request.uri = uri.to_s | ||
end | ||
rescue URI::InvalidURIError | ||
# ignore this URI. | ||
# This can occur when the user uses the filter_sensitive_data option | ||
# to put a substitution string in their URI | ||
end | ||
|
||
def denormalize_http_header_keys(object) | ||
object.headers = {}.tap do |denormalized| | ||
object.headers.each do |k, v| | ||
denormalized[denormalize_header_key(k)] = v | ||
end if object.headers | ||
end | ||
end | ||
|
||
def denormalize_header_key(key) | ||
key.split('-'). # 'user-agent' => %w(user agent) | ||
each { |w| w.capitalize! }. # => %w(User Agent) | ||
join('-') | ||
end | ||
|
||
def normalize_body(object) | ||
object.body = '' if object.body.nil? | ||
end | ||
end | ||
end | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace :vcr do | ||
desc "Migrate cassettes from the VCR 1.x format to the VCR 2.x format." | ||
task :migrate_cassettes do | ||
dir = ENV.fetch('DIR') { raise "You must pass the cassette library directory as DIR=<directory>" } | ||
require 'vcr/cassette/migrator' | ||
VCR::Cassette::Migrator.new(dir).migrate! | ||
end | ||
end | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
require 'tmpdir' | ||
require 'vcr/cassette/migrator' | ||
|
||
describe VCR::Cassette::Migrator do | ||
let(:original_contents) { <<-EOF | ||
--- | ||
- !ruby/struct:VCR::HTTPInteraction | ||
request: !ruby/struct:VCR::Request | ||
method: :get | ||
uri: http://example.com:80/foo | ||
body: | ||
headers: | ||
response: !ruby/struct:VCR::Response | ||
status: !ruby/struct:VCR::ResponseStatus | ||
code: 200 | ||
message: OK | ||
headers: | ||
content-type: | ||
- text/html;charset=utf-8 | ||
content-length: | ||
- "9" | ||
body: Hello foo | ||
http_version: "1.1" | ||
- !ruby/struct:VCR::HTTPInteraction | ||
request: !ruby/struct:VCR::Request | ||
method: :get | ||
uri: http://localhost:7777/bar | ||
body: | ||
headers: | ||
response: !ruby/struct:VCR::Response | ||
status: !ruby/struct:VCR::ResponseStatus | ||
code: 200 | ||
message: OK | ||
headers: | ||
content-type: | ||
- text/html;charset=utf-8 | ||
content-length: | ||
- "9" | ||
body: Hello bar | ||
http_version: "1.1" | ||
EOF | ||
} | ||
|
||
let(:updated_contents) { <<-EOF | ||
--- | ||
- request: | ||
method: get | ||
uri: http://example.com/foo | ||
body: "" | ||
headers: {} | ||
response: | ||
status: | ||
code: 200 | ||
message: OK | ||
headers: | ||
Content-Type: | ||
- text/html;charset=utf-8 | ||
Content-Length: | ||
- "9" | ||
body: Hello foo | ||
http_version: "1.1" | ||
- request: | ||
method: get | ||
uri: http://localhost:7777/bar | ||
body: "" | ||
headers: {} | ||
response: | ||
status: | ||
code: 200 | ||
message: OK | ||
headers: | ||
Content-Type: | ||
- text/html;charset=utf-8 | ||
Content-Length: | ||
- "9" | ||
body: Hello bar | ||
http_version: "1.1" | ||
EOF | ||
} | ||
|
||
attr_accessor :dir | ||
|
||
around(:each) do |example| | ||
Dir.mktmpdir do |dir| | ||
self.dir = dir | ||
example.run | ||
end | ||
end | ||
|
||
# Use syck on all rubies for consistent results... | ||
before(:each) do | ||
YAML::ENGINE.yamler = 'syck' if defined?(YAML::ENGINE) | ||
end | ||
|
||
after(:each) do | ||
YAML::ENGINE.yamler = 'psych' if defined?(YAML::ENGINE) | ||
end | ||
|
||
let(:out_io) { StringIO.new } | ||
let(:file_name) { File.join(dir, "example.yml") } | ||
let(:output) { out_io.rewind; out_io.read } | ||
|
||
subject { described_class.new(dir, out_io) } | ||
|
||
it 'migrates a cassette from the 1.x to 2.x format' do | ||
File.open(file_name, 'w') { |f| f.write(original_contents) } | ||
subject.migrate! | ||
File.read(file_name).should eq(updated_contents) | ||
output.should match(/Migrated example.yml/) | ||
end | ||
|
||
it 'ignores files that do not contain arrays' do | ||
File.open(file_name, 'w') { |f| f.write(true.to_yaml) } | ||
subject.migrate! | ||
File.read(file_name).should eq(true.to_yaml) | ||
output.should match(/Ignored example.yml since it does not appear to be a valid VCR 1.x cassette/) | ||
end | ||
|
||
it 'ignores files that contain YAML arrays of other things' do | ||
File.open(file_name, 'w') { |f| f.write([{}, {}].to_yaml) } | ||
subject.migrate! | ||
File.read(file_name).should eq([{}, {}].to_yaml) | ||
output.should match(/Ignored example.yml since it does not appear to be a valid VCR 1.x cassette/) | ||
end | ||
|
||
it 'ignores URIs that have sensitive data substitutions' do | ||
modified_contents = original_contents.gsub('example.com', '<HOST>') | ||
File.open(file_name, 'w') { |f| f.write(modified_contents) } | ||
subject.migrate! | ||
File.read(file_name).should eq(updated_contents.gsub('example.com', '<HOST>:80')) | ||
end | ||
|
||
it 'ignores files that are empty' do | ||
File.open(file_name, 'w') { |f| f.write('') } | ||
subject.migrate! | ||
File.read(file_name).should eq('') | ||
output.should match(/Ignored example.yml since it could not be parsed as YAML/) | ||
end | ||
|
||
shared_examples_for "ignoring invalid YAML" do | ||
it 'ignores files that cannot be parsed as valid YAML (such as ERB cassettes)' do | ||
modified_contents = original_contents.gsub(/\A---/, "---\n<% 3.times do %>") | ||
modified_contents = modified_contents.gsub(/\z/, "<% end %>") | ||
File.open(file_name, 'w') { |f| f.write(modified_contents) } | ||
subject.migrate! | ||
File.read(file_name).should eq(modified_contents) | ||
output.should match(/Ignored example.yml since it could not be parsed as YAML/) | ||
end | ||
end | ||
|
||
context 'with syck' do | ||
it_behaves_like "ignoring invalid YAML" | ||
end | ||
|
||
context 'with psych' do | ||
before(:each) do | ||
pending "psych not available" unless defined?(YAML::ENGINE) | ||
YAML::ENGINE.yamler = 'psych' | ||
end | ||
|
||
it_behaves_like "ignoring invalid YAML" | ||
end | ||
end | ||
|
Oops, something went wrong.