shipping 1.2.1 → 1.3.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.
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