test/stripe/stripe_client_test.rb in stripe-4.24.0 vs test/stripe/stripe_client_test.rb in stripe-5.0.0
- old
+ new
@@ -15,10 +15,54 @@
assert_equal client, StripeClient.active_client
end
end
end
+ context ".clear_all_connection_managers" do
+ should "clear connection managers across all threads" do
+ stub_request(:post, "#{Stripe.api_base}/path")
+ .to_return(body: JSON.generate(object: "account"))
+
+ num_threads = 3
+
+ # Poorly named class -- note this is actually a concurrent queue.
+ recv_queue = Queue.new
+ send_queue = Queue.new
+
+ threads = num_threads.times.map do |_|
+ Thread.start do
+ # Causes a connection manager to be created on this thread and a
+ # connection within that manager to be created for API access.
+ manager = StripeClient.default_connection_manager
+ manager.execute_request(:post, "#{Stripe.api_base}/path")
+
+ # Signal to the main thread we're ready.
+ recv_queue << true
+
+ # Wait for the main thread to signal continue.
+ send_queue.pop
+
+ # This check isn't great, but it's otherwise difficult to tell that
+ # anything happened with just the public-facing API.
+ assert_equal({}, manager.instance_variable_get(:@active_connections))
+ end
+ end
+
+ # Wait for threads to start up.
+ threads.each { recv_queue.pop }
+
+ # Do the clear (the method we're actually trying to test).
+ StripeClient.clear_all_connection_managers
+
+ # Tell threads to run their check.
+ threads.each { send_queue << true }
+
+ # And finally, give all threads time to perform their check.
+ threads.each(&:join)
+ end
+ end
+
context ".default_client" do
should "be a StripeClient" do
assert_kind_of StripeClient, StripeClient.default_client
end
@@ -30,51 +74,80 @@
thread.join
refute_equal StripeClient.default_client, other_thread_client
end
end
- context ".default_conn" do
- should "be a Faraday::Connection" do
- assert_kind_of Faraday::Connection, StripeClient.default_conn
+ context ".default_connection_manager" do
+ should "be a ConnectionManager" do
+ assert_kind_of ConnectionManager,
+ StripeClient.default_connection_manager
end
should "be a different connection on each thread" do
- other_thread_conn = nil
+ other_thread_manager = nil
thread = Thread.new do
- other_thread_conn = StripeClient.default_conn
+ other_thread_manager = StripeClient.default_connection_manager
end
thread.join
- refute_equal StripeClient.default_conn, other_thread_conn
+ refute_equal StripeClient.default_connection_manager, other_thread_manager
end
end
context ".should_retry?" do
setup do
Stripe.stubs(:max_network_retries).returns(2)
end
- should "retry on timeout" do
- assert StripeClient.should_retry?(Faraday::TimeoutError.new(""), 0)
+ should "retry on Errno::ECONNREFUSED" do
+ assert StripeClient.should_retry?(Errno::ECONNREFUSED.new,
+ method: :post, num_retries: 0)
end
- should "retry on a failed connection" do
- assert StripeClient.should_retry?(Faraday::ConnectionFailed.new(""), 0)
+ should "retry on Net::OpenTimeout" do
+ assert StripeClient.should_retry?(Net::OpenTimeout.new,
+ method: :post, num_retries: 0)
end
- should "retry on a conflict" do
- error = make_rate_limit_error
- e = Faraday::ClientError.new(error[:error][:message], status: 409)
- assert StripeClient.should_retry?(e, 0)
+ should "retry on Net::ReadTimeout" do
+ assert StripeClient.should_retry?(Net::ReadTimeout.new,
+ method: :post, num_retries: 0)
end
+ should "retry on SocketError" do
+ assert StripeClient.should_retry?(SocketError.new,
+ method: :post, num_retries: 0)
+ end
+
+ should "retry on a 409 Conflict" do
+ assert StripeClient.should_retry?(Stripe::StripeError.new(http_status: 409),
+ method: :post, num_retries: 0)
+ end
+
+ should "retry on a 500 Internal Server Error when non-POST" do
+ assert StripeClient.should_retry?(Stripe::StripeError.new(http_status: 500),
+ method: :get, num_retries: 0)
+ end
+
+ should "retry on a 503 Service Unavailable" do
+ assert StripeClient.should_retry?(Stripe::StripeError.new(http_status: 503),
+ method: :post, num_retries: 0)
+ end
+
should "not retry at maximum count" do
- refute StripeClient.should_retry?(RuntimeError.new, Stripe.max_network_retries)
+ refute StripeClient.should_retry?(RuntimeError.new,
+ method: :post, num_retries: Stripe.max_network_retries)
end
should "not retry on a certificate validation error" do
- refute StripeClient.should_retry?(Faraday::SSLError.new(""), 0)
+ refute StripeClient.should_retry?(OpenSSL::SSL::SSLError.new,
+ method: :post, num_retries: 0)
end
+
+ should "not retry on a 500 Internal Server Error when POST" do
+ refute StripeClient.should_retry?(Stripe::StripeError.new(http_status: 500),
+ method: :post, num_retries: 0)
+ end
end
context ".sleep_time" do
should "should grow exponentially" do
StripeClient.stubs(:rand).returns(1)
@@ -113,19 +186,20 @@
assert_equal(base_value * 8, StripeClient.sleep_time(4))
end
end
context "#initialize" do
- should "set Stripe.default_conn" do
+ should "set Stripe.default_connection_manager" do
client = StripeClient.new
- assert_equal StripeClient.default_conn, client.conn
+ assert_equal StripeClient.default_connection_manager,
+ client.connection_manager
end
should "set a different connection if one was specified" do
- conn = Faraday.new
- client = StripeClient.new(conn)
- assert_equal conn, client.conn
+ connection_manager = ConnectionManager.new
+ client = StripeClient.new(connection_manager)
+ assert_equal connection_manager, client.connection_manager
end
end
context "#execute_request" do
context "headers" do
@@ -176,11 +250,11 @@
num_retries: 0,
path: "/v1/account")
Util.expects(:log_debug).with("Request details",
body: "",
idempotency_key: "abc",
- query_params: nil)
+ query: nil)
Util.expects(:log_info).with("Response from Stripe API",
account: "acct_123",
api_version: "2010-11-12",
elapsed: 0.0,
@@ -401,10 +475,24 @@
end
assert_equal 'Invalid response object from API: "" (HTTP response code was 200)', e.message
end
+ should "handle low level error" do
+ stub_request(:post, "#{Stripe.api_base}/v1/charges")
+ .to_raise(Errno::ECONNREFUSED.new)
+
+ client = StripeClient.new
+ e = assert_raises Stripe::APIConnectionError do
+ client.execute_request(:post, "/v1/charges")
+ end
+
+ assert_equal StripeClient::ERROR_MESSAGE_CONNECTION % Stripe.api_base +
+ "\n\n(Network error: Connection refused)",
+ e.message
+ end
+
should "handle error response with unknown value" do
stub_request(:post, "#{Stripe.api_base}/v1/charges")
.to_return(body: JSON.generate(bar: "foo"), status: 500)
client = StripeClient.new
@@ -736,36 +824,133 @@
assert_equal 7, ret
end
should "reset local thread state after a call" do
begin
- Thread.current[:stripe_client] = :stripe_client
+ StripeClient.current_thread_context.active_client = :stripe_client
client = StripeClient.new
client.request {}
- assert_equal :stripe_client, Thread.current[:stripe_client]
+ assert_equal :stripe_client,
+ StripeClient.current_thread_context.active_client
ensure
- Thread.current[:stripe_client] = nil
+ StripeClient.current_thread_context.active_client = nil
end
end
+
+ should "correctly return last responses despite multiple clients" do
+ charge_resp = { object: "charge" }
+ coupon_resp = { object: "coupon" }
+
+ stub_request(:post, "#{Stripe.api_base}/v1/charges")
+ .to_return(body: JSON.generate(charge_resp))
+ stub_request(:post, "#{Stripe.api_base}/v1/coupons")
+ .to_return(body: JSON.generate(coupon_resp))
+
+ client1 = StripeClient.new
+ client2 = StripeClient.new
+
+ client2_resp = nil
+ _charge, client1_resp = client1.request do
+ Charge.create
+
+ # This is contrived, but we run one client nested in the `request`
+ # block of another one just to ensure that the parent is still
+ # unwinding when this goes through. If the parent's last response
+ # were to be overridden by this client (through a bug), then it would
+ # happen here.
+ _coupon, client2_resp = client2.request do
+ Coupon.create
+ end
+ end
+
+ assert_equal charge_resp, client1_resp.data
+ assert_equal coupon_resp, client2_resp.data
+ end
+
+ should "correctly return last responses despite multiple threads" do
+ charge_resp = { object: "charge" }
+ coupon_resp = { object: "coupon" }
+
+ stub_request(:post, "#{Stripe.api_base}/v1/charges")
+ .to_return(body: JSON.generate(charge_resp))
+ stub_request(:post, "#{Stripe.api_base}/v1/coupons")
+ .to_return(body: JSON.generate(coupon_resp))
+
+ client = StripeClient.new
+
+ # Poorly named class -- note this is actually a concurrent queue.
+ recv_queue = Queue.new
+ send_queue = Queue.new
+
+ # Start a thread, make an API request, but then idle in the `request`
+ # block until the main thread has been able to make its own API request
+ # and signal that it's done. If this thread's last response were to be
+ # overridden by the main thread (through a bug), then this routine
+ # should suss it out.
+ resp1 = nil
+ thread = Thread.start do
+ _charge, resp1 = client.request do
+ Charge.create
+
+ # Idle in `request` block until main thread signals.
+ send_queue.pop
+ end
+
+ # Signal main thread that we're done and it can run its checks.
+ recv_queue << true
+ end
+
+ # Make an API request.
+ _coupon, resp2 = client.request do
+ Coupon.create
+ end
+
+ # Tell background thread to finish `request`, then wait for it to
+ # signal back to us that it's ready.
+ send_queue << true
+ recv_queue.pop
+
+ assert_equal charge_resp, resp1.data
+ assert_equal coupon_resp, resp2.data
+
+ # And for maximum hygiene, make sure that our thread rejoins.
+ thread.join
+ end
+
+ should "error if calls to #request are nested on the same thread" do
+ client = StripeClient.new
+ client.request do
+ e = assert_raises(RuntimeError) do
+ client.request {}
+ end
+ assert_equal "calls to StripeClient#request cannot be nested within a thread",
+ e.message
+ end
+ end
end
context "#proxy" do
should "run the request through the proxy" do
begin
- Thread.current[:stripe_client_default_conn] = nil
+ StripeClient.current_thread_context.default_connection_manager = nil
- Stripe.proxy = "http://localhost:8080"
+ Stripe.proxy = "http://user:pass@localhost:8080"
client = StripeClient.new
client.request {}
- assert_equal "http://localhost:8080", Stripe::StripeClient.default_conn.proxy.uri.to_s
+ connection = Stripe::StripeClient.default_connection_manager.connection_for(Stripe.api_base)
+
+ assert_equal "localhost", connection.proxy_address
+ assert_equal 8080, connection.proxy_port
+ assert_equal "user", connection.proxy_user
+ assert_equal "pass", connection.proxy_pass
ensure
Stripe.proxy = nil
- Thread.current[:stripe_client_default_conn] = nil
+ StripeClient.current_thread_context.default_connection_manager = nil
end
end
end
context "#telemetry" do