vcr 2.0.0.beta1 → 2.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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