vcr 2.0.0.beta1 → 2.0.0.beta2

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.
Files changed (88) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +3 -0
  3. data/CHANGELOG.md +37 -2
  4. data/Gemfile +2 -2
  5. data/README.md +10 -1
  6. data/Rakefile +43 -7
  7. data/Upgrade.md +45 -0
  8. data/features/.nav +1 -0
  9. data/features/cassettes/automatic_re_recording.feature +19 -17
  10. data/features/cassettes/dynamic_erb.feature +32 -28
  11. data/features/cassettes/exclusive.feature +28 -24
  12. data/features/cassettes/format.feature +213 -31
  13. data/features/cassettes/update_content_length_header.feature +20 -18
  14. data/features/configuration/filter_sensitive_data.feature +4 -4
  15. data/features/configuration/hooks.feature +27 -23
  16. data/features/http_libraries/em_http_request.feature +79 -75
  17. data/features/record_modes/all.feature +14 -14
  18. data/features/record_modes/new_episodes.feature +15 -15
  19. data/features/record_modes/none.feature +15 -15
  20. data/features/record_modes/once.feature +15 -15
  21. data/features/request_matching/body.feature +25 -23
  22. data/features/request_matching/custom_matcher.feature +25 -23
  23. data/features/request_matching/headers.feature +32 -36
  24. data/features/request_matching/host.feature +27 -25
  25. data/features/request_matching/identical_request_sequence.feature +27 -25
  26. data/features/request_matching/method.feature +27 -25
  27. data/features/request_matching/path.feature +27 -25
  28. data/features/request_matching/playback_repeats.feature +27 -25
  29. data/features/request_matching/uri.feature +27 -25
  30. data/features/request_matching/uri_without_param.feature +28 -26
  31. data/features/step_definitions/cli_steps.rb +71 -17
  32. data/features/support/env.rb +3 -1
  33. data/features/support/http_lib_filters.rb +6 -3
  34. data/features/support/vcr_cucumber_helpers.rb +4 -2
  35. data/lib/vcr.rb +6 -2
  36. data/lib/vcr/cassette.rb +75 -51
  37. data/lib/vcr/cassette/migrator.rb +111 -0
  38. data/lib/vcr/cassette/serializers.rb +35 -0
  39. data/lib/vcr/cassette/serializers/json.rb +23 -0
  40. data/lib/vcr/cassette/serializers/psych.rb +24 -0
  41. data/lib/vcr/cassette/serializers/syck.rb +35 -0
  42. data/lib/vcr/cassette/serializers/yaml.rb +24 -0
  43. data/lib/vcr/configuration.rb +6 -1
  44. data/lib/vcr/errors.rb +1 -1
  45. data/lib/vcr/library_hooks/excon.rb +1 -7
  46. data/lib/vcr/library_hooks/typhoeus.rb +6 -22
  47. data/lib/vcr/library_hooks/webmock.rb +1 -1
  48. data/lib/vcr/middleware/faraday.rb +1 -1
  49. data/lib/vcr/request_matcher_registry.rb +43 -30
  50. data/lib/vcr/structs.rb +209 -0
  51. data/lib/vcr/tasks/vcr.rake +9 -0
  52. data/lib/vcr/version.rb +1 -1
  53. data/spec/fixtures/cassette_spec/1_x_cassette.yml +110 -0
  54. data/spec/fixtures/cassette_spec/example.yml +79 -78
  55. data/spec/fixtures/cassette_spec/with_localhost_requests.yml +79 -77
  56. data/spec/fixtures/fake_example.com_responses.yml +78 -76
  57. data/spec/fixtures/match_requests_on.yml +147 -145
  58. data/spec/monkey_patches.rb +5 -5
  59. data/spec/support/http_library_adapters.rb +48 -0
  60. data/spec/support/shared_example_groups/hook_into_http_library.rb +53 -20
  61. data/spec/support/sinatra_app.rb +12 -0
  62. data/spec/vcr/cassette/http_interaction_list_spec.rb +1 -1
  63. data/spec/vcr/cassette/migrator_spec.rb +183 -0
  64. data/spec/vcr/cassette/serializers_spec.rb +122 -0
  65. data/spec/vcr/cassette_spec.rb +147 -83
  66. data/spec/vcr/configuration_spec.rb +11 -1
  67. data/spec/vcr/library_hooks/typhoeus_spec.rb +3 -3
  68. data/spec/vcr/library_hooks/webmock_spec.rb +7 -1
  69. data/spec/vcr/request_ignorer_spec.rb +1 -1
  70. data/spec/vcr/request_matcher_registry_spec.rb +46 -4
  71. data/spec/vcr/structs_spec.rb +309 -0
  72. data/spec/vcr_spec.rb +7 -0
  73. data/vcr.gemspec +9 -12
  74. metadata +75 -61
  75. data/lib/vcr/structs/http_interaction.rb +0 -58
  76. data/lib/vcr/structs/normalizers/body.rb +0 -24
  77. data/lib/vcr/structs/normalizers/header.rb +0 -64
  78. data/lib/vcr/structs/normalizers/status_message.rb +0 -17
  79. data/lib/vcr/structs/normalizers/uri.rb +0 -34
  80. data/lib/vcr/structs/request.rb +0 -13
  81. data/lib/vcr/structs/response.rb +0 -13
  82. data/lib/vcr/structs/response_status.rb +0 -5
  83. data/lib/vcr/util/yaml.rb +0 -11
  84. data/spec/support/shared_example_groups/normalizers.rb +0 -94
  85. data/spec/vcr/structs/http_interaction_spec.rb +0 -89
  86. data/spec/vcr/structs/request_spec.rb +0 -39
  87. data/spec/vcr/structs/response_spec.rb +0 -44
  88. data/spec/vcr/structs/response_status_spec.rb +0 -9
