stripe 5.2.0 → 5.6.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: 0ec1d1414aafc87b63dca172263ecd4666467cda2ab39eaac1abdbd13e7cd90b
4
- data.tar.gz: 3adde22d80c8f867e34d2735dd0aea142f0b69e6cd2a84e13fe7c6a53655ee85
3
+ metadata.gz: b717ef70627c0f7467317a2e2f08e8d553b04411b6324df41f4cc5eac6bd6800
4
+ data.tar.gz: 26a05a4ceeb4412c95c2ad12776785c72bd291841c5843eba2ec03a6dc21a722
5
5
  SHA512:
6
- metadata.gz: e4086cfa6bc5281bf1e31e88a2435156d6928716d7022e319afe5845157571731ad260c3728c6bf021d593f11727fa6fdba06c26667d7d5774b6a29cfadf9829
7
- data.tar.gz: 4b6477d988e90607c8ad8c1d8a4a11e3f0afb3494948403d2b6b9e23cde4a6be0dcb9779100306c28449e6d7d24876043edcc2239ab00cdb1dc7fd88942b57de
6
+ metadata.gz: ef1b962195d8a8510a1d360d8fbd18a246c65363877b43a451bccd1340c1ea18008581c04c4a0e62eb6eed9f2f8afebfccf9c4f5d059b08a2ccd921cae5b5cd0
7
+ data.tar.gz: 98fb62f907a16aadb4cab5564053ce85e96931193a8e1eeccb8ef10ebcfa726ddf086e51aef09db0825c685129867d5a91716addcc2252ae771869ca4d83bc8a
data/.rubocop.yml CHANGED
@@ -4,6 +4,12 @@ AllCops:
4
4
  DisplayCopNames: true
5
5
  TargetRubyVersion: 2.3
6
6
 
7
+ Exclude:
8
+ # brandur: Exclude ephmeral script-like files that I use to try and
9
+ # reproduce problems with the library. If you know of a better way of doing
10
+ # this (e.g. exclude files not tracked by Git), feel free to change it.
11
+ - "example_*"
12
+
7
13
  Layout/CaseIndentation:
8
14
  EnforcedStyle: end
