stripe 4.24.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|