@@ -0,0 +1,209 @@
1
+ require 'time'
2
+ require 'forwardable'
3
+
4
+ module VCR
5
+ module Normalizers
6
+ module Body
7
+ def initialize(*args)
8
+ super
9
+ # Ensure that the body is a raw string, in case the string instance
10
+ # has been subclassed or extended with additional instance variables
11
+ # or attributes, so that it is serialized to YAML as a raw string.
12
+ # This is needed for rest-client. See this ticket for more info:
13
+ # http://github.com/myronmarston/vcr/issues/4
14
+ self.body = String.new(body.to_s)
15
+ end
16
+ end
17
+
18
+ module Header
19
+ def initialize(*args)
20
+ super
21
+ normalize_headers
22
+ end
23
+
24
+ private
25
+
26
+ def normalize_headers
27
+ new_headers = {}
28
+
29
+ headers.each do |k, v|
30
+ val_array = case v
31
+ when Array then v
32
+ when nil then []
33
+ else [v]
34
+ end
35
+
36
+ new_headers[k] = convert_to_raw_strings(val_array)
37
+ end if headers
38
+
39
+ self.headers = new_headers
40
+ end
41
+
42
+ def convert_to_raw_strings(array)
43
+ # Ensure the values are raw strings.
44
+ # Apparently for Paperclip uploads to S3, headers
45
+ # get serialized with some extra stuff which leads
46
+ # to a seg fault. See this issue for more info:
47
+ # https://github.com/myronmarston/vcr/issues#issue/39
48
+ array.map do |v|
49
+ case v
50
+ when String; String.new(v)
51
+ when Array; convert_to_raw_strings(v)
52
+ else v
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ module OrderedHashSerializer
60
+ def each
61
+ @ordered_keys.each do |key|
62
+ yield key, self[key]
63
+ end
64
+ end
65
+
66
+ if RUBY_VERSION =~ /1.9/
67
+ # 1.9 hashes are already ordered.
68
+ def self.apply_to(*args); end
69
+ else
70
+ def self.apply_to(hash, keys)
71
+ hash.instance_variable_set(:@ordered_keys, keys)
72
+ hash.extend self
73
+ end
74
+ end
75
+ end
76
+
77
+ class Request < Struct.new(:method, :uri, :body, :headers)
78
+ include Normalizers::Header
79
+ include Normalizers::Body
80
+
81
+ def to_hash
82
+ {
83
+ 'method' => method.to_s,
84
+ 'uri' => uri,
85
+ 'body' => body,
86
+ 'headers' => headers
87
+ }.tap { |h| OrderedHashSerializer.apply_to(h, members) }
88
+ end
89
+
90
+ def self.from_hash(hash)
91
+ method = hash['method']
92
+ method &&= method.to_sym
93
+ new method,
94
+ hash['uri'],
95
+ hash['body'],
96
+ hash['headers']
97
+ end
98
+
99
+ @@object_method = Object.instance_method(:method)
100
+ def method(*args)
101
+ return super if args.empty?
102
+ @@object_method.bind(self).call(*args)
103
+ end
104
+ end
105
+
106
+ class HTTPInteraction < Struct.new(:request, :response, :recorded_at)
107
+ extend ::Forwardable
108
+ def_delegators :request, :uri, :method
109
+
110
+ def initialize(*args)
111
+ @ignored = false
112
+ super
113
+ self.recorded_at ||= Time.now
114
+ end
115
+
116
+ def to_hash
117
+ {
118
+ 'request' => request.to_hash,
119
+ 'response' => response.to_hash,
120
+ 'recorded_at' => recorded_at.httpdate
121
+ }.tap do |hash|
122
+ OrderedHashSerializer.apply_to(hash, members)
123
+ end
124
+ end
125
+
126
+ def self.from_hash(hash)
127
+ new Request.from_hash(hash.fetch('request', {})),
128
+ Response.from_hash(hash.fetch('response', {})),
129
+ Time.httpdate(hash.fetch('recorded_at'))
130
+ end
131
+
132
+ def ignore!
133
+ @ignored = true
134
+ end
135
+
136
+ def ignored?
137
+ !!@ignored
138
+ end
139
+
140
+ def filter!(text, replacement_text)
141
+ return self if [text, replacement_text].any? { |t| t.to_s.empty? }
142
+ filter_object!(self, text, replacement_text)
143
+ end
144
+
145
+ private
146
+
147
+ def filter_object!(object, text, replacement_text)
148
+ if object.respond_to?(:gsub)
149
+ object.gsub!(text, replacement_text) if object.include?(text)
150
+ elsif Hash === object
151
+ filter_hash!(object, text, replacement_text)
152
+ elsif object.respond_to?(:each)
153
+ # This handles nested arrays and structs
154
+ object.each { |o| filter_object!(o, text, replacement_text) }
155
+ end
156
+
157
+ object
158
+ end
159
+
160
+ def filter_hash!(hash, text, replacement_text)
161
+ filter_object!(hash.values, text, replacement_text)
162
+
163
+ hash.keys.each do |k|
164
+ new_key = filter_object!(k.dup, text, replacement_text)
165
+ hash[new_key] = hash.delete(k) unless k == new_key
166
+ end
167
+ end
168
+ end
169
+
170
+ class Response < Struct.new(:status, :headers, :body, :http_version)
171
+ include Normalizers::Header
172
+ include Normalizers::Body
173
+
174
+ def to_hash
175
+ {
176
+ 'status' => status.to_hash,
177
+ 'headers' => headers,
178
+ 'body' => body,
179
+ 'http_version' => http_version
180
+ }.tap { |h| OrderedHashSerializer.apply_to(h, members) }
181
+ end
182
+
183
+ def self.from_hash(hash)
184
+ new ResponseStatus.from_hash(hash.fetch('status', {})),
185
+ hash['headers'],
186
+ hash['body'],
187
+ hash['http_version']
188
+ end
189
+
190
+ def update_content_length_header
191
+ # TODO: should this be the bytesize?
192
+ value = body ? body.length.to_s : '0'
193
+ key = %w[ Content-Length content-length ].find { |k| headers.has_key?(k) }
194
+ headers[key] = [value] if key
195
+ end
196
+ end
197
+
198
+ class ResponseStatus < Struct.new(:code, :message)
199
+ def to_hash
200
+ {
201
+ 'code' => code, 'message' => message
202
+ }.tap { |h| OrderedHashSerializer.apply_to(h, members) }
203
+ end
204
+
205
+ def self.from_hash(hash)
206
+ new hash['code'], hash['message']
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,9 @@
1
+ namespace :vcr do
2
+ desc "Migrate cassettes from the VCR 1.x format to the VCR 2.x format."
3
+ task :migrate_cassettes do
4
+ dir = ENV.fetch('DIR') { raise "You must pass the cassette library directory as DIR=<directory>" }
5
+ require 'vcr/cassette/migrator'
6
+ VCR::Cassette::Migrator.new(dir).migrate!
7
+ end
8
+ end
9
+
data/lib/vcr/version.rb CHANGED
@@ -3,7 +3,7 @@ module VCR
3
3
 
