shipping 1.2.1 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -4,8 +4,14 @@ require 'rake/testtask'
4
4
  require 'rake/rdoctask'
5
5
  require 'rake/gempackagetask'
6
6
  require 'rake/contrib/rubyforgepublisher'
7
+ require File.dirname(__FILE__) + '/lib/shipping'
7
8
 
8
- PKG_VERSION = "1.2.1"
9
+ PKG_VERSION = Shipping::VERSION
10
+ PKG_NAME = "shipping"
11
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
12
+ RUBY_FORGE_PROJECT = "shipping"
13
+ RUBY_FORGE_USER = ENV['RUBY_FORGE_USER'] || "cardmagic"
14
+ RELEASE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
9
15
 
10
16
  PKG_FILES = FileList[
11
17
  "lib/**/*", "bin/*", "test/**/*", "[A-Z]*", "Rakefile", "doc/**/*"
@@ -90,7 +96,156 @@ end
90
96
 
91
97
  desc "Publish new documentation"
92
98
  task :publish do
93
- `ssh rufy update-shipping-doc`
94
- `scp pkg/shipping-1.0.0.* rufy:www/shipping.rufy.com/docs/`
95
99
  Rake::RubyForgePublisher.new('shipping', 'cardmagic').upload
100
+ `ssh rufy update-shipping-doc`
101
+ end
102
+
103
+ desc "Publish the release files to RubyForge."
104
+ task :upload => [:package] do
105
+ files = ["gem", "tgz", "zip"].map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" }
106
+
107
+ if RUBY_FORGE_PROJECT then
108
+ require 'net/http'
109
+ require 'open-uri'
110
+
111
+ project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/"
112
+ project_data = open(project_uri) { |data| data.read }
113
+ group_id = project_data[/[?&]group_id=(\d+)/, 1]
114
+ raise "Couldn't get group id" unless group_id
115
+
116
+ # This echos password to shell which is a bit sucky
117
+ if ENV["RUBY_FORGE_PASSWORD"]
118
+ password = ENV["RUBY_FORGE_PASSWORD"]
119
+ else
120
+ password = Proc.new do
121
+ sync = STDOUT.sync
122
+ begin
123
+ echo false
124
+ STDOUT.sync = true
125
+ print "#{RUBY_FORGE_USER}@rubyforge.org's password: "
126
+ STDIN.gets.chomp
127
+ ensure
128
+ echo true
129
+ STDOUT.sync = sync
130
+ puts
131
+ end
132
+ end.call
133
+ end
134
+
135
+ login_response = Net::HTTP.start("rubyforge.org", 80) do |http|
136
+ data = [
137
+ "login=1",
138
+ "form_loginname=#{RUBY_FORGE_USER}",
139
+ "form_pw=#{password}"
140
+ ].join("&")
141
+ http.post("/account/login.php", data)
142
+ end
143
+
144
+ cookie = login_response["set-cookie"]
145
+ raise "Login failed" unless cookie
146
+ headers = { "Cookie" => cookie }
147
+
148
+ release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}"
149
+ release_data = open(release_uri, headers) { |data| data.read }
150
+ package_id = release_data[/[?&]package_id=(\d+)/, 1]
151
+ raise "Couldn't get package id" unless package_id
152
+
153
+ first_file = true
154
+ release_id = ""
155
+
156
+ files.each do |filename|
157
+ basename = File.basename(filename)
158
+ file_ext = File.extname(filename)
159
+ file_data = File.open(filename, "rb") { |file| file.read }
160
+
161
+ puts "Releasing #{basename}..."
162
+
163
+ release_response = Net::HTTP.start("rubyforge.org", 80) do |http|
164
+ release_date = Time.now.strftime("%Y-%m-%d %H:%M")
165
+ type_map = {
166
+ ".zip" => "3000",
167
+ ".tgz" => "3110",
168
+ ".gz" => "3110",
169
+ ".gem" => "1400"
170
+ }; type_map.default = "9999"
171
+ type = type_map[file_ext]
172
+ boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor"
173
+
174
+ query_hash = if first_file then
175
+ {
176
+ "group_id" => group_id,
177
+ "package_id" => package_id,
178
+ "release_name" => RELEASE_NAME,
179
+ "release_date" => release_date,
180
+ "type_id" => type,
181
+ "processor_id" => "8000", # Any
182
+ "release_notes" => "",
183
+ "release_changes" => "",
184
+ "preformatted" => "1",
185
+ "submit" => "1"
186
+ }
187
+ else
188
+ {
189
+ "group_id" => group_id,
190
+ "release_id" => release_id,
191
+ "package_id" => package_id,
192
+ "step2" => "1",
193
+ "type_id" => type,
194
+ "processor_id" => "8000", # Any
195
+ "submit" => "Add This File"
196
+ }
197
+ end
198
+
199
+ query = "?" + query_hash.map do |(name, value)|
200
+ [name, URI.encode(value)].join("=")
201
+ end.join("&")
202
+
203
+ data = [
204
+ "--" + boundary,
205
+ "Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"",
206
+ "Content-Type: application/octet-stream",
207
+ "Content-Transfer-Encoding: binary",
208
+ "", file_data, ""
209
+ ].join("\x0D\x0A")
210
+
211
+ release_headers = headers.merge(
212
+ "Content-Type" => "multipart/form-data; boundary=#{boundary}"
213
+ )
214
+
215
+ target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php"
216
+ http.post(target + query, data, release_headers)
217
+ end
218
+
219
+ if first_file then
220
+ release_id = release_response.body[/release_id=(\d+)/, 1]
221
+ raise("Couldn't get release id") unless release_id
222
+ end
223
+
224
+ first_file = false
225
+ end
226
+ end
227
+ end
228
+
229
+ begin
230
+ if !defined?(USE_TERMIOS) || USE_TERMIOS
231
+ require 'termios'
232
+ else
233
+ raise LoadError
234
+ end
235
+
236
+ # Enable or disable stdin echoing to the terminal.
237
+ def echo(enable)
238
+ term = Termios::getattr(STDIN)
239
+
240
+ if enable
241
+ term.c_lflag |= (Termios::ECHO | Termios::ICANON)
242
+ else
243
+ term.c_lflag &= ~Termios::ECHO
244
+ end
245
+
246
+ Termios::setattr(STDIN, Termios::TCSANOW, term)
247
+ end
248
+ rescue LoadError
249
+ def echo(enable)
250
+ end
96
251
  end
