Permalink
Browse files

Handle case where UPS returns minimal response due to shipment just b…

…eing created
  • Loading branch information...
1 parent 25e9c72 commit bc7785befa8b2249c9711afe382dad4ef2785c51 Jeff Keen committed Apr 8, 2013
Showing with 135 additions and 42 deletions.
  1. +21 −20 lib/trackerific/services/ups.rb
  2. +60 −0 spec/fixtures/ups_no_info_yet_response.xml
  3. +54 −22 spec/lib/trackerific/services/ups_spec.rb
@@ -2,7 +2,7 @@
module Trackerific
require 'httparty'
-
+
# Provides package tracking support for UPS.
class UPS < Trackerific::Service
# setup HTTParty
@@ -13,22 +13,22 @@ class UPS < Trackerific::Service
when 'test','development' then 'https://wwwcie.ups.com/ups.app/xml'
when 'production' then 'https://www.ups.com/ups.app/xml'
end : 'https://www.ups.com/ups.app/xml'
-
+
class << self
# An Array of Regexp that matches valid UPS package IDs
# @return [Array, Regexp] the regular expression
# @api private
def package_id_matchers
[ /^.Z/, /^[HK].{10}$/ ]
- end
+ end
# The required parameters for tracking a UPS package
# @return [Array] the required parameters for tracking a UPS package
# @api private
def required_parameters
[:key, :user_id, :password]
end
end
-
+
# Tracks a UPS package
# @param [String] package_id the package identifier
# @return [Trackerific::Details] the tracking details
@@ -41,6 +41,7 @@ def track_package(package_id)
super
# connect to UPS via HTTParty
http_response = self.class.post('/Track', :body => build_xml_request)
+
# throw any HTTP errors
http_response.error! unless http_response.code == 200
# Check the response for errors, return a Trackerific::Error, or parse
@@ -51,27 +52,27 @@ def track_package(package_id)
else raise Trackerific::Error, "Invalid response code returned from server."
end
end
-
+
protected
-
+
# Parses the response from UPS
# @return [Trackerific::Details]
# @api private
def parse_success_response(http_response)
-
+
# get estimated delivery date
deliveryDateString = http_response['TrackResponse']['Shipment']['ScheduledDeliveryDate']
-
+
if deliveryDateString.present?
deliveryDateYear = deliveryDateString[0..3]
deliveryDateMonth = deliveryDateString[4..5]
deliveryDateDay = deliveryDateString[6..7]
deliveryDate = Date.new(deliveryDateYear.to_i, deliveryDateMonth.to_i, deliveryDateDay.to_i)
end
-
+
# get the activity from the UPS response
activity = http_response['TrackResponse']['Shipment']['Package']['Activity']
-
+
# if there's only one activity in the list, we need to put it in an array
activity = [activity] if activity.is_a? Hash
# UPS does not provide a summary, so we'll just use the last tracking status
@@ -89,19 +90,19 @@ def parse_success_response(http_response)
code = a['Status']['StatusType']['Code']
loc = a['ActivityLocation']['Address'].map {|k,v| v}.join(" ")
address = a['ActivityLocation']['Address']
-
+
events << Trackerific::Event.new(
:date => date,
:code => code,
:description => desc,
- :location => Trackerific::Location.new(:city => address["City"], :state => address["StateProvinceCode"], :country => address["CountryCode"]),
+ :location => Trackerific::Location.new(:city => address["City"], :state => address["StateProvinceCode"], :country => address["CountryCode"])
)
end
-
- origin = http_response['TrackResponse']['Shipment']["Shipper"]["Address"]
- destination = http_response['TrackResponse']['Shipment']["ShipTo"]["Address"]
-
- Trackerific::Details.new(
+
+ origin = http_response['TrackResponse']['Shipment']["Shipper"]["Address"] || {}
+ destination = http_response['TrackResponse']['Shipment']["ShipTo"]["Address"] || {}
+
+ Trackerific::Details.new({
:package_id => @package_id,
:summary => summary,
:origin => Trackerific::Location.new(:address => origin["AddressLine1"], :city => origin["City"], :state => origin["StateProvinceCode"], :country => origin["CountryCode"]),
@@ -110,16 +111,16 @@ def parse_success_response(http_response)
:service_code => http_response["TrackResponse"]["Shipment"]["Service"]["Code"],
:service => http_response["TrackResponse"]["Shipment"]["Service"]["Description"],
:estimated_delivery_date => deliveryDate
- )
+ })
end
-
+
# Parses a UPS tracking response, and returns any errors
# @return [String] the UPS tracking error
# @api private
def parse_error_response(http_response)
http_response['TrackResponse']['Response']['Error']['ErrorDescription']
end
-
+
# Builds the XML request to send to UPS for tracking a package
# @return [String] the XML request
# @api private
@@ -0,0 +1,60 @@
+<?xml version=\"1.0\"?>
+<TrackResponse>
+ <Response>
+ <ResponseStatusCode>1</ResponseStatusCode>
+ <ResponseStatusDescription>Success</ResponseStatusDescription>
+ </Response>
+ <Shipment>
+ <Shipper>
+ <ShipperNumber>62TB77</ShipperNumber>
+ </Shipper>
+ <ShipTo>
+ <Address>
+ <City>SANTA MONICA</City>
+ <StateProvinceCode>CA</StateProvinceCode>
+ <PostalCode>90404</PostalCode>
+ <CountryCode>US</CountryCode>
+ </Address>
+ </ShipTo>
+ <ShipmentWeight>
+ <UnitOfMeasurement>
+ <Code>LBS</Code>
+ </UnitOfMeasurement>
+ <Weight>1.50</Weight>
+ </ShipmentWeight>
+ <Service>
+ <Code>003</Code>
+ <Description>GROUND</Description>
+ </Service>
+ <ShipmentIdentificationNumber>1Z62TB770395011219</ShipmentIdentificationNumber>
+ <PickupDate>20130406</PickupDate>
+ <ScheduledDeliveryDate>20130411</ScheduledDeliveryDate>
+ <Package>
+ <TrackingNumber>1Z62TB770395011219</TrackingNumber>
+ <Activity>
+ <ActivityLocation>
+ <Address>
+ <CountryCode>US</CountryCode>
+ </Address>
+ </ActivityLocation>
+ <Status>
+ <StatusType>
+ <Code>M</Code>
+ <Description>BILLING INFORMATION RECEIVED</Description>
+ </StatusType>
+ <StatusCode>
+ <Code>MP</Code>
+ </StatusCode>
+ </Status>
+ <Date>20130406</Date>
+ <Time>120016</Time>
+ </Activity>
+ <PackageWeight>
+ <UnitOfMeasurement>
+ <Code>LBS</Code>
+ </UnitOfMeasurement>
+ <Weight>1.50</Weight>
+ </PackageWeight>
+ </Package>
+ </Shipment>
+</TrackResponse>
@@ -4,18 +4,18 @@
describe "Trackerific::UPS" do
include Fixtures
-
+
specify("it should descend from Trackerific::Service") {
Trackerific::UPS.superclass.should be Trackerific::Service
}
-
+
describe :required_parameters do
subject { Trackerific::UPS.required_parameters }
it { should include(:key) }
it { should include(:user_id) }
it { should include(:password) }
end
-
+
describe :valid_options do
it "should include required_parameters" do
valid = Trackerific::UPS.valid_options
@@ -24,75 +24,107 @@
end
end
end
-
+
describe :package_id_matchers do
subject { Trackerific::UPS.package_id_matchers }
it("should be an Array of Regexp") { should each { |m| m.should be_a Regexp } }
end
-
+
describe :track_package do
before(:all) do
@package_id = '1Z12345E0291980793'
@ups = Trackerific::UPS.new :key => 'testkey', :user_id => 'testuser', :password => 'secret'
end
-
+
context "with a successful response from the server" do
-
+
before(:all) do
FakeWeb.register_uri(:post, UPS_TRACK_URL, :body => load_fixture(:ups_success_response))
end
-
+
before(:each) do
@tracking = @ups.track_package(@package_id)
end
-
+
subject { @tracking }
it("should return a Trackerific::Details") { should be_a Trackerific::Details }
describe "origin" do
subject { @tracking.origin }
it("should be a location object") { should be_a Trackerific::Location }
end
-
+
describe "destination" do
subject { @tracking.destination }
it("should be a location object") { should be_a Trackerific::Location }
end
-
+
describe "events.length" do
subject { @tracking.events.length }
it { should >= 1 }
end
-
+
describe "service code" do
subject { @tracking.service_code }
it {should == "02"}
end
-
+
describe "service" do
subject { @tracking.service }
it {should == "2ND DAY AIR"}
end
-
+
describe :summary do
subject { @tracking.summary }
it { should_not be_empty }
end
-
+
end
-
+
context "with an error response from the server" do
-
+
before(:all) do
FakeWeb.register_uri(:post, UPS_TRACK_URL, :body => load_fixture(:ups_error_response))
end
-
+
specify { lambda { @ups.track_package("invalid package id") }.should raise_error(Trackerific::Error) }
-
+
end
-
+
+ context "with a successful and very fresh/minimal response from the server" do
+ before(:all) do
+ FakeWeb.register_uri(:post, UPS_TRACK_URL, :body => load_fixture(:ups_no_info_yet_response))
+ end
+ before(:each) do
+ @tracking = @ups.track_package(@package_id)
+ end
+
+ subject { @tracking }
+ it("should return a Trackerific::Details") { should be_a Trackerific::Details }
+
+ describe "origin" do
+ subject { @tracking.origin }
+ it("should be a location object") { should be_a Trackerific::Location }
+ end
+
+ describe "destination" do
+ subject { @tracking.destination }
+ it("should be a location object") { should be_a Trackerific::Location }
+ end
+
+ describe "events.length" do
+ subject { @tracking.events.length }
+ it { should == 1 }
+ end
+
+ describe :summary do
+ subject { @tracking.summary }
+ it { should_not be_empty }
+ end
+ end
+
pending "when server returns corrupted xml"
-
+
end
-
+
end

0 comments on commit bc7785b

Please sign in to comment.