diff --git a/.gitignore b/.gitignore index 4203e5c..b33710a 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ *.DS_Store *.keep coverage + +/spec/vcr/*.yml diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..83e16f8 --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--color +--require spec_helper diff --git a/Gemfile b/Gemfile index e1378ac..5255f98 100644 --- a/Gemfile +++ b/Gemfile @@ -23,6 +23,10 @@ gem 'sdoc', '~> 0.4.0', group: :doc gem 'pg' +gem 'active_shipping' + +gem 'rails_12factor', group: :production + # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' @@ -41,6 +45,11 @@ group :development, :test do gem 'sqlite3' gem 'pry-rails' gem 'factory_girl_rails' + gem 'vcr' +end + +group :test do + gem 'webmock' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 90797c7..d790ee4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -20,6 +20,14 @@ GEM erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) + active_shipping (1.6.5) + active_utils (~> 3.2.0) + activesupport (>= 3.2, < 5.0.0) + nokogiri (>= 1.6) + quantified (~> 1.0.1) + active_utils (3.2.0) + activesupport (>= 3.2) + i18n activejob (4.2.5) activesupport (= 4.2.5) globalid (>= 0.3.0) @@ -36,6 +44,7 @@ GEM minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) + addressable (2.4.0) arel (6.0.3) better_errors (2.1.1) coderay (>= 1.0.0) @@ -54,6 +63,8 @@ GEM execjs coffee-script-source (1.10.0) concurrent-ruby (1.0.0) + crack (0.4.3) + safe_yaml (~> 1.0.0) debug_inspector (0.0.2) diff-lcs (1.2.5) docile (1.1.5) @@ -70,6 +81,7 @@ GEM railties (>= 3.0.0) globalid (0.3.6) activesupport (>= 4.1.0) + hashdiff (0.2.3) i18n (0.7.0) jbuilder (2.4.0) activesupport (>= 3.0.0, < 5.1) @@ -97,6 +109,7 @@ GEM slop (~> 3.4) pry-rails (0.3.4) pry (>= 0.9.10) + quantified (1.0.1) rack (1.6.4) rack-test (0.6.3) rack (>= 1.0) @@ -119,6 +132,11 @@ GEM rails-deprecated_sanitizer (>= 1.0.1) rails-html-sanitizer (1.0.2) loofah (~> 2.0) + rails_12factor (0.0.3) + rails_serve_static_assets + rails_stdout_logging + rails_serve_static_assets (0.0.4) + rails_stdout_logging (0.0.4) railties (4.2.5) actionpack (= 4.2.5) activesupport (= 4.2.5) @@ -144,6 +162,7 @@ GEM rspec-mocks (~> 3.4.0) rspec-support (~> 3.4.0) rspec-support (3.4.1) + safe_yaml (1.0.4) sass (3.4.21) sass-rails (5.0.4) railties (>= 4.0.0, < 5.0) @@ -179,16 +198,22 @@ GEM uglifier (2.7.2) execjs (>= 0.3.0) json (>= 1.8.0) + vcr (3.0.1) web-console (2.2.1) activemodel (>= 4.0) binding_of_caller (>= 0.7.2) railties (>= 4.0) sprockets-rails (>= 2.0, < 4.0) + webmock (1.22.6) + addressable (>= 2.3.6) + crack (>= 0.3.2) + hashdiff PLATFORMS ruby DEPENDENCIES + active_shipping better_errors binding_of_caller byebug @@ -200,6 +225,7 @@ DEPENDENCIES pg pry-rails rails (= 4.2.5) + rails_12factor rspec-rails sass-rails (~> 5.0) sdoc (~> 0.4.0) @@ -208,7 +234,9 @@ DEPENDENCIES sqlite3 turbolinks uglifier (>= 1.3.0) + vcr web-console (~> 2.0) + webmock BUNDLED WITH 1.11.2 diff --git a/app/assets/javascripts/estimates.coffee b/app/assets/javascripts/estimates.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/estimates.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/stylesheets/estimates.scss b/app/assets/stylesheets/estimates.scss new file mode 100644 index 0000000..602fef5 --- /dev/null +++ b/app/assets/stylesheets/estimates.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the estimates controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/estimates_controller.rb b/app/controllers/estimates_controller.rb new file mode 100644 index 0000000..8fdcb47 --- /dev/null +++ b/app/controllers/estimates_controller.rb @@ -0,0 +1,84 @@ +require 'active_shipping' +require './lib/ups_services' + +class EstimatesController < ApplicationController + # create constants for origin object, package grams_or_ounces and package dimensions, and country + # also create carrier constants which is a ups carrier and a fedex carrier? + UPS = ActiveShipping::UPS.new(login: ENV['UPS_LOGIN'], password: ENV['UPS_PASSWORD'], key: ENV['UPS_KEY']) + + USPS = ActiveShipping::USPS.new(login: ENV['USPS_LOGIN']) + + # assume all packages are being sent within the US + COUNTRY = "US" + # assume all packages are originating from Ada's betsy distribution center + ORIGIN = ActiveShipping::Location.new(country: COUNTRY, state: 'WA', city: 'Seattle', zip: '98101') + # assume all packages have the same ounces and dimensions, for now + OUNCES = 120 + DIMENSIONS = [15, 10, 4.5] + + def estimate + # only one package for all products in order, for now + package = [ActiveShipping::Package.new(OUNCES, DIMENSIONS, units: :imperial, value: params[:value])] + # destination address info comes from query params provided from betsy app's API call + destination = ActiveShipping::Location.new(country: COUNTRY, state: params[:destination][:state], city: params[:destination][:city], zip: params[:destination][:zip]) + # method call + begin + ups_estimates = UpsServices.transform_codes_into_names(get_ups_estimates(ORIGIN, destination, package)) + rescue + return render :json => [], :status => :bad_request + end + # method call + begin + usps_estimates = get_usps_estimates(ORIGIN, destination, package) + rescue + return render :json => [], :status => :bad_request + end + # response includes rates and dates from both UPS and USPS + response = {"UPS Service Options" => ups_estimates, "USPS Service Options" => usps_estimates } + render :json => response.as_json, :status => :ok + end + +private + + def get_ups_estimates(origin, destination, package) + rates_response = UPS.find_rates(ORIGIN, destination, package) + delivery_dates_response = UPS.get_delivery_date_estimates(ORIGIN, destination, package) + # shipping_estimate will be a hash made up of service_code keys + # each key points to a hash which contains the cost and delivery date estimate + # for the corresponding service type. + shipping_estimate = Hash.new + rates_response.rates.each do |rate| + # skip this rate if service code is nil + next if rate.service_code.nil? + # otherwise, create a key for the corresponding service code + # the value is a hash containing the corresponding cost + shipping_estimate["#{rate.service_code}"] = { "cost" => "#{rate.total_price}" } + end + delivery_dates_response.delivery_estimates.each do |estimate| + # skip this estimate if service code is nil + next if estimate.service_code.nil? + # otherwise check to see if the service code already exists in the hash + if shipping_estimate["#{estimate.service_code}"].nil? + # if the service code doesn't yet exist in the hash, create a key for it + # with the value being a hash corresponding to the date + shipping_estimate["#{estimate.service_code}"] = { "date" => "#{estimate.date}" } + else + # if it already exists, merge the date corresponding to this estimate into the the + # hash associated with this service code key + shipping_estimate["#{estimate.service_code}"].merge!({ "date" => "#{estimate.date}"}) + end + end + return shipping_estimate + end + + def get_usps_estimates(origin, destination, package) + rates_response = USPS.find_rates(ORIGIN, destination, package) + usps_rates = rates_response.rates + shipping_estimate = Hash.new + usps_rates.each do |rate| + next if rate.service_name.nil? + shipping_estimate["#{rate.service_name}"] = { "cost" => "#{rate.price}", "date" => "#{rate.delivery_date}"} + end + return shipping_estimate + end +end diff --git a/app/helpers/estimates_helper.rb b/app/helpers/estimates_helper.rb new file mode 100644 index 0000000..019d7a6 --- /dev/null +++ b/app/helpers/estimates_helper.rb @@ -0,0 +1,2 @@ +module EstimatesHelper +end diff --git a/config/application.rb b/config/application.rb index 84b564d..2e04936 100644 --- a/config/application.rb +++ b/config/application.rb @@ -9,6 +9,7 @@ require "action_mailer/railtie" require "action_view/railtie" require "sprockets/railtie" +# require "dotenv" # require "rails/test_unit/railtie" # Require the gems listed in Gemfile, including any gems diff --git a/config/routes.rb b/config/routes.rb index 3f66539..4a8dda8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ Rails.application.routes.draw do + get '/' => 'estimates#estimate' # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..4dfbb16 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,16 @@ +# encoding: UTF-8 +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 0) do + +end diff --git a/lib/ups_services.rb b/lib/ups_services.rb new file mode 100644 index 0000000..e1596a5 --- /dev/null +++ b/lib/ups_services.rb @@ -0,0 +1,29 @@ +class UpsServices + SERVICES = { + "01" => "UPS Next Day Air", + "02" => "UPS Second Day Air", + "03" => "UPS Ground", + "07" => "UPS Worldwide Express", + "08" => "UPS Worldwide Expedited", + "11" => "UPS Standard", + "12" => "UPS Three-Day Select", + "13" => "UPS Next Day Air Saver", + "14" => "UPS Next Day Air Early A.M.", + "54" => "UPS Worldwide Express Plus", + "59" => "UPS Second Day Air A.M.", + "65" => "UPS Saver", + "82" => "UPS Today Standard", + "83" => "UPS Today Dedicated Courier", + "84" => "UPS Today Intercity", + "85" => "UPS Today Express", + "86" => "UPS Today Express Saver" + } + + def self.transform_codes_into_names(hash) + mappings = Hash.new + hash.keys.each do |key| + mappings[key] = SERVICES[key] + end + Hash[hash.map {|k, v| [mappings[k], v] }] + end +end diff --git a/spec/controllers/estimates_controller_spec.rb b/spec/controllers/estimates_controller_spec.rb new file mode 100644 index 0000000..1bd6c22 --- /dev/null +++ b/spec/controllers/estimates_controller_spec.rb @@ -0,0 +1,34 @@ +require 'rails_helper' +require 'support/vcr_setup' + +RSpec.describe EstimatesController, type: :controller do + let!(:package) do + { + value: 2000 + } + end + + let!(:destination) do + { + destination: { + state: "CA", + city: "San Francisco", + zip: 94104 + } + } + end + + describe "GET 'estimate'" do + + it "is successful", :vcr do + get :estimate, destination.merge(package) + expect(response.response_code).to eq 200 + end + + # describe 'get_usps_estimates' do + # it 'returns a hash with service names and cost' do + # expect(response.body).to eq "" + # end + # end + end +end diff --git a/spec/helpers/estimates_helper_spec.rb b/spec/helpers/estimates_helper_spec.rb new file mode 100644 index 0000000..0cba7f7 --- /dev/null +++ b/spec/helpers/estimates_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the EstimatesHelper. For example: +# +# describe EstimatesHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe EstimatesHelper, type: :helper do + # pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb new file mode 100644 index 0000000..6f1ab14 --- /dev/null +++ b/spec/rails_helper.rb @@ -0,0 +1,57 @@ +# This file is copied to spec/ when you run 'rails generate rspec:install' +ENV['RAILS_ENV'] ||= 'test' +require File.expand_path('../../config/environment', __FILE__) +# Prevent database truncation if the environment is production +abort("The Rails environment is running in production mode!") if Rails.env.production? +require 'spec_helper' +require 'rspec/rails' +# Add additional requires below this line. Rails is not loaded until this point! + +# Requires supporting ruby files with custom matchers and macros, etc, in +# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are +# run as spec files by default. This means that files in spec/support that end +# in _spec.rb will both be required and run as specs, causing the specs to be +# run twice. It is recommended that you do not name files matching this glob to +# end with _spec.rb. You can configure this pattern with the --pattern +# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. +# +# The following line is provided for convenience purposes. It has the downside +# of increasing the boot-up time by auto-requiring all files in the support +# directory. Alternatively, in the individual `*_spec.rb` files, manually +# require only the support files necessary. +# +# Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } + +# Checks for pending migration and applies them before tests are run. +# If you are not using ActiveRecord, you can remove this line. +ActiveRecord::Migration.maintain_test_schema! + +RSpec.configure do |config| + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + config.fixture_path = "#{::Rails.root}/spec/fixtures" + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, remove the following line or assign false + # instead of true. + config.use_transactional_fixtures = true + + # RSpec Rails can automatically mix in different behaviours to your tests + # based on their file location, for example enabling you to call `get` and + # `post` in specs under `spec/controllers`. + # + # You can disable this behaviour by removing the line below, and instead + # explicitly tag your specs with their type, e.g.: + # + # RSpec.describe UsersController, :type => :controller do + # # ... + # end + # + # The different available types are documented in the features, such as in + # https://relishapp.com/rspec/rspec-rails/docs + config.infer_spec_type_from_file_location! + + # Filter lines from Rails gems in backtraces. + config.filter_rails_from_backtrace! + # arbitrary gems may also be filtered via: + # config.filter_gems_from_backtrace("gem name") +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..fdf3f19 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,104 @@ +# This file was generated by the `rails generate rspec:install` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# The `.rspec` file also contains a few flags that are not defaults but that +# users commonly want. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration + +require 'simplecov' +SimpleCov.start do + add_filter '/spec/' +end + +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # These two settings work together to allow you to limit a spec run + # to individual examples or groups you care about by tagging them with + # `:focus` metadata. When nothing is tagged with `:focus`, all examples + # get run. + config.filter_run :focus + config.run_all_when_everything_filtered = true + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = 'doc' + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end + +end +shared_context 'vcr helpers', :vcr do + def only_when_recording + yield if VCR.current_cassette.recording? + end +end diff --git a/spec/support/vcr_setup.rb b/spec/support/vcr_setup.rb new file mode 100644 index 0000000..d277646 --- /dev/null +++ b/spec/support/vcr_setup.rb @@ -0,0 +1,7 @@ +VCR.configure do |c| + #the directory where your cassettes will be saved + c.cassette_library_dir = 'spec/vcr' + # your HTTP request service. + c.hook_into :webmock + c.configure_rspec_metadata! +end diff --git a/spec/vcr/EstimatesController/GET_estimate_/is_successful.yml b/spec/vcr/EstimatesController/GET_estimate_/is_successful.yml new file mode 100644 index 0000000..074b5b5 --- /dev/null +++ b/spec/vcr/EstimatesController/GET_estimate_/is_successful.yml @@ -0,0 +1,252 @@ +--- +http_interactions: +- request: + method: post + uri: https://onlinetools.ups.com/ups.app/xml/Rate + body: + encoding: UTF-8 + string: | + + + 6D02C98DCD321654 + meighanr + Fuzzyfionas123 + + + + + Rate + Shop + + + 01 + + + 01 + + + +
+ Seattle + WA + 98101 + US + true +
+
+ +
+ San Francisco + CA + 94104 + US + true +
+
+ + + 02 + + + + IN + + 15.0 + 10.0 + 4.5 + + + + LBS + + 7.5 + + + +
+
+ headers: + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Date: + - Fri, 22 Jan 2016 22:47:13 GMT + Server: + - Apache + X-Frame-Options: + - SAMEORIGIN + Pragma: + - no-cache + Content-Length: + - '7200' + X-Content-Type-Options: + - nosniff + Content-Type: + - application/xml + body: + encoding: UTF-8 + string: |- + + 1Success03Your invoice may vary from the displayed reference ratesLBS8.0USD15.36USD0.00USD15.36USD15.36USD0.00USD15.367.5LBS8.012Your invoice may vary from the displayed reference ratesLBS8.0USD29.97USD0.00USD29.973USD29.97USD0.00USD29.977.5LBS8.002Your invoice may vary from the displayed reference ratesLBS8.0USD42.89USD0.00USD42.892USD42.89USD0.00USD42.897.5LBS8.013Your invoice may vary from the displayed reference ratesLBS8.0USD95.35USD0.00USD95.351USD95.35USD0.00USD95.357.5LBS8.014Your invoice may vary from the displayed reference ratesLBS8.0USD135.48USD0.00USD135.4818:00 A.M.USD135.48USD0.00USD135.487.5LBS8.001Your invoice may vary from the displayed reference ratesLBS8.0USD104.21USD0.00USD104.21110:30 A.M.USD104.21USD0.00USD104.217.5LBS8.0 + http_version: + recorded_at: Fri, 22 Jan 2016 22:47:14 GMT +- request: + method: post + uri: https://onlinetools.ups.com/ups.app/xml/TimeInTransit + body: + encoding: UTF-8 + string: | + + + 6D02C98DCD321654 + meighanr + Fuzzyfionas123 + + + + + TimeInTransit + + + + Seattle + WA + US + 98101 + true + + + + + San Francisco + CA + US + 94104 + true + + + + + KGS + + 3.402 + + + USD + 2000 + + 20160122 + + headers: + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Date: + - Fri, 22 Jan 2016 22:47:14 GMT + Server: + - Apache + X-Frame-Options: + - SAMEORIGIN + Pragma: + - no-cache + Content-Length: + - '4779' + X-Content-Type-Options: + - nosniff + Content-Type: + - application/xml + body: + encoding: UTF-8 + string: 1Success2016-01-22SEATTLEWAUNITED + STATESUS98101SAN + FRANCISCOCAUNITED + STATESUS94104KGS3.4USD2000.00Services + listed as guaranteed are backed by a money-back guarantee for transportation + charges only. UPS guarantees the day of delivery for every ground package + you ship to any address within all 50 states and Puerto Rico. See Terms and + Conditions in the Service Guide for details.1DMSUPS + Next Day Air Early (Saturday Delivery)N12016-01-2217:00:002016-01-23SAT16:00:001Saturday + Delivery is available for an additional charge.1DASUPS + Next Day Air (Saturday Delivery)N12016-01-2217:00:002016-01-23SAT16:00:001Saturday + Delivery is available for an additional charge.1DMUPS + Next Day Air EarlyN12016-01-2217:00:002016-01-25MON16:00:001DAUPS + Next Day AirN12016-01-2217:00:002016-01-25MON16:00:001DPUPS + Next Day Air SaverN12016-01-2217:00:002016-01-25MON16:00:002DAUPS + 2nd Day AirN22016-01-2217:00:002016-01-26TUE16:00:00GNDUPS + GroundN22016-01-2217:00:002016-01-26TUE16:00:0035 + http_version: + recorded_at: Fri, 22 Jan 2016 22:47:15 GMT +- request: + method: get + uri: http://production.shippingapis.com/ShippingAPI.dll?API=RateV4&XML=%3C?xml%20version=%221.0%22?%3E%0A%3CRateV4Request%20USERID=%22677JADED7283%22%3E%0A%20%20%3CPackage%20ID=%220%22%3E%0A%20%20%20%20%3CService%3EALL%3C/Service%3E%0A%20%20%20%20%3CFirstClassMailType/%3E%0A%20%20%20%20%3CZipOrigination%3E98101%3C/ZipOrigination%3E%0A%20%20%20%20%3CZipDestination%3E94104%3C/ZipDestination%3E%0A%20%20%20%20%3CPounds%3E0%3C/Pounds%3E%0A%20%20%20%20%3COunces%3E120.0%3C/Ounces%3E%0A%20%20%20%20%3CContainer%3ERECTANGULAR%3C/Container%3E%0A%20%20%20%20%3CSize%3ELARGE%3C/Size%3E%0A%20%20%20%20%3CWidth%3E10.00%3C/Width%3E%0A%20%20%20%20%3CLength%3E15.00%3C/Length%3E%0A%20%20%20%20%3CHeight%3E4.50%3C/Height%3E%0A%20%20%20%20%3CGirth%3E29.00%3C/Girth%3E%0A%20%20%20%20%3CMachinable%3ETRUE%3C/Machinable%3E%0A%20%20%3C/Package%3E%0A%3C/RateV4Request%3E%0A + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + X-Backside-Transport: + - OK OK + Cache-Control: + - private + Content-Type: + - text/xml + Server: + - Microsoft-IIS/7.5 + X-Aspnet-Version: + - 2.0.50727 + X-Powered-By: + - ASP.NET + Date: + - Fri, 22 Jan 2016 22:47:14 GMT + X-Client-Ip: + - 56.0.33.9 + X-Global-Transaction-Id: + - '3076197101' + Access-Control-Allow-Origin: + - "*" + Connection: + - Keep-Alive + Cteonnt-Length: + - '1072' + X-Frame-Options: + - SAMEORIGIN + Content-Length: + - '393' + body: + encoding: ASCII-8BIT + string: |- + + 98101941040120.0LARGETRUE5Priority Mail Express 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt;65.50Priority Mail Express 1-Day&lt;sup&gt;&#8482;&lt;/sup&gt; Hold For Pickup65.50Priority Mail 2-Day&lt;sup&gt;&#8482;&lt;/sup&gt;22.65USPS Retail Ground&lt;sup&gt;&#8482;&lt;/sup&gt;19.27Media Mail Parcel6.22Library Mail Parcel5.95 + http_version: + recorded_at: Fri, 22 Jan 2016 22:47:15 GMT +recorded_with: VCR 3.0.1