data/lib/shipping.rb CHANGED
@@ -24,6 +24,8 @@
24
24
  # Copyright:: Copyright (c) 2005 Lucas Carlson
25
25
  # License:: LGPL
26
26
 
27
+ $:.unshift(File.dirname(__FILE__))
28
+
27
29
  begin
28
30
  require 'rubygems'
29
31
  rescue LoadError
@@ -35,6 +37,7 @@ require 'yaml'
35
37
  require 'rexml/document'
36
38
  require 'net/http'
37
39
  require 'net/https'
40
+ require 'base64'
38
41
 
39
42
  require 'extensions'
40
43
  require 'shipping/base'
data/lib/shipping/base.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  # License:: LGPL
4
4
 
5
5
  module Shipping
6
+ VERSION = "1.3.0"
7
+
6
8
  class ShippingError < StandardError; end
7
9
 
8
10
  class Base
@@ -11,13 +13,13 @@ module Shipping
11
13
  attr_writer :ups_account, :ups_user, :ups_password
12
14
  attr_writer :fedex_account, :fedex_meter, :fedex_url
13
15
 
14
- attr_accessor :name, :phone, :email, :address, :address2, :city, :state, :zip, :country
16
+ attr_accessor :name, :phone, :company, :email, :address, :address2, :city, :state, :zip, :country
15
17
  attr_accessor :sender_name, :sender_phone, :sender_email, :sender_address, :sender_city, :sender_state, :sender_zip, :sender_country
16
18
 
17
19
  attr_accessor :weight, :weight_units, :insured_value, :declared_value, :transaction_type, :description
18
20
  attr_accessor :package_total, :packaging_type, :service_type
19
21
 
20
- attr_accessor :ship_date, :dropoff_type, :pay_type, :currency_code
22
+ attr_accessor :ship_date, :dropoff_type, :pay_type, :currency_code, :image_type, :label_type
21
23
 
22
24
  def initialize(options = {})
23
25
  prefs = File.expand_path(options[:prefs] || "~/.shipping.yml")