9
15
 
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.6.0 - 2019-10-04
4
+ * [#861](https://github.com/stripe/stripe-ruby/pull/861) Nicer error when specifying non-nil non-string opt value
5
+
6
+ ## 5.5.0 - 2019-10-03
7
+ * [#859](https://github.com/stripe/stripe-ruby/pull/859) User-friendly messages and retries for `EOFError`, `Errno::ECONNRESET`, `Errno::ETIMEDOUT`, and `Errno::EHOSTUNREACH` network errors
8
+
9
+ ## 5.4.1 - 2019-10-01
10
+ * [#858](https://github.com/stripe/stripe-ruby/pull/858) Drop Timecop dependency
11
+
12
+ ## 5.4.0 - 2019-10-01
13
+ * [#857](https://github.com/stripe/stripe-ruby/pull/857) Move to monotonic time for duration calculations
14
+
15
+ ## 5.3.0 - 2019-10-01
16
+ * [#853](https://github.com/stripe/stripe-ruby/pull/853) Support `Stripe-Should-Retry` header
17
+
3
18
  ## 5.2.0 - 2019-09-19
4
19
  * [#851](https://github.com/stripe/stripe-ruby/pull/851) Introduce system for garbage collecting connection managers
5
20
 
data/Gemfile CHANGED
@@ -11,7 +11,6 @@ group :development do
11
11
  gem "rake"
12
12
  gem "shoulda-context"
13
13
  gem "test-unit"
14
- gem "timecop"
15
14
  gem "webmock"
16
15
 
17
16
  # Rubocop changes pretty quickly: new cops get added and old cops change
data/VERSION CHANGED
@@ -1 +1 @@
1
- 5.2.0
1
+ 5.6.0
@@ -6,7 +6,7 @@ module Stripe
6
6
  # that it's possible to do so from a static context (i.e. without a
7
7
  # pre-existing collection of subresources on the parent).
8
8
  #
9
- # For examle, a transfer gains the static methods for reversals so that the
9
+ # For example, a transfer gains the static methods for reversals so that the
10
10
  # methods `.create_reversal`, `.retrieve_reversal`, `.update_reversal`,
11
11
  # etc. all become available.
12
12
  module NestedResource
@@ -14,9 +14,11 @@ module Stripe
14
14
  resource_plural: nil)
15
15
  resource_plural ||= "#{resource}s"
16
16
  path ||= resource_plural
17
+
17
18
  raise ArgumentError, "operations array required" if operations.nil?
18
19
 
19
20
  resource_url_method = :"#{resource}s_url"
21
+
20
22
  define_singleton_method(resource_url_method) do |id, nested_id = nil|
21
23
  url = "#{resource_url}/#{CGI.escape(id)}/#{CGI.escape(path)}"
22
24
  url += "/#{CGI.escape(nested_id)}" unless nested_id.nil?
@@ -27,39 +29,39 @@ module Stripe
27
29
  case operation
28
30
  when :create
29
31
  define_singleton_method(:"create_#{resource}") \
30
- do |id, params = {}, opts = {}|
31
- url = send(resource_url_method, id)
32
- resp, opts = request(:post, url, params, opts)
33
- Util.convert_to_stripe_object(resp.data, opts)
34
- end
32
+ do |id, params = {}, opts = {}|
33
+ url = send(resource_url_method, id)
34
+ resp, opts = request(:post, url, params, opts)
35
+ Util.convert_to_stripe_object(resp.data, opts)
36
+ end
35
37
  when :retrieve
36
38
  define_singleton_method(:"retrieve_#{resource}") \
37
- do |id, nested_id, opts = {}|
38
- url = send(resource_url_method, id, nested_id)
39
- resp, opts = request(:get, url, {}, opts)
40
- Util.convert_to_stripe_object(resp.data, opts)
41
- end
39
+ do |id, nested_id, opts = {}|
40
+ url = send(resource_url_method, id, nested_id)
41
+ resp, opts = request(:get, url, {}, opts)
42
+ Util.convert_to_stripe_object(resp.data, opts)
43
+ end
42
44
  when :update
43
45
  define_singleton_method(:"update_#{resource}") \
44
- do |id, nested_id, params = {}, opts = {}|
45
- url = send(resource_url_method, id, nested_id)
46
- resp, opts = request(:post, url, params, opts)
47
- Util.convert_to_stripe_object(resp.data, opts)
48
- end
46
+ do |id, nested_id, params = {}, opts = {}|
47
+ url = send(resource_url_method, id, nested_id)
48
+ resp, opts = request(:post, url, params, opts)
49
+ Util.convert_to_stripe_object(resp.data, opts)
50
+ end
49
51
  when :delete
50
52
  define_singleton_method(:"delete_#{resource}") \
51
- do |id, nested_id, params = {}, opts = {}|
52
- url = send(resource_url_method, id, nested_id)
53
- resp, opts = request(:delete, url, params, opts)
54
- Util.convert_to_stripe_object(resp.data, opts)
55
- end
53
+ do |id, nested_id, params = {}, opts = {}|
54
+ url = send(resource_url_method, id, nested_id)
55
+ resp, opts = request(:delete, url, params, opts)
56
+ Util.convert_to_stripe_object(resp.data, opts)
57
+ end
56
58
  when :list
57
59
  define_singleton_method(:"list_#{resource_plural}") \
58
- do |id, params = {}, opts = {}|
59
- url = send(resource_url_method, id)
60
- resp, opts = request(:get, url, params, opts)
61
- Util.convert_to_stripe_object(resp.data, opts)
62
- end
60
+ do |id, params = {}, opts = {}|
61
+ url = send(resource_url_method, id)
62
+ resp, opts = request(:get, url, params, opts)
63
+ Util.convert_to_stripe_object(resp.data, opts)
64
+ end
63
65
  else
64
66
  raise ArgumentError, "Unknown operation: #{operation.inspect}"
65
67
  end
@@ -8,6 +8,8 @@ module Stripe
8
8
  warn_on_opts_in_params(params)
9
9
 
10
10
  opts = Util.normalize_opts(opts)
11
+ error_on_non_string_user_opts(opts)
12
+
11
13
  opts[:client] ||= StripeClient.active_client
12
14
 
13
15
  headers = opts.clone
@@ -31,10 +33,24 @@ module Stripe
31
33
  [resp, opts_to_persist]
32
34
  end
33
35
 
36
+ private def error_on_non_string_user_opts(opts)
37
+ Util::OPTS_USER_SPECIFIED.each do |opt|
38
+ next unless opts.key?(opt)
39
+
40
+ val = opts[opt]
41
+ next if val.nil?
42
+ next if val.is_a?(String)
43
+
44
+ raise ArgumentError,
45
+ "request option '#{opt}' should be a string value " \
46
+ "(was a #{val.class})"
47
+ end
48
+ end
49
+
34
50
  private def warn_on_opts_in_params(params)
35
51
  Util::OPTS_USER_SPECIFIED.each do |opt|
36
52
  if params.key?(opt)
37
- warn("WARNING: #{opt} should be in opts instead of params.")
53
+ warn("WARNING: '#{opt}' should be in opts instead of params.")
38
54
  end
39
55
  end
40
56
  end
@@ -10,14 +10,15 @@ module Stripe
10
10
  # Note that this class in itself is *not* thread safe. We expect it to be
11
11
  # instantiated once per thread.
12
12
  class ConnectionManager
13
- # Timestamp indicating when the connection manager last made a request.
14
- # This is used by `StripeClient` to determine whether a connection manager
15
- # should be garbage collected or not.
13
+ # Timestamp (in seconds procured from the system's monotonic clock)
14
+ # indicating when the connection manager last made a request. This is used
15
+ # by `StripeClient` to determine whether a connection manager should be
16
+ # garbage collected or not.
16
17
  attr_reader :last_used
17
18
 
18
19
  def initialize
19
20
  @active_connections = {}
20
- @last_used = Time.now
21
+ @last_used = Util.monotonic_time
21
22
 
22
23
  # A connection manager may be accessed across threads as one thread makes
23
24
  # requests on it while another is trying to clear it (either because it's
@@ -78,7 +79,7 @@ module Stripe
78
79
  raise ArgumentError, "query should be a string" \
79
80
  if query && !query.is_a?(String)
80
81
 
81
- @last_used = Time.now
82
+ @last_used = Util.monotonic_time
82
83
 
83
84
  connection = connection_for(uri)
84
85
 
@@ -9,7 +9,7 @@ module Stripe
9
9
  # synchronize global access to them.
10
10
  @thread_contexts_with_connection_managers = []
11
11
  @thread_contexts_with_connection_managers_mutex = Mutex.new
12
- @last_connection_manager_gc = Time.now
12
+ @last_connection_manager_gc = Util.monotonic_time
13
13
 
14
14
  # Initializes a new `StripeClient`.
15
15
  #
@@ -81,17 +81,23 @@ module Stripe
81
81
  def self.should_retry?(error, method:, num_retries:)
82
82
  return false if num_retries >= Stripe.max_network_retries
83
83
 
84
- # Retry on timeout-related problems (either on open or read).
85
- return true if error.is_a?(Net::OpenTimeout)
86
- return true if error.is_a?(Net::ReadTimeout)
84
+ case error
85
+ when Net::OpenTimeout, Net::ReadTimeout
86
+ # Retry on timeout-related problems (either on open or read).
87
+ true
88
+ when EOFError, Errno::ECONNREFUSED, Errno::ECONNRESET,
89
+ Errno::EHOSTUNREACH, Errno::ETIMEDOUT, SocketError
90
+ # Destination refused the connection, the connection was reset, or a
91
+ # variety of other connection failures. This could occur from a single
92
+ # saturated server, so retry in case it's intermittent.
93
+ true
94
+ when Stripe::StripeError
95
+ # The API may ask us not to retry (e.g. if doing so would be a no-op),
96
+ # or advise us to retry (e.g. in cases of lock timeouts). Defer to
97
+ # those instructions if given.
98
+ return false if error.http_headers["stripe-should-retry"] == "false"
99
+ return true if error.http_headers["stripe-should-retry"] == "true"
87
100
 
88
- # Destination refused the connection, the connection was reset, or a
89
- # variety of other connection failures. This could occur from a single
90
- # saturated server, so retry in case it's intermittent.
91
- return true if error.is_a?(Errno::ECONNREFUSED)
92
- return true if error.is_a?(SocketError)
93
-
94
- if error.is_a?(Stripe::StripeError)
95
101
  # 409 Conflict
96
102
  return true if error.http_status == 409
97
103
 
@@ -113,10 +119,10 @@ module Stripe
113
119
  return true if error.http_status == 500 && method != :post
114
120
 
115
121
  # 503 Service Unavailable
116
- return true if error.http_status == 503
122
+ error.http_status == 503
123
+ else
124
+ false
117
125
  end
118
-
119
- false
120
126
  end
121
127
 
122
128
  def self.sleep_time(num_retries)
@@ -290,7 +296,11 @@ module Stripe
290
296
  # The original error message is also appended onto the final exception for
291
297
  # full transparency.
292
298
  NETWORK_ERROR_MESSAGES_MAP = {
299
+ EOFError => ERROR_MESSAGE_CONNECTION,
293
300
  Errno::ECONNREFUSED => ERROR_MESSAGE_CONNECTION,
301
+ Errno::ECONNRESET => ERROR_MESSAGE_CONNECTION,
302
+ Errno::EHOSTUNREACH => ERROR_MESSAGE_CONNECTION,
303
+ Errno::ETIMEDOUT => ERROR_MESSAGE_TIMEOUT_CONNECT,
294
304
  SocketError => ERROR_MESSAGE_CONNECTION,
295
305
 
296
306
  Net::OpenTimeout => ERROR_MESSAGE_TIMEOUT_CONNECT,
@@ -362,11 +372,11 @@ module Stripe
362
372
  # For internal use only. Does not provide a stable API and may be broken
363
373
  # with future non-major changes.
364
374
  def self.maybe_gc_connection_managers
365
- if @last_connection_manager_gc + CONNECTION_MANAGER_GC_PERIOD > Time.now
366
- return nil
367
- end
375
+ next_gc_time = @last_connection_manager_gc + CONNECTION_MANAGER_GC_PERIOD
376
+ return nil if next_gc_time > Util.monotonic_time
368
377
 
369
- last_used_threshold = Time.now - CONNECTION_MANAGER_GC_LAST_USED_EXPIRY
378
+ last_used_threshold =
379
+ Util.monotonic_time - CONNECTION_MANAGER_GC_LAST_USED_EXPIRY
370
380
 
371
381
  pruned_thread_contexts = []
372
382
  @thread_contexts_with_connection_managers.each do |thread_context|
@@ -379,7 +389,7 @@ module Stripe
379
389
  end
380
390
 
381
391
  @thread_contexts_with_connection_managers -= pruned_thread_contexts
382
- @last_connection_manager_gc = Time.now
392
+ @last_connection_manager_gc = Util.monotonic_time
383
393
 
384
394
  pruned_thread_contexts.count
385
395
  end
@@ -439,7 +449,7 @@ module Stripe
439
449
  private def execute_request_with_rescues(method, api_base, context)
440
450
  num_retries = 0
441
451
  begin
442
- request_start = Time.now
452
+ request_start = Util.monotonic_time
443
453
  log_request(context, num_retries)
444
454
  resp = yield
445
455
  context = context.dup_from_response_headers(resp)
@@ -449,7 +459,8 @@ module Stripe
449
459
  log_response(context, request_start, resp.code.to_i, resp.body)
450
460
 
451
461
  if Stripe.enable_telemetry? && context.request_id
452
- request_duration_ms = ((Time.now - request_start) * 1000).to_int
462
+ request_duration_ms =
463
+ ((Util.monotonic_time - request_start) * 1000).to_int
453
464
  @last_request_metrics =
454
465
  StripeRequestMetrics.new(context.request_id, request_duration_ms)
455
466
  end
@@ -720,7 +731,7 @@ module Stripe
720
731
  Util.log_info("Response from Stripe API",
721
732
  account: context.account,
722
733
  api_version: context.api_version,
723
- elapsed: Time.now - request_start,
734
+ elapsed: Util.monotonic_time - request_start,
724
735
  idempotency_key: context.idempotency_key,
725
736
  method: context.method,
726
737
  path: context.path,
@@ -742,7 +753,7 @@ module Stripe
742
753
 
743
754
  private def log_response_error(context, request_start, error)
744
755
  Util.log_error("Request error",
745
- elapsed: Time.now - request_start,
756
+ elapsed: Util.monotonic_time - request_start,
746
757
  error_message: error.message,
747
758
  idempotency_key: context.idempotency_key,
748
759
  method: context.method,
data/lib/stripe/util.rb CHANGED
@@ -172,6 +172,18 @@ module Stripe
172
172
  result
173
173
  end
174
174
 
175
+ # `Time.now` can be unstable in cases like an administrator manually
176
+ # updating its value or a reconcilation via NTP. For this reason, prefer
177
+ # the use of the system's monotonic clock especially where comparing times
178
+ # to calculate an elapsed duration.
179
+ #
180
+ # Shortcut for getting monotonic time, mostly for purposes of line length
181
+ # and test stubbing. Returns time in seconds since the event used for
182
+ # monotonic reference purposes by the platform (e.g. system boot time).
183
+ def self.monotonic_time
184
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
185
+ end
186
+
175
187
  def self.normalize_id(id)
176
188
  if id.is_a?(Hash) # overloaded id
177
189
  params_hash = id.dup
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stripe
4
- VERSION = "5.2.0"
4
+ VERSION = "5.6.0"
5
5
  end
@@ -227,6 +227,23 @@ module Stripe
227
227
  end
228
228
  end
229
229
 
230
+ should "error if a user-specified opt is given a non-nil non-string value" do
231
+ stub_request(:post, "#{Stripe.api_base}/v1/charges")
232
+ .to_return(body: JSON.generate(charge_fixture))
233
+
234
+ # Works fine if not included or a string.
235
+ Stripe::Charge.create({ amount: 100, currency: "usd" }, {})
236
+ Stripe::Charge.create({ amount: 100, currency: "usd" }, idempotency_key: "12345")
237
+
238
+ # Errors on a non-string.
239
+ e = assert_raises(ArgumentError) do
240
+ Stripe::Charge.create({ amount: 100, currency: "usd" }, idempotency_key: :foo)
241
+ end
242
+ assert_equal "request option 'idempotency_key' should be a string value " \
243
+ "(was a Symbol)",
244
+ e.message
245
+ end
246
+
230
247
  should "requesting with a unicode ID should result in a request" do
231
248
  stub_request(:get, "#{Stripe.api_base}/v1/customers/%E2%98%83")
232
249
  .to_return(body: JSON.generate(make_missing_id_error), status: 404)
@@ -10,10 +10,9 @@ module Stripe
10
10
 
11
11
  context "#initialize" do
12
12
  should "set #last_used to current time" do
13
- t = Time.new(2019)
14
- Timecop.freeze(t) do
15
- assert_equal t, Stripe::ConnectionManager.new.last_used
16
- end
13
+ t = 123.0
14
+ Util.stubs(:monotonic_time).returns(t)
15
+ assert_equal t, Stripe::ConnectionManager.new.last_used
17
16
  end
18
17
  end
19
18
 
@@ -147,19 +146,17 @@ module Stripe
147
146
  stub_request(:post, "#{Stripe.api_base}/path")
148
147
  .to_return(body: JSON.generate(object: "account"))
149
148
 
150
- t = Time.new(2019)
149
+ t = 123.0
150
+ Util.stubs(:monotonic_time).returns(t)
151
151
 
152
- # Make sure the connection manager is initialized at a different time
153
- # than the one we're going to measure at because `#last_used` is also
154
- # set by the constructor.
155
- manager = Timecop.freeze(t) do
156
- Stripe::ConnectionManager.new
157
- end
152
+ manager = Stripe::ConnectionManager.new
158
153
 
159
- Timecop.freeze(t + 1) do
160
- manager.execute_request(:post, "#{Stripe.api_base}/path")
161
- assert_equal t + 1, manager.last_used
162
- end
154
+ # `#last_used` is also set by the constructor, so make sure we get a
155
+ # new value for it.
156
+ Util.stubs(:monotonic_time).returns(t + 1.0)
157
+
158
+ manager.execute_request(:post, "#{Stripe.api_base}/path")
159
+ assert_equal t + 1.0, manager.last_used
163
160
  end
164
161
  end
165
162
  end
@@ -27,47 +27,63 @@ module Stripe
27
27
  StripeClient.clear_all_connection_managers
28
28
 
29
29
  # Establish a base time.
30
- t = Time.new(2019)
30
+ t = 0.0
31
31
 
32
32
  # And pretend that `StripeClient` was just initialized for the first
33
33
  # time. (Don't access instance variables like this, but it's tricky to
34
34
  # test properly otherwise.)
35
35
  StripeClient.instance_variable_set(:@last_connection_manager_gc, t)
36
36
 
37
- Timecop.freeze(t) do
38
- # Execute an initial request to ensure that a connection manager was
39
- # created.
40
- client = StripeClient.new
41
- client.execute_request(:post, "/v1/path")
37
+ #
38
+ # t
39
+ #
40
+ Util.stubs(:monotonic_time).returns(t)
42
41
 
43
- # The GC shouldn't run yet (a `nil` return indicates that GC didn't run).
44
- assert_equal nil, StripeClient.maybe_gc_connection_managers
45
- end
42
+ # Execute an initial request to ensure that a connection manager was
43
+ # created.
44
+ client = StripeClient.new
45
+ client.execute_request(:post, "/v1/path")
46
+
47
+ # The GC shouldn't run yet (a `nil` return indicates that GC didn't run).
48
+ assert_equal nil, StripeClient.maybe_gc_connection_managers
46
49
 
50
+ #
51
+ # t + StripeClient::CONNECTION_MANAGER_GC_PERIOD - 1
52
+ #
47
53
  # Move time to just *before* garbage collection is eligible to run.
48
54
  # Nothing should happen.
49
- Timecop.freeze(t + StripeClient::CONNECTION_MANAGER_GC_PERIOD - 1) do
50
- assert_equal nil, StripeClient.maybe_gc_connection_managers
51
- end
55
+ #
56
+ Util.stubs(:monotonic_time).returns(t + StripeClient::CONNECTION_MANAGER_GC_PERIOD - 1)
57
+
58
+ assert_equal nil, StripeClient.maybe_gc_connection_managers
52
59
 
60
+ #
61
+ # t + StripeClient::CONNECTION_MANAGER_GC_PERIOD + 1
62
+ #
53
63
  # Move time to just *after* garbage collection is eligible to run.
54
64
  # Garbage collection will run, but because the connection manager is
55
65
  # not passed its expiry age, it will not be collected. Zero is returned
56
66
  # to indicate so.
57
- Timecop.freeze(t + StripeClient::CONNECTION_MANAGER_GC_PERIOD + 1) do
58
- assert_equal 0, StripeClient.maybe_gc_connection_managers
59
- end
67
+ #
68
+ Util.stubs(:monotonic_time).returns(t + StripeClient::CONNECTION_MANAGER_GC_PERIOD + 1)
69
+
70
+ assert_equal 0, StripeClient.maybe_gc_connection_managers
60
71
 
72
+ #
73
+ # t + StripeClient::CONNECTION_MANAGER_GC_LAST_USED_EXPIRY + 1
74
+ #
61
75
  # Move us far enough into the future that we're passed the horizons for
62
76
  # both a GC run as well as well as the expiry age of a connection
63
77
  # manager. That means the GC will run and collect the connection
64
78
  # manager that we created above.
65
- Timecop.freeze(t + StripeClient::CONNECTION_MANAGER_GC_LAST_USED_EXPIRY + 1) do
66
- assert_equal 1, StripeClient.maybe_gc_connection_managers
79
+ #
80
+ Util.stubs(:monotonic_time).returns(t + StripeClient::CONNECTION_MANAGER_GC_LAST_USED_EXPIRY + 1)
67
81
 
68
- # And as an additional check, the connection manager of the current thread context should have been set to `nil` as it was GCed.
69
- assert_nil StripeClient.current_thread_context.default_connection_manager
70
- end
82
+ assert_equal 1, StripeClient.maybe_gc_connection_managers
83
+
84
+ # And as an additional check, the connection manager of the current
85
+ # thread context should have been set to `nil` as it was GCed.
86
+ assert_nil StripeClient.current_thread_context.default_connection_manager
71
87
  end
72
88
  end
73
89
 
@@ -156,6 +172,26 @@ module Stripe
156
172
  method: :post, num_retries: 0)
157
173
  end
158
174
 
175
+ should "retry on EOFError" do
176
+ assert StripeClient.should_retry?(EOFError.new,
177
+ method: :post, num_retries: 0)
178
+ end
179
+
180
+ should "retry on Errno::ECONNRESET" do
181
+ assert StripeClient.should_retry?(Errno::ECONNRESET.new,
182
+ method: :post, num_retries: 0)
183
+ end
184
+
185
+ should "retry on Errno::ETIMEDOUT" do
186
+ assert StripeClient.should_retry?(Errno::ETIMEDOUT.new,
187
+ method: :post, num_retries: 0)
188
+ end
189
+
190
+ should "retry on Errno::EHOSTUNREACH" do
191
+ assert StripeClient.should_retry?(Errno::EHOSTUNREACH.new,
192
+ method: :post, num_retries: 0)
193
+ end
194
+
159
195
  should "retry on Net::OpenTimeout" do
160
196
  assert StripeClient.should_retry?(Net::OpenTimeout.new,
161
197
  method: :post, num_retries: 0)
@@ -171,6 +207,28 @@ module Stripe
171
207
  method: :post, num_retries: 0)
172
208
  end
173
209
 
210
+ should "retry when the `Stripe-Should-Retry` header is `true`" do
211
+ headers = StripeResponse::Headers.new(
212
+ "Stripe-Should-Retry" => ["true"]
213
+ )
214
+
215
+ # Note we send status 400 here, which would normally not be retried.
216
+ assert StripeClient.should_retry?(Stripe::StripeError.new(http_headers: headers,
217
+ http_status: 400),
218
+ method: :post, num_retries: 0)
219
+ end
220
+
221
+ should "not retry when the `Stripe-Should-Retry` header is `false`" do
222
+ headers = StripeResponse::Headers.new(
223
+ "Stripe-Should-Retry" => ["false"]
224
+ )
225
+
226
+ # Note we send status 409 here, which would normally be retried.
227
+ refute StripeClient.should_retry?(Stripe::StripeError.new(http_headers: headers,
228
+ http_status: 409),
229
+ method: :post, num_retries: 0)
230
+ end
231
+
174
232
  should "retry on a 409 Conflict" do
175
233
  assert StripeClient.should_retry?(Stripe::StripeError.new(http_status: 409),
176
234
  method: :post, num_retries: 0)
@@ -279,16 +337,11 @@ module Stripe
279
337
  context "logging" do
280
338
  setup do
281
339
  # Freeze time for the purposes of the `elapsed` parameter that we
282
- # emit for responses. I didn't want to bring in a new dependency for
283
- # this, but Mocha's `anything` parameter can't match inside of a hash
284
- # and is therefore not useful for this purpose. If we switch over to
285
- # rspec-mocks at some point, we can probably remove Timecop from the
286
- # project.
287
- Timecop.freeze(Time.local(1990))
288
- end
289
-
290
- teardown do
291
- Timecop.return
340
+ # emit for responses. Mocha's `anything` parameter can't match inside
341
+ # of a hash and is therefore not useful for this purpose. If we
342
+ # switch over to rspec-mocks at some point, we can probably remove
343
+ # this.
344
+ Util.stubs(:monotonic_time).returns(0.0)
292
345
  end
293
346
 
294
347
  should "produce appropriate logging" do
data/test/test_helper.rb CHANGED
@@ -8,7 +8,6 @@ require "test/unit"
8
8
  require "mocha/setup"
9
9
  require "stringio"
10
10
  require "shoulda/context"
11
- require "timecop"
12
11
  require "webmock/test_unit"
13
12
 
14
13
  PROJECT_ROOT = ::File.expand_path("../", __dir__)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stripe
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.0
4
+ version: 5.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stripe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-20 00:00:00.000000000 Z
11
+ date: 2019-10-04 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Stripe is the easiest way to accept payments online. See https://stripe.com
14
14
  for details.
@@ -246,7 +246,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
246
246
  - !ruby/object:Gem::Version
247
247
  version: '0'
248
248
  requirements: []
249
- rubygems_version: 3.0.3
249
+ rubygems_version: 3.0.6
250
250
  signing_key:
251
251
  specification_version: 4
252
252
  summary: Ruby bindings for the Stripe API