stripe 5.3.0 → 5.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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