@@ -6,22 +6,24 @@
6
6
  # See http://www.fedex.com/us/solutions/wis/pdf/xml_transguide.pdf?link=4 for the full XML-based API
7
7
 
8
8
  module Shipping
9
- class FedEx < Base
10
- # Gets the total price of the shipping
11
- def price
12
- get_price
13
- return REXML::XPath.first(@response, "//FDXRateReply/EstimatedCharges/DiscountedCharges/NetCharge").text.to_f
14
- rescue
15
- raise ShippingError, get_error
16
- end
17
-
18
- # Gets the base price of the shipping (with discounts taken into consideration)
19
- def base_price
20
- get_price
21
- return REXML::XPath.first(@response, "//FDXRateReply/EstimatedCharges/DiscountedCharges/BaseCharge").text.to_f
22
- rescue
23
- raise ShippingError, get_error
24
- end
9
+ class FedEx < Base
10
+ # Gets the list price the regular consumer would have to pay. Discount price is what the
11
+ # person with this particular account number will pay
12
+ def price
13
+ get_price
14
+ return REXML::XPath.first(@response, "//FDXRateReply/EstimatedCharges/ListCharges/NetCharge").text.to_f
15
+ rescue
16
+ raise ShippingError, get_error
17
+ end
18
+
19
+ # Gets the discount price of the shipping (with discounts taken into consideration).
20
+ # "base price" doesn't include surcharges like fuel cost, so I don't think it is the correct price to get
21
+ def discount_price
22
+ get_price
23
+ return REXML::XPath.first(@response, "//FDXRateReply/EstimatedCharges/DiscountedCharges/NetCharge").text.to_f
24
+ rescue
25
+ raise ShippingError, get_error
26
+ end
25
27
 
26
28
  # still not sure what the best way to handle this transaction's return data would be. Possibly a hash of the form {service => delivery_estimate}?
27
29
  def express_service_availability
@@ -84,18 +86,159 @@ module Shipping
84
86
 
85
87
  return REXML::XPath.first(@response, "//FDXSubscriptionReply/MeterNumber").text
86
88
  end
87
-
89
+
88
90
  # require 'fileutils'
89
91
  # fedex = Shipping::FedEx.new :name => 'John Doe', ... , :sender_zip => 97202
90
92
  # label = fedex.label
91
- # puts label.url
92
93
  # puts label.tracking_number
93
- #
94
- # In the future you will be able to do:
94
+ # FileUtils.cp label.image.path, '/path/to/my/images/directory/'
95
+ #
96
+ # There are several types of labels that can be returned by changing @image_type.
97
+ # PNG is selected by default.
95
98
  #
