google4r 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/CHANGES +5 -0
  2. data/LICENSE +22 -0
  3. data/README +75 -0
  4. data/lib/google4r/checkout.rb +36 -0
  5. data/lib/google4r/checkout/commands.rb +267 -0
  6. data/lib/google4r/checkout/frontend.rb +100 -0
  7. data/lib/google4r/checkout/notifications.rb +533 -0
  8. data/lib/google4r/checkout/shared.rb +501 -0
  9. data/lib/google4r/checkout/xml_generation.rb +271 -0
  10. data/lib/google4r/maps.rb +174 -0
  11. data/test/checkout/integration/checkout_command_test.rb +103 -0
  12. data/test/checkout/unit/address_test.rb +131 -0
  13. data/test/checkout/unit/area_test.rb +41 -0
  14. data/test/checkout/unit/checkout_command_test.rb +112 -0
  15. data/test/checkout/unit/checkout_command_xml_generator_test.rb +187 -0
  16. data/test/checkout/unit/command_test.rb +126 -0
  17. data/test/checkout/unit/flat_rate_shipping_test.rb +114 -0
  18. data/test/checkout/unit/frontend_test.rb +63 -0
  19. data/test/checkout/unit/item_test.rb +159 -0
  20. data/test/checkout/unit/marketing_preferences_test.rb +65 -0
  21. data/test/checkout/unit/merchant_code_test.rb +122 -0
  22. data/test/checkout/unit/new_order_notification_test.rb +115 -0
  23. data/test/checkout/unit/notification_acknowledgement_test.rb +43 -0
  24. data/test/checkout/unit/notification_handler_test.rb +93 -0
  25. data/test/checkout/unit/order_adjustment_test.rb +95 -0
  26. data/test/checkout/unit/order_state_change_notification_test.rb +159 -0
  27. data/test/checkout/unit/pickup_shipping_test.rb +70 -0
  28. data/test/checkout/unit/private_data_parser_test.rb +68 -0
  29. data/test/checkout/unit/shipping_adjustment_test.rb +100 -0
  30. data/test/checkout/unit/shipping_method_test.rb +41 -0
  31. data/test/checkout/unit/shopping_cart_test.rb +146 -0
  32. data/test/checkout/unit/tax_rule_test.rb +65 -0
  33. data/test/checkout/unit/tax_table_test.rb +82 -0
  34. data/test/checkout/unit/us_country_area_test.rb +76 -0
  35. data/test/checkout/unit/us_state_area_test.rb +70 -0
  36. data/test/checkout/unit/us_zip_area_test.rb +66 -0
  37. data/test/maps/geocoder_test.rb +143 -0
  38. data/var/cacert.pem +7815 -0
  39. metadata +100 -0
