google4r 0.0.1

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 (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