braintree 1.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.
Files changed (65) hide show
  1. data/LICENSE +22 -0
  2. data/README.rdoc +62 -0
  3. data/lib/braintree.rb +66 -0
  4. data/lib/braintree/address.rb +122 -0
  5. data/lib/braintree/base_module.rb +29 -0
  6. data/lib/braintree/configuration.rb +99 -0
  7. data/lib/braintree/credit_card.rb +231 -0
  8. data/lib/braintree/credit_card_verification.rb +31 -0
  9. data/lib/braintree/customer.rb +231 -0
  10. data/lib/braintree/digest.rb +20 -0
  11. data/lib/braintree/error_codes.rb +95 -0
  12. data/lib/braintree/error_result.rb +39 -0
  13. data/lib/braintree/errors.rb +29 -0
  14. data/lib/braintree/http.rb +105 -0
  15. data/lib/braintree/paged_collection.rb +55 -0
  16. data/lib/braintree/ssl_expiration_check.rb +28 -0
  17. data/lib/braintree/successful_result.rb +38 -0
  18. data/lib/braintree/test/credit_card_numbers.rb +50 -0
  19. data/lib/braintree/test/transaction_amounts.rb +10 -0
  20. data/lib/braintree/transaction.rb +360 -0
  21. data/lib/braintree/transaction/address_details.rb +15 -0
  22. data/lib/braintree/transaction/credit_card_details.rb +22 -0
  23. data/lib/braintree/transaction/customer_details.rb +13 -0
  24. data/lib/braintree/transparent_redirect.rb +110 -0
  25. data/lib/braintree/util.rb +94 -0
  26. data/lib/braintree/validation_error.rb +15 -0
  27. data/lib/braintree/validation_error_collection.rb +80 -0
  28. data/lib/braintree/version.rb +9 -0
  29. data/lib/braintree/xml.rb +12 -0
  30. data/lib/braintree/xml/generator.rb +80 -0
  31. data/lib/braintree/xml/libxml.rb +69 -0
  32. data/lib/braintree/xml/parser.rb +93 -0
  33. data/lib/ssl/securetrust_ca.crt +44 -0
  34. data/lib/ssl/valicert_ca.crt +18 -0
  35. data/spec/integration/braintree/address_spec.rb +352 -0
  36. data/spec/integration/braintree/credit_card_spec.rb +676 -0
  37. data/spec/integration/braintree/customer_spec.rb +664 -0
  38. data/spec/integration/braintree/http_spec.rb +201 -0
  39. data/spec/integration/braintree/test/transaction_amounts_spec.rb +29 -0
  40. data/spec/integration/braintree/transaction_spec.rb +900 -0
  41. data/spec/integration/spec_helper.rb +38 -0
  42. data/spec/script/httpsd.rb +27 -0
  43. data/spec/spec_helper.rb +41 -0
  44. data/spec/unit/braintree/address_spec.rb +86 -0
  45. data/spec/unit/braintree/configuration_spec.rb +190 -0
  46. data/spec/unit/braintree/credit_card_spec.rb +137 -0
  47. data/spec/unit/braintree/credit_card_verification_spec.rb +17 -0
  48. data/spec/unit/braintree/customer_spec.rb +103 -0
  49. data/spec/unit/braintree/digest_spec.rb +28 -0
  50. data/spec/unit/braintree/error_result_spec.rb +42 -0
  51. data/spec/unit/braintree/errors_spec.rb +81 -0
  52. data/spec/unit/braintree/http_spec.rb +42 -0
  53. data/spec/unit/braintree/paged_collection_spec.rb +128 -0
  54. data/spec/unit/braintree/ssl_expiration_check_spec.rb +92 -0
  55. data/spec/unit/braintree/successful_result_spec.rb +27 -0
  56. data/spec/unit/braintree/transaction/credit_card_details_spec.rb +22 -0
  57. data/spec/unit/braintree/transaction_spec.rb +136 -0
  58. data/spec/unit/braintree/transparent_redirect_spec.rb +154 -0
  59. data/spec/unit/braintree/util_spec.rb +142 -0
  60. data/spec/unit/braintree/validation_error_collection_spec.rb +128 -0
  61. data/spec/unit/braintree/validation_error_spec.rb +19 -0
  62. data/spec/unit/braintree/xml/libxml_spec.rb +51 -0
  63. data/spec/unit/braintree/xml_spec.rb +122 -0
  64. data/spec/unit/spec_helper.rb +1 -0
  65. metadata +118 -0