@@ -0,0 +1,271 @@
1
+ #--
2
+ # Project: google4r
3
+ # File: lib/google4r/checkout/xml_generation.rb
4
+ # Author: Manuel Holtgrewe <purestorm at ggnore dot net>
5
+ # Copyright: (c) 2007 by Manuel Holtgrewe
6
+ # License: MIT License as follows:
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining
9
+ # a copy of this software and associated documentation files (the
10
+ # "Software"), to deal in the Software without restriction, including
11
+ # without limitation the rights to use, copy, modify, merge, publish,
12
+ # distribute, sublicense, and/or sell copies of the Software, and to permit
13
+ # persons to whom the Software is furnished to do so, subject to the
14
+ # following conditions:
15
+ #
16
+ # The above copyright notice and this permission notice shall be included
17
+ # in all copies or substantial portions of the Software.
18
+ #
19
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
25
+ # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+ #++
27
+ # This file contains the classes that allow to persist the object hierarchies
28
+ # that are created with the Google Checkout API to XML.
29
+
30
+ require 'stringio'
31
+ require 'rexml/document'
32
+
33
+ module Google4R #:nodoc:
34
+ module Checkout #:nodoc:
35
+ # Use the CheckoutXmlGenerator to create an XML document from a CheckoutCommand
36
+ # object.
37
+ #
38
+ # Usage:
39
+ #
40
+ # checkout = CheckoutCommand.new
41
+ # # set up the CheckoutCommand
42
+ #
43
+ # generator = CheckoutCommandXmlGenerator.new(checkout)
44
+ # puts generator.generate # => "<xml? version=..."
45
+ # File.new('some.xml', 'w') { |f| f.write generator.generate }
46
+ #--
47
+ # TODO
48
+ #
49
+ # * Refactor the big, monolitic generator into smaller, easier testable ones. One
50
+ # for each major part of the resulting XML document. This will also reduce the
51
+ # overhead in generating other types of XML documents.
52
+ #++
53
+ class CheckoutCommandXmlGenerator
54
+ # Initializes the CheckoutCommandXmlGenerator with the CheckoutCommand it is
55
+ # to conver to XML.
56
+ def initialize(checkout_command)
57
+ @checkout_command = checkout_command
58
+ end
59
+
60
+ # Creates an XML document from the checkout_command passed into the constructor. Returns
61
+ # the resulting XML string.
62
+ def generate
63
+ @document = REXML::Document.new
64
+
65
+ declaration = REXML::XMLDecl.new
66
+ declaration.encoding = 'utf-8'
67
+ @document << declaration
68
+
69
+ self.process_checkout_command(@checkout_command)
70
+
71
+ io = StringIO.new
72
+ @document.write(io, 0) # TODO: Maybe replace 0 by -1 so no spaces are inserted?
73
+ return io.string
74
+ end
75
+
76
+ protected
77
+
78
+ def process_checkout_command(command)
79
+ root = @document.add_element('checkout-shopping-cart', { 'xmlns' => 'http://checkout.google.com/schema/2' })
80
+
81
+ self.process_shopping_cart(root, command.cart)
82
+
83
+ # <merchant-checkout-flow-support>
84
+ flow_element = root.add_element('checkout-flow-support').add_element('merchant-checkout-flow-support')
85
+
86
+ # <continue-shopping-url>
87
+ if not command.continue_shopping_url.nil? then
88
+ flow_element.add_element('continue-shopping-url').text = command.continue_shopping_url
89
+ end
90
+
91
+ # <edit-cart-url>
92
+ if not command.edit_cart_url.nil? then
93
+ flow_element.add_element('edit-cart-url').text = command.edit_cart_url
94
+ end
95
+
96
+ # <request-buyer-phone-number>
97
+ if not command.request_buyer_phone_number.nil? then
98
+ flow_element.add_element('request-buyer-phone-number').text =
99
+ if command.request_buyer_phone_number then
100
+ "true"
101
+ else
102
+ "false"
103
+ end
104
+ end
105
+
106
+ # <tax-tables>
107
+ command.tax_tables.each do |tax_table|
108
+ end
109
+
110
+ # <shipping-methods>
111
+ shippings_element = flow_element.add_element('shipping-methods')
112
+ command.shipping_methods.each do |shipping_method|
113
+ self.process_shipping_method(shippings_element, shipping_method)
114
+ end
115
+ end
116
+
117
+ def process_shopping_cart(parent, cart)
118
+ cart_element = parent.add_element('shopping-cart')
119
+
120
+ # add <cart-expiration> tag to the cart if a time has been added to the cart
121
+ if not cart.expires_at.nil? then
122
+ cart_element.add_element('cart-expiration').add_element('good-until-date').text =
123
+ cart.expires_at.iso8601
124
+ end
125
+
126
+ # add <merchant-private-data> to the cart if any has been set
127
+ if not cart.private_data.nil? then
128
+ self.process_hash(cart_element.add_element('merchant-private-data'), cart.private_data)
129
+ end
130
+
131
+ # process the items in the cart
132
+ items_element = cart_element.add_element('items')
133
+ cart.items.each do |item|
134
+ self.process_item(items_element, item)
135
+ end
136
+ end
137
+
138
+ # Adds an <item> tag to the tag parent with the appropriate values.
139
+ def process_item(parent, item)
140
+ item_element = parent.add_element('item')
141
+
142
+ item_element.add_element('item-name').text = item.name
143
+ item_element.add_element('item-description').text = item.description
144
+
145
+ item_element.add_element('unit-price', { 'currency' => item.unit_price.currency }).text = item.unit_price.to_s
146
+ item_element.add_element('quantity').text = item.quantity.to_i
147
+
148
+ if not item.id.nil? then
149
+ item_element.add_element('merchant-item-id').text = item.id
150
+ end
151
+
152
+ if not item.private_data.nil? then
153
+ self.process_hash(item_element.add_element('merchant-private-item-data'), item.private_data)
154
+ end
155
+
156
+ # The above was easy; now we need to get the appropriate tax table for this
157
+ # item. The Item class makes sure that the table exists.
158
+ if not item.tax_table.nil? then
159
+ item_element.add_element('tax-table-selector').text = item.tax_table.name
160
+ end
161
+ end
162
+
163
+ # Adds an item for the given shipping method.
164
+ def process_shipping_method(parent, shipping_method)
165
+ if shipping_method.kind_of? PickupShipping then
166
+ process_pickup_shipping(parent, shipping_method)
167
+ elsif shipping_method.kind_of? FlatRateShipping then
168
+ process_flat_rate_shipping(parent, shipping_method)
169
+ else
170
+ raise "Unknown ShippingMethod type of #{shipping_method.inspect}!"
171
+ end
172
+ end
173
+
174
+ def process_flat_rate_shipping(parent, shipping)
175
+ element = parent.add_element('flat-rate-shipping')
176
+ element.add_attribute('name', shipping.name)
177
+ element.add_element('price', { 'currency' => shipping.price.currency }).text = shipping.price.to_s
178
+
179
+ if shipping.excluded_areas.length + shipping.allowed_areas.length > 0 then
180
+ restrictions_tag = element.add_element('shipping-restrictions')
181
+
182
+ if shipping.allowed_areas.length > 0 then
183
+ allowed_tag = restrictions_tag.add_element('allowed-areas')
184
+
185
+ shipping.allowed_areas.each do |area|
186
+ self.process_area(allowed_tag, area)
187
+ end
188
+ end
189
+
190
+ if shipping.excluded_areas.length > 0 then
191
+ excluded_tag = restrictions_tag.add_element('excluded-areas')
192
+
193
+ shipping.excluded_areas.each do |area|
194
+ self.process_area(excluded_tag, area)
195
+ end
196
+ end
197
+ end
198
+ end
199
+
200
+ def process_pickup_shipping(parent, shipping)
201
+ element = parent.add_element('pickup')
202
+ element.add_attribute('name', shipping.name)
203
+ element.add_element('price', { 'currency' => shipping.price.currency }).text = shipping.price.to_s
204
+ end
205
+
206
+ # Adds an appropriate tag for the given Area subclass instance to the parent Element.
207
+ def process_area(parent, area)
208
+ if area.kind_of? UsZipArea then
209
+ parent.add_element('us-zip-area').add_element('zip-pattern').text = area.pattern
210
+ elsif area.kind_of? UsCountryArea then
211
+ parent.add_element('us-country-area', { 'country-area' => area.area })
212
+ elsif area.kind_of? UsStateArea then
213
+ parent.add_element('us-state-area').add_element('state').text = area.state
214
+ else
215
+ raise "Area of unknown type: #{area.inspect}."
216
+ end
217
+ end
218
+
219
+ # Converts a Hash into an XML structure. The keys are converted to tag names. If
220
+ # the values are Hashs themselves then process_hash is called upon them. If the
221
+ # values are Arrays then a new element with the key's name will be created.
222
+ #
223
+ # If a value is an Array then this array will be flattened before it is processed.
224
+ # Thus, nested arrays are not allowed.
225
+ #
226
+ # === Example
227
+ #
228
+ # process_hash(parent, { 'foo' => { 'bar' => 'baz' } })
229
+ #
230
+ # # will produce a structure that is equivalent to.
231
+ #
232
+ # <foo>
233
+ # <bar>baz</bar>
234
+ # </foo>
235
+ #
236
+ #
237
+ # process_hash(parent, { 'foo' => [ { 'bar' => 'baz' }, "d'oh", 2 ] })
238
+ #
239
+ # # will produce a structure that is equivalent to.
240
+ #
241
+ # <foo>
242
+ # <bar>baz</bar>
243
+ # </foo>
244
+ # <foo>d&amp;</foo>
245
+ # <foo>2</foo>
246
+ def process_hash(parent, hash)
247
+ hash.each do |key, value|
248
+ if value.kind_of? Array then
249
+ value.flatten.each do |arr_entry|
250
+ if arr_entry.kind_of? Hash then
251
+ self.process_hash(parent.add_element(self.str2tag_name(key.to_s)), arr_entry)
252
+ else
253
+ parent.add_element(self.str2tag_name(key.to_s)).text = arr_entry.to_s
254
+ end
255
+ end
256
+ elsif value.kind_of? Hash then
257
+ process_hash(parent.add_element(self.str2tag_name(key.to_s)), value)
258
+ else
259
+ parent.add_element(self.str2tag_name(key.to_s))
260
+ end
261
+ end
262
+ end
263
+
264
+ # Converts a string to a valid XML tag name. Whitespace will be converted into a dash/minus
265
+ # sign, non alphanumeric characters that are neither "-" nor "_" nor ":" will be stripped.
266
+ def str2tag_name(str)
267
+ str.gsub(%r{\s}, '-').gsub(%r{[^a-zA-Z0-9\-\_:]}, '')
268
+ end
269
+ end
270
+ end
271
+ end
@@ -0,0 +1,174 @@
1
+ #--
2
+ # Project: google4r
3
+ # File: lib/google4r/geocoder.rb
4
+ # Author: Manuel Holtgrewe <purestorm at ggnore dot net>
5
+ # Copyright: (c) 2006 by Manuel Holtgrewe
6
+ # License: MIT License as follows:
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining
9
+ # a copy of this software and associated documentation files (the
10
+ # "Software"), to deal in the Software without restriction, including
11
+ # without limitation the rights to use, copy, modify, merge, publish,
12
+ # distribute, sublicense, and/or sell copies of the Software, and to permit
13
+ # persons to whom the Software is furnished to do so, subject to the
14
+ # following conditions:
15
+ #
16
+ # The above copyright notice and this permission notice shall be included
17
+ # in all copies or substantial portions of the Software.
18
+ #
19
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
25
+ # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+ #++
27
+ # This file defines the code which is necessary to access the Google Maps
28
+ # Geocoder.
29
+
30
+ require 'net/http'
31
+ require 'uri'
32
+ require 'json'
33
+
34
+ module Google4R
35
+ module Maps
36
+ # Thrown if no API has been given or it is incorrect.
37
+ class KeyException < Exception
38
+ end
39
+
40
+ # Thrown when it was impossible to connect to the server.
41
+ class ConnectionException < Exception
42
+ end
43
+
44
+ # Allows to geocode a location.
45
+ #
46
+ # Uses the Google Maps API to find information about locations specified by strings. You need
47
+ # a Google Maps API key to use the Geocoder.
48
+ #
49
+ # The result has the same format as documented in [1] (within <kml>) if it is directly converted
50
+ # into Ruby: A value consisting of nested Hashes and Arrays.
51
+ #
52
+ # After querying, you can access the last server response code using #last_status_code.
53
+ #
54
+ # Notice that you can also use Google's geolocator service to locate ZIPs by querying for the
55
+ # ZIP and country name.
56
+ #
57
+ # Usage Example:
58
+ #
59
+ # api_key = 'abcdefg'
60
+ # geocoder = Google4R::Geocoder.new(api_key)
61
+ # result = geocoder.query("1 Infinite Loop, Cupertino")
62
+ # puts result["Placemark"][0]["address"] # => "1 Infinite Loop, Cupertino, CA 95014"
63
+ #
64
+ # 1:: http://www.google.de/apis/maps/documentation/#Geocoding_Structured
65
+ class Geocoder
66
+ # Returns the last status code returned by the server.
67
+ attr_reader :last_status_code
68
+
69
+ # The hardcoded URL of Google's geolocator API.
70
+ GET_URL = 'http://maps.google.com/maps/geo?q=%s&output=%s&key=%s'.freeze
71
+
72
+ # Response code constants.
73
+ G_GEO_SUCCESS = 200
74
+ G_GEO_SERVER_ERROR = 500
75
+ G_GEO_MISSING_ADDRESS = 601
76
+ G_GEO_UNKNOWN_ADDRESS = 602
77
+ G_UNAVAILABLE_ADDRESS = 603
78
+ G_GEO_BAD_KEY = 610
79
+
80
+ # Creates a new Geocoder object. You have to supply a valid Google Maps
81
+ # API key.
82
+ def initialize(key)
83
+ @api_key = key
84
+ end
85
+
86
+ # === Parameters
87
+ #
88
+ # query:: The place to locate.
89
+ #
90
+ # === Exceptions
91
+ #
92
+ # Throws a KeyException if the key for this Geocoder instance is invalid and
93
+ # throws a ConnectionException if the Geocoder instance could not connect to
94
+ # Google's server or an server error occured.
95
+ #
96
+ # === Return Values
97
+ #
98
+ # Returns data in the same format as documented in [1]
99
+ #
100
+ # Example of returned values:
101
+ #
102
+ # {
103
+ # "name": "1600 Amphitheatre Parkway, Mountain View, CA, USA",
104
+ # "Status": {
105
+ # "code": 200,
106
+ # "request": "geocode"
107
+ # },
108
+ # "Placemark": [
109
+ # {
110
+ # "address": "1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA",
111
+ # "AddressDetails": {
112
+ # "Country": {
113
+ # "CountryNameCode": "US",
114
+ # "AdministrativeArea": {
115
+ # "AdministrativeAreaName": "CA",
116
+ # "SubAdministrativeArea": {
117
+ # "SubAdministrativeAreaName": "Santa Clara",
118
+ # "Locality": {
119
+ # "LocalityName": "Mountain View",
120
+ # "Thoroughfare": {
121
+ # "ThoroughfareName": "1600 Amphitheatre Pkwy"
122
+ # },
123
+ # "PostalCode": {
124
+ # "PostalCodeNumber": "94043"
125
+ # }
126
+ # }
127
+ # }
128
+ # }
129
+ # },
130
+ # "Accuracy": 8
131
+ # },
132
+ # "Point": {
133
+ # "coordinates": [-122.083739, 37.423021, 0]
134
+ # }
135
+ # }
136
+ # ]
137
+ # }
138
+ #
139
+ # 1:: http://www.google.de/apis/maps/documentation/#Geocoding_Structured
140
+ def query(query)
141
+ # Check that a Google Maps key has been specified.
142
+ raise KeyException.new("Cannot use Google geocoder without an API key.") if @api_key.nil?
143
+
144
+ # Compute the URL to send a GET query to.
145
+ url = URI.escape(GET_URL % [ query, 'json', @api_key.to_s ])
146
+
147
+ # Perform the query via HTTP.
148
+ response =
149
+ begin
150
+ Net::HTTP.get_response(URI.parse(url))
151
+ rescue Exception => e
152
+ raise ConnectionException.new("Could not connect to '#{url}': #{e.message}")
153
+ end
154
+ body = response.body
155
+
156
+ # Parse the response JSON.
157
+ result = JSON.parse(body)
158
+
159
+ @last_status_code = result['Status']['code']
160
+
161
+ # Check that the query was successful.
162
+ if @last_status_code == G_GEO_BAD_KEY then
163
+ raise KeyException.new("Invalid API key: '#{@api_key}'.")
164
+ elsif @last_status_code == G_GEO_SERVER_ERROR then
165
+ raise ConnectionException.new("There was an error when connecting to the server. Result code was: #{status}.")
166
+ elsif @last_status_code == G_GEO_UNKNOWN_ADDRESS then
167
+ return nil
168
+ end
169
+
170
+ return result
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,103 @@
1
+ #--
2
+ # Project: google_checkout4r
3
+ # File: test/checkout/integration/checkout_command.rb
4
+ # Author: Manuel Holtgrewe <purestorm at ggnore dot net>
5
+ # Copyright: (c) 2007 by Manuel Holtgrewe
6
+ # License: MIT License as follows:
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining
9
+ # a copy of this software and associated documentation files (the
10
+ # "Software"), to deal in the Software without restriction, including
11
+ # without limitation the rights to use, copy, modify, merge, publish,
12
+ # distribute, sublicense, and/or sell copies of the Software, and to permit
13
+ # persons to whom the Software is furnished to do so, subject to the
14
+ # following conditions:
15
+ #
16
+ # The above copyright notice and this permission notice shall be included
17
+ # in all copies or substantial portions of the Software.
18
+ #
19
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20
+ # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
23
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
24
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
25
+ # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
+ #++
27
+
28
+ require File.expand_path(File.dirname(__FILE__)) + '/../../test_helper'
29
+
30
+ require 'test/checkout/frontend_configuration'
31
+
32
+ require 'google4r/checkout'
33
+
34
+ # Integration tests for the CheckoutCommand class.
35
+ #
36
+ # Tests the CheckoutCommand class against the Google Checkout Web Service.
37
+ class Google4R::Checkout::CheckoutCommandIntegrationTest < Test::Unit::TestCase
38
+ include Google4R::Checkout
39
+
40
+ def setup
41
+ @frontend = Frontend.new(FRONTEND_CONFIGURATION)
42
+ @frontend.tax_table_factory = TestTaxTableFactory.new
43
+ @command = @frontend.create_checkout_command
44
+ end
45
+
46
+ def test_sending_to_google_works_with_valid_request
47
+ setup_command(@command)
48
+ result = @command.send_to_google_checkout
49
+ assert_kind_of CheckoutRedirectResponse, result
50
+ end
51
+
52
+ def test_using_invalid_credentials_raise_google_checkout_error
53
+ invalid_patches = [ [ :merchant_id, 'invalid' ], [ :merchant_key, 'invalid' ] ]
54
+
55
+ invalid_patches.each do |patch|
56
+ config = FRONTEND_CONFIGURATION.dup
57
+ config[patch[0]] = patch[1]
58
+ @frontend = Frontend.new(config)
59
+ @frontend.tax_table_factory = TestTaxTableFactory.new
60
+ @command = @frontend.create_checkout_command
61
+
62
+ setup_command(@command)
63
+ assert_raises(GoogleCheckoutError) { @command.send_to_google_checkout }
64
+ end
65
+ end
66
+
67
+ def test_invalid_xml_raises_google_checkout_error
68
+ class << @command
69
+ def to_xml
70
+ ''
71
+ end
72
+ end
73
+
74
+ setup_command(@command)
75
+ assert_raises(GoogleCheckoutError) { @command.send_to_google_checkout }
76
+ end
77
+
78
+ protected
79
+
80
+ # Sets up the given CheckoutCommand so it contains some
81
+ # shipping methods and its cart contains some items.
82
+ def setup_command(command)
83
+ # Add shipping methods.
84
+ command.create_shipping_method(FlatRateShipping) do |shipping|
85
+ shipping.name = 'UPS Ground Shipping'
86
+ shipping.price = Money.new(2000) # USD 20
87
+ shipping.create_allowed_area(UsCountryArea) do |area|
88
+ area.area = UsCountryArea::ALL
89
+ end
90
+ end
91
+
92
+ # Add items to the cart.
93
+ 1.upto(5) do |i|
94
+ command.cart.create_item do |item|
95
+ item.name = "Test Item #{i}"
96
+ item.description = "This is a test item (#{i})"
97
+ item.unit_price = Money.new(350)
98
+ item.quantity = i * 3
99
+ item.id = "test-#{i}-123456789"
100
+ end
101
+ end
102
+ end
103
+ end