96
- # FileUtils.cp label.image.path, '/path/to/my/images/directory/'
97
- #
98
- def label
99
+ def label
100
+ @required = [:phone, :email, :address, :city, :state, :zip ]
101
+ @required += [:sender_phone, :sender_email, :sender_address, :sender_city, :sender_state, :sender_zip ]
102
+ @required += [:fedex_account, :fedex_url]
103
+
104
+ @transaction_type ||= 'rate_ground'
105
+ @weight = (@weight.to_f*10).round/10.0
106
+ @declared_value = (@declared_value.to_f*100).round/100.0 unless @declared_value.blank?
107
+ state = STATES.has_value?(@state.downcase) ? STATES.index(@state.downcase).upcase : @state.upcase
108
+ sender_state = STATES.has_value?(@sender_state.downcase) ? STATES.index(@sender_state.downcase).upcase : @sender_state.upcase
109
+
110
+ @data = String.new
111
+
112
+ b = Builder::XmlMarkup.new :target => @data
113
+ b.instruct!
114
+ b.FDXShipRequest('xmlns:api' => 'http://www.fedex.com/fsmapi', 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', 'xsi:noNamespaceSchemaLocation' => 'FDXShipRequest.xsd') { |b|
115
+ b.RequestHeader { |b|
116
+ b.AccountNumber @fedex_account
117
+ b.MeterNumber @fedex_meter
118
+ b.CarrierCode TransactionTypes[@transaction_type][1]
119
+ }
120
+ b.ShipDate((Time.now).strftime("%Y-%m-%d"))
121
+ b.ShipTime((Time.now).strftime("%H:%M:%S"))
122
+ b.DropoffType @dropoff_type || 'REGULARPICKUP'
123
+ b.Service ServiceTypes[@service_type] || ServiceTypes['ground_service'] # default to ground service
124
+ b.Packaging PackageTypes[@packaging_type] || 'YOURPACKAGING'
125
+ b.WeightUnits @weight_units || 'LBS' # or KGS
126
+ b.Weight @weight
127
+ b.CurrencyCode @currency_code || 'USD'
128
+ b.Origin { |b|
129
+ b.Contact { |b|
130
+ if @sender_name.to_s.size > 2
131
+ b.PersonName @sender_name
132
+ b.CompanyName @sender_company unless @sender_company.blank?
133
+ elsif @sender_company.to_s.size > 2
134
+ b.PersonName @sender_name unless @sender_name.blank?
135
+ b.CompanyName @sender_company
136
+ else
137
+ raise ShippingError, "Either the sender_name or the sender_company value must be bigger than 2 characters."
138
+ end
139
+ b.Department @sender_department unless @sender_department.blank?
140
+ b.PhoneNumber @sender_phone.gsub(/[^\d]/,"")
141
+ b.PagerNumber @sender_pager.gsub(/[^\d]/,"") if @sender_pager.class == String
142
+ b.FaxNumber @sender_fax.gsub(/[^\d]/,"") if @sender_fax.class == String
143
+ b.tag! :"E-MailAddress", @sender_email
144
+ }
145
+ b.Address { |b|
146
+ b.Line1 @sender_address
147
+ b.Line2 @sender_address2 unless @sender_address2.blank?
148
+ b.City @sender_city
149
+ b.StateOrProvinceCode sender_state
150
+ b.PostalCode @sender_zip
151
+ b.CountryCode @sender_country || 'US'
152
+ }
153
+ }
154
+ b.Destination { |b|
155
+ b.Contact { |b|
156
+ if @name.to_s.size > 2
157
+ b.PersonName @name
158
+ b.CompanyName @company unless @company.blank?
159
+ elsif @company.to_s.size > 2
160
+ b.PersonName @name unless @name.blank?
161
+ b.CompanyName @company
162
+ else
163
+ raise ShippingError, "Either the name or the company value must be bigger than 2 characters."
164
+ end
165
+ b.Department @department unless @department.blank?
166
+ b.PhoneNumber @phone.gsub(/[^\d]/,"")
167
+ b.PagerNumber @pager.gsub(/[^\d]/,"") if @pager.class == String
168
+ b.FaxNumber @fax.gsub(/[^\d]/,"") if @fax.class == String
169
+ b.tag! :"E-MailAddress", @email
170
+ }
171
+ b.Address { |b|
172
+ b.Line1 @address
173
+ b.Line2 @address2 unless @address2.blank?
174
+ b.City @city
175
+ b.StateOrProvinceCode state
176
+ b.PostalCode @zip
177
+ b.CountryCode @country || 'US'
178
+ }
179
+ }
180
+ b.Payment { |b|
181
+ b.PayorType PaymentTypes[@pay_type] || 'SENDER'
182
+ b.Payor { |b|
183
+ b.AccountNumber @payor_account_number
184
+ b.CountryCode @payor_country_code unless @payor_country_code.blank?
185
+ } unless @payor_account_number.blank?
186
+ }
187
+ b.RMA { |b|
188
+ b.Number @rma_number
189
+ } unless @rma_number.blank?
190
+ b.SpecialServices { |b|
191
+ b.EMailNotification { |b|
192
+ b.ShipAlertOptionalMessage @message
193
+ b.Shipper { |b|
194
+ b.ShipAlert @shipper_ship_alert ? 'true' : 'false'
195
+ b.LanguageCode @shipper_language || 'EN' # FR also available
196
+ }
197
+ b.Recipient { |b|
198
+ b.ShipAlert @recipient_ship_alert ? 'true' : 'false'
199
+ b.LanguageCode @recipient_language || 'EN' # FR also available
200
+ }
201
+ b.Other { |b|
202
+ b.tag! :"E-MailAddress", @other_email
203
+ b.ShipAlert @other_ship_alert ? 'true' : 'false'
204
+ b.LanguageCode @other_language || 'EN' # FR also available
205
+ } unless @other_email.blank?
206
+ }
207
+ } unless @message.blank?
208
+ b.Label { |b|
209
+ b.Type @label_type || '2DCOMMON'
210
+ b.ImageType @image_type || 'PNG'
211
+ }
212
+ }
213
+ get_response @fedex_url
214
+
215
+ begin
216
+ response = Hash.new
217
+ response[:tracking_number] = REXML::XPath.first(@response, "//FDXShipReply/Tracking/TrackingNumber").text
218
+ response[:encoded_image] = REXML::XPath.first(@response, "//FDXShipReply/Labels/OutboundLabel").text
219
+ response[:image] = Tempfile.new("shipping_label")
220
+ response[:image].write Base64.decode64( response[:encoded_image] )
221
+ response[:image].rewind
222
+ rescue
223
+ raise ShippingError, get_error
224
+ end
225
+
226
+ # allows for things like fedex.label.url
227
+ def response.method_missing(name, *args)
228
+ has_key?(name) ? self[name] : super
229
+ end
230
+
231
+ # don't allow people to edit the response
232
+ response.freeze
233
+ end
234
+
235
+ # require 'fileutils'
236
+ # fedex = Shipping::FedEx.new :name => 'John Doe', ... , :sender_zip => 97202
237
+ # label = fedex.email_label
238
+ # puts label.url
239
+ # puts label.tracking_number
240
+ #
241
+ def return_label
99
242
  @required = [:phone, :email, :address, :city, :state, :zip ]