@@ -0,0 +1,92 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ # Helper method to confirm we have the right values
4
+ def fetch_expiration_date(host, port=443)
5
+ cmd = "echo | openssl s_client -connect #{host}:#{port} 2>&1 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | openssl x509 -noout -subject -dates | grep notAfter"
6
+ date = `#{cmd}`.sub(/^.*=/, '')
7
+
8
+ Date.parse(date)
9
+ end
10
+
11
+ describe Braintree::SSLExpirationCheck do
12
+ SANDBOX = "sandbox.braintreegateway.com"
13
+ QA = "qa.braintreegateway.com"
14
+ PRODUCTION = "www.braintreegateway.com"
15
+
16
+ describe "check_dates" do
17
+ it "is done when the client library is loaded" do
18
+ Braintree::SSLExpirationCheck.ssl_expiration_dates_checked.should == true
19
+ end
20
+
21
+ describe "QA Cert" do
22
+ it "logs when the cert is expired" do
23
+ Braintree::SSLExpirationCheck.stub(:qa_expiration_date).and_return(Date.today - 1)
24
+
25
+ output = StringIO.new
26
+ Braintree::Configuration.logger = Logger.new(output)
27
+ Braintree::Configuration.logger.level = Logger::WARN
28
+
29
+ Braintree::SSLExpirationCheck.check_dates
30
+
31
+ output.string.should match(/\[Braintree\] The SSL Certificate for the QA environment will expire on \d{4}-\d{2}-\d{2}\. Please check for an updated client library\./)
32
+ end
33
+
34
+ it "logs when the cert is close to expiring" do
35
+ Braintree::SSLExpirationCheck.stub(:qa_expiration_date).and_return(Date.today)
36
+ output = StringIO.new
37
+ Braintree::Configuration.logger = Logger.new(output)
38
+ Braintree::Configuration.logger.level = Logger::WARN
39
+
40
+ Braintree::SSLExpirationCheck.check_dates
41
+
42
+ output.string.should match(/\[Braintree\] The SSL Certificate for the QA environment will expire on \d{4}-\d{2}-\d{2}\. Please check for an updated client library\./)
43
+ end
44
+
45
+ it "doesn't log when the cert is not expired" do
46
+ Braintree::SSLExpirationCheck.stub(:qa_expiration_date).and_return(Date.today + 365)
47
+ output = StringIO.new
48
+ Braintree::Configuration.logger = Logger.new(output)
49
+ Braintree::Configuration.logger.level = Logger::WARN
50
+
51
+ Braintree::SSLExpirationCheck.check_dates
52
+
53
+ output.string.should == ""
54
+ end
55
+ end
56
+
57
+ # We assume that testing logging for one is good enough for all, so we won't duplicate those tests from above
58
+ it "checks the sandbox cert" do
59
+ Braintree::SSLExpirationCheck.stub(:sandbox_expiration_date).and_return(Date.today)
60
+ output = StringIO.new
61
+ Braintree::Configuration.logger = Logger.new(output)
62
+ Braintree::Configuration.logger.level = Logger::WARN
63
+
64
+ Braintree::SSLExpirationCheck.check_dates
65
+
66
+ output.string.should match(/\[Braintree\] The SSL Certificate for the Sandbox environment will expire on \d{4}-\d{2}-\d{2}\. Please check for an updated client library\./)
67
+ end
68
+
69
+ xit "Patrick -- waiting on a production box -- checks the production server cert"
70
+ end
71
+
72
+ describe "production_expiration_date" do
73
+ xit "Patrick -- waiting on a production box -- is the date the production cert expires" do
74
+ Braintree::SSLExpirationCheck.production_expiration_date.should be_a(Date)
75
+ Braintree::SSLExpirationCheck.qa_expiration_date.should == fetch_expiration_date(PRODUCTION)
76
+ end
77
+ end
78
+
79
+ describe "qa_expiration_date" do
80
+ it "is the date the QA cert expires" do
81
+ Braintree::SSLExpirationCheck.qa_expiration_date.should be_a(Date)
82
+ Braintree::SSLExpirationCheck.qa_expiration_date.should == fetch_expiration_date(QA)
83
+ end
84
+ end
85
+
86
+ describe "sandbox_expiration_date" do
87
+ it "is the date the Sandbox cert expires" do
88
+ Braintree::SSLExpirationCheck.sandbox_expiration_date.should be_a(Date)
89
+ Braintree::SSLExpirationCheck.sandbox_expiration_date.should == fetch_expiration_date(SANDBOX)
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,27 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ describe Braintree::SuccessfulResult do
4
+ describe "initialize" do
5
+ it "creates attr readers the values in the hash" do
6
+ result = Braintree::SuccessfulResult.new(
7
+ :foo => "foo_value",
8
+ :bar => "bar_value"
9
+ )
10
+ result.success?.should == true
11
+ result.foo.should == "foo_value"
12
+ result.bar.should == "bar_value"
13
+ end
14
+
15
+ it "can be initialized without any values" do
16
+ result = Braintree::SuccessfulResult.new
17
+ result.success?.should == true
18
+ end
19
+ end
20
+
21
+ describe "inspect" do
22
+ it "is pretty" do
23
+ result = Braintree::SuccessfulResult.new(:foo => "foo_value")
24
+ result.inspect.should == "#<Braintree::SuccessfulResult foo:\"foo_value\">"
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ require File.dirname(__FILE__) + "/../../spec_helper"
2
+
3
+ describe Braintree::Transaction::CreditCardDetails do
4
+ describe "expiration_date" do
5
+ it "concats expiration_month and expiration_year" do
6
+ details = Braintree::Transaction::CreditCardDetails.new(
7
+ :expiration_month => "08",
8
+ :expiration_year => "2009"
9
+ )
10
+ details.expiration_date.should == "08/2009"
11
+ end
12
+ end
13
+
14
+ describe "masked_number" do
15
+ it "concatenates the bin, some *'s, and the last_4" do
16
+ details = Braintree::Transaction::CreditCardDetails.new(
17
+ :bin => "510510", :last_4 => "5100"
18
+ )
19
+ details.masked_number.should == "510510******5100"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,136 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ describe Braintree::Transaction do
4
+ describe "self.create" do
5
+ it "raises an exception if hash includes an invalid key" do
6
+ expect do
7
+ Braintree::Transaction.create(:amount => "Joe", :invalid_key => "foo")
8
+ end.to raise_error(ArgumentError, "invalid keys: invalid_key")
9
+ end
10
+ end
11
+
12
+ describe "self.create_from_transparent_redirect" do
13
+ it "raises an exception if the query string is forged" do
14
+ expect do
15
+ Braintree::Transaction.create_from_transparent_redirect("forged=query_string")
16
+ end.to raise_error(Braintree::ForgedQueryString)
17
+ end
18
+ end
19
+
20
+ describe "self.create_transaction_url" do
21
+ it "returns the url" do
22
+ Braintree::Transaction.create_transaction_url.should == "http://localhost:3000/merchants/integration_merchant_id/transactions/all/create_via_transparent_redirect_request"
23
+ end
24
+ end
25
+
26
+ describe "self.submit_for_settlement" do
27
+ it "raises an ArgumentError if transaction_id is an invalid format" do
28
+ expect do
29
+ Braintree::Transaction.submit_for_settlement("invalid-transaction-id")
30
+ end.to raise_error(ArgumentError, "transaction_id is invalid")
31
+ end
32
+ end
33
+
34
+ describe "initialize" do
35
+ it "sets up customer attributes in customer_details" do
36
+ transaction = Braintree::Transaction._new(
37
+ :customer => {
38
+ :id => "123",
39
+ :first_name => "Adam",
40
+ :last_name => "Taylor",
41
+ :company => "Ledner LLC",
42
+ :email => "adam.taylor@lednerllc.com",
43
+ :website => "lednerllc.com",
44
+ :phone => "1-999-652-4189 x56883",
45
+ :fax => "012-161-8055"
46
+ }
47
+ )
48
+ transaction.customer_details.id.should == "123"
49
+ transaction.customer_details.first_name.should == "Adam"
50
+ transaction.customer_details.last_name.should == "Taylor"
51
+ transaction.customer_details.company.should == "Ledner LLC"
52
+ transaction.customer_details.email.should == "adam.taylor@lednerllc.com"
53
+ transaction.customer_details.website.should == "lednerllc.com"
54
+ transaction.customer_details.phone.should == "1-999-652-4189 x56883"
55
+ transaction.customer_details.fax.should == "012-161-8055"
56
+ end
57
+
58
+ it "sets up credit card attributes in credit_card_details" do
59
+ transaction = Braintree::Transaction._new(
60
+ :credit_card => {
61
+ :token => "mzg2",
62
+ :bin => "411111",
63
+ :last_4 => "1111",
64
+ :card_type => "Visa",
65
+ :expiration_month => "08",
66
+ :expiration_year => "2009",
67
+ :issuer_location => "US"
68
+ }
69
+ )
70
+ transaction.credit_card_details.token.should == "mzg2"
71
+ transaction.credit_card_details.bin.should == "411111"
72
+ transaction.credit_card_details.last_4.should == "1111"
73
+ transaction.credit_card_details.card_type.should == "Visa"
74
+ transaction.credit_card_details.expiration_month.should == "08"
75
+ transaction.credit_card_details.expiration_year.should == "2009"
76
+ transaction.credit_card_details.issuer_location.should == "US"
77
+ end
78
+
79
+ it "handles receiving custom as an empty string" do
80
+ transaction = Braintree::Transaction._new(
81
+ :custom => "\n "
82
+ )
83
+ end
84
+ end
85
+
86
+ describe "inspect" do
87
+ it "includes the id, type, amount, and status first" do
88
+ transaction = Braintree::Transaction._new(
89
+ :id => "1234",
90
+ :type => "sale",
91
+ :amount => "100.00",
92
+ :status => "authorized"
93
+ )
94
+ output = transaction.inspect
95
+ output.should include(%Q(#<Braintree::Transaction id: "1234", type: "sale", amount: "100.00", status: "authorized"))
96
+ end
97
+ end
98
+
99
+ describe "==" do
100
+ it "returns true when it should" do
101
+ first = Braintree::Transaction._new(:id => 123)
102
+ second = Braintree::Transaction._new(:id => 123)
103
+
104
+ first.should == second
105
+ second.should == first
106
+ end
107
+
108
+ it "returns false when it should" do
109
+ first = Braintree::Transaction._new(:id => 123)
110
+ second = Braintree::Transaction._new(:id => 124)
111
+
112
+ first.should_not == second
113
+ second.should_not == first
114
+ end
115
+ end
116
+
117
+ describe "new" do
118
+ it "is protected" do
119
+ expect do
120
+ Braintree::Transaction.new
121
+ end.to raise_error(NoMethodError, /protected method .new/)
122
+ end
123
+ end
124
+
125
+ describe "refunded?" do
126
+ it "is true if the transaciton has been refunded" do
127
+ transaction = Braintree::Transaction._new(:refund_id => "123")
128
+ transaction.refunded?.should == true
129
+ end
130
+
131
+ it "is false if the transaciton has not been refunded" do
132
+ transaction = Braintree::Transaction._new(:refund_id => nil)
133
+ transaction.refunded?.should == false
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,154 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ describe Braintree::TransparentRedirect do
4
+ describe "self.create_credit_card_data" do
5
+ it "raises an exception if any keys are invalid" do
6
+ expect do
7
+ Braintree::TransparentRedirect.create_credit_card_data(
8
+ :credit_card => {:number => "ok", :invalid_key => "bad"}
9
+ )
10
+ end.to raise_error(ArgumentError, "invalid keys: credit_card[invalid_key]")
11
+ end
12
+ end
13
+
14
+ describe "self.create_customer_data" do
15
+ it "raises an exception if any keys are invalid" do
16
+ expect do
17
+ Braintree::TransparentRedirect.create_customer_data(
18
+ :customer => {:first_name => "ok", :invalid_key => "bad"}
19
+ )
20
+ end.to raise_error(ArgumentError, "invalid keys: customer[invalid_key]")
21
+ end
22
+ end
23
+
24
+ describe "self.parse_and_validate_query_string" do
25
+ it "returns the parsed query string params if the hash is valid" do
26
+ query_string_without_hash = "one=1&two=2&http_status=200"
27
+ hash = Braintree::Digest.hexdigest(query_string_without_hash)
28
+
29
+ query_string_with_hash = "#{query_string_without_hash}&hash=#{hash}"
30
+ result = Braintree::TransparentRedirect.parse_and_validate_query_string query_string_with_hash
31
+ result.should == {:one => "1", :two => "2", :http_status => "200", :hash => hash}
32
+ end
33
+
34
+ it "raises Braintree::ForgedQueryString if the hash param is not valid" do
35
+ query_string_without_hash = "one=1&two=2"
36
+ hash = Digest::SHA1.hexdigest("invalid#{query_string_without_hash}")
37
+
38
+ query_string_with_hash = "#{query_string_without_hash}&hash=#{hash}"
39
+ expect do
40
+ Braintree::TransparentRedirect.parse_and_validate_query_string query_string_with_hash
41
+ end.to raise_error(Braintree::ForgedQueryString)
42
+ end
43
+
44
+ it "raises Braintree::ForgedQueryString if hash is missing from the query string" do
45
+ expect do
46
+ Braintree::TransparentRedirect.parse_and_validate_query_string "query_string=without_a_hash"
47
+ end.to raise_error(Braintree::ForgedQueryString)
48
+ end
49
+
50
+ it "raises an AuthenticationError if authentication fails" do
51
+ expect do
52
+ Braintree::TransparentRedirect.parse_and_validate_query_string add_hash_to_query_string("http_status=401")
53
+ end.to raise_error(Braintree::AuthenticationError)
54
+ end
55
+
56
+ it "raises an AuthorizationError if authorization fails" do
57
+ expect do
58
+ Braintree::TransparentRedirect.parse_and_validate_query_string add_hash_to_query_string("http_status=403")
59
+ end.to raise_error(Braintree::AuthorizationError)
60
+ end
61
+
62
+ it "raises a ServerError if the server 500's" do
63
+ expect do
64
+ Braintree::TransparentRedirect.parse_and_validate_query_string add_hash_to_query_string("http_status=500")
65
+ end.to raise_error(Braintree::ServerError)
66
+ end
67
+
68
+ it "raises a DownForMaintenanceError if the server is down for maintenance" do
69
+ expect do
70
+ Braintree::TransparentRedirect.parse_and_validate_query_string add_hash_to_query_string("http_status=503")
71
+ end.to raise_error(Braintree::DownForMaintenanceError)
72
+ end
73
+
74
+ it "raises an UnexpectedError if some other code is returned" do
75
+ expect do
76
+ Braintree::TransparentRedirect.parse_and_validate_query_string add_hash_to_query_string("http_status=600")
77
+ end.to raise_error(Braintree::UnexpectedError, "Unexpected HTTP_RESPONSE 600")
78
+ end
79
+ end
80
+
81
+ describe "self.transaction_data" do
82
+ it "raises an exception if any keys are invalid" do
83
+ expect do
84
+ Braintree::TransparentRedirect.transaction_data(
85
+ :transaction => {:amount => "100.00", :invalid_key => "bad"}
86
+ )
87
+ end.to raise_error(ArgumentError, "invalid keys: transaction[invalid_key]")
88
+ end
89
+
90
+ it "raises an exception if not given a type" do
91
+ expect do
92
+ Braintree::TransparentRedirect.transaction_data(
93
+ :redirect_url => "http://example.com",
94
+ :transaction => {:amount => "100.00"}
95
+ )
96
+ end.to raise_error(ArgumentError, "expected transaction[type] of sale or credit, was: nil")
97
+ end
98
+
99
+ it "raises an exception if not given a type of sale or credit" do
100
+ expect do
101
+ Braintree::TransparentRedirect.transaction_data(
102
+ :redirect_url => "http://example.com",
103
+ :transaction => {:amount => "100.00", :type => "auth"}
104
+ )
105
+ end.to raise_error(ArgumentError, "expected transaction[type] of sale or credit, was: \"auth\"")
106
+ end
107
+ end
108
+
109
+ describe "self.update_credit_card_data" do
110
+ it "raises an exception if any keys are invalid" do
111
+ expect do
112
+ Braintree::TransparentRedirect.update_credit_card_data(
113
+ :credit_card => {:number => "ok", :invalid_key => "bad"}
114
+ )
115
+ end.to raise_error(ArgumentError, "invalid keys: credit_card[invalid_key]")
116
+ end
117
+
118
+ it "raises an exception if not given a payment_method_token" do
119
+ expect do
120
+ Braintree::TransparentRedirect.update_credit_card_data({})
121
+ end.to raise_error(ArgumentError, "expected params to contain :payment_method_token of payment method to update")
122
+ end
123
+ end
124
+
125
+ describe "self.update_customer_data" do
126
+ it "raises an exception if any keys are invalid" do
127
+ expect do
128
+ Braintree::TransparentRedirect.update_customer_data(
129
+ :customer => {:first_name => "ok", :invalid_key => "bad"}
130
+ )
131
+ end.to raise_error(ArgumentError, "invalid keys: customer[invalid_key]")
132
+ end
133
+
134
+ it "raises an exception if not given a customer_id" do
135
+ expect do
136
+ Braintree::TransparentRedirect.update_customer_data({})
137
+ end.to raise_error(ArgumentError, "expected params to contain :customer_id of customer to update")
138
+ end
139
+ end
140
+
141
+ describe "self._data" do
142
+ it "raises an exception if :redirect_url isn't given" do
143
+ expect do
144
+ Braintree::TransparentRedirect._data(:redirect_url => nil)
145
+ end.to raise_error(ArgumentError, "expected params to contain :redirect_url")
146
+ end
147
+ end
148
+
149
+ def add_hash_to_query_string(query_string_without_hash)
150
+ hash = Braintree::TransparentRedirect._hash(query_string_without_hash)
151
+ query_string_without_hash + "&hash=" + hash
152
+ end
153
+ end
154
+
@@ -0,0 +1,142 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ describe Braintree::Util do
4
+ describe "self.verify_keys" do
5
+ it "raises an exception if the hash contains an invalid key" do
6
+ expect do
7
+ Braintree::Util.verify_keys([:allowed], :allowed => "ok", :disallowed => "bad")
8
+ end.to raise_error(ArgumentError, "invalid keys: disallowed")
9
+ end
10
+
11
+ it "raises an exception with all keys listed if the hash contains invalid keys" do
12
+ expect do
13
+ Braintree::Util.verify_keys([:allowed], :allowed => "ok", :disallowed => "bad", "also_invalid" => true)
14
+ end.to raise_error(ArgumentError, "invalid keys: also_invalid, disallowed")
15
+ end
16
+
17
+ it "raises an exception if a nested hash contains an invalid key" do
18
+ expect do
19
+ Braintree::Util.verify_keys(
20
+ [:allowed, {:nested => [:nested_allowed, :nested_allowed2]}],
21
+ :allowed => "ok",
22
+ :top_level_invalid => "bad",
23
+ :nested => {
24
+ :nested_allowed => "ok",
25
+ :nested_allowed2 => "also ok",
26
+ :nested_invalid => "bad"
27
+ }
28
+ )
29
+ end.to raise_error(ArgumentError, "invalid keys: nested[nested_invalid], top_level_invalid")
30
+ end
31
+
32
+ it "raises an exception if a deeply nested hash contains an invalid key" do
33
+ expect do
34
+ Braintree::Util.verify_keys(
35
+ [:allowed, {:nested => [:nested_allowed, :nested_allowed2, {:deeply_allowed => [:super_deep_allowed]}]}],
36
+ :allowed => "ok",
37
+ :top_level_invalid => "bad",
38
+ :nested => {
39
+ :nested_allowed => "ok",
40
+ :nested_allowed2 => "also ok",
41
+ :nested_invalid => "bad",
42
+ :deeply_allowed => {
43
+ :super_deep_allowed => "yep",
44
+ :real_deep_invalid => "nope"
45
+ }
46
+ }
47
+ )
48
+ end.to raise_error(ArgumentError, "invalid keys: nested[deeply_allowed][real_deep_invalid], nested[nested_invalid], top_level_invalid")
49
+ end
50
+ end
51
+
52
+ describe "self._flatten_hash_keys" do
53
+ it "flattens hash keys" do
54
+ Braintree::Util._flatten_hash_keys(:nested => {
55
+ :nested_allowed => "ok",
56
+ :nested_allowed2 => "also ok",
57
+ :nested_invalid => "bad"
58
+ }).should == ["nested[nested_allowed2]", "nested[nested_allowed]", "nested[nested_invalid]"]
59
+ end
60
+ end
61
+
62
+ describe "self._flatten_valid_keys" do
63
+ it "flattens hash keys" do
64
+ Braintree::Util._flatten_valid_keys(
65
+ [:top_level, {:nested => [:nested_allowed, :nested_allowed2]}]
66
+ ).should == ["nested[nested_allowed2]", "nested[nested_allowed]", "top_level"]
67
+ end
68
+ end
69
+
70
+ describe "self.extract_attribute_as_array" do
71
+ it "delets the attribute from the hash" do
72
+ hash = {:foo => ["x"], :bar => :baz}
73
+ Braintree::Util.extract_attribute_as_array(hash, :foo)
74
+ hash.should == {:bar => :baz}
75
+ end
76
+
77
+ it "puts the attribute in an array if it's not an array" do
78
+ hash = {:foo => "x", :bar => :baz}
79
+ result = Braintree::Util.extract_attribute_as_array(hash, :foo)
80
+ result.should == ["x"]
81
+ end
82
+
83
+ it "returns the value if it's already an array" do
84
+ hash = {:foo => ["one", "two"], :bar => :baz}
85
+ result = Braintree::Util.extract_attribute_as_array(hash, :foo)
86
+ result.should == ["one", "two"]
87
+ end
88
+ end
89
+
90
+ describe "self.hash_to_query_string" do
91
+ it "generates a query string from the hash" do
92
+ hash = {:foo => {:key_one => "value_one", :key_two => "value_two"}}
93
+ Braintree::Util.hash_to_query_string(hash).should == "foo%5Bkey_one%5D=value_one&foo%5Bkey_two%5D=value_two"
94
+ end
95
+ end
96
+
97
+ describe "self.parse_query_string" do
98
+ it "parses the query string" do
99
+ query_string = "foo=bar%20baz&hash=a1b2c3"
100
+ Braintree::Util.parse_query_string(query_string).should == {:foo => "bar baz", :hash => "a1b2c3"}
101
+ end
102
+ end
103
+
104
+ describe "self.raise_exception_for_status_code" do
105
+ it "raises an AuthenticationError if authentication fails" do
106
+ expect do
107
+ Braintree::Util.raise_exception_for_status_code(401)
108
+ end.to raise_error(Braintree::AuthenticationError)
109
+ end
110
+
111
+ it "raises an AuthorizationError if authorization fails" do
112
+ expect do
113
+ Braintree::Util.raise_exception_for_status_code(403)
114
+ end.to raise_error(Braintree::AuthorizationError)
115
+ end
116
+
117
+ it "raises a ServerError if the server 500's" do
118
+ expect do
119
+ Braintree::Util.raise_exception_for_status_code(500)
120
+ end.to raise_error(Braintree::ServerError)
121
+ end
122
+
123
+ it "raises a DownForMaintenanceError if the server is down for maintenance" do
124
+ expect do
125
+ Braintree::Util.raise_exception_for_status_code(503)
126
+ end.to raise_error(Braintree::DownForMaintenanceError)
127
+ end
128
+
129
+ it "raises an UnexpectedError if some other code is returned" do
130
+ expect do
131
+ Braintree::Util.raise_exception_for_status_code(600)
132
+ end.to raise_error(Braintree::UnexpectedError, "Unexpected HTTP_RESPONSE 600")
133
+ end
134
+ end
135
+
136
+
137
+ describe "self.url_encode" do
138
+ it "url encodes the given text" do
139
+ Braintree::Util.url_encode("foo?bar").should == "foo%3Fbar"
140
+ end
141
+ end
142
+ end