test/stripe/stripe_client_test.rb in stripe-5.30.0 vs test/stripe/stripe_client_test.rb in stripe-5.31.0
- old
+ new
@@ -2,10 +2,34 @@
require ::File.expand_path("../test_helper", __dir__)
module Stripe
class StripeClientTest < Test::Unit::TestCase
+ context "initializing a StripeClient" do
+ should "allow a String to be passed in order to set the api key" do
+ assert_equal StripeClient.new("test_123").config.api_key, "test_123"
+ end
+
+ should "allow for overrides via a Hash" do
+ config = { api_key: "test_123", open_timeout: 100 }
+ client = StripeClient.new(config)
+
+ assert_equal client.config.api_key, "test_123"
+ assert_equal client.config.open_timeout, 100
+ end
+
+ should "support deprecated ConnectionManager objects" do
+ assert StripeClient.new(Stripe::ConnectionManager.new).config.is_a?(Stripe::StripeConfiguration)
+ end
+
+ should "support passing a full configuration object" do
+ config = Stripe.config.reverse_duplicate_merge({ api_key: "test_123", open_timeout: 100 })
+ client = StripeClient.new(config)
+ assert_equal config, client.config
+ end
+ end
+
context ".active_client" do
should "be .default_client outside of #request" do
assert_equal StripeClient.default_client, StripeClient.active_client
end
@@ -80,13 +104,69 @@
Util.stubs(:monotonic_time).returns(t + StripeClient::CONNECTION_MANAGER_GC_LAST_USED_EXPIRY + 1)
assert_equal 1, StripeClient.maybe_gc_connection_managers
# And as an additional check, the connection manager of the current
- # thread context should have been set to `nil` as it was GCed.
- assert_nil StripeClient.current_thread_context.default_connection_manager
+ # thread context should have been removed as it was GCed.
+ assert_equal({}, StripeClient.current_thread_context.default_connection_managers)
end
+
+ should "only garbage collect when all connection managers for a thread are expired" do
+ stub_request(:post, "#{Stripe.api_base}/v1/path")
+ .to_return(body: JSON.generate(object: "account"))
+
+ # Make sure we start with a blank slate (state may have been left in
+ # place by other tests).
+ StripeClient.clear_all_connection_managers
+
+ # Establish a base time.
+ t = 0.0
+
+ # And pretend that `StripeClient` was just initialized for the first
+ # time. (Don't access instance variables like this, but it's tricky to
+ # test properly otherwise.)
+ StripeClient.instance_variable_set(:@last_connection_manager_gc, t)
+
+ #
+ # t
+ #
+ Util.stubs(:monotonic_time).returns(t)
+
+ # Execute an initial request to ensure that a connection manager was
+ # created.
+ client = StripeClient.new
+ client.execute_request(:post, "/v1/path")
+
+ # Create a new client with a unique config to make sure the thread has two
+ # connection managers
+ active_client = StripeClient.new(max_network_retries: 10)
+ active_client.execute_request(:post, "/v1/path")
+
+ assert_equal 2, StripeClient.current_thread_context.default_connection_managers.keys.count
+ assert_equal nil, StripeClient.maybe_gc_connection_managers
+
+ # t + StripeClient::CONNECTION_MANAGER_GC_LAST_USED_EXPIRY + 1
+ #
+ # Move us far enough into the future that we're passed the horizons for
+ # both a GC run as well as well as the expiry age of a connection
+ # manager. That means the GC will run and collect the connection
+ # manager that we created above.
+ #
+ Util.stubs(:monotonic_time).returns(t + StripeClient::CONNECTION_MANAGER_GC_LAST_USED_EXPIRY + 1)
+
+ # Manually set the active_client's last_used time into the future to prevent GC.
+ StripeClient.default_connection_manager(active_client.config)
+ .instance_variable_set(:@last_used, Util.monotonic_time + 1)
+
+ assert_equal 0, StripeClient.maybe_gc_connection_managers
+
+ # Move time into the future past the last GC round
+ current_time = Util.monotonic_time
+ Util.stubs(:monotonic_time).returns(current_time * 2)
+
+ assert_equal 1, StripeClient.maybe_gc_connection_managers
+ end
end
context ".clear_all_connection_managers" do
should "clear connection managers across all threads" do
stub_request(:post, "#{Stripe.api_base}/path")
@@ -127,10 +207,31 @@
threads.each { send_queue << true }
# And finally, give all threads time to perform their check.
threads.each(&:join)
end
+
+ should "clear only connection managers with a given configuration" do
+ StripeClient.clear_all_connection_managers
+
+ client1 = StripeClient.new(read_timeout: 5.0)
+ StripeClient.default_connection_manager(client1.config)
+ client2 = StripeClient.new(read_timeout: 2.0)
+ StripeClient.default_connection_manager(client2.config)
+
+ thread_contexts = StripeClient.instance_variable_get(:@thread_contexts_with_connection_managers)
+ assert_equal 1, thread_contexts.count
+ thread_context = thread_contexts.first
+
+ refute_nil thread_context.default_connection_managers[client1.config.key]
+ refute_nil thread_context.default_connection_managers[client2.config.key]
+
+ StripeClient.clear_all_connection_managers(config: client1.config)
+
+ assert_nil thread_context.default_connection_managers[client1.config.key]
+ refute_nil thread_context.default_connection_managers[client2.config.key]
+ end
end
context ".default_client" do
should "be a StripeClient" do
assert_kind_of StripeClient, StripeClient.default_client
@@ -158,15 +259,32 @@
other_thread_manager = StripeClient.default_connection_manager
end
thread.join
refute_equal StripeClient.default_connection_manager, other_thread_manager
end
+
+ should "create a separate connection manager per configuration" do
+ config = Stripe::StripeConfiguration.setup { |c| c.open_timeout = 100 }
+ connection_manager_one = StripeClient.default_connection_manager
+ connection_manager_two = StripeClient.default_connection_manager(config)
+
+ assert_equal connection_manager_one.config.open_timeout, 30
+ assert_equal connection_manager_two.config.open_timeout, 100
+ end
+
+ should "create a single connection manager for identical configurations" do
+ StripeClient.clear_all_connection_managers
+
+ 2.times { StripeClient.default_connection_manager(Stripe::StripeConfiguration.setup) }
+
+ assert_equal 1, StripeClient.instance_variable_get(:@thread_contexts_with_connection_managers).first.default_connection_managers.size
+ end
end
context ".should_retry?" do
setup do
- Stripe.stubs(:max_network_retries).returns(2)
+ Stripe::StripeConfiguration.any_instance.stubs(:max_network_retries).returns(2)
end
should "retry on Errno::ECONNREFUSED" do
assert StripeClient.should_retry?(Errno::ECONNREFUSED.new,
method: :post, num_retries: 0)
@@ -273,32 +391,32 @@
end
context ".sleep_time" do
should "should grow exponentially" do
StripeClient.stubs(:rand).returns(1)
- Stripe.stubs(:max_network_retry_delay).returns(999)
+ Stripe.config.stubs(:max_network_retry_delay).returns(999)
assert_equal(Stripe.initial_network_retry_delay, StripeClient.sleep_time(1))
assert_equal(Stripe.initial_network_retry_delay * 2, StripeClient.sleep_time(2))
assert_equal(Stripe.initial_network_retry_delay * 4, StripeClient.sleep_time(3))
assert_equal(Stripe.initial_network_retry_delay * 8, StripeClient.sleep_time(4))
end
should "enforce the max_network_retry_delay" do
StripeClient.stubs(:rand).returns(1)
- Stripe.stubs(:initial_network_retry_delay).returns(1)
- Stripe.stubs(:max_network_retry_delay).returns(2)
+ Stripe.config.stubs(:initial_network_retry_delay).returns(1)
+ Stripe.config.stubs(:max_network_retry_delay).returns(2)
assert_equal(1, StripeClient.sleep_time(1))
assert_equal(2, StripeClient.sleep_time(2))
assert_equal(2, StripeClient.sleep_time(3))
assert_equal(2, StripeClient.sleep_time(4))
end
should "add some randomness" do
random_value = 0.8
StripeClient.stubs(:rand).returns(random_value)
- Stripe.stubs(:initial_network_retry_delay).returns(1)
- Stripe.stubs(:max_network_retry_delay).returns(8)
+ Stripe.config.stubs(:initial_network_retry_delay).returns(1)
+ Stripe.config.stubs(:max_network_retry_delay).returns(8)
base_value = Stripe.initial_network_retry_delay * (0.5 * (1 + random_value))
# the initial value cannot be smaller than the base,
# so the randomness is ignored
@@ -307,10 +425,27 @@
# after the first one, the randomness is applied
assert_equal(base_value * 2, StripeClient.sleep_time(2))
assert_equal(base_value * 4, StripeClient.sleep_time(3))
assert_equal(base_value * 8, StripeClient.sleep_time(4))
end
+
+ should "permit passing in a configuration object" do
+ StripeClient.stubs(:rand).returns(1)
+ config = Stripe::StripeConfiguration.setup do |c|
+ c.initial_network_retry_delay = 1
+ c.max_network_retry_delay = 2
+ end
+
+ # Set the global configuration to be different than the client
+ Stripe.config.stubs(:initial_network_retry_delay).returns(100)
+ Stripe.config.stubs(:max_network_retry_delay).returns(200)
+
+ assert_equal(1, StripeClient.sleep_time(1, config: config))
+ assert_equal(2, StripeClient.sleep_time(2, config: config))
+ assert_equal(2, StripeClient.sleep_time(3, config: config))
+ assert_equal(2, StripeClient.sleep_time(4, config: config))
+ end
end
context "#execute_request" do
context "headers" do
should "support literal headers" do
@@ -340,10 +475,14 @@
# emit for responses. Mocha's `anything` parameter can't match inside
# of a hash and is therefore not useful for this purpose. If we
# switch over to rspec-mocks at some point, we can probably remove
# this.
Util.stubs(:monotonic_time).returns(0.0)
+
+ # Stub the Stripe.config so that mocha matchers will succeed. Currently,
+ # mocha does not support using param matchers within hashes.
+ StripeClient.any_instance.stubs(:config).returns(Stripe.config)
end
should "produce appropriate logging" do
body = JSON.generate(object: "account")
@@ -351,33 +490,38 @@
account: "acct_123",
api_version: "2010-11-12",
idempotency_key: "abc",
method: :post,
num_retries: 0,
- path: "/v1/account")
+ path: "/v1/account",
+ config: Stripe.config)
Util.expects(:log_debug).with("Request details",
body: "",
idempotency_key: "abc",
- query: nil)
+ query: nil,
+ config: Stripe.config)
Util.expects(:log_info).with("Response from Stripe API",
account: "acct_123",
api_version: "2010-11-12",
elapsed: 0.0,
idempotency_key: "abc",
method: :post,
path: "/v1/account",
request_id: "req_123",
- status: 200)
+ status: 200,
+ config: Stripe.config)
Util.expects(:log_debug).with("Response details",
body: body,
idempotency_key: "abc",
- request_id: "req_123")
+ request_id: "req_123",
+ config: Stripe.config)
Util.expects(:log_debug).with("Dashboard link for request",
idempotency_key: "abc",
request_id: "req_123",
- url: Util.request_id_dashboard_url("req_123", Stripe.api_key))
+ url: Util.request_id_dashboard_url("req_123", Stripe.api_key),
+ config: Stripe.config)
stub_request(:post, "#{Stripe.api_base}/v1/account")
.to_return(
body: body,
headers: {
@@ -402,20 +546,22 @@
account: nil,
api_version: nil,
idempotency_key: nil,
method: :post,
num_retries: 0,
- path: "/v1/account")
+ path: "/v1/account",
+ config: Stripe.config)
Util.expects(:log_info).with("Response from Stripe API",
account: nil,
api_version: nil,
elapsed: 0.0,
idempotency_key: nil,
method: :post,
path: "/v1/account",
request_id: nil,
- status: 500)
+ status: 500,
+ config: Stripe.config)
error = {
code: "code",
message: "message",
param: "param",
@@ -426,11 +572,12 @@
error_code: error[:code],
error_message: error[:message],
error_param: error[:param],
error_type: error[:type],
idempotency_key: nil,
- request_id: nil)
+ request_id: nil,
+ config: Stripe.config)
stub_request(:post, "#{Stripe.api_base}/v1/account")
.to_return(
body: JSON.generate(error: error),
status: 500
@@ -447,27 +594,30 @@
account: nil,
api_version: nil,
idempotency_key: nil,
method: :post,
num_retries: 0,
- path: "/oauth/token")
+ path: "/oauth/token",
+ config: Stripe.config)
Util.expects(:log_info).with("Response from Stripe API",
account: nil,
api_version: nil,
elapsed: 0.0,
idempotency_key: nil,
method: :post,
path: "/oauth/token",
request_id: nil,
- status: 400)
+ status: 400,
+ config: Stripe.config)
Util.expects(:log_error).with("Stripe OAuth error",
status: 400,
error_code: "invalid_request",
error_description: "No grant type specified",
idempotency_key: nil,
- request_id: nil)
+ request_id: nil,
+ config: Stripe.config)
stub_request(:post, "#{Stripe.connect_base}/oauth/token")
.to_return(body: JSON.generate(error: "invalid_request",
error_description: "No grant type specified"), status: 400)
@@ -786,11 +936,11 @@
end
end
context "idempotency keys" do
setup do
- Stripe.stubs(:max_network_retries).returns(2)
+ Stripe::StripeConfiguration.any_instance.stubs(:max_network_retries).returns(2)
end
should "not add an idempotency key to GET requests" do
SecureRandom.expects(:uuid).times(0)
stub_request(:get, "#{Stripe.api_base}/v1/charges/ch_123")
@@ -836,11 +986,11 @@
end
end
context "retry logic" do
setup do
- Stripe.stubs(:max_network_retries).returns(2)
+ Stripe::StripeConfiguration.any_instance.stubs(:max_network_retries).returns(2)
end
should "retry failed requests and raise if error persists" do
StripeClient.expects(:sleep_time).at_least_once.returns(0)
stub_request(:post, "#{Stripe.api_base}/v1/charges")
@@ -868,10 +1018,25 @@
end
client = StripeClient.new
client.execute_request(:post, "/v1/charges")
end
+
+ should "pass the client configuration when retrying" do
+ StripeClient.expects(:sleep_time)
+ .with(any_of(1, 2),
+ has_entry(:config, kind_of(Stripe::StripeConfiguration)))
+ .at_least_once.returns(0)
+
+ stub_request(:post, "#{Stripe.api_base}/v1/charges")
+ .to_raise(Errno::ECONNREFUSED.new)
+
+ client = StripeClient.new
+ assert_raises Stripe::APIConnectionError do
+ client.execute_request(:post, "/v1/charges")
+ end
+ end
end
context "params serialization" do
should "allows empty strings in params" do
client = StripeClient.new
@@ -1077,11 +1242,11 @@
end
context "#proxy" do
should "run the request through the proxy" do
begin
- StripeClient.current_thread_context.default_connection_manager = nil
+ StripeClient.clear_all_connection_managers
Stripe.proxy = "http://user:pass@localhost:8080"
client = StripeClient.new
client.request {}
@@ -1093,10 +1258,10 @@
assert_equal "user", connection.proxy_user
assert_equal "pass", connection.proxy_pass
ensure
Stripe.proxy = nil
- StripeClient.current_thread_context.default_connection_manager = nil
+ StripeClient.clear_all_connection_managers
end
end
end
context "#telemetry" do