stripe-ruby/test/stripe/util_test.rb
Brandur ce69d749e1 Implement STRIPE_LOG for stripe-ruby
Adds logging support for stripe-ruby in a similar way that we did it for
stripe-python [1], with the idea that users you can optionally get some
additional low-cost-to-configure logging for operational visibility or
debugging.

I made a few tweaks from the Python implementation (which I'll try to
contribute back to there):

* Added an elapsed parameter to responses so you can tell how long they
  lasted.
* Mixed in idempotency_key to all lines that users have a way to
  aggregate logs related to a request from start to finish.
* Standardized naming between different log lines as much as possible.
* Detect a TTY and produce output that's colorized and formatted.

[1] https://github.com/stripe/stripe-python/pull/269
2017-08-03 13:39:15 -07:00

314 lines
9.8 KiB
Ruby

require File.expand_path('../../test_helper', __FILE__)
module Stripe
class UtilTest < Test::Unit::TestCase
should "#encode_parameters should prepare parameters for an HTTP request" do
params = {
:a => 3,
:b => "+foo?",
:c => "bar&baz",
:d => { :a => "a", :b => "b" },
:e => [0, 1],
:f => "",
# note the empty hash won't even show up in the request
:g => [],
}
assert_equal(
"a=3&b=%2Bfoo%3F&c=bar%26baz&d[a]=a&d[b]=b&e[]=0&e[]=1&f=",
Stripe::Util.encode_parameters(params)
)
end
should "#encode_params should throw an error on an array of maps that cannot be encoded" do
params = {
:a => [
{ :a => 1, :b => 2 },
{ :c => 3, :a => 4 },
]
}
e = assert_raises(ArgumentError) do
Stripe::Util.encode_parameters(params)
end
expected = "All maps nested in an array should start with the same key " +
"(expected starting key 'a', got 'c')"
assert_equal expected, e.message
# Make sure the check is recursive by taking our original params and
# nesting it into yet another map and array. Should throw exactly the
# same error because it's still the in inner array of maps that's wrong.
params = {
:x => [
params
]
}
e = assert_raises(ArgumentError) do
Stripe::Util.encode_parameters(params)
end
assert_equal expected, e.message
end
should "#url_encode should prepare strings for HTTP" do
assert_equal "foo", Stripe::Util.url_encode("foo")
assert_equal "foo", Stripe::Util.url_encode(:foo)
assert_equal "foo%2B", Stripe::Util.url_encode("foo+")
assert_equal "foo%26", Stripe::Util.url_encode("foo&")
assert_equal "foo[bar]", Stripe::Util.url_encode("foo[bar]")
end
should "#flatten_params should encode parameters according to Rails convention" do
params = [
[:a, 3],
[:b, "foo?"],
[:c, "bar&baz"],
[:d, { :a => "a", :b => "b" }],
[:e, [0, 1]],
[:f, [
{ :foo => "1", :ghi => "2" },
{ :foo => "3", :bar => "4" },
]],
]
assert_equal([
["a", 3],
["b", "foo?"],
["c", "bar&baz"],
["d[a]", "a"],
["d[b]", "b"],
["e[]", 0],
["e[]", 1],
# *The key here is the order*. In order to be properly interpreted as
# an array of hashes on the server, everything from a single hash must
# come in at once. A duplicate key in an array triggers a new element.
["f[][foo]", "1"],
["f[][ghi]", "2"],
["f[][foo]", "3"],
["f[][bar]", "4"],
], Stripe::Util.flatten_params(params))
end
should "#symbolize_names should convert names to symbols" do
start = {
'foo' => 'bar',
'array' => [{ 'foo' => 'bar' }],
'nested' => {
1 => 2,
:symbol => 9,
'string' => nil
}
}
finish = {
:foo => 'bar',
:array => [{ :foo => 'bar' }],
:nested => {
1 => 2,
:symbol => 9,
:string => nil
}
}
symbolized = Stripe::Util.symbolize_names(start)
assert_equal(finish, symbolized)
end
should "#normalize_opts should reject nil keys" do
assert_raise { Stripe::Util.normalize_opts(nil) }
assert_raise { Stripe::Util.normalize_opts(:api_key => nil) }
end
should "#convert_to_stripe_object should pass through unknown types" do
obj = Util.convert_to_stripe_object(7, {})
assert_equal 7, obj
end
should "#convert_to_stripe_object should turn hashes into StripeObjects" do
obj = Util.convert_to_stripe_object({ :foo => "bar" }, {})
assert obj.is_a?(StripeObject)
assert_equal "bar", obj.foo
end
should "#convert_to_stripe_object should turn lists into ListObjects" do
obj = Util.convert_to_stripe_object({ :object => "list" }, {})
assert obj.is_a?(ListObject)
end
should "#convert_to_stripe_object should marshal other classes" do
obj = Util.convert_to_stripe_object({ :object => "account" }, {})
assert obj.is_a?(Account)
end
should "#convert_to_stripe_object should marshal arrays" do
obj = Util.convert_to_stripe_object([1, 2, 3], {})
assert_equal [1, 2, 3], obj
end
should "#array_to_hash should convert an array into a hash with integer keys" do
assert_equal({"0" => 1, "1" => 2, "2" => 3}, Util.array_to_hash([1, 2, 3]))
end
context ".request_id_dashboard_url" do
should "generate a livemode URL" do
assert_equal "https://dashboard.stripe.com/live/logs/request-id",
Util.request_id_dashboard_url("request-id", "sk_live_123")
end
should "generate a testmode URL" do
assert_equal "https://dashboard.stripe.com/test/logs/request-id",
Util.request_id_dashboard_url("request-id", "sk_test_123")
end
should "default to a testmode URL" do
assert_equal "https://dashboard.stripe.com/test/logs/request-id",
Util.request_id_dashboard_url("request-id", nil)
end
end
context ".log_*" do
setup do
@old_log_level = Stripe.log_level
Stripe.log_level = nil
@old_stdout = $stdout
$stdout = StringIO.new
end
teardown do
Stripe.log_level = @old_log_level
$stdout = @old_stdout
end
context ".log_debug" do
should "not log if logging is disabled" do
Util.log_debug("foo")
assert_equal "", $stdout.string
end
should "log if level set to debug" do
Stripe.log_level = Stripe::LEVEL_DEBUG
Util.log_debug("foo")
assert_equal "message=foo level=debug \n", $stdout.string
end
should "not log if level set to info" do
Stripe.log_level = Stripe::LEVEL_INFO
Util.log_debug("foo")
assert_equal "", $stdout.string
end
end
context ".log_info" do
should "not log if logging is disabled" do
Util.log_info("foo")
assert_equal "", $stdout.string
end
should "log if level set to debug" do
Stripe.log_level = Stripe::LEVEL_DEBUG
Util.log_info("foo")
assert_equal "message=foo level=info \n", $stdout.string
end
should "log if level set to info" do
Stripe.log_level = Stripe::LEVEL_INFO
Util.log_info("foo")
assert_equal "message=foo level=info \n", $stdout.string
end
end
end
context ".normalize_headers" do
should "normalize the format of a header key" do
assert_equal({ "Request-Id" => nil },
Util.normalize_headers({ "Request-Id" => nil }))
assert_equal({ "Request-Id" => nil },
Util.normalize_headers({ "request-id" => nil }))
assert_equal({ "Request-Id" => nil },
Util.normalize_headers({ "Request-ID" => nil }))
assert_equal({ "Request-Id" => nil },
Util.normalize_headers({ :request_id => nil }))
end
should "tolerate bad formatting" do
assert_equal({ "Request-Id" => nil },
Util.normalize_headers({ "-Request--Id-" => nil }))
assert_equal({ "Request-Id" => nil },
Util.normalize_headers({ :request__id => nil }))
end
end
#
# private
#
# I don't feel particularly good about using #send to invoke these, but I
# want them hidden from the public interface, and each method is far easier
# to test in isolation (as opposed to going through a public method).
#
context ".colorize" do
should "colorize for a TTY" do
assert_equal "\033[0;32;49mfoo\033[0m",
Util.send(:colorize, "foo", :green, true)
end
should "not colorize otherwise" do
assert_equal "foo", Util.send(:colorize, "foo", :green, false)
end
end
context ".log_internal" do
should "log in a terminal friendly way" do
out = StringIO.new
# Sketchy as anything, but saves us from pulling in a mocking library.
# Open this instance of StringIO, and add a method override so that it
# looks like a TTY.
out.instance_eval do
def isatty; true; end
end
Util.send(:log_internal, "message", { foo: "bar" },
color: :green, level: Stripe::LEVEL_DEBUG, out: out)
assert_equal "\e[0;32;49mDEBU\e[0m message \e[0;32;49mfoo\e[0m=bar\n",
out.string
end
should "log in a data friendly way" do
out = StringIO.new
Util.send(:log_internal, "message", { foo: "bar" },
color: :green, level: Stripe::LEVEL_DEBUG, out: out)
assert_equal "message=message level=debug foo=bar\n",
out.string
end
end
context ".wrap_logfmt_value" do
should "pass through simple values" do
assert_equal "abc", Util.send(:wrap_logfmt_value, "abc")
assert_equal "123", Util.send(:wrap_logfmt_value, "123")
assert_equal "a-b_c/d", Util.send(:wrap_logfmt_value, "a-b_c/d")
end
should "pass through numerics" do
assert_equal 123, Util.send(:wrap_logfmt_value, 123)
assert_equal 1.23, Util.send(:wrap_logfmt_value, 1.23)
end
should "wrap more complex values in double quotes" do
assert_equal %{"abc=123"}, Util.send(:wrap_logfmt_value, %{abc=123})
end
should "escape double quotes already in the value" do
assert_equal %{"abc=\\"123\\""}, Util.send(:wrap_logfmt_value, %{abc="123"})
end
should "remove newlines" do
assert_equal %{"abc"}, Util.send(:wrap_logfmt_value, "a\nb\nc")
end
should "not error if given a non-string" do
assert_equal "true", Util.send(:wrap_logfmt_value, true)
end
end
end
end