100
243
  @required += [:sender_phone, :sender_email, :sender_address, :sender_city, :sender_state, :sender_zip ]
101
244
  @required += [:fedex_account, :fedex_url, :weight ]
@@ -222,37 +365,18 @@ module Shipping
222
365
  response[:userid] = REXML::XPath.first(@response, "//FDXEmailLabelReply/UserID").text
223
366
  response[:password] = REXML::XPath.first(@response, "//FDXEmailLabelReply/Password").text
224
367
  response[:tracking_number] = REXML::XPath.first(@response, "//FDXEmailLabelReply/Package/TrackingNumber").text
225
- rescue
226
- raise ShippingError, get_error
227
- end
368
+ rescue
369
+ raise ShippingError, get_error
370
+ end
228
371
 
229
- # allows for things like fedex.label.url
230
- def response.method_missing(name, *args)
231
- has_key?(name) ? self[name] : super
232
- end
372
+ # allows for things like fedex.label.url
373
+ def response.method_missing(name, *args)
374
+ has_key?(name) ? self[name] : super
375
+ end
233
376
 
234
- =begin
235
- # Initial work on the screen-grabbing necessary to grab the data of the image
236
-
237
- f = Tempfile.new response.password
238
- uri = URI.parse response.url
239
-
240
- http = Net::HTTP.new uri.host, uri.port
241
- if uri.port == 443
242
- http.use_ssl = true
243
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
244
- end
245
- req = Net::HTTP::Get.new(uri.path)
246
- req.basic_auth response.userid, response.password
247
- res = http.request(req)
248
- f << res.body
249
- f.seek(0)
250
- response[:image] = f
251
- =end
252
-
253
- # don't allow people to edit the response
254
- return response.freeze
255
- end
377
+ # don't allow people to edit the response
378
+ return response.freeze
379
+ end
256
380
 
257
381
  private
258
382
 
@@ -278,6 +402,7 @@ module Shipping
278
402
  b.Packaging PackageTypes[@packaging_type] || 'YOURPACKAGING'
279
403
  b.WeightUnits @weight_units || 'LBS'
280
404
  b.Weight @weight
