mustwin-vcr 2.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/features/about_these_examples.md +18 -0
- data/features/cassettes/allow_unused_http_interactions.feature +100 -0
- data/features/cassettes/automatic_re_recording.feature +72 -0
- data/features/cassettes/decompress.feature +74 -0
- data/features/cassettes/dynamic_erb.feature +100 -0
- data/features/cassettes/exclusive.feature +126 -0
- data/features/cassettes/format.feature +323 -0
- data/features/cassettes/freezing_time.feature +68 -0
- data/features/cassettes/naming.feature +28 -0
- data/features/cassettes/no_cassette.feature +152 -0
- data/features/cassettes/update_content_length_header.feature +112 -0
- data/features/configuration/allow_http_connections_when_no_cassette.feature +55 -0
- data/features/configuration/cassette_library_dir.feature +31 -0
- data/features/configuration/debug_logging.feature +59 -0
- data/features/configuration/default_cassette_options.feature +100 -0
- data/features/configuration/filter_sensitive_data.feature +153 -0
- data/features/configuration/hook_into.feature +172 -0
- data/features/configuration/ignore_request.feature +192 -0
- data/features/configuration/preserve_exact_body_bytes.feature +108 -0
- data/features/configuration/query_parser.feature +84 -0
- data/features/configuration/uri_parser.feature +89 -0
- data/features/getting_started.md +82 -0
- data/features/hooks/after_http_request.feature +58 -0
- data/features/hooks/around_http_request.feature +57 -0
- data/features/hooks/before_http_request.feature +63 -0
- data/features/hooks/before_playback.feature +184 -0
- data/features/hooks/before_record.feature +172 -0
- data/features/http_libraries/em_http_request.feature +250 -0
- data/features/http_libraries/net_http.feature +179 -0
- data/features/middleware/faraday.feature +56 -0
- data/features/middleware/rack.feature +92 -0
- data/features/record_modes/all.feature +82 -0
- data/features/record_modes/new_episodes.feature +79 -0
- data/features/record_modes/none.feature +72 -0
- data/features/record_modes/once.feature +95 -0
- data/features/request_matching/README.md +30 -0
- data/features/request_matching/body.feature +91 -0
- data/features/request_matching/body_as_json.feature +90 -0
- data/features/request_matching/custom_matcher.feature +135 -0
- data/features/request_matching/headers.feature +85 -0
- data/features/request_matching/host.feature +95 -0
- data/features/request_matching/identical_request_sequence.feature +89 -0
- data/features/request_matching/method.feature +96 -0
- data/features/request_matching/path.feature +96 -0
- data/features/request_matching/playback_repeats.feature +98 -0
- data/features/request_matching/query.feature +97 -0
- data/features/request_matching/uri.feature +94 -0
- data/features/request_matching/uri_without_param.feature +101 -0
- data/features/step_definitions/cli_steps.rb +193 -0
- data/features/support/env.rb +44 -0
- data/features/support/http_lib_filters.rb +53 -0
- data/features/test_frameworks/cucumber.feature +211 -0
- data/features/test_frameworks/rspec_macro.feature +81 -0
- data/features/test_frameworks/rspec_metadata.feature +150 -0
- data/features/test_frameworks/test_unit.feature +49 -0
- data/lib/vcr.rb +347 -0
- data/lib/vcr/cassette.rb +291 -0
- data/lib/vcr/cassette/erb_renderer.rb +55 -0
- data/lib/vcr/cassette/http_interaction_list.rb +108 -0
- data/lib/vcr/cassette/migrator.rb +118 -0
- data/lib/vcr/cassette/persisters.rb +42 -0
- data/lib/vcr/cassette/persisters/file_system.rb +64 -0
- data/lib/vcr/cassette/serializers.rb +57 -0
- data/lib/vcr/cassette/serializers/json.rb +48 -0
- data/lib/vcr/cassette/serializers/psych.rb +48 -0
- data/lib/vcr/cassette/serializers/syck.rb +61 -0
- data/lib/vcr/cassette/serializers/yaml.rb +50 -0
- data/lib/vcr/configuration.rb +555 -0
- data/lib/vcr/deprecations.rb +109 -0
- data/lib/vcr/errors.rb +266 -0
- data/lib/vcr/extensions/net_http_response.rb +36 -0
- data/lib/vcr/library_hooks.rb +18 -0
- data/lib/vcr/library_hooks/excon.rb +27 -0
- data/lib/vcr/library_hooks/fakeweb.rb +196 -0
- data/lib/vcr/library_hooks/faraday.rb +51 -0
- data/lib/vcr/library_hooks/typhoeus.rb +120 -0
- data/lib/vcr/library_hooks/typhoeus_0.4.rb +103 -0
- data/lib/vcr/library_hooks/webmock.rb +164 -0
- data/lib/vcr/middleware/excon.rb +221 -0
- data/lib/vcr/middleware/excon/legacy_methods.rb +33 -0
- data/lib/vcr/middleware/faraday.rb +118 -0
- data/lib/vcr/middleware/rack.rb +79 -0
- data/lib/vcr/request_handler.rb +114 -0
- data/lib/vcr/request_ignorer.rb +43 -0
- data/lib/vcr/request_matcher_registry.rb +149 -0
- data/lib/vcr/structs.rb +578 -0
- data/lib/vcr/tasks/vcr.rake +9 -0
- data/lib/vcr/test_frameworks/cucumber.rb +64 -0
- data/lib/vcr/test_frameworks/rspec.rb +47 -0
- data/lib/vcr/util/hooks.rb +61 -0
- data/lib/vcr/util/internet_connection.rb +43 -0
- data/lib/vcr/util/logger.rb +59 -0
- data/lib/vcr/util/variable_args_block_caller.rb +13 -0
- data/lib/vcr/util/version_checker.rb +48 -0
- data/lib/vcr/version.rb +34 -0
- data/spec/acceptance/threading_spec.rb +34 -0
- data/spec/fixtures/cassette_spec/1_x_cassette.yml +110 -0
- data/spec/fixtures/cassette_spec/empty.yml +0 -0
- data/spec/fixtures/cassette_spec/example.yml +111 -0
- data/spec/fixtures/cassette_spec/with_localhost_requests.yml +111 -0
- data/spec/fixtures/fake_example_responses.yml +110 -0
- data/spec/fixtures/match_requests_on.yml +187 -0
- data/spec/lib/vcr/cassette/erb_renderer_spec.rb +53 -0
- data/spec/lib/vcr/cassette/http_interaction_list_spec.rb +295 -0
- data/spec/lib/vcr/cassette/migrator_spec.rb +195 -0
- data/spec/lib/vcr/cassette/persisters/file_system_spec.rb +69 -0
- data/spec/lib/vcr/cassette/persisters_spec.rb +39 -0
- data/spec/lib/vcr/cassette/serializers_spec.rb +176 -0
- data/spec/lib/vcr/cassette_spec.rb +618 -0
- data/spec/lib/vcr/configuration_spec.rb +326 -0
- data/spec/lib/vcr/deprecations_spec.rb +85 -0
- data/spec/lib/vcr/errors_spec.rb +162 -0
- data/spec/lib/vcr/extensions/net_http_response_spec.rb +86 -0
- data/spec/lib/vcr/library_hooks/excon_spec.rb +104 -0
- data/spec/lib/vcr/library_hooks/fakeweb_spec.rb +169 -0
- data/spec/lib/vcr/library_hooks/faraday_spec.rb +68 -0
- data/spec/lib/vcr/library_hooks/typhoeus_0.4_spec.rb +36 -0
- data/spec/lib/vcr/library_hooks/typhoeus_spec.rb +162 -0
- data/spec/lib/vcr/library_hooks/webmock_spec.rb +118 -0
- data/spec/lib/vcr/library_hooks_spec.rb +51 -0
- data/spec/lib/vcr/middleware/faraday_spec.rb +182 -0
- data/spec/lib/vcr/middleware/rack_spec.rb +115 -0
- data/spec/lib/vcr/request_ignorer_spec.rb +70 -0
- data/spec/lib/vcr/request_matcher_registry_spec.rb +345 -0
- data/spec/lib/vcr/structs_spec.rb +732 -0
- data/spec/lib/vcr/test_frameworks/cucumber_spec.rb +107 -0
- data/spec/lib/vcr/test_frameworks/rspec_spec.rb +83 -0
- data/spec/lib/vcr/util/hooks_spec.rb +158 -0
- data/spec/lib/vcr/util/internet_connection_spec.rb +37 -0
- data/spec/lib/vcr/util/version_checker_spec.rb +31 -0
- data/spec/lib/vcr/version_spec.rb +27 -0
- data/spec/lib/vcr_spec.rb +349 -0
- data/spec/monkey_patches.rb +182 -0
- data/spec/spec_helper.rb +62 -0
- data/spec/support/configuration_stubbing.rb +8 -0
- data/spec/support/cucumber_helpers.rb +35 -0
- data/spec/support/fixnum_extension.rb +10 -0
- data/spec/support/http_library_adapters.rb +289 -0
- data/spec/support/limited_uri.rb +21 -0
- data/spec/support/ruby_interpreter.rb +7 -0
- data/spec/support/shared_example_groups/excon.rb +63 -0
- data/spec/support/shared_example_groups/hook_into_http_library.rb +594 -0
- data/spec/support/shared_example_groups/request_hooks.rb +59 -0
- data/spec/support/sinatra_app.rb +86 -0
- data/spec/support/vcr_localhost_server.rb +76 -0
- data/spec/support/vcr_stub_helpers.rb +17 -0
- metadata +677 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require 'erb'
|
|
2
|
+
|
|
3
|
+
module VCR
|
|
4
|
+
class Cassette
|
|
5
|
+
# @private
|
|
6
|
+
class ERBRenderer
|
|
7
|
+
def initialize(raw_template, erb, cassette_name=nil)
|
|
8
|
+
@raw_template, @erb, @cassette_name = raw_template, erb, cassette_name
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def render
|
|
12
|
+
return @raw_template if @raw_template.nil? || !use_erb?
|
|
13
|
+
binding = binding_for_variables if erb_variables
|
|
14
|
+
template.result(binding)
|
|
15
|
+
rescue NameError => e
|
|
16
|
+
handle_name_error(e)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def handle_name_error(e)
|
|
22
|
+
example_hash = (erb_variables || {}).merge(e.name => 'some value')
|
|
23
|
+
|
|
24
|
+
raise Errors::MissingERBVariableError.new(
|
|
25
|
+
"The ERB in the #{@cassette_name} cassette file references undefined variable #{e.name}. " +
|
|
26
|
+
"Pass it to the cassette using :erb => #{ example_hash.inspect }."
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def use_erb?
|
|
31
|
+
!!@erb
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def erb_variables
|
|
35
|
+
@erb if @erb.is_a?(Hash)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def template
|
|
39
|
+
@template ||= ERB.new(@raw_template)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
@@struct_cache = Hash.new do |hash, attributes|
|
|
43
|
+
hash[attributes] = Struct.new(*attributes)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def variables_object
|
|
47
|
+
@variables_object ||= @@struct_cache[erb_variables.keys].new(*erb_variables.values)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def binding_for_variables
|
|
51
|
+
@binding_for_variables ||= variables_object.instance_eval { binding }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
module VCR
|
|
2
|
+
class Cassette
|
|
3
|
+
# @private
|
|
4
|
+
class HTTPInteractionList
|
|
5
|
+
include Logger::Mixin
|
|
6
|
+
|
|
7
|
+
# @private
|
|
8
|
+
module NullList
|
|
9
|
+
extend self
|
|
10
|
+
def response_for(*a); nil; end
|
|
11
|
+
def has_interaction_matching?(*a); false; end
|
|
12
|
+
def has_used_interaction_matching?(*a); false; end
|
|
13
|
+
def remaining_unused_interaction_count(*a); 0; end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :interactions, :request_matchers, :allow_playback_repeats, :parent_list
|
|
17
|
+
|
|
18
|
+
def initialize(interactions, request_matchers, allow_playback_repeats = false, parent_list = NullList, log_prefix = '')
|
|
19
|
+
@interactions = interactions.dup
|
|
20
|
+
@request_matchers = request_matchers
|
|
21
|
+
@allow_playback_repeats = allow_playback_repeats
|
|
22
|
+
@parent_list = parent_list
|
|
23
|
+
@used_interactions = []
|
|
24
|
+
@log_prefix = log_prefix
|
|
25
|
+
|
|
26
|
+
interaction_summaries = interactions.map { |i| "#{request_summary(i.request)} => #{response_summary(i.response)}" }
|
|
27
|
+
log "Initialized HTTPInteractionList with request matchers #{request_matchers.inspect} and #{interactions.size} interaction(s): { #{interaction_summaries.join(', ')} }", 1
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def response_for(request)
|
|
31
|
+
if index = matching_interaction_index_for(request)
|
|
32
|
+
interaction = @interactions.delete_at(index)
|
|
33
|
+
@used_interactions.unshift interaction
|
|
34
|
+
log "Found matching interaction for #{request_summary(request)} at index #{index}: #{response_summary(interaction.response)}", 1
|
|
35
|
+
interaction.response
|
|
36
|
+
elsif interaction = matching_used_interaction_for(request)
|
|
37
|
+
interaction.response
|
|
38
|
+
else
|
|
39
|
+
@parent_list.response_for(request)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def has_interaction_matching?(request)
|
|
44
|
+
!!matching_interaction_index_for(request) ||
|
|
45
|
+
!!matching_used_interaction_for(request) ||
|
|
46
|
+
@parent_list.has_interaction_matching?(request)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def has_used_interaction_matching?(request)
|
|
50
|
+
@used_interactions.any? { |i| interaction_matches_request?(request, i) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def remaining_unused_interaction_count
|
|
54
|
+
@interactions.size
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Checks if there are no unused interactions left.
|
|
58
|
+
#
|
|
59
|
+
# @raise [VCR::Errors::UnusedHTTPInteractionError] if not all interactions were played back.
|
|
60
|
+
def assert_no_unused_interactions!
|
|
61
|
+
return unless has_unused_interactions?
|
|
62
|
+
logger = Logger.new(nil)
|
|
63
|
+
|
|
64
|
+
descriptions = @interactions.map do |i|
|
|
65
|
+
" - #{logger.request_summary(i.request, @request_matchers)} => #{logger.response_summary(i.response)}"
|
|
66
|
+
end.join("\n")
|
|
67
|
+
|
|
68
|
+
raise Errors::UnusedHTTPInteractionError, "There are unused HTTP interactions left in the cassette:\n#{descriptions}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
# @return [Boolean] Whether or not there are unused interactions left in the list.
|
|
74
|
+
def has_unused_interactions?
|
|
75
|
+
@interactions.size > 0
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def request_summary(request)
|
|
79
|
+
super(request, @request_matchers)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def matching_interaction_index_for(request)
|
|
83
|
+
@interactions.index { |i| interaction_matches_request?(request, i) }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def matching_used_interaction_for(request)
|
|
87
|
+
return nil unless @allow_playback_repeats
|
|
88
|
+
@used_interactions.find { |i| interaction_matches_request?(request, i) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def interaction_matches_request?(request, interaction)
|
|
92
|
+
log "Checking if #{request_summary(request)} matches #{request_summary(interaction.request)} using #{@request_matchers.inspect}", 1
|
|
93
|
+
@request_matchers.all? do |matcher_name|
|
|
94
|
+
matcher = VCR.request_matchers[matcher_name]
|
|
95
|
+
matcher.matches?(request, interaction.request).tap do |matched|
|
|
96
|
+
matched = matched ? 'matched' : 'did not match'
|
|
97
|
+
log "#{matcher_name} (#{matched}): current request #{request_summary(request)} vs #{request_summary(interaction.request)}", 2
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def log_prefix
|
|
103
|
+
@log_prefix
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
require 'vcr'
|
|
2
|
+
|
|
3
|
+
module VCR
|
|
4
|
+
class Cassette
|
|
5
|
+
# @private
|
|
6
|
+
class Migrator
|
|
7
|
+
def initialize(dir, out = $stdout)
|
|
8
|
+
@dir, @out = dir, out
|
|
9
|
+
@yaml_load_errors = yaml_load_errors
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def migrate!
|
|
13
|
+
@out.puts "Migrating VCR cassettes in #{@dir}..."
|
|
14
|
+
Dir["#{@dir}/**/*.yml"].each do |cassette|
|
|
15
|
+
migrate_cassette(cassette)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def migrate_cassette(cassette)
|
|
22
|
+
unless http_interactions = load_yaml(cassette)
|
|
23
|
+
@out.puts " - Ignored #{relative_casssette_name(cassette)} since it could not be parsed as YAML (does it have some ERB?)"
|
|
24
|
+
return
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
unless valid_vcr_1_cassette?(http_interactions)
|
|
28
|
+
@out.puts " - Ignored #{relative_casssette_name(cassette)} since it does not appear to be a valid VCR 1.x cassette"
|
|
29
|
+
return
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
http_interactions.map! do |interaction|
|
|
33
|
+
interaction.response.adapter_metadata = {}
|
|
34
|
+
interaction.recorded_at = File.mtime(cassette)
|
|
35
|
+
remove_unnecessary_standard_port(interaction)
|
|
36
|
+
denormalize_http_header_keys(interaction.request)
|
|
37
|
+
denormalize_http_header_keys(interaction.response)
|
|
38
|
+
normalize_body(interaction.request)
|
|
39
|
+
normalize_body(interaction.response)
|
|
40
|
+
interaction.to_hash
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
hash = {
|
|
44
|
+
"http_interactions" => http_interactions,
|
|
45
|
+
"recorded_with" => "VCR #{VCR.version}"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
def hash.each
|
|
49
|
+
yield 'http_interactions', self['http_interactions']
|
|
50
|
+
yield 'recorded_with', self['recorded_with']
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
File.open(cassette, 'w') { |f| f.write ::YAML.dump(hash) }
|
|
54
|
+
@out.puts " - Migrated #{relative_casssette_name(cassette)}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def load_yaml(cassette)
|
|
58
|
+
::YAML.load_file(cassette)
|
|
59
|
+
rescue *@yaml_load_errors
|
|
60
|
+
return nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def yaml_load_errors
|
|
64
|
+
[ArgumentError].tap do |errors|
|
|
65
|
+
errors << Psych::SyntaxError if defined?(Psych::SyntaxError)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def relative_casssette_name(cassette)
|
|
70
|
+
cassette.gsub(%r|\A#{Regexp.escape(@dir)}/?|, '')
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def valid_vcr_1_cassette?(content)
|
|
74
|
+
content.is_a?(Array) &&
|
|
75
|
+
content.map(&:class).uniq == [HTTPInteraction]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def remove_unnecessary_standard_port(interaction)
|
|
79
|
+
uri = VCR.configuration.uri_parser.parse(interaction.request.uri)
|
|
80
|
+
if uri.scheme == 'http' && uri.port == 80 ||
|
|
81
|
+
uri.scheme == 'https' && uri.port == 443
|
|
82
|
+
uri.port = nil
|
|
83
|
+
interaction.request.uri = uri.to_s
|
|
84
|
+
end
|
|
85
|
+
rescue URI::InvalidURIError
|
|
86
|
+
# ignore this URI.
|
|
87
|
+
# This can occur when the user uses the filter_sensitive_data option
|
|
88
|
+
# to put a substitution string in their URI
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def denormalize_http_header_keys(object)
|
|
92
|
+
object.headers = {}.tap do |denormalized|
|
|
93
|
+
object.headers.each do |k, v|
|
|
94
|
+
denormalized[denormalize_header_key(k)] = v
|
|
95
|
+
end if object.headers
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def denormalize_header_key(key)
|
|
100
|
+
key.split('-'). # 'user-agent' => %w(user agent)
|
|
101
|
+
each { |w| w.capitalize! }. # => %w(User Agent)
|
|
102
|
+
join('-')
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
EMPTY_STRING = if String.method_defined?(:force_encoding)
|
|
106
|
+
''.force_encoding("US-ASCII")
|
|
107
|
+
else
|
|
108
|
+
''
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def normalize_body(object)
|
|
112
|
+
object.body = EMPTY_STRING if object.body.nil?
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module VCR
|
|
2
|
+
class Cassette
|
|
3
|
+
# Keeps track of the cassette persisters in a hash-like object.
|
|
4
|
+
class Persisters
|
|
5
|
+
autoload :FileSystem, 'vcr/cassette/persisters/file_system'
|
|
6
|
+
|
|
7
|
+
# @private
|
|
8
|
+
def initialize
|
|
9
|
+
@persisters = {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Gets the named persister.
|
|
13
|
+
#
|
|
14
|
+
# @param name [Symbol] the name of the persister
|
|
15
|
+
# @return the named persister
|
|
16
|
+
# @raise [ArgumentError] if there is not a persister for the given name
|
|
17
|
+
def [](name)
|
|
18
|
+
@persisters.fetch(name) do |_|
|
|
19
|
+
@persisters[name] = case name
|
|
20
|
+
when :file_system then FileSystem
|
|
21
|
+
else raise ArgumentError, "The requested VCR cassette persister " +
|
|
22
|
+
"(#{name.inspect}) is not registered."
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Registers a persister.
|
|
28
|
+
#
|
|
29
|
+
# @param name [Symbol] the name of the persister
|
|
30
|
+
# @param value [#[], #[]=] the persister object. It must implement `[]` and `[]=`.
|
|
31
|
+
def []=(name, value)
|
|
32
|
+
if @persisters.has_key?(name)
|
|
33
|
+
warn "WARNING: There is already a VCR cassette persister " +
|
|
34
|
+
"registered for #{name.inspect}. Overriding it."
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
@persisters[name] = value
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
3
|
+
module VCR
|
|
4
|
+
class Cassette
|
|
5
|
+
class Persisters
|
|
6
|
+
# The only built-in cassette persister. Persists cassettes to the file system.
|
|
7
|
+
module FileSystem
|
|
8
|
+
extend self
|
|
9
|
+
|
|
10
|
+
# @private
|
|
11
|
+
attr_reader :storage_location
|
|
12
|
+
|
|
13
|
+
# @private
|
|
14
|
+
def storage_location=(dir)
|
|
15
|
+
FileUtils.mkdir_p(dir) if dir
|
|
16
|
+
@storage_location = dir ? absolute_path_for(dir) : nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Gets the cassette for the given storage key (file name).
|
|
20
|
+
#
|
|
21
|
+
# @param [String] file_name the file name
|
|
22
|
+
# @return [String] the cassette content
|
|
23
|
+
def [](file_name)
|
|
24
|
+
path = absolute_path_to_file(file_name)
|
|
25
|
+
return nil unless File.exist?(path)
|
|
26
|
+
File.read(path)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Sets the cassette for the given storage key (file name).
|
|
30
|
+
#
|
|
31
|
+
# @param [String] file_name the file name
|
|
32
|
+
# @param [String] content the content to store
|
|
33
|
+
def []=(file_name, content)
|
|
34
|
+
path = absolute_path_to_file(file_name)
|
|
35
|
+
directory = File.dirname(path)
|
|
36
|
+
FileUtils.mkdir_p(directory) unless File.exist?(directory)
|
|
37
|
+
File.open(path, 'w') { |f| f.write(content) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @private
|
|
41
|
+
def absolute_path_to_file(file_name)
|
|
42
|
+
return nil unless storage_location
|
|
43
|
+
File.join(storage_location, sanitized_file_name_from(file_name))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def absolute_path_for(path)
|
|
49
|
+
Dir.chdir(path) { Dir.pwd }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def sanitized_file_name_from(file_name)
|
|
53
|
+
parts = file_name.to_s.split('.')
|
|
54
|
+
|
|
55
|
+
if parts.size > 1 && !parts.last.include?(File::SEPARATOR)
|
|
56
|
+
file_extension = '.' + parts.pop
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
parts.join('.').gsub(/[^\w\-\/]+/, '_') + file_extension.to_s
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module VCR
|
|
2
|
+
class Cassette
|
|
3
|
+
# Keeps track of the cassette serializers in a hash-like object.
|
|
4
|
+
class Serializers
|
|
5
|
+
autoload :YAML, 'vcr/cassette/serializers/yaml'
|
|
6
|
+
autoload :Syck, 'vcr/cassette/serializers/syck'
|
|
7
|
+
autoload :Psych, 'vcr/cassette/serializers/psych'
|
|
8
|
+
autoload :JSON, 'vcr/cassette/serializers/json'
|
|
9
|
+
|
|
10
|
+
# @private
|
|
11
|
+
def initialize
|
|
12
|
+
@serializers = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Gets the named serializer.
|
|
16
|
+
#
|
|
17
|
+
# @param name [Symbol] the name of the serializer
|
|
18
|
+
# @return the named serializer
|
|
19
|
+
# @raise [ArgumentError] if there is not a serializer for the given name
|
|
20
|
+
def [](name)
|
|
21
|
+
@serializers.fetch(name) do |_|
|
|
22
|
+
@serializers[name] = case name
|
|
23
|
+
when :yaml then YAML
|
|
24
|
+
when :syck then Syck
|
|
25
|
+
when :psych then Psych
|
|
26
|
+
when :json then JSON
|
|
27
|
+
else raise ArgumentError.new("The requested VCR cassette serializer (#{name.inspect}) is not registered.")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Registers a serializer.
|
|
33
|
+
#
|
|
34
|
+
# @param name [Symbol] the name of the serializer
|
|
35
|
+
# @param value [#file_extension, #serialize, #deserialize] the serializer object. It must implement
|
|
36
|
+
# `file_extension()`, `serialize(Hash)` and `deserialize(String)`.
|
|
37
|
+
def []=(name, value)
|
|
38
|
+
if @serializers.has_key?(name)
|
|
39
|
+
warn "WARNING: There is already a VCR cassette serializer registered for #{name.inspect}. Overriding it."
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
@serializers[name] = value
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @private
|
|
47
|
+
module EncodingErrorHandling
|
|
48
|
+
def handle_encoding_errors
|
|
49
|
+
yield
|
|
50
|
+
rescue *self::ENCODING_ERRORS => e
|
|
51
|
+
e.message << "\nNote: Using VCR's `:preserve_exact_body_bytes` option may help prevent this error in the future."
|
|
52
|
+
raise
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|