stripe 5.3.0 → 5.7.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: d03cea94ef3cacc081d711f3f1fe3bb779791c77e35f4c087ee8c69ffecabb93
4
- data.tar.gz: a3546067f4fe07f56ab18458691d5261f0a533760da07995dedbd3758259ec18
3
+ metadata.gz: 8572565264d4271941fd03cf4cf4f9e02a8f98bfd918193b9b1ce18f3c9ba8ee
4
+ data.tar.gz: e2c2f14fcc9429ab22f9ca25bcdab3a80425cab368f7638e2a26ea9698a2a9ec
5
5
  SHA512:
6
- metadata.gz: d821b782237ca17e82a770e6a8ec11d97732290af7f06aaae680d0830cc63b523b7e9d8831d96294f4b80a5facb42993bbddb4a27017542fafd3399b3a513aaf
7
- data.tar.gz: 665f013eec87c9c1b9deac66cec3125a3d6e65920c1d2f45bb509c4b5371222817d6bbe53f7f05406d8f477da061c3d1c3a5e5d94b6047d0d56d3736cf3f710a
6
+ metadata.gz: 4abc40405003b613b3361999bbc6a8be9be66a821826535f29ee6f2843a265b9148ef1ebb22b7aaa48539aabde2358598bccb684460cc9c07f19a0ec47b2bd3e
7
+ data.tar.gz: c052b3c37a92cafc3801c1c242bf55fddc3e8ed335783da746b7214bba63061478697d3d95bc540cacdd8d0d2f049d0836d4e202fd75923840284408d5de006f
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.7.0 - 2019-10-10
4
+ * [#865](https://github.com/stripe/stripe-ruby/pull/865) Support backwards pagination with list's `#auto_paging_each`
5
+
6
+ ## 5.6.0 - 2019-10-04
7
+ * [#861](https://github.com/stripe/stripe-ruby/pull/861) Nicer error when specifying non-nil non-string opt value
8
+
9
+ ## 5.5.0 - 2019-10-03
10
+ * [#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
11
+
12
+ ## 5.4.1 - 2019-10-01
13
+ * [#858](https://github.com/stripe/stripe-ruby/pull/858) Drop Timecop dependency
14
+
15
+ ## 5.4.0 - 2019-10-01
16
+ * [#857](https://github.com/stripe/stripe-ruby/pull/857) Move to monotonic time for duration calculations
17
+
3
18
  ## 5.3.0 - 2019-10-01
4
19
  * [#853](https://github.com/stripe/stripe-ruby/pull/853) Support `Stripe-Should-Retry` header
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.3.0
1
+ 5.7.0
@@ -11,12 +11,7 @@ module Stripe
11
11
 
12
12
  # set filters so that we can fetch the same limit, expansions, and
13
13
  # predicates when accessing the next and previous pages
14
- #
15
- # just for general cleanliness, remove any paging options
16
14
  obj.filters = filters.dup
17
- obj.filters.delete(:ending_before)
18
- obj.filters.delete(:starting_after)
19
-
20
15
  obj
21
16
  end
22
17
  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
 
@@ -59,8 +59,18 @@ module Stripe
59
59
 
60
60
  page = self
61
61
  loop do
62
- page.each(&blk)
63
- page = page.next_page
62
+ # Backward iterating activates if we have an `ending_before` constraint
63
+ # and _just_ an `ending_before` constraint. If `starting_after` was
64
+ # also used, we iterate forwards normally.
65
+ if filters.include?(:ending_before) &&
66
+ !filters.include?(:starting_after)
67
+ page.reverse_each(&blk)
68
+ page = page.previous_page
69
+ else
70
+ page.each(&blk)
71
+ page = page.next_page
72
+ end
73
+
64
74
  break if page.empty?
65
75
  end
66
76
  end
@@ -96,6 +106,8 @@ module Stripe
96
106
  # This method will try to respect the limit of the current page. If none
97
107
  # was given, the default limit will be fetched again.
98
108
  def previous_page(params = {}, opts = {})
109
+ return self.class.empty_list(opts) unless has_more
110
+
99
111
  first_id = data.first.id
100
112
 
101
113
  params = filters.merge(ending_before: first_id).merge(params)
@@ -107,5 +119,11 @@ module Stripe
107
119
  url ||
108
120
  raise(ArgumentError, "List object does not contain a 'url' field.")
109
121
  end
122
+
123
+ # Iterates through each resource in the page represented by the current
124
+ # `ListObject` in reverse.
125
+ def reverse_each(&blk)
126
+ data.reverse_each(&blk)
127
+ end
110
128
  end
111
129
  end
@@ -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,17 @@ 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)
87
-
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)
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
95
  # The API may ask us not to retry (e.g. if doing so would be a no-op),
96
96
  # or advise us to retry (e.g. in cases of lock timeouts). Defer to
97
97
  # those instructions if given.
@@ -119,10 +119,10 @@ module Stripe
119
119
  return true if error.http_status == 500 && method != :post
120
120
 
121
121
  # 503 Service Unavailable
122
- return true if error.http_status == 503
122
+ error.http_status == 503
123
+ else
124
+ false
123
125
  end
124
-
125
- false
126
126
  end
127
127
 
128
128
  def self.sleep_time(num_retries)
@@ -296,7 +296,11 @@ module Stripe
296
296
  # The original error message is also appended onto the final exception for
297
297
  # full transparency.
298
298
  NETWORK_ERROR_MESSAGES_MAP = {
299
+ EOFError => ERROR_MESSAGE_CONNECTION,
299
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,
300
304
  SocketError => ERROR_MESSAGE_CONNECTION,
301
305
 
302
306
  Net::OpenTimeout => ERROR_MESSAGE_TIMEOUT_CONNECT,
@@ -368,11 +372,11 @@ module Stripe
368
372
  # For internal use only. Does not provide a stable API and may be broken
369
373
  # with future non-major changes.
370
374
  def self.maybe_gc_connection_managers
371
- if @last_connection_manager_gc + CONNECTION_MANAGER_GC_PERIOD > Time.now
372
- return nil
373
- end
375
+ next_gc_time = @last_connection_manager_gc + CONNECTION_MANAGER_GC_PERIOD
376
+ return nil if next_gc_time > Util.monotonic_time
374
377
 
375
- 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
376
380
 
377
381
  pruned_thread_contexts = []
378
382
  @thread_contexts_with_connection_managers.each do |thread_context|
@@ -385,7 +389,7 @@ module Stripe
385
389
  end
386
390
 
387
391
  @thread_contexts_with_connection_managers -= pruned_thread_contexts
388
- @last_connection_manager_gc = Time.now
392
+ @last_connection_manager_gc = Util.monotonic_time
389
393
 
390
394
  pruned_thread_contexts.count
391
395
  end
@@ -445,7 +449,7 @@ module Stripe
445
449
  private def execute_request_with_rescues(method, api_base, context)
446
450
  num_retries = 0
447
451
  begin
448
- request_start = Time.now
452
+ request_start = Util.monotonic_time
449
453
  log_request(context, num_retries)
450
454
  resp = yield
451
455
  context = context.dup_from_response_headers(resp)
@@ -455,7 +459,8 @@ module Stripe
455
459
  log_response(context, request_start, resp.code.to_i, resp.body)
456
460
 
457
461
  if Stripe.enable_telemetry? && context.request_id
458
- request_duration_ms = ((Time.now - request_start) * 1000).to_int
462
+ request_duration_ms =
463
+ ((Util.monotonic_time - request_start) * 1000).to_int
459
464
  @last_request_metrics =
460
465
  StripeRequestMetrics.new(context.request_id, request_duration_ms)
461
466
  end
@@ -726,7 +731,7 @@ module Stripe
726
731
  Util.log_info("Response from Stripe API",
727
732
  account: context.account,
728
733
  api_version: context.api_version,
729
- elapsed: Time.now - request_start,
734
+ elapsed: Util.monotonic_time - request_start,
730
735
  idempotency_key: context.idempotency_key,
731
736
  method: context.method,
732
737
  path: context.path,
@@ -748,7 +753,7 @@ module Stripe
748
753
 
749
754
  private def log_response_error(context, request_start, error)
750
755
  Util.log_error("Request error",
751
- elapsed: Time.now - request_start,
756
+ elapsed: Util.monotonic_time - request_start,
752
757
  error_message: error.message,
753
758
  idempotency_key: context.idempotency_key,
754
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.3.0"
4
+ VERSION = "5.7.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
@@ -25,20 +25,80 @@ module Stripe
25
25
  assert_equal expected, list.each.to_a
26
26
  end
27
27
 
28
- should "provide #auto_paging_each" do
28
+ should "provide #reverse_each" do
29
29
  arr = [
30
30
  { id: 1 },
31
31
  { id: 2 },
32
32
  { id: 3 },
33
33
  ]
34
+ expected = Util.convert_to_stripe_object(arr.reverse, {})
35
+ list = Stripe::ListObject.construct_from(data: arr)
36
+ assert_equal expected, list.reverse_each.to_a
37
+ end
38
+
39
+ should "provide #auto_paging_each that supports forward pagination" do
40
+ arr = [
41
+ { id: 1 },
42
+ { id: 2 },
43
+ { id: 3 },
44
+ { id: 4 },
45
+ { id: 5 },
46
+ { id: 6 },
47
+ ]
34
48
  expected = Util.convert_to_stripe_object(arr, {})
35
49
 
50
+ # Initial list object to page on. Notably, its last data element will be
51
+ # used as a cursor to fetch the next page.
36
52
  list = TestListObject.construct_from(data: [{ id: 1 }],
37
53
  has_more: true,
38
54
  url: "/things")
55
+ list.filters = { limit: 3 }
56
+
57
+ # The test will start with the synthetic list object above, and use it as
58
+ # a starting point to fetch two more pages. The second page indicates
59
+ # that there are no more elements by setting `has_more` to `false`, and
60
+ # iteration stops.
39
61
  stub_request(:get, "#{Stripe.api_base}/things")
40
- .with(query: { starting_after: "1" })
41
- .to_return(body: JSON.generate(data: [{ id: 2 }, { id: 3 }], has_more: false))
62
+ .with(query: { starting_after: "1", limit: "3" })
63
+ .to_return(body: JSON.generate(data: [{ id: 2 }, { id: 3 }, { id: 4 }], has_more: true, url: "/things"))
64
+ stub_request(:get, "#{Stripe.api_base}/things")
65
+ .with(query: { starting_after: "4", limit: "3" })
66
+ .to_return(body: JSON.generate(data: [{ id: 5 }, { id: 6 }], has_more: false, url: "/things"))
67
+
68
+ assert_equal expected, list.auto_paging_each.to_a
69
+ end
70
+
71
+ should "provide #auto_paging_each that supports backward pagination with `ending_before`" do
72
+ arr = [
73
+ { id: 6 },
74
+ { id: 5 },
75
+ { id: 4 },
76
+ { id: 3 },
77
+ { id: 2 },
78
+ { id: 1 },
79
+ ]
80
+ expected = Util.convert_to_stripe_object(arr, {})
81
+
82
+ # Initial list object to page on. Notably, its first data element will be
83
+ # used as a cursor to fetch the next page.
84
+ list = TestListObject.construct_from(data: [{ id: 6 }],
85
+ has_more: true,
86
+ url: "/things")
87
+
88
+ # We also add an `ending_before` filter on the list to simulate backwards
89
+ # pagination.
90
+ list.filters = { ending_before: 7, limit: 3 }
91
+
92
+ # The test will start with the synthetic list object above, and use it as
93
+ # a starting point to fetch two more pages. The second page indicates
94
+ # that there are no more elements by setting `has_more` to `false`, and
95
+ # iteration stops.
96
+ stub_request(:get, "#{Stripe.api_base}/things")
97
+ .with(query: { ending_before: "6", limit: "3" })
98
+ .to_return(body: JSON.generate(data: [{ id: 3 }, { id: 4 }, { id: 5 }], has_more: true, url: "/things"))
99
+ stub_request(:get, "#{Stripe.api_base}/things")
100
+ .with(query: { ending_before: "3", limit: "3" })
101
+ .to_return(body: JSON.generate(data: [{ id: 1 }, { id: 2 }], has_more: false, url: "/things"))
42
102
 
43
103
  assert_equal expected, list.auto_paging_each.to_a
44
104
  end
@@ -97,7 +157,7 @@ module Stripe
97
157
  .with(query: { "expand[]" => "data.source", "limit" => "3", "starting_after" => "1" })
98
158
  .to_return(body: JSON.generate(data: [{ id: 2 }], has_more: false))
99
159
  next_list = list.next_page
100
- assert_equal({ expand: ["data.source"], limit: 3 }, next_list.filters)
160
+ assert_equal({ expand: ["data.source"], limit: 3, starting_after: 1 }, next_list.filters)
101
161
  end
102
162
 
103
163
  should "fetch an empty page through #next_page" do
@@ -114,23 +174,25 @@ module Stripe
114
174
 
115
175
  should "fetch a next page through #previous_page" do
116
176
  list = TestListObject.construct_from(data: [{ id: 2 }],
177
+ has_more: true,
117
178
  url: "/things")
118
179
  stub_request(:get, "#{Stripe.api_base}/things")
119
180
  .with(query: { ending_before: "2" })
120
- .to_return(body: JSON.generate(data: [{ id: 1 }]))
181
+ .to_return(body: JSON.generate(data: [{ id: 1 }], has_more: false))
121
182
  next_list = list.previous_page
122
183
  refute next_list.empty?
123
184
  end
124
185
 
125
186
  should "fetch a next page through #previous_page and respect limit" do
126
187
  list = TestListObject.construct_from(data: [{ id: 2 }],
188
+ has_more: true,
127
189
  url: "/things")
128
190
  list.filters = { expand: ["data.source"], limit: 3 }
129
191
  stub_request(:get, "#{Stripe.api_base}/things")
130
192
  .with(query: { "expand[]" => "data.source", "limit" => "3", "ending_before" => "2" })
131
- .to_return(body: JSON.generate(data: [{ id: 1 }]))
193
+ .to_return(body: JSON.generate(data: [{ id: 1 }], has_more: false))
132
194
  next_list = list.previous_page
133
- assert_equal({ expand: ["data.source"], limit: 3 }, next_list.filters)
195
+ assert_equal({ ending_before: 2, expand: ["data.source"], limit: 3 }, next_list.filters)
134
196
  end
135
197
  end
136
198
  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
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
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)
@@ -301,16 +337,11 @@ module Stripe
301
337
  context "logging" do
302
338
  setup do
303
339
  # Freeze time for the purposes of the `elapsed` parameter that we
304
- # emit for responses. I didn't want to bring in a new dependency for
305
- # this, but Mocha's `anything` parameter can't match inside of a hash
306
- # and is therefore not useful for this purpose. If we switch over to
307
- # rspec-mocks at some point, we can probably remove Timecop from the
308
- # project.
309
- Timecop.freeze(Time.local(1990))
310
- end
311
-
312
- teardown do
313
- 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)
314
345
  end
315
346
 
316
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.3.0
4
+ version: 5.7.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-10-01 00:00:00.000000000 Z
11
+ date: 2019-10-10 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