cucumber 4.0.0.rc.5 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 388c05be14607a5aaa4acaef66d2bb5f6dab1258bc3e785e71a077fae3d366a7
4
- data.tar.gz: 2546749fa602b12b756e0dee45ff756fc89507329504b94f633b39cd5692464e
3
+ metadata.gz: b3c9c9e718ba5a344adfb97aaebfb58f5c99bb6f8142bb74c115c9b7fe94d0f3
4
+ data.tar.gz: 3a70805fa19b0b83393e61853a6803eecc3b5cfd578a3f1e2201c258fa11b107
5
5
  SHA512:
6
- metadata.gz: ed762cc475552e7698cf9ebae51b00366f88f000fcbbbd9697bfa1056a2ec7a7a9d49bb946e0f785d8c1d435da443bd2cca834a2309a11403836dc11f93de169
7
- data.tar.gz: 112fb462eb9a3bdb1056c3ad32670ad1160ad0f40a6b804bc09b5e948443073df55a8b6c2818c0cb8f7794623b8e71f04d913a1559bb0b69536cc7bf7634873c
6
+ metadata.gz: 81ed3402348bf4882f9e8a3b43e3b926396813d844cb93c00e1bfecac15f3fb7672750e0162c01182bada9aed90aff96d1751d66fe3bcd7ff9a0bb0b10052c29
7
+ data.tar.gz: 8f13726940d23325f2f8278dd6c6ddabbbe4cec7fa3b61d7066c2c068b3c326af011bb6eca6b4a18c0cc9ffa00043bb535d2932185259f60baf51ebefbd19087
@@ -9,6 +9,113 @@ This document is formatted according to the principles of [Keep A CHANGELOG](htt
9
9
  Please visit [cucumber/CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CONTRIBUTING.md) for more info on how to contribute to Cucumber.
10
10
 
11
11
  ----
12
+
13
+ ## [In GIT](https://github.com/cucumber/cucumber-ruby/compare/v5.0.0...master)
14
+
15
+ ### Added
16
+
17
+ ### Changed
18
+
19
+ ### Removed
20
+
21
+ ### Deprecated
22
+
23
+ ### Fixed
24
+
25
+ ## [5.0.0](https://github.com/cucumber/cucumber-ruby/compare/v4.1.0...5.0.0)
26
+
27
+ ### Added
28
+
29
+ * `--publish` automatically publishes reports to [reports.cucumber.io](https://reports.cucumber.io)
30
+ * `--publish-quiet` does not print information banner about [reports.cucumber.io](https://reports.cucumber.io)
31
+
32
+ ### Changed
33
+
34
+ * `-q, --quiet` will also imply `--publish-quiet` in addition to `--no-snippets --no-source --no-duration`
35
+
36
+ ### Removed
37
+
38
+ * Dropped support for Ruby [2.3](https://www.ruby-lang.org/en/news/2019/03/31/support-of-ruby-2-3-has-ended/)
39
+ and [2.4](https://www.ruby-lang.org/en/news/2020/04/05/support-of-ruby-2-4-has-ended/)
40
+
41
+ ### Fixed
42
+
43
+ * Update code to be compatible with `diff-lcs` versions 1.3 and 1.4
44
+ * Defer registration of `at_exit` hook that flushes and closes formatter streams
45
+ ([#1458](https://github.com/cucumber/cucumber-ruby/pull/1458))
46
+ * Updated gems (see git diff for details)
47
+ * `cucumber-expressions`
48
+ * `cucumber-gherkin`
49
+ * `cucumber-create-meta`
50
+ * `cucumber-messages`
51
+ * Fix issue with timestamp nanos [#1438](https://github.com/cucumber/cucumber-ruby/issues/1438)
52
+ * `cucumber-html-formatter`
53
+ * Add filtering capabilities [#1444](https://github.com/cucumber/cucumber-ruby/issues/1444)
54
+ * Fix Interceptor that was raising exception when calling `puts` on the wrapped stream ([#1445](https://github.com/cucumber/cucumber-ruby/issues/1445))
55
+
56
+ ## [4.1.0](https://github.com/cucumber/cucumber-ruby/compare/v4.0.1...v4.1.0)
57
+
58
+ ### Changed
59
+
60
+ * Use [`cucumber-create-meta`](https://rubygems.org/gems/cucumber-create-meta) to produce the `Meta` message before the run.
61
+
62
+ * Updated gems:
63
+ * `cucumber-wire` ~> 3.1.0
64
+ * `cucumber-core` ~> 7.1.0
65
+ * `cucumber-gherkin` ~> 14.0.1
66
+ * Fix issue with empty feature files [#1427](https://github.com/cucumber/cucumber-ruby/issues/1427)
67
+ * `cucumber-messages` ~> 12.2.0
68
+ * `cucumber-html-formatter` ~> 7.0.0
69
+ * Fix issue with Hook attachments [#1420](https://github.com/cucumber/cucumber-ruby/issues/1420)
70
+
71
+ ### Fixed
72
+
73
+ * `AfterStep` hook do not cause issue when running `message` formatter. [#1433](https://github.com/cucumber/cucumber-ruby/issues/1433) - [#1434](https://github.com/cucumber/cucumber-ruby/pull/1434)
74
+
75
+
76
+ ## [4.0.1](https://github.com/cucumber/cucumber-ruby/compare/v4.0.0...v4.0.1)
77
+
78
+ ### Fixed
79
+
80
+ * force reference to `diff-lcs` to 1.3 as 1.4 introduced breaking changes.
81
+
82
+ ## [4.0.0](https://github.com/cucumber/cucumber-ruby/compare/v4.0.0.rc.5...v4.0.0)
83
+
84
+ ### Changed
85
+
86
+ * `log` method can now be called with non-string objects and will run `.to_s` on them. [#1410](https://github.com/cucumber/cucumber-ruby/issues/1410)
87
+
88
+ ### Improved
89
+
90
+ * Display snippet when using undefined parameter type [#1411](https://github.com/cucumber/cucumber-ruby/issues/1411)
91
+
92
+ ## [4.0.0.rc.6](https://github.com/cucumber/cucumber-ruby/compare/v4.0.0.rc.5...4.0.0.rc.6)
93
+
94
+ ### Changed
95
+
96
+ * Code snippet for an undefined step with a Doc String will ouput `doc_string` instead of `string` in block params
97
+ ([#1401](https://github.com/cucumber/cucumber-ruby/issues/1401)
98
+ [#1402](https://github.com/cucumber/cucumber-ruby/pull/1402)
99
+ [karamosky](https://github.com/karamosky))
100
+
101
+ * Updated monorepo libraries:
102
+ - cucumber-gherkin ~> 13
103
+ - cucumber-html-formatter ~> 6
104
+ - cucumber-cucumber-expressions ~> 10
105
+
106
+ * Use `cucumber-ruby-core` 7.0.0
107
+
108
+ * Use `cucumber-ruby-wire` 3.0.0
109
+
110
+ * Use `body` field of attachments
111
+
112
+ ### Improved
113
+
114
+ * `--out url` updates:
115
+ * supports redirects
116
+ * use `PUT` method by default
117
+ * use a cURL like options (for example: `cucumber --out 'http://example.com -X POST -H Content-Type: json`)
118
+
12
119
  ## [4.0.0.rc.5](https://github.com/cucumber/cucumber-ruby/compare/v4.0.0.rc.4...4.0.0.rc.5)
13
120
 
14
121
  ### Added
@@ -53,7 +160,7 @@ Please visit [cucumber/CONTRIBUTING.md](https://github.com/cucumber/cucumber/blo
53
160
  * If you wish to alter this, then you can set a top level config option: `Cucumber.use_legacy_autoloader`
54
161
  * Like most config options, setting this inside a `spec_helper.rb` or `env.rb` file is advised
55
162
  * For more information on this change, including why it was made. Please read this
56
- [Blog Post](www.google.com)
163
+ [Blog Post](https://cucumber.io/blog/open-source/tweaking-cucumber-rubys-auto-loader/)
57
164
  ([#1349](https://github.com/cucumber/cucumber-ruby/pull/1349),
58
165
  [#1043](https://github.com/cucumber/cucumber-ruby/issues/1043)
59
166
  [luke-hill](https://github.com/luke-hill))
@@ -59,13 +59,13 @@ help us to correct style violations reported here:
59
59
 
60
60
  ## Release Process
61
61
 
62
- * Bump the version number in `lib/cucumber/version`.
63
- * Make sure `CHANGELOG.md` is updated with the upcoming version number, and has entries for all fixes.
64
-
65
- Now release it
66
-
67
- bundle update
68
- bundle exec rake
69
- git commit -m "Release X.Y.Z"
70
- # Make sure you run gem signin as the cukebot@cucumber.io user before running the following step. Credentials can be found in 1Password
71
- rake release
62
+ * Upgrade gems with `scripts/update-gemspec`
63
+ * Bump the version number in `lib/cucumber/version`
64
+ * Update `CHANGELOG.md` with the upcoming version number and create a new `In Git` section
65
+ * Remove empty sections from `CHANGELOG.md`
66
+ * Now release it:
67
+
68
+ ```
69
+ git commit -am "Release X.Y.Z"
70
+ make release
71
+ ```
@@ -24,23 +24,21 @@ module Autotest::CucumberMixin
24
24
  add_sigint_handler
25
25
 
26
26
  loop do # ^c handler
27
- begin
28
- get_to_green
29
- if tainted
30
- rerun_all_tests
31
- rerun_all_features if all_good
32
- else
33
- hook :all_good
34
- end
35
- wait_for_changes
36
- # Once tests and features are green, reset features every
37
- # time a file is changed to see if anything breaks.
38
- reset_features
39
- rescue Interrupt
40
- break if wants_to_quit
41
- reset
42
- reset_features
27
+ get_to_green
28
+ if tainted
29
+ rerun_all_tests
30
+ rerun_all_features if all_good
31
+ else
32
+ hook :all_good
43
33
  end
34
+ wait_for_changes
35
+ # Once tests and features are green, reset features every
36
+ # time a file is changed to see if anything breaks.
37
+ reset_features
38
+ rescue Interrupt
39
+ break if wants_to_quit
40
+ reset
41
+ reset_features
44
42
  end
45
43
  hook :quit
46
44
  end
@@ -9,6 +9,7 @@ require 'cucumber/core/test/result'
9
9
  module Cucumber
10
10
  module Cli
11
11
  class Options
12
+ CUCUMBER_PUBLISH_URL = ENV['CUCUMBER_PUBLISH_URL'] || 'https://messages.cucumber.io/api/reports'
12
13
  INDENT = ' ' * 53
13
14
  BUILTIN_FORMATS = {
14
15
  'pretty' => ['Cucumber::Formatter::Pretty', 'Prints the feature as is - in colours.'],
@@ -93,6 +94,10 @@ module Cucumber
93
94
 
94
95
  @args.options do |opts| # rubocop:disable Metrics/BlockLength
95
96
  opts.banner = banner
97
+ opts.on('--publish', 'Publish a report to https://reports.cucumber.io') do
98
+ set_option :publish_enabled, true
99
+ end
100
+ opts.on('--publish-quiet', 'Don\'t print information banner about publishing reports') { set_option :publish_quiet }
96
101
  opts.on('-r LIBRARY|DIR', '--require LIBRARY|DIR', *require_files_msg) { |lib| require_files(lib) }
97
102
 
98
103
  opts.on('-j DIR', '--jars DIR', 'Load all the jars under DIR') { |jars| load_jars(jars) } if Cucumber::JRUBY
@@ -117,7 +122,7 @@ module Cucumber
117
122
  opts.on('-s', '--no-source', "Don't print the file and line of the step definition with the steps.") { set_option :source, false }
118
123
  opts.on('-i', '--no-snippets', "Don't print snippets for pending steps.") { set_option :snippets, false }
119
124
  opts.on('-I', '--snippet-type TYPE', *snippet_type_msg) { |v| set_option :snippet_type, v.to_sym }
120
- opts.on('-q', '--quiet', 'Alias for --no-snippets --no-source.') { shut_up }
125
+ opts.on('-q', '--quiet', 'Alias for --no-snippets --no-source --no-duration --publish-quiet.') { shut_up }
121
126
  opts.on('--no-duration', "Don't print the duration at the end of the summary") { set_option :duration, false }
122
127
  opts.on('-b', '--backtrace', 'Show full backtrace for all errors.') { Cucumber.use_full_backtrace = true }
123
128
  opts.on('-S', '--[no-]strict', *strict_msg) { |setting| set_strict(setting) }
@@ -145,6 +150,8 @@ Specify SEED to reproduce the shuffling from a previous run.
145
150
  opts.on_tail('-h', '--help', "You're looking at it.") { exit_ok(opts.help) }
146
151
  end.parse!
147
152
 
153
+ process_publish_options
154
+
148
155
  @args.map! { |a| "#{a}:#{@options[:lines]}" } if @options[:lines]
149
156
 
150
157
  extract_environment_variables
@@ -182,6 +189,18 @@ Specify SEED to reproduce the shuffling from a previous run.
182
189
 
183
190
  private
184
191
 
192
+ def process_publish_options
193
+ @options[:publish_enabled] = true if truthy_string?(ENV['CUCUMBER_PUBLISH_ENABLED']) || ENV['CUCUMBER_PUBLISH_TOKEN']
194
+ @options[:formats] << publisher if @options[:publish_enabled]
195
+
196
+ @options[:publish_quiet] = true if truthy_string?(ENV['CUCUMBER_PUBLISH_QUIET'])
197
+ end
198
+
199
+ def truthy_string?(str)
200
+ return false if str.nil?
201
+ str !~ /^(false|no|0)$/i
202
+ end
203
+
185
204
  def color_msg
186
205
  [
187
206
  'Whether or not to use ANSI color in the output. Cucumber decides',
@@ -302,9 +321,8 @@ Specify SEED to reproduce the shuffling from a previous run.
302
321
  "formatter's docs to see whether to pass a file, dir or URL.",
303
322
  "\n",
304
323
  'When using a URL, the output of the formatter will be sent as the HTTP request body.',
305
- 'HTTP headers and request method can be set with http- prefixed query parameters,',
306
- 'for example ?http-content-type=application/json&http-method=PUT.',
307
- 'All http- prefixed query parameters will be removed from the sent query parameters.'
324
+ 'HTTP headers and request method can be set with cURL like options.',
325
+ 'Example: --out "http://example.com -X POST -H Content-Type:text/json"'
308
326
  ]
309
327
  end
310
328
 
@@ -352,6 +370,12 @@ Specify SEED to reproduce the shuffling from a previous run.
352
370
  Dir["#{jars}/**/*.jar"].each { |jar| require jar }
353
371
  end
354
372
 
373
+ def publisher
374
+ url = CUCUMBER_PUBLISH_URL
375
+ url += %( -H "Authorization: Bearer #{ENV['CUCUMBER_PUBLISH_TOKEN']}") if ENV['CUCUMBER_PUBLISH_TOKEN']
376
+ ['message', {}, url]
377
+ end
378
+
355
379
  def language(lang)
356
380
  require 'gherkin/dialect'
357
381
 
@@ -416,6 +440,7 @@ Specify SEED to reproduce the shuffling from a previous run.
416
440
  end
417
441
 
418
442
  def shut_up
443
+ @options[:publish_quiet] = true
419
444
  @options[:snippets] = false
420
445
  @options[:source] = false
421
446
  @options[:duration] = false
@@ -62,6 +62,14 @@ module Cucumber
62
62
  @options[:dry_run]
63
63
  end
64
64
 
65
+ def publish_enabled?
66
+ @options[:publish_enabled]
67
+ end
68
+
69
+ def publish_quiet?
70
+ @options[:publish_quiet]
71
+ end
72
+
65
73
  def fail_fast?
66
74
  @options[:fail_fast]
67
75
  end
@@ -197,14 +205,12 @@ module Cucumber
197
205
 
198
206
  def formatter_factories
199
207
  formats.map do |format, formatter_options, path_or_io|
200
- begin
201
- factory = formatter_class(format)
202
- yield factory,
203
- formatter_options,
204
- path_or_io
205
- rescue Exception => e # rubocop:disable Lint/RescueException
206
- raise e, "#{e.message}\nError creating formatter: #{format}", e.backtrace
207
- end
208
+ factory = formatter_class(format)
209
+ yield factory,
210
+ formatter_options,
211
+ path_or_io
212
+ rescue Exception => e # rubocop:disable Lint/RescueException
213
+ raise e, "#{e.message}\nError creating formatter: #{format}", e.backtrace
208
214
  end
209
215
  end
210
216
 
@@ -256,6 +262,7 @@ module Cucumber
256
262
  strict: Cucumber::Core::Test::Result::StrictConfiguration.new,
257
263
  require: [],
258
264
  dry_run: false,
265
+ publish_quiet: false,
259
266
  fail_fast: false,
260
267
  formats: [],
261
268
  excludes: [],
@@ -49,7 +49,4 @@ module Cucumber
49
49
  super(messages.join("\n"))
50
50
  end
51
51
  end
52
-
53
- class LogTypeInvalid < StandardError
54
- end
55
52
  end
@@ -38,7 +38,8 @@ module Cucumber
38
38
  TestStepCreated,
39
39
  TestStepFinished,
40
40
  TestStepStarted,
41
- Envelope
41
+ Envelope,
42
+ UndefinedParameterType
42
43
  )
43
44
  end
44
45
  end
@@ -0,0 +1,10 @@
1
+ require 'cucumber/core/events'
2
+
3
+ module Cucumber
4
+ module Events
5
+ class UndefinedParameterType < Core::Event.new(:type_name, :expression)
6
+ attr_reader :type_name
7
+ attr_reader :expression
8
+ end
9
+ end
10
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Metrics/ModuleLength
4
+
3
5
  require 'cucumber/formatter/ansicolor'
4
6
  require 'cucumber/formatter/duration'
5
7
  require 'cucumber/gherkin/i18n'
@@ -116,14 +118,21 @@ module Cucumber
116
118
  @snippets_input << Console::SnippetData.new(keyword, test_step)
117
119
  end
118
120
 
121
+ def collect_undefined_parameter_type_names(undefined_parameter_type)
122
+ @undefined_parameter_types << undefined_parameter_type.type_name
123
+ end
124
+
119
125
  def print_snippets(options)
120
126
  return unless options[:snippets]
121
- return if @snippets_input.empty?
122
127
 
123
128
  snippet_text_proc = lambda do |step_keyword, step_name, multiline_arg|
124
129
  snippet_text(step_keyword, step_name, multiline_arg)
125
130
  end
126
- do_print_snippets(snippet_text_proc)
131
+ do_print_snippets(snippet_text_proc) unless @snippets_input.empty?
132
+
133
+ @undefined_parameter_types.map do |type_name|
134
+ do_print_undefined_parameter_type_snippet(type_name)
135
+ end
127
136
  end
128
137
 
129
138
  def do_print_snippets(snippet_text_proc)
@@ -181,6 +190,24 @@ module Cucumber
181
190
  @io.puts "Using the #{profiles_sentence} profile#{'s' if profiles.size > 1}..."
182
191
  end
183
192
 
193
+ def do_print_undefined_parameter_type_snippet(type_name)
194
+ camelized = type_name.split(/_|-/).collect(&:capitalize).join
195
+
196
+ @io.puts [
197
+ "The parameter #{type_name} is not defined. You can define a new one with:",
198
+ '',
199
+ 'ParameterType(',
200
+ " name: '#{type_name}',",
201
+ ' regexp: /some regexp here/,',
202
+ " type: #{camelized},",
203
+ ' # The transformer takes as many arguments as there are capture groups in the regexp,',
204
+ ' # or just one if there are none.',
205
+ " transformer: ->(s) { #{camelized}.new(s) }",
206
+ ')',
207
+ ''
208
+ ].join("\n")
209
+ end
210
+
184
211
  private
185
212
 
186
213
  FORMATS = Hash.new { |hash, format| hash[format] = method(format).to_proc }
@@ -219,3 +246,4 @@ module Cucumber
219
246
  end
220
247
  end
221
248
  end
249
+ # rubocop:enable Metrics/ModuleLength
@@ -1,92 +1,150 @@
1
1
  require 'net/http'
2
+ require 'tempfile'
2
3
 
3
4
  module Cucumber
4
5
  module Formatter
5
6
  class HTTPIO
6
7
  class << self
7
8
  # Returns an IO that will write to a HTTP request's body
8
- def open(url, https_verify_mode = nil)
9
+ # https_verify_mode can be set to OpenSSL::SSL::VERIFY_NONE
10
+ # to ignore unsigned certificate - setting to nil will verify the certificate
11
+ def open(url, https_verify_mode = nil, reporter = nil)
9
12
  @https_verify_mode = https_verify_mode
10
- uri, method, headers = build_uri_method_headers(url)
11
-
12
- @req = build_request(uri, method, headers)
13
- @http = build_client(uri, https_verify_mode)
14
-
15
- read_io, write_io = IO.pipe
16
- @req.body_stream = read_io
17
-
18
- class << write_io
19
- attr_writer :request_thread
20
-
21
- def start_request(http, req)
22
- @req_thread = Thread.new do
23
- begin
24
- res = http.request(req)
25
- raise StandardError, "request to #{req.uri} failed with status #{res.code}" if res.code.to_i >= 400
26
- rescue StandardError => e
27
- @http_error = e
28
- end
29
- end
30
- end
31
-
32
- def close
33
- super
34
- begin
35
- @req_thread.join
36
- rescue StandardError
37
- nil
38
- end
39
- raise @http_error unless @http_error.nil?
40
- end
13
+ uri, method, headers = CurlOptionParser.parse(url)
14
+ IOHTTPBuffer.new(uri, method, headers, https_verify_mode, reporter)
15
+ end
16
+ end
17
+ end
18
+
19
+ class CurlOptionParser
20
+ def self.parse(options)
21
+ chunks = options.split(/\s/).compact
22
+ http_method = 'PUT'
23
+ url = chunks[0]
24
+ headers = ''
25
+
26
+ last_flag = nil
27
+ chunks.each do |chunk|
28
+ if ['-X', '--request'].include?(chunk)
29
+ last_flag = '-X'
30
+ next
41
31
  end
42
- write_io.start_request(@http, @req)
43
32
 
44
- write_io
33
+ if chunk == '-H'
34
+ last_flag = '-H'
35
+ next
36
+ end
37
+
38
+ if last_flag == '-X'
39
+ http_method = chunk
40
+ last_flag = nil
41
+ end
42
+
43
+ headers += chunk if last_flag == '-H'
45
44
  end
46
45
 
47
- def build_uri_method_headers(url)
48
- uri = URI(url)
49
- query_pairs = uri.query ? URI.decode_www_form(uri.query) : []
50
-
51
- # Build headers from query parameters prefixed with http- and extract HTTP method
52
- http_query_pairs = query_pairs.select { |pair| pair[0] =~ /^http-/ }
53
- http_query_hash_without_prefix = Hash[http_query_pairs.map do |pair|
54
- [
55
- pair[0][5..-1].downcase, # remove http- prefix
56
- pair[1]
57
- ]
58
- end]
59
- method = http_query_hash_without_prefix.delete('method') || 'POST'
60
- headers = {
61
- 'transfer-encoding' => 'chunked'
62
- }.merge(http_query_hash_without_prefix)
63
-
64
- # Update the query with the http-* parameters removed
65
- remaining_query_pairs = query_pairs - http_query_pairs
66
- new_query_hash = Hash[remaining_query_pairs]
67
- uri.query = URI.encode_www_form(new_query_hash) unless new_query_hash.empty?
68
- [uri, method, headers]
46
+ [
47
+ url,
48
+ http_method,
49
+ make_headers(headers)
50
+ ]
51
+ end
52
+
53
+ def self.make_headers(headers)
54
+ hash_headers = {}
55
+ str_scanner = /("(?<key>[^":]+)\s*:\s*(?<value>[^":]+)")|('(?<key1>[^':]+)\s*:\s*(?<value1>[^':]+)')/
56
+
57
+ headers.scan(str_scanner) do |header|
58
+ header = header.compact!
59
+ hash_headers[header[0]] = header[1]&.strip
69
60
  end
70
61
 
71
- private
62
+ hash_headers
63
+ end
64
+ end
72
65
 
73
- def build_request(uri, method, headers)
74
- method_class_name = "#{method[0].upcase}#{method[1..-1].downcase}"
75
- req = Net::HTTP.const_get(method_class_name).new(uri)
76
- headers.each do |header, value|
77
- req[header] = value
78
- end
79
- req
66
+ class IOHTTPBuffer
67
+ attr_reader :uri, :method, :headers
68
+
69
+ def initialize(uri, method, headers = {}, https_verify_mode = nil, reporter = nil)
70
+ @uri = URI(uri)
71
+ @method = method
72
+ @headers = headers
73
+ @write_io = Tempfile.new('cucumber', encoding: 'UTF-8')
74
+ @https_verify_mode = https_verify_mode
75
+ @reporter = reporter || NoReporter.new
76
+ end
77
+
78
+ def close
79
+ resource_uri = send_content(@uri, @method, @headers)
80
+
81
+ @reporter.report(resource_uri)
82
+ @write_io.close
83
+ end
84
+
85
+ def write(data)
86
+ @write_io.write(data)
87
+ end
88
+
89
+ def flush
90
+ @write_io.flush
91
+ end
92
+
93
+ def closed?
94
+ @write_io.closed?
95
+ end
96
+
97
+ private
98
+
99
+ def send_content(uri, method, headers, attempt = 10)
100
+ content = @write_io
101
+ http = build_client(uri, @https_verify_mode)
102
+
103
+ raise StandardError, "request to #{uri} failed (too many redirections)" if attempt <= 0
104
+ req = build_request(
105
+ uri,
106
+ method,
107
+ headers.merge(
108
+ 'Content-Length' => content.size.to_s
109
+ )
110
+ )
111
+
112
+ content.rewind
113
+ req.body_stream = content
114
+
115
+ begin
116
+ response = http.request(req)
117
+ rescue SystemCallError
118
+ # We may get the redirect response before pushing the file.
119
+ response = http.request(build_request(uri, method, headers))
80
120
  end
81
121
 
82
- def build_client(uri, https_verify_mode)
83
- http = Net::HTTP.new(uri.hostname, uri.port)
84
- if uri.scheme == 'https'
85
- http.use_ssl = true
86
- http.verify_mode = https_verify_mode if https_verify_mode
87
- end
88
- http
122
+ case response
123
+ when Net::HTTPSuccess
124
+ uri
125
+ when Net::HTTPRedirection
126
+ send_content(URI(response['Location']), method, headers, attempt - 1)
127
+ else
128
+ raise StandardError, "request to #{uri} failed with status #{response.code}"
129
+ end
130
+ end
131
+
132
+ def build_request(uri, method, headers)
133
+ method_class_name = "#{method[0].upcase}#{method[1..-1].downcase}"
134
+ req = Net::HTTP.const_get(method_class_name).new(uri)
135
+ headers.each do |header, value|
136
+ req[header] = value
137
+ end
138
+ req
139
+ end
140
+
141
+ def build_client(uri, https_verify_mode)
142
+ http = Net::HTTP.new(uri.hostname, uri.port)
143
+ if uri.scheme == 'https'
144
+ http.use_ssl = true
145
+ http.verify_mode = https_verify_mode if https_verify_mode
89
146
  end
147
+ http
90
148
  end
91
149
  end
92
150
  end