Back to Repositories

Validating Cassette Migration Workflow in VCR

This test suite validates the VCR Cassette Migrator functionality, which handles the migration of VCR cassettes from version 1.x to 2.x format. The tests ensure proper handling of YAML formatting, data structures, and edge cases during the migration process.

Test Coverage Overview

The test suite provides comprehensive coverage of the VCR::Cassette::Migrator functionality. It validates the migration of cassette files from VCR 1.x to 2.x format, including proper YAML structure transformation and data preservation.

  • Tests successful migration of valid cassette files
  • Verifies handling of invalid YAML files
  • Checks processing of sensitive data substitutions
  • Tests empty file handling

Implementation Analysis

The testing approach utilizes RSpec’s behavior-driven development patterns with extensive use of let blocks for test setup and shared examples for common test scenarios.

  • Uses before hooks for test environment setup
  • Implements shared examples for YAML parsing tests
  • Utilizes StringIO for output capture
  • Employs platform-specific conditional testing

Technical Details

  • RSpec testing framework
  • YAML parsing with both Syck and Psych engines
  • File system operations with FileUtils
  • Temporary directory management
  • Platform-specific adaptations for JRuby
  • Mock time management for consistent testing

Best Practices Demonstrated

The test suite exemplifies several testing best practices in Ruby testing. It demonstrates thorough isolation of test cases, proper setup and teardown procedures, and comprehensive edge case handling.

  • Isolated test environment using temporary directories
  • Comprehensive file content verification
  • Cross-platform compatibility considerations
  • Clear test case organization and documentation

vcr/vcr

spec/lib/vcr/cassette/migrator_spec.rb

            
require 'tmpdir'
require 'vcr/cassette/migrator'
require 'yaml'

RSpec.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
---
http_interactions:
- request:
    method: get
    uri: http://example.com/foo
    body:
      encoding: US-ASCII
      string: ""
    headers: {}

  response:
    status:
      code: 200
      message: OK
    headers:
      Content-Type:
      - text/html;charset=utf-8
      Content-Length:
      - "9"
    body:
      encoding: UTF-8
      string: Hello foo
    http_version: "1.1"
  recorded_at: Wed, 04 May 2011 12:30:00 GMT
- request:
    method: get
    uri: http://localhost:7777/bar
    body:
      encoding: US-ASCII
      string: ""
    headers: {}

  response:
    status:
      code: 200
      message: OK
    headers:
      Content-Type:
      - text/html;charset=utf-8
      Content-Length:
      - "9"
    body:
      encoding: UTF-8
      string: Hello bar
    http_version: "1.1"
  recorded_at: Wed, 04 May 2011 12:30:00 GMT
recorded_with: VCR #{VCR.version}
EOF
  }

  let(:dir) { './tmp/migrator' }

  before(:each) do
    # ensure the directory is empty
    FileUtils.rm_rf dir
    FileUtils.mkdir_p dir
  end

  before(:each) do
    # the encoding won't be set on rubies that don't support it
    updated_contents.gsub!(/^\s+encoding:.*$/, '')
  end unless ''.respond_to?(:encoding)

  # JRuby serializes YAML with some slightly different whitespace.
  before(:each) do
    [original_contents, updated_contents].each do |contents|
      contents.gsub!(/^(\s+)-/, '\1  -')
    end
    updated_contents.gsub!(/^(- |  )/, '  \1')
  end if RUBY_PLATFORM == 'java'

  let(:filemtime) { Time.utc(2011, 5, 4, 12, 30) }
  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) }

  before(:each) do
    allow(File).to receive(:mtime).with(file_name).and_return(filemtime)
  end

  def load_file(file_name)
    if ::YAML.respond_to?(:unsafe_load_file)
      ::YAML.unsafe_load_file(file_name)
    else
      ::YAML.load_file(file_name)
    end
  end

  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!
    expect(load_file(file_name)).to eq(YAML.load(updated_contents))
    expect(output).to 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!
    expect(File.read(file_name)).to eq(true.to_yaml)
    expect(output).to 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!
    expect(File.read(file_name)).to eq([{}, {}].to_yaml)
    expect(output).to 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!
    expect(load_file(file_name)).to eq(YAML.load(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!
    expect(File.read(file_name)).to eq('')
    expect(output).to 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!
      expect(File.read(file_name)).to eq(modified_contents)
      expect(output).to 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
      YAML::ENGINE.yamler = 'psych'
    end

    it_behaves_like "ignoring invalid YAML"
  end if defined?(YAML::ENGINE)
end