4
4
  def version
5
5
  @version ||= begin
6
- string = '2.0.0.beta1'
6
+ string = '2.0.0.beta2'
7
7
 
8
8
  def string.parts
9
9
  split('.').map { |p| p.to_i }
@@ -0,0 +1,110 @@
1
+ ---
2
+ - !ruby/struct:VCR::HTTPInteraction
3
+ request: !ruby/struct:VCR::Request
4
+ method: :get
5
+ uri: http://example.com:80/
6
+ body:
7
+ headers:
8
+ response: !ruby/struct:VCR::Response
9
+ status: !ruby/struct:VCR::ResponseStatus
10
+ code: 200
11
+ message: OK
12
+ headers:
13
+ last-modified:
14
+ - Tue, 15 Nov 2005 13:24:10 GMT
15
+ connection:
16
+ - Keep-Alive
17
+ date:
18
+ - Thu, 25 Feb 2010 07:52:38 GMT
19
+ content-type:
20
+ - text/html; charset=UTF-8
21
+ etag:
22
+ - "\"24ec5-1b6-4059a80bfd280\""
23
+ server:
24
+ - Apache/2.2.3 (CentOS)
25
+ content-length:
26
+ - "438"
27
+ age:
28
+ - "3009"
29
+ accept-ranges:
30
+ - bytes
31
+ body: |
32
+ <HTML>
33
+ <HEAD>
34
+ <TITLE>Example Web Page</TITLE>
35
+ </HEAD>
36
+ <body>
37
+ <p>You have reached this web page by typing &quot;example.com&quot;,
38
+ &quot;example.net&quot;,
39
+ or &quot;example.org&quot; into your web browser.</p>
40
+ <p>These domain names are reserved for use in documentation and are not available
41
+ for registration. See <a href="http://www.rfc-editor.org/rfc/rfc2606.txt">RFC
42
+ 2606</a>, Section 3.</p>
43
+ </BODY>
44
+ </HTML>
45
+
46
+ http_version: "1.1"
47
+ - !ruby/struct:VCR::HTTPInteraction
48
+ request: !ruby/struct:VCR::Request
49
+ method: :get
50
+ uri: http://example.com:80/foo
51
+ body:
52
+ headers:
53
+ response: !ruby/struct:VCR::Response
54
+ status: !ruby/struct:VCR::ResponseStatus
55
+ code: 404
56
+ message: Not Found
57
+ headers:
58
+ connection:
59
+ - close
60
+ content-type:
61
+ - text/html; charset=iso-8859-1
62
+ date:
63
+ - Thu, 25 Feb 2010 07:52:38 GMT
64
+ server:
65
+ - Apache/2.2.3 (CentOS)
66
+ content-length:
67
+ - "277"
68
+ body: |
69
+ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
70
+ <html><head>
71
+ <title>404 Not Found</title>
72
+ </head><body>
73
+ <h1>Not Found</h1>
74
+ <p>The requested URL /foo was not found on this server.</p>
75
+ <hr>
76
+ <address>Apache/2.2.3 (CentOS) Server at example.com Port 80</address>
77
+ </body></html>
78
+
79
+ http_version: "1.1"
80
+ - !ruby/struct:VCR::HTTPInteraction
81
+ request: !ruby/struct:VCR::Request
82
+ method: :get
83
+ uri: http://example.com:80/
84
+ body:
85
+ headers:
86
+ response: !ruby/struct:VCR::Response
87
+ status: !ruby/struct:VCR::ResponseStatus
88
+ code: 200
89
+ message: OK
90
+ headers:
91
+ last-modified:
92
+ - Tue, 15 Nov 2005 13:24:10 GMT
93
+ connection:
94
+ - Keep-Alive
95
+ date:
96
+ - Thu, 25 Feb 2010 07:52:38 GMT
97
+ content-type:
98
+ - text/html; charset=UTF-8
99
+ etag:
100
+ - "\"24ec5-1b6-4059a80bfd280\""
101
+ server:
102
+ - Apache/2.2.3 (CentOS)
103
+ content-length:
104
+ - "438"
105
+ age:
106
+ - "3009"
107
+ accept-ranges:
108
+ - bytes
109
+ body: Another example.com response
110
+ http_version: "1.1"
@@ -1,110 +1,111 @@
1
- ---
2
- - !ruby/struct:VCR::HTTPInteraction
3
- request: !ruby/struct:VCR::Request
4
- method: :get
5
- uri: http://example.com:80/
6
- body:
7
- headers:
8
- response: !ruby/struct:VCR::Response
9
- status: !ruby/struct:VCR::ResponseStatus
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: http://example.com/
6
+ body: ''
7
+ headers: {}
8
+ response:
9
+ status:
10
10
  code: 200
