stripe 4.24.0 → 5.0.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 +4 -4
- data/.rubocop.yml +17 -4
- data/.rubocop_todo.yml +10 -9
- data/.travis.yml +1 -5
- data/CHANGELOG.md +22 -0
- data/Gemfile +2 -12
- data/README.md +10 -10
- data/Rakefile +8 -7
- data/VERSION +1 -1
- data/lib/stripe.rb +56 -15
- data/lib/stripe/api_operations/list.rb +0 -6
- data/lib/stripe/connection_manager.rb +131 -0
- data/lib/stripe/error_object.rb +94 -0
- data/lib/stripe/errors.rb +15 -2
- data/lib/stripe/list_object.rb +2 -1
- data/lib/stripe/multipart_encoder.rb +131 -0
- data/lib/stripe/object_types.rb +0 -1
- data/lib/stripe/resources.rb +0 -1
- data/lib/stripe/resources/account.rb +1 -5
- data/lib/stripe/resources/account_link.rb +1 -1
- data/lib/stripe/resources/alipay_account.rb +1 -1
- data/lib/stripe/resources/apple_pay_domain.rb +1 -1
- data/lib/stripe/resources/application_fee.rb +1 -12
- data/lib/stripe/resources/application_fee_refund.rb +1 -1
- data/lib/stripe/resources/balance.rb +1 -1
- data/lib/stripe/resources/balance_transaction.rb +1 -1
- data/lib/stripe/resources/bank_account.rb +1 -1
- data/lib/stripe/resources/bitcoin_receiver.rb +1 -1
- data/lib/stripe/resources/bitcoin_transaction.rb +1 -1
- data/lib/stripe/resources/capability.rb +1 -1
- data/lib/stripe/resources/card.rb +1 -1
- data/lib/stripe/resources/charge.rb +7 -69
- data/lib/stripe/resources/checkout/session.rb +1 -1
- data/lib/stripe/resources/country_spec.rb +1 -1
- data/lib/stripe/resources/coupon.rb +1 -1
- data/lib/stripe/resources/credit_note.rb +1 -1
- data/lib/stripe/resources/customer.rb +3 -63
- data/lib/stripe/resources/customer_balance_transaction.rb +1 -1
- data/lib/stripe/resources/discount.rb +1 -1
- data/lib/stripe/resources/dispute.rb +1 -7
- data/lib/stripe/resources/ephemeral_key.rb +1 -1
- data/lib/stripe/resources/event.rb +1 -1
- data/lib/stripe/resources/exchange_rate.rb +1 -1
- data/lib/stripe/resources/file.rb +3 -13
- data/lib/stripe/resources/file_link.rb +1 -1
- data/lib/stripe/resources/invoice.rb +6 -1
- data/lib/stripe/resources/invoice_item.rb +1 -1
- data/lib/stripe/resources/invoice_line_item.rb +1 -1
- data/lib/stripe/resources/issuing/authorization.rb +1 -1
- data/lib/stripe/resources/issuing/card.rb +1 -1
- data/lib/stripe/resources/issuing/card_details.rb +1 -1
- data/lib/stripe/resources/issuing/cardholder.rb +1 -1
- data/lib/stripe/resources/issuing/dispute.rb +1 -1
- data/lib/stripe/resources/issuing/transaction.rb +1 -1
- data/lib/stripe/resources/login_link.rb +1 -1
- data/lib/stripe/resources/order.rb +1 -9
- data/lib/stripe/resources/order_return.rb +1 -1
- data/lib/stripe/resources/payment_intent.rb +1 -1
- data/lib/stripe/resources/payment_method.rb +1 -1
- data/lib/stripe/resources/payout.rb +1 -7
- data/lib/stripe/resources/person.rb +1 -1
- data/lib/stripe/resources/plan.rb +1 -1
- data/lib/stripe/resources/product.rb +1 -1
- data/lib/stripe/resources/radar/early_fraud_warning.rb +1 -1
- data/lib/stripe/resources/radar/value_list.rb +1 -1
- data/lib/stripe/resources/radar/value_list_item.rb +1 -1
- data/lib/stripe/resources/recipient.rb +1 -5
- data/lib/stripe/resources/recipient_transfer.rb +1 -1
- data/lib/stripe/resources/refund.rb +1 -1
- data/lib/stripe/resources/reporting/report_run.rb +1 -1
- data/lib/stripe/resources/reporting/report_type.rb +1 -1
- data/lib/stripe/resources/reversal.rb +1 -1
- data/lib/stripe/resources/review.rb +1 -1
- data/lib/stripe/resources/setup_intent.rb +1 -1
- data/lib/stripe/resources/sigma/scheduled_query_run.rb +1 -1
- data/lib/stripe/resources/sku.rb +1 -1
- data/lib/stripe/resources/source.rb +1 -7
- data/lib/stripe/resources/source_transaction.rb +1 -1
- data/lib/stripe/resources/subscription.rb +9 -9
- data/lib/stripe/resources/subscription_item.rb +1 -1
- data/lib/stripe/resources/subscription_schedule.rb +1 -1
- data/lib/stripe/resources/tax_id.rb +1 -1
- data/lib/stripe/resources/tax_rate.rb +1 -1
- data/lib/stripe/resources/terminal/connection_token.rb +1 -1
- data/lib/stripe/resources/terminal/location.rb +1 -1
- data/lib/stripe/resources/terminal/reader.rb +1 -1
- data/lib/stripe/resources/three_d_secure.rb +1 -1
- data/lib/stripe/resources/token.rb +1 -1
- data/lib/stripe/resources/topup.rb +1 -1
- data/lib/stripe/resources/transfer.rb +1 -6
- data/lib/stripe/resources/usage_record.rb +1 -17
- data/lib/stripe/resources/usage_record_summary.rb +1 -1
- data/lib/stripe/resources/webhook_endpoint.rb +1 -1
- data/lib/stripe/stripe_client.rb +281 -183
- data/lib/stripe/stripe_object.rb +4 -23
- data/lib/stripe/stripe_response.rb +53 -21
- data/lib/stripe/util.rb +10 -11
- data/lib/stripe/version.rb +1 -1
- data/lib/stripe/webhook.rb +1 -1
- data/stripe.gemspec +6 -9
- data/test/stripe/account_test.rb +0 -16
- data/test/stripe/api_operations_test.rb +2 -2
- data/test/stripe/api_resource_test.rb +2 -10
- data/test/stripe/charge_test.rb +0 -16
- data/test/stripe/connection_manager_test.rb +138 -0
- data/test/stripe/customer_test.rb +1 -44
- data/test/stripe/errors_test.rb +29 -8
- data/test/stripe/file_test.rb +0 -10
- data/test/stripe/invoice_test.rb +17 -1
- data/test/stripe/list_object_test.rb +0 -16
- data/test/stripe/login_link_test.rb +1 -1
- data/test/stripe/multipart_encoder_test.rb +130 -0
- data/test/stripe/payment_intent_test.rb +1 -1
- data/test/stripe/setup_intent_test.rb +1 -1
- data/test/stripe/source_test.rb +0 -18
- data/test/stripe/stripe_client_test.rb +214 -29
- data/test/stripe/stripe_object_test.rb +7 -35
- data/test/stripe/stripe_response_test.rb +70 -24
- data/test/stripe/subscription_test.rb +2 -2
- data/test/stripe/webhook_test.rb +2 -2
- data/test/stripe_mock.rb +4 -3
- data/test/stripe_test.rb +0 -13
- data/test/test_helper.rb +10 -5
- metadata +11 -39
- data/lib/stripe/resources/issuer_fraud_record.rb +0 -9
- data/test/stripe/file_upload_test.rb +0 -79
- data/test/stripe/issuer_fraud_record_test.rb +0 -20
- data/test/stripe/usage_record_test.rb +0 -28
@@ -53,50 +53,6 @@ module Stripe
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
-
context "#create_subscription" do
|
57
|
-
should "create a new subscription" do
|
58
|
-
customer = Stripe::Customer.retrieve("cus_123")
|
59
|
-
subscription = customer.create_subscription(items: [{ plan: "silver" }])
|
60
|
-
assert_requested :post, "#{Stripe.api_base}/v1/customers/#{customer.id}/subscriptions"
|
61
|
-
assert subscription.is_a?(Stripe::Subscription)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
context "#create_upcoming_invoice" do
|
66
|
-
should "create a new invoice" do
|
67
|
-
customer = Stripe::Customer.retrieve("cus_123")
|
68
|
-
invoice = customer.create_upcoming_invoice
|
69
|
-
assert_requested :post, "#{Stripe.api_base}/v1/invoices"
|
70
|
-
assert invoice.is_a?(Stripe::Invoice)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
context "#update_subscription" do
|
75
|
-
should "update a subscription" do
|
76
|
-
customer = Stripe::Customer.retrieve("cus_123")
|
77
|
-
|
78
|
-
# deprecated API and not in schema
|
79
|
-
stub_request(:post, "#{Stripe.api_base}/v1/customers/#{customer.id}/subscription")
|
80
|
-
.with(body: { plan: "silver" })
|
81
|
-
.to_return(body: JSON.generate(object: "subscription"))
|
82
|
-
subscription = customer.update_subscription(plan: "silver")
|
83
|
-
assert subscription.is_a?(Stripe::Subscription)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
context "#cancel_subscription" do
|
88
|
-
should "cancel a subscription" do
|
89
|
-
customer = Stripe::Customer.retrieve("cus_123")
|
90
|
-
|
91
|
-
# deprecated API and not in schema
|
92
|
-
stub_request(:delete, "#{Stripe.api_base}/v1/customers/#{customer.id}/subscription")
|
93
|
-
.with(query: { at_period_end: "true" })
|
94
|
-
.to_return(body: JSON.generate(object: "subscription"))
|
95
|
-
subscription = customer.cancel_subscription(at_period_end: "true")
|
96
|
-
assert subscription.is_a?(Stripe::Subscription)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
56
|
context "#delete_discount" do
|
101
57
|
should "delete a discount" do
|
102
58
|
customer = Stripe::Customer.retrieve("cus_123")
|
@@ -113,6 +69,7 @@ module Stripe
|
|
113
69
|
assert discount.is_a?(Stripe::Discount)
|
114
70
|
end
|
115
71
|
end
|
72
|
+
|
116
73
|
context "#create_source" do
|
117
74
|
should "create a source" do
|
118
75
|
Stripe::Customer.create_source(
|
data/test/stripe/errors_test.rb
CHANGED
@@ -4,16 +4,37 @@ require ::File.expand_path("../test_helper", __dir__)
|
|
4
4
|
|
5
5
|
module Stripe
|
6
6
|
class StripeErrorTest < Test::Unit::TestCase
|
7
|
-
context "
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
context "StripeError" do
|
8
|
+
context "#initialize" do
|
9
|
+
should "initialize error if json_body is set" do
|
10
|
+
e = StripeError.new("message", json_body: { error: { code: "some_error" } })
|
11
|
+
assert_not_nil e.error
|
12
|
+
assert_equal "some_error", e.error.code
|
13
|
+
assert_nil e.error.charge
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "#to_s" do
|
18
|
+
should "convert to string" do
|
19
|
+
e = StripeError.new("message")
|
20
|
+
assert_equal "message", e.to_s
|
21
|
+
|
22
|
+
e = StripeError.new("message", http_status: 200)
|
23
|
+
assert_equal "(Status 200) message", e.to_s
|
11
24
|
|
12
|
-
|
13
|
-
|
25
|
+
e = StripeError.new("message", http_status: nil, http_body: nil, json_body: nil, http_headers: { request_id: "request-id" })
|
26
|
+
assert_equal "(Request request-id) message", e.to_s
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
14
30
|
|
15
|
-
|
16
|
-
|
31
|
+
context "OAuth::OAuthError" do
|
32
|
+
context "#initialize" do
|
33
|
+
should "initialize error if json_body is set" do
|
34
|
+
e = OAuth::OAuthError.new("message", "description", json_body: { error: "some_oauth_error" })
|
35
|
+
assert_not_nil e.error
|
36
|
+
assert_equal "some_oauth_error", e.error.error
|
37
|
+
end
|
17
38
|
end
|
18
39
|
end
|
19
40
|
end
|
data/test/stripe/file_test.rb
CHANGED
@@ -52,16 +52,6 @@ module Stripe
|
|
52
52
|
assert file.is_a?(Stripe::File)
|
53
53
|
end
|
54
54
|
|
55
|
-
should "be creatable with Faraday::UploadIO" do
|
56
|
-
file = Stripe::File.create(
|
57
|
-
purpose: "dispute_evidence",
|
58
|
-
file: Faraday::UploadIO.new(::File.new(__FILE__), nil),
|
59
|
-
file_link_data: { create: true }
|
60
|
-
)
|
61
|
-
assert_requested :post, "#{Stripe.uploads_base}/v1/files"
|
62
|
-
assert file.is_a?(Stripe::File)
|
63
|
-
end
|
64
|
-
|
65
55
|
should "be creatable with a string" do
|
66
56
|
file = Stripe::File.create(
|
67
57
|
purpose: "dispute_evidence",
|
data/test/stripe/invoice_test.rb
CHANGED
@@ -142,7 +142,7 @@ module Stripe
|
|
142
142
|
end
|
143
143
|
end
|
144
144
|
|
145
|
-
context "
|
145
|
+
context ".upcoming" do
|
146
146
|
should "retrieve upcoming invoices" do
|
147
147
|
invoice = Stripe::Invoice.upcoming(
|
148
148
|
customer: "cus_123",
|
@@ -192,6 +192,22 @@ module Stripe
|
|
192
192
|
end
|
193
193
|
end
|
194
194
|
|
195
|
+
context ".list_upcoming_line_items" do
|
196
|
+
should "retrieve upcoming invoices" do
|
197
|
+
line_items = Stripe::Invoice.list_upcoming_line_items(
|
198
|
+
customer: "cus_123",
|
199
|
+
subscription: "sub_123"
|
200
|
+
)
|
201
|
+
assert_requested :get, "#{Stripe.api_base}/v1/invoices/upcoming/lines",
|
202
|
+
query: {
|
203
|
+
customer: "cus_123",
|
204
|
+
subscription: "sub_123",
|
205
|
+
}
|
206
|
+
assert line_items.data.is_a?(Array)
|
207
|
+
assert line_items.data[0].is_a?(Stripe::InvoiceLineItem)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
195
211
|
context "#void_invoice" do
|
196
212
|
should "void invoice" do
|
197
213
|
invoice = Stripe::Invoice.retrieve("in_123")
|
@@ -132,22 +132,6 @@ module Stripe
|
|
132
132
|
next_list = list.previous_page
|
133
133
|
assert_equal({ expand: ["data.source"], limit: 3 }, next_list.filters)
|
134
134
|
end
|
135
|
-
|
136
|
-
#
|
137
|
-
# backward compatibility
|
138
|
-
#
|
139
|
-
|
140
|
-
# note that the name #all is deprecated, as is using it fetch the next page
|
141
|
-
# in a list
|
142
|
-
should "be able to retrieve full lists given a listobject" do
|
143
|
-
c = Stripe::Charge.all
|
144
|
-
assert c.is_a?(Stripe::ListObject)
|
145
|
-
assert_equal("/v1/charges", c.resource_url)
|
146
|
-
all = c.all
|
147
|
-
assert all.is_a?(Stripe::ListObject)
|
148
|
-
assert_equal("/v1/charges", all.resource_url)
|
149
|
-
assert all.data.is_a?(Array)
|
150
|
-
end
|
151
135
|
end
|
152
136
|
end
|
153
137
|
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require ::File.expand_path("../test_helper", __dir__)
|
4
|
+
|
5
|
+
module Stripe
|
6
|
+
class MultipartEncoderTest < Test::Unit::TestCase
|
7
|
+
should "multipart encode parameters" do
|
8
|
+
Tempfile.create("image.jpg") do |f|
|
9
|
+
f.write "file-content"
|
10
|
+
f.flush
|
11
|
+
f.rewind
|
12
|
+
|
13
|
+
encoder = MultipartEncoder.new
|
14
|
+
encoder.encode(
|
15
|
+
file: f,
|
16
|
+
other_param: "other-param-content"
|
17
|
+
)
|
18
|
+
encoder.close
|
19
|
+
body = encoder.body
|
20
|
+
|
21
|
+
assert_equal <<~BODY.rstrip, body
|
22
|
+
--#{encoder.boundary}\r
|
23
|
+
Content-Disposition: form-data; name="file"; filename="#{::File.basename(f.path)}"\r
|
24
|
+
Content-Type: application/octet-stream\r
|
25
|
+
\r
|
26
|
+
file-content\r
|
27
|
+
--#{encoder.boundary}\r
|
28
|
+
Content-Disposition: form-data; name="other_param"\r
|
29
|
+
\r
|
30
|
+
other-param-content\r
|
31
|
+
--#{encoder.boundary}--
|
32
|
+
BODY
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
should "encode file-like objects" do
|
37
|
+
klass = Class.new do
|
38
|
+
def read
|
39
|
+
"klass-read-content"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
encoder = MultipartEncoder.new
|
44
|
+
encoder.encode(
|
45
|
+
file_like: klass.new
|
46
|
+
)
|
47
|
+
encoder.close
|
48
|
+
body = encoder.body
|
49
|
+
|
50
|
+
assert_equal <<~BODY.rstrip, body
|
51
|
+
--#{encoder.boundary}\r
|
52
|
+
Content-Disposition: form-data; name="file_like"; filename="blob"\r
|
53
|
+
Content-Type: application/octet-stream\r
|
54
|
+
\r
|
55
|
+
klass-read-content\r
|
56
|
+
--#{encoder.boundary}--
|
57
|
+
BODY
|
58
|
+
end
|
59
|
+
|
60
|
+
should "escape quotes and line break characters in parameter names" do
|
61
|
+
encoder = MultipartEncoder.new
|
62
|
+
encoder.encode(
|
63
|
+
%("quoted\n\r") => "content"
|
64
|
+
)
|
65
|
+
encoder.close
|
66
|
+
body = encoder.body
|
67
|
+
|
68
|
+
assert_equal <<~BODY.rstrip, body
|
69
|
+
--#{encoder.boundary}\r
|
70
|
+
Content-Disposition: form-data; name="%22quoted %22"\r
|
71
|
+
\r
|
72
|
+
content\r
|
73
|
+
--#{encoder.boundary}--
|
74
|
+
BODY
|
75
|
+
end
|
76
|
+
|
77
|
+
context ".encode" do
|
78
|
+
should "provide an easy encoding shortcut" do
|
79
|
+
body, content_type = MultipartEncoder.encode(
|
80
|
+
param: "content"
|
81
|
+
)
|
82
|
+
assert_include body, %(Content-Disposition: form-data; name="param")
|
83
|
+
assert_include content_type, "#{MultipartEncoder::MULTIPART_FORM_DATA}; boundary="
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "#body" do
|
88
|
+
should "error if not yet closed" do
|
89
|
+
encoder = MultipartEncoder.new
|
90
|
+
|
91
|
+
e = assert_raises RuntimeError do
|
92
|
+
encoder.body
|
93
|
+
end
|
94
|
+
assert_equal "object must be closed before getting body", e.message
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context "#close" do
|
99
|
+
should "error if closed twice" do
|
100
|
+
encoder = MultipartEncoder.new
|
101
|
+
encoder.close
|
102
|
+
|
103
|
+
e = assert_raises RuntimeError do
|
104
|
+
encoder.close
|
105
|
+
end
|
106
|
+
assert_equal "object already closed", e.message
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context "#content_type" do
|
111
|
+
should "produce a content type containing boundary" do
|
112
|
+
encoder = MultipartEncoder.new
|
113
|
+
assert_equal "#{MultipartEncoder::MULTIPART_FORM_DATA}; boundary=#{encoder.boundary}",
|
114
|
+
encoder.content_type
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context "#encode" do
|
119
|
+
should "error if already closed" do
|
120
|
+
encoder = MultipartEncoder.new
|
121
|
+
encoder.close
|
122
|
+
|
123
|
+
e = assert_raises RuntimeError do
|
124
|
+
encoder.encode(param: "content")
|
125
|
+
end
|
126
|
+
assert_equal "no more parameters can be written to closed object", e.message
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
data/test/stripe/source_test.rb
CHANGED
@@ -51,24 +51,6 @@ module Stripe
|
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
-
context "#delete" do
|
55
|
-
should "warn that #delete is deprecated" do
|
56
|
-
old_stderr = $stderr
|
57
|
-
$stderr = StringIO.new
|
58
|
-
begin
|
59
|
-
source = Stripe::Source.construct_from(customer: "cus_123",
|
60
|
-
id: "src_123",
|
61
|
-
object: "source")
|
62
|
-
source.delete
|
63
|
-
message = "NOTE: Stripe::Source#delete is " \
|
64
|
-
"deprecated; use #detach instead"
|
65
|
-
assert_match Regexp.new(message), $stderr.string
|
66
|
-
ensure
|
67
|
-
$stderr = old_stderr
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
54
|
should "not be listable" do
|
73
55
|
assert_raises NoMethodError do
|
74
56
|
Stripe::Source.list
|
@@ -17,6 +17,50 @@ module Stripe
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
+
context ".clear_all_connection_managers" do
|
21
|
+
should "clear connection managers across all threads" do
|
22
|
+
stub_request(:post, "#{Stripe.api_base}/path")
|
23
|
+
.to_return(body: JSON.generate(object: "account"))
|
24
|
+
|
25
|
+
num_threads = 3
|
26
|
+
|
27
|
+
# Poorly named class -- note this is actually a concurrent queue.
|
28
|
+
recv_queue = Queue.new
|
29
|
+
send_queue = Queue.new
|
30
|
+
|
31
|
+
threads = num_threads.times.map do |_|
|
32
|
+
Thread.start do
|
33
|
+
# Causes a connection manager to be created on this thread and a
|
34
|
+
# connection within that manager to be created for API access.
|
35
|
+
manager = StripeClient.default_connection_manager
|
36
|
+
manager.execute_request(:post, "#{Stripe.api_base}/path")
|
37
|
+
|
38
|
+
# Signal to the main thread we're ready.
|
39
|
+
recv_queue << true
|
40
|
+
|
41
|
+
# Wait for the main thread to signal continue.
|
42
|
+
send_queue.pop
|
43
|
+
|
44
|
+
# This check isn't great, but it's otherwise difficult to tell that
|
45
|
+
# anything happened with just the public-facing API.
|
46
|
+
assert_equal({}, manager.instance_variable_get(:@active_connections))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Wait for threads to start up.
|
51
|
+
threads.each { recv_queue.pop }
|
52
|
+
|
53
|
+
# Do the clear (the method we're actually trying to test).
|
54
|
+
StripeClient.clear_all_connection_managers
|
55
|
+
|
56
|
+
# Tell threads to run their check.
|
57
|
+
threads.each { send_queue << true }
|
58
|
+
|
59
|
+
# And finally, give all threads time to perform their check.
|
60
|
+
threads.each(&:join)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
20
64
|
context ".default_client" do
|
21
65
|
should "be a StripeClient" do
|
22
66
|
assert_kind_of StripeClient, StripeClient.default_client
|
@@ -32,18 +76,19 @@ module Stripe
|
|
32
76
|
end
|
33
77
|
end
|
34
78
|
|
35
|
-
context ".
|
36
|
-
should "be a
|
37
|
-
assert_kind_of
|
79
|
+
context ".default_connection_manager" do
|
80
|
+
should "be a ConnectionManager" do
|
81
|
+
assert_kind_of ConnectionManager,
|
82
|
+
StripeClient.default_connection_manager
|
38
83
|
end
|
39
84
|
|
40
85
|
should "be a different connection on each thread" do
|
41
|
-
|
86
|
+
other_thread_manager = nil
|
42
87
|
thread = Thread.new do
|
43
|
-
|
88
|
+
other_thread_manager = StripeClient.default_connection_manager
|
44
89
|
end
|
45
90
|
thread.join
|
46
|
-
refute_equal StripeClient.
|
91
|
+
refute_equal StripeClient.default_connection_manager, other_thread_manager
|
47
92
|
end
|
48
93
|
end
|
49
94
|
|
@@ -52,26 +97,54 @@ module Stripe
|
|
52
97
|
Stripe.stubs(:max_network_retries).returns(2)
|
53
98
|
end
|
54
99
|
|
55
|
-
should "retry on
|
56
|
-
assert StripeClient.should_retry?(
|
100
|
+
should "retry on Errno::ECONNREFUSED" do
|
101
|
+
assert StripeClient.should_retry?(Errno::ECONNREFUSED.new,
|
102
|
+
method: :post, num_retries: 0)
|
103
|
+
end
|
104
|
+
|
105
|
+
should "retry on Net::OpenTimeout" do
|
106
|
+
assert StripeClient.should_retry?(Net::OpenTimeout.new,
|
107
|
+
method: :post, num_retries: 0)
|
108
|
+
end
|
109
|
+
|
110
|
+
should "retry on Net::ReadTimeout" do
|
111
|
+
assert StripeClient.should_retry?(Net::ReadTimeout.new,
|
112
|
+
method: :post, num_retries: 0)
|
113
|
+
end
|
114
|
+
|
115
|
+
should "retry on SocketError" do
|
116
|
+
assert StripeClient.should_retry?(SocketError.new,
|
117
|
+
method: :post, num_retries: 0)
|
118
|
+
end
|
119
|
+
|
120
|
+
should "retry on a 409 Conflict" do
|
121
|
+
assert StripeClient.should_retry?(Stripe::StripeError.new(http_status: 409),
|
122
|
+
method: :post, num_retries: 0)
|
57
123
|
end
|
58
124
|
|
59
|
-
should "retry on a
|
60
|
-
assert StripeClient.should_retry?(
|
125
|
+
should "retry on a 500 Internal Server Error when non-POST" do
|
126
|
+
assert StripeClient.should_retry?(Stripe::StripeError.new(http_status: 500),
|
127
|
+
method: :get, num_retries: 0)
|
61
128
|
end
|
62
129
|
|
63
|
-
should "retry on a
|
64
|
-
|
65
|
-
|
66
|
-
assert StripeClient.should_retry?(e, 0)
|
130
|
+
should "retry on a 503 Service Unavailable" do
|
131
|
+
assert StripeClient.should_retry?(Stripe::StripeError.new(http_status: 503),
|
132
|
+
method: :post, num_retries: 0)
|
67
133
|
end
|
68
134
|
|
69
135
|
should "not retry at maximum count" do
|
70
|
-
refute StripeClient.should_retry?(RuntimeError.new,
|
136
|
+
refute StripeClient.should_retry?(RuntimeError.new,
|
137
|
+
method: :post, num_retries: Stripe.max_network_retries)
|
71
138
|
end
|
72
139
|
|
73
140
|
should "not retry on a certificate validation error" do
|
74
|
-
refute StripeClient.should_retry?(
|
141
|
+
refute StripeClient.should_retry?(OpenSSL::SSL::SSLError.new,
|
142
|
+
method: :post, num_retries: 0)
|
143
|
+
end
|
144
|
+
|
145
|
+
should "not retry on a 500 Internal Server Error when POST" do
|
146
|
+
refute StripeClient.should_retry?(Stripe::StripeError.new(http_status: 500),
|
147
|
+
method: :post, num_retries: 0)
|
75
148
|
end
|
76
149
|
end
|
77
150
|
|
@@ -115,15 +188,16 @@ module Stripe
|
|
115
188
|
end
|
116
189
|
|
117
190
|
context "#initialize" do
|
118
|
-
should "set Stripe.
|
191
|
+
should "set Stripe.default_connection_manager" do
|
119
192
|
client = StripeClient.new
|
120
|
-
assert_equal StripeClient.
|
193
|
+
assert_equal StripeClient.default_connection_manager,
|
194
|
+
client.connection_manager
|
121
195
|
end
|
122
196
|
|
123
197
|
should "set a different connection if one was specified" do
|
124
|
-
|
125
|
-
client = StripeClient.new(
|
126
|
-
assert_equal
|
198
|
+
connection_manager = ConnectionManager.new
|
199
|
+
client = StripeClient.new(connection_manager)
|
200
|
+
assert_equal connection_manager, client.connection_manager
|
127
201
|
end
|
128
202
|
end
|
129
203
|
|
@@ -178,7 +252,7 @@ module Stripe
|
|
178
252
|
Util.expects(:log_debug).with("Request details",
|
179
253
|
body: "",
|
180
254
|
idempotency_key: "abc",
|
181
|
-
|
255
|
+
query: nil)
|
182
256
|
|
183
257
|
Util.expects(:log_info).with("Response from Stripe API",
|
184
258
|
account: "acct_123",
|
@@ -403,6 +477,20 @@ module Stripe
|
|
403
477
|
assert_equal 'Invalid response object from API: "" (HTTP response code was 200)', e.message
|
404
478
|
end
|
405
479
|
|
480
|
+
should "handle low level error" do
|
481
|
+
stub_request(:post, "#{Stripe.api_base}/v1/charges")
|
482
|
+
.to_raise(Errno::ECONNREFUSED.new)
|
483
|
+
|
484
|
+
client = StripeClient.new
|
485
|
+
e = assert_raises Stripe::APIConnectionError do
|
486
|
+
client.execute_request(:post, "/v1/charges")
|
487
|
+
end
|
488
|
+
|
489
|
+
assert_equal StripeClient::ERROR_MESSAGE_CONNECTION % Stripe.api_base +
|
490
|
+
"\n\n(Network error: Connection refused)",
|
491
|
+
e.message
|
492
|
+
end
|
493
|
+
|
406
494
|
should "handle error response with unknown value" do
|
407
495
|
stub_request(:post, "#{Stripe.api_base}/v1/charges")
|
408
496
|
.to_return(body: JSON.generate(bar: "foo"), status: 500)
|
@@ -738,14 +826,106 @@ module Stripe
|
|
738
826
|
|
739
827
|
should "reset local thread state after a call" do
|
740
828
|
begin
|
741
|
-
|
829
|
+
StripeClient.current_thread_context.active_client = :stripe_client
|
742
830
|
|
743
831
|
client = StripeClient.new
|
744
832
|
client.request {}
|
745
833
|
|
746
|
-
assert_equal :stripe_client,
|
834
|
+
assert_equal :stripe_client,
|
835
|
+
StripeClient.current_thread_context.active_client
|
747
836
|
ensure
|
748
|
-
|
837
|
+
StripeClient.current_thread_context.active_client = nil
|
838
|
+
end
|
839
|
+
end
|
840
|
+
|
841
|
+
should "correctly return last responses despite multiple clients" do
|
842
|
+
charge_resp = { object: "charge" }
|
843
|
+
coupon_resp = { object: "coupon" }
|
844
|
+
|
845
|
+
stub_request(:post, "#{Stripe.api_base}/v1/charges")
|
846
|
+
.to_return(body: JSON.generate(charge_resp))
|
847
|
+
stub_request(:post, "#{Stripe.api_base}/v1/coupons")
|
848
|
+
.to_return(body: JSON.generate(coupon_resp))
|
849
|
+
|
850
|
+
client1 = StripeClient.new
|
851
|
+
client2 = StripeClient.new
|
852
|
+
|
853
|
+
client2_resp = nil
|
854
|
+
_charge, client1_resp = client1.request do
|
855
|
+
Charge.create
|
856
|
+
|
857
|
+
# This is contrived, but we run one client nested in the `request`
|
858
|
+
# block of another one just to ensure that the parent is still
|
859
|
+
# unwinding when this goes through. If the parent's last response
|
860
|
+
# were to be overridden by this client (through a bug), then it would
|
861
|
+
# happen here.
|
862
|
+
_coupon, client2_resp = client2.request do
|
863
|
+
Coupon.create
|
864
|
+
end
|
865
|
+
end
|
866
|
+
|
867
|
+
assert_equal charge_resp, client1_resp.data
|
868
|
+
assert_equal coupon_resp, client2_resp.data
|
869
|
+
end
|
870
|
+
|
871
|
+
should "correctly return last responses despite multiple threads" do
|
872
|
+
charge_resp = { object: "charge" }
|
873
|
+
coupon_resp = { object: "coupon" }
|
874
|
+
|
875
|
+
stub_request(:post, "#{Stripe.api_base}/v1/charges")
|
876
|
+
.to_return(body: JSON.generate(charge_resp))
|
877
|
+
stub_request(:post, "#{Stripe.api_base}/v1/coupons")
|
878
|
+
.to_return(body: JSON.generate(coupon_resp))
|
879
|
+
|
880
|
+
client = StripeClient.new
|
881
|
+
|
882
|
+
# Poorly named class -- note this is actually a concurrent queue.
|
883
|
+
recv_queue = Queue.new
|
884
|
+
send_queue = Queue.new
|
885
|
+
|
886
|
+
# Start a thread, make an API request, but then idle in the `request`
|
887
|
+
# block until the main thread has been able to make its own API request
|
888
|
+
# and signal that it's done. If this thread's last response were to be
|
889
|
+
# overridden by the main thread (through a bug), then this routine
|
890
|
+
# should suss it out.
|
891
|
+
resp1 = nil
|
892
|
+
thread = Thread.start do
|
893
|
+
_charge, resp1 = client.request do
|
894
|
+
Charge.create
|
895
|
+
|
896
|
+
# Idle in `request` block until main thread signals.
|
897
|
+
send_queue.pop
|
898
|
+
end
|
899
|
+
|
900
|
+
# Signal main thread that we're done and it can run its checks.
|
901
|
+
recv_queue << true
|
902
|
+
end
|
903
|
+
|
904
|
+
# Make an API request.
|
905
|
+
_coupon, resp2 = client.request do
|
906
|
+
Coupon.create
|
907
|
+
end
|
908
|
+
|
909
|
+
# Tell background thread to finish `request`, then wait for it to
|
910
|
+
# signal back to us that it's ready.
|
911
|
+
send_queue << true
|
912
|
+
recv_queue.pop
|
913
|
+
|
914
|
+
assert_equal charge_resp, resp1.data
|
915
|
+
assert_equal coupon_resp, resp2.data
|
916
|
+
|
917
|
+
# And for maximum hygiene, make sure that our thread rejoins.
|
918
|
+
thread.join
|
919
|
+
end
|
920
|
+
|
921
|
+
should "error if calls to #request are nested on the same thread" do
|
922
|
+
client = StripeClient.new
|
923
|
+
client.request do
|
924
|
+
e = assert_raises(RuntimeError) do
|
925
|
+
client.request {}
|
926
|
+
end
|
927
|
+
assert_equal "calls to StripeClient#request cannot be nested within a thread",
|
928
|
+
e.message
|
749
929
|
end
|
750
930
|
end
|
751
931
|
end
|
@@ -753,18 +933,23 @@ module Stripe
|
|
753
933
|
context "#proxy" do
|
754
934
|
should "run the request through the proxy" do
|
755
935
|
begin
|
756
|
-
|
936
|
+
StripeClient.current_thread_context.default_connection_manager = nil
|
757
937
|
|
758
|
-
Stripe.proxy = "http://localhost:8080"
|
938
|
+
Stripe.proxy = "http://user:pass@localhost:8080"
|
759
939
|
|
760
940
|
client = StripeClient.new
|
761
941
|
client.request {}
|
762
942
|
|
763
|
-
|
943
|
+
connection = Stripe::StripeClient.default_connection_manager.connection_for(Stripe.api_base)
|
944
|
+
|
945
|
+
assert_equal "localhost", connection.proxy_address
|
946
|
+
assert_equal 8080, connection.proxy_port
|
947
|
+
assert_equal "user", connection.proxy_user
|
948
|
+
assert_equal "pass", connection.proxy_pass
|
764
949
|
ensure
|
765
950
|
Stripe.proxy = nil
|
766
951
|
|
767
|
-
|
952
|
+
StripeClient.current_thread_context.default_connection_manager = nil
|
768
953
|
end
|
769
954
|
end
|
770
955
|
end
|