405
+ b.ListRate true #tells fedex to return list rates as well as discounted rates
281
406
  b.OriginAddress { |b|
282
407
  b.StateOrProvinceCode self.class.state_from_zip(@sender_zip)
283
408
  b.PostalCode @sender_zip
@@ -297,14 +422,14 @@ module Shipping
297
422
  get_response @fedex_url
298
423
  end
299
424
 
300
- def get_error
301
- return if @response.class != REXML::Document
425
+ def get_error
426
+ return if @response.class != REXML::Document
302
427
 
303
- code = REXML::XPath.first(@response, "//Error/Code").text
304
- message = REXML::XPath.first(@response, "//Error/Message").text
428
+ code = REXML::XPath.first(@response, "//Error/Code").text
429
+ message = REXML::XPath.first(@response, "//Error/Message").text
305
430
 
306
- return "Error #{code}: #{message}"
307
- end
431
+ return "Error #{code}: #{message}"
432
+ end
308
433
 
309
434
  # The following type hashes are to allow cross-api data retrieval
310
435
 
@@ -327,30 +452,30 @@ module Shipping
327
452
  "international_ground_service" => "INTERNATIONALGROUND"
328
453
  }
329
454
 
330
- PackageTypes = {
331
- "fedex_envelope" => "FEDEXENVELOPE",
332
- "fedex_pak" => "FEDEXPAK",
333
- "fedex_box" => "FEDEXBOX",
334
- "fedex_tube" => "FEDEXTUBE",
335
- "fedex_10_kg_box" => "FEDEX10KGBOX",
336
- "fedex_25_kg_box" => "FEDEX25KGBOX",
337
- "your_packaging" => "YOURPACKAGING"
338
- }
339
-
340
- DropoffTypes = {
341
- 'regular_pickup' => 'REGULARPICKUP',
342
- 'request_courier' => 'REQUESTCOURIER',
343
- 'dropbox' => 'DROPBOX',
344
- 'business_service_center' => 'BUSINESSSERVICECENTER',
345
- 'station' => 'STATION'
346
- }
347
-
348
- PaymentTypes = {
349
- 'sender' => 'SENDER',
350
- 'recipient' => 'RECIPIENT',
351
- 'third_party' => 'THIRDPARTY',
352
- 'collect' => 'COLLECT'
353
- }
455
+ PackageTypes = {
456
+ "fedex_envelope" => "FEDEXENVELOPE",
457
+ "fedex_pak" => "FEDEXPAK",
458
+ "fedex_box" => "FEDEXBOX",
459
+ "fedex_tube" => "FEDEXTUBE",
460
+ "fedex_10_kg_box" => "FEDEX10KGBOX",
461
+ "fedex_25_kg_box" => "FEDEX25KGBOX",
462
+ "your_packaging" => "YOURPACKAGING"
463
+ }
464
+
465
+ DropoffTypes = {
466
+ 'regular_pickup' => 'REGULARPICKUP',
467
+ 'request_courier' => 'REQUESTCOURIER',
468
+ 'dropbox' => 'DROPBOX',
469
+ 'business_service_center' => 'BUSINESSSERVICECENTER',
470
+ 'station' => 'STATION'
471
+ }
472
+
473
+ PaymentTypes = {
474
+ 'sender' => 'SENDER',
475
+ 'recipient' => 'RECIPIENT',
476
+ 'third_party' => 'THIRDPARTY',
477
+ 'collect' => 'COLLECT'
478
+ }
354
479
 
355
480
 
356
481
  TransactionTypes = {
@@ -5,11 +5,12 @@ class FedExTest < Test::Unit::TestCase
5
5
  end
6
6
 
7
7
  def test_price
8
- assert_equal 4.67, @ship.price
8
+ assert_in_delta 5.00, @ship.price, 1
9
9
  end
10
10
 
11
- def test_base_price
12
- assert_equal 5.07, @ship.base_price
11
+ def test_discount_price
12
+ assert_in_delta 5.00, @ship.discount_price, 1
13
+ assert @ship.discount_price < @ship.price
13
14
  end
14
15
 
15
16
  def test_fails
data/test/ups/ups_test.rb CHANGED
@@ -5,7 +5,7 @@ class UpsTest < Test::Unit::TestCase
5
5
  end
6
6
 
7
7
  def test_price
8
- assert_equal 8.82, @ship.price
8
+ assert_in_delta 8.82, @ship.price, 1
9
9
  end
10
10
 
11
11
  def test_valid_address
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.10
3
3
  specification_version: 1
4
4
  name: shipping
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.2.1
7
- date: 2005-09-15
6
+ version: 1.3.0
7
+ date: 2005-09-24
8
8
  summary: A general shipping module to find out the shipping prices via UPS or FedEx.
9
9
  require_paths:
10
10
  - lib