11
11
  message: OK
12
- headers:
13
- last-modified:
12
+ headers:
13
+ Last-Modified:
14
14
  - Tue, 15 Nov 2005 13:24:10 GMT
15
- connection:
15
+ Connection:
16
16
  - Keep-Alive
17
- date:
17
+ Date:
18
18
  - Thu, 25 Feb 2010 07:52:38 GMT
19
- content-type:
19
+ Content-Type:
20
20
  - text/html; charset=UTF-8
21
- etag:
22
- - "\"24ec5-1b6-4059a80bfd280\""
23
- server:
21
+ Etag:
22
+ - ! '"24ec5-1b6-4059a80bfd280"'
23
+ Server:
24
24
  - Apache/2.2.3 (CentOS)
25
- content-length:
26
- - "438"
27
- age:
28
- - "3009"
29
- accept-ranges:
25
+ Content-Length:
26
+ - '438'
27
+ Age:
28
+ - '3009'
29
+ Accept-Ranges:
30
30
  - bytes
31
- body: |
32
- <HTML>
33
- <HEAD>
34
- <TITLE>Example Web Page</TITLE>
35
- </HEAD>
36
- <body>
37
- <p>You have reached this web page by typing &quot;example.com&quot;,
38
- &quot;example.net&quot;,
39
- or &quot;example.org&quot; into your web browser.</p>
40
- <p>These domain names are reserved for use in documentation and are not available
41
- for registration. See <a href="http://www.rfc-editor.org/rfc/rfc2606.txt">RFC
42
- 2606</a>, Section 3.</p>
43
- </BODY>
44
- </HTML>
45
-
46
- http_version: "1.1"
47
- - !ruby/struct:VCR::HTTPInteraction
48
- request: !ruby/struct:VCR::Request
49
- method: :get
50
- uri: http://example.com:80/foo
51
- body:
52
- headers:
53
- response: !ruby/struct:VCR::Response
54
- status: !ruby/struct:VCR::ResponseStatus
31
+ body: ! "<HTML>\n<HEAD>\n <TITLE>Example Web Page</TITLE>\n</HEAD> \n<body> \n<p>You
32
+ have reached this web page by typing &quot;example.com&quot;,\n&quot;example.net&quot;,\n
33
+ \ or &quot;example.org&quot; into your web browser.</p>\n<p>These domain names
34
+ are reserved for use in documentation and are not available \n for registration.
35
+ See <a href=\"http://www.rfc-editor.org/rfc/rfc2606.txt\">RFC \n 2606</a>,
36
+ Section 3.</p>\n</BODY>\n</HTML>\n"
37
+ http_version: '1.1'
38
+ recorded_at: Tue, 01 Nov 2011 04:49:54 GMT
39
+ - request:
40
+ method: get
41
+ uri: http://example.com/foo
42
+ body: ''
43
+ headers: {}
44
+ response:
45
+ status:
55
46
  code: 404
