braintree 1.0.0

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