56
47
  message: Not Found
57
- headers:
58
- connection:
48
+ headers:
49
+ Connection:
59
50
  - close
60
- content-type:
51
+ Content-Type:
61
52
  - text/html; charset=iso-8859-1
62
- date:
53
+ Date:
63
54
  - Thu, 25 Feb 2010 07:52:38 GMT
64
- server:
55
+ Server:
65
56
  - Apache/2.2.3 (CentOS)
66
- content-length:
67
- - "277"
68
- body: |
69
- <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
57
+ Content-Length:
58
+ - '277'
59
+ body: ! '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
60
+
70
61
  <html><head>
62
+
71
63
  <title>404 Not Found</title>
64
+
72
65
  </head><body>
66
+
73
67
  <h1>Not Found</h1>
68
+
74
69
  <p>The requested URL /foo was not found on this server.</p>
70
+
75
71
  <hr>
72
+
76
73
  <address>Apache/2.2.3 (CentOS) Server at example.com Port 80</address>
74
+
77
75
  </body></html>
78
76
 
79
- http_version: "1.1"
80
- - !ruby/struct:VCR::HTTPInteraction
81
- request: !ruby/struct:VCR::Request
82
- method: :get
83
- uri: http://example.com:80/
84
- body:
85
- headers:
86
- response: !ruby/struct:VCR::Response
87
- status: !ruby/struct:VCR::ResponseStatus
77
+ '
78
+ http_version: '1.1'
79
+ recorded_at: Tue, 01 Nov 2011 04:49:54 GMT
80
+ - request:
81
+ method: get
82
+ uri: http://example.com/
83
+ body: ''
84
+ headers: {}
85
+ response:
86
+ status:
88
87
  code: 200
89
88
  message: OK
90
- headers:
91
- last-modified:
89
+ headers:
90
+ Last-Modified:
92
91
  - Tue, 15 Nov 2005 13:24:10 GMT
93
- connection:
92
+ Connection:
94
93
  - Keep-Alive
95
- date:
94
+ Date:
96
95
  - Thu, 25 Feb 2010 07:52:38 GMT
97
- content-type:
96
+ Content-Type:
98
97
  - text/html; charset=UTF-8
99
- etag:
100
- - "\"24ec5-1b6-4059a80bfd280\""
101
- server:
98
+ Etag:
99
+ - ! '"24ec5-1b6-4059a80bfd280"'
100
+ Server:
102
101
  - Apache/2.2.3 (CentOS)
103
- content-length:
104
- - "438"
105
- age:
106
- - "3009"
107
- accept-ranges:
102
+ Content-Length:
103
+ - '438'
104
+ Age:
105
+ - '3009'
106
+ Accept-Ranges:
108
107
  - bytes
109
108
  body: Another example.com response
110
- http_version: "1.1"
109
+ http_version: '1.1'
110
+ recorded_at: Tue, 01 Nov 2011 04:49:54 GMT
111
+ recorded_with: VCR 1.11.3