endicia_ruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +23 -0
- data/Rakefile +6 -0
- data/config/endicia.yml +10 -0
- data/endicia_ruby.gemspec +30 -0
- data/lib/endicia_ruby/label.rb +53 -0
- data/lib/endicia_ruby/label_response.rb +46 -0
- data/lib/endicia_ruby/refund.rb +53 -0
- data/lib/endicia_ruby/refund_response.rb +39 -0
- data/lib/endicia_ruby/request.rb +168 -0
- data/lib/endicia_ruby/version.rb +3 -0
- data/lib/endicia_ruby.rb +7 -0
- data/spec/label_response_spec.rb +84 -0
- data/spec/label_spec.rb +64 -0
- data/spec/refund_spec.rb +63 -0
- data/spec/spec_helper.rb +55 -0
- data/spec/vcr-cassettes/Endicia_Label/_request_label/handles_a_nonsense_request.yml +124 -0
- data/spec/vcr-cassettes/Endicia_Label/_request_label/makes_a_request_and_properly_parses_the_result.yml +164 -0
- data/spec/vcr-cassettes/Endicia_Refund/_request_refund/returns_info_about_each_tracking_number.yml +217 -0
- metadata +202 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f2de8392ca2c214fdb8b8a14f2b05bc4521349c8
|
4
|
+
data.tar.gz: 5becd6d57d1652bb94d369618d51703305597d4c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0d2f26229ecb9af9e764dda8f9fe3d63b7cf99f7047f655d56b5ee4af8b65121d003d5945a40901c7c539bfbcd6312b453eedb094a63901bc4541bfdff1ce938
|
7
|
+
data.tar.gz: 1a4d7e2fc91b8afc9662f042fb07afdbd581692b836a13556e7eea45dbc4eb9b8c5410b5132212e07694ccd991e331f5ab8f88bcf84555731e7b0e1cacf60a4f
|
data/.gitignore
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
endicia_ruby
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.1.0
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Dave Copeland
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# endicia_ruby
|
2
|
+
|
3
|
+
**Do not introduce Stitch Fix secret info to this repository** We would like this to be open-sourced, but are keeping it private until that can be done. So please avoid adding any Stitch Fix repo keys, API credentials, or anything like that here.
|
4
|
+
|
5
|
+
Simple wrapper around Endicia's label generation and refunding API. This currently supports only those features that Stitch Fix requires.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'endicia_ruby'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install endicia_ruby
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
TBD
|
data/Rakefile
ADDED
data/config/endicia.yml
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
development: &development
|
2
|
+
credentials:
|
3
|
+
AccountID: <%= ENV['ENDICIA_ACCOUNT_ID'] || '2500334' %>
|
4
|
+
RequesterID: <%= ENV['ENDICIA_REQUESTER_ID'] || 'lxxx' %>
|
5
|
+
PassPhrase: <%= ENV['ENDICIA_PASSPHRASE'] || 'endicia.com' %>
|
6
|
+
environment: <%= ENV['ENDICIA_ENV'] || 'test' %>
|
7
|
+
|
8
|
+
test:
|
9
|
+
<<: *development
|
10
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'endicia_ruby/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "endicia_ruby"
|
8
|
+
spec.version = Endicia::VERSION
|
9
|
+
spec.authors = ["Jon Dean", "Dave Copeland"]
|
10
|
+
spec.email = ["jon@stitchfix.com","dave@stitchfix.com"]
|
11
|
+
spec.summary = %q{Wrapper around Endicia's APIs}
|
12
|
+
spec.description = %q{Wrapper around Endicia's APIs}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "nokogiri"
|
22
|
+
spec.add_dependency "activesupport"
|
23
|
+
spec.add_dependency "httparty"
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "rspec"
|
27
|
+
spec.add_development_dependency "webmock"
|
28
|
+
spec.add_development_dependency "vcr"
|
29
|
+
spec.add_development_dependency "faker"
|
30
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
require_relative 'label_response'
|
4
|
+
require_relative 'request'
|
5
|
+
|
6
|
+
module Endicia
|
7
|
+
class Label < Request
|
8
|
+
|
9
|
+
# values is a Hash of the API values you wish to send to Endicia, nested as the documentation describes
|
10
|
+
# options is a Hash of other options (see default_options() for available options)
|
11
|
+
def request_label(values = {}, options = {})
|
12
|
+
# Build the options for this method with passed in values overriding defaults
|
13
|
+
root_node_attrs = root_attributes.merge!(values[:attrs] || {})
|
14
|
+
# Include API credentials in the values we use to build the XML
|
15
|
+
node_values = @options[:credentials].merge!(values[:nodes] || {})
|
16
|
+
|
17
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
18
|
+
xml.LabelRequest(root_node_attrs) do
|
19
|
+
recursive_build_xml_nodes!(xml, node_values)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
xml_body = builder.to_xml
|
23
|
+
|
24
|
+
# Log the XML of the request if desired
|
25
|
+
log("ENDICIA LABEL REQUEST: #{format_xml_for_logging(xml_body)}") if options[:log_requests]
|
26
|
+
|
27
|
+
# Post the XML to the appropriate API URL
|
28
|
+
url = "#{api_url_base}/GetPostageLabelXML"
|
29
|
+
|
30
|
+
# Endicia's test server has an invalid certificate o_O
|
31
|
+
raw_response = self.class.post(url, body: "labelRequestXML=#{xml_body}", verify: environment != :test)
|
32
|
+
|
33
|
+
# Log the XML of the response if desired
|
34
|
+
log("ENDICIA LABEL RESPONSE: #{format_xml_for_logging(raw_response.body)}") if options[:log_responses]
|
35
|
+
|
36
|
+
# Build a nice response object and return it
|
37
|
+
Endicia::LabelResponse.new(raw_response).tap do |the_label|
|
38
|
+
the_label.request_body = xml_body.to_s
|
39
|
+
the_label.request_url = url
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def root_attributes
|
46
|
+
{
|
47
|
+
Test: api_test_mode_value,
|
48
|
+
LabelType: 'Default',
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'active_support/core_ext/string/inflections'
|
2
|
+
|
3
|
+
module Endicia
|
4
|
+
class LabelResponse
|
5
|
+
attr_accessor :image,
|
6
|
+
:status,
|
7
|
+
:tracking_number,
|
8
|
+
:final_postage,
|
9
|
+
:transaction_date_time,
|
10
|
+
:transaction_id,
|
11
|
+
:postmark_date,
|
12
|
+
:postage_balance,
|
13
|
+
:pic,
|
14
|
+
:error_message,
|
15
|
+
:reference_id,
|
16
|
+
:reference_id2,
|
17
|
+
:reference_id3,
|
18
|
+
:reference_id4,
|
19
|
+
:requester_id,
|
20
|
+
:cost_center,
|
21
|
+
:request_body,
|
22
|
+
:request_url,
|
23
|
+
:response_body
|
24
|
+
def initialize(result)
|
25
|
+
self.response_body = filter_response_body(result.body.dup)
|
26
|
+
data = result["LabelRequestResponse"] || {}
|
27
|
+
data.each do |k, v|
|
28
|
+
if k == 'Base64LabelImage'
|
29
|
+
k = "image"
|
30
|
+
elsif k == 'Label'
|
31
|
+
# when customs form is included in xml, Endicia returns the image in a different format
|
32
|
+
k = "image"
|
33
|
+
v = v["Image"]["__content__"]
|
34
|
+
end
|
35
|
+
setter = :"#{k.tableize.singularize}="
|
36
|
+
send(setter, v) if !k['xmlns'] && respond_to?(setter)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def filter_response_body(string)
|
42
|
+
# Strip image data for readability:
|
43
|
+
string.sub(/<.*Image>.+<\/.*Image>/, "<Image>[data]</Image>")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'nokogiri'
|
3
|
+
require_relative 'request'
|
4
|
+
require_relative 'refund_response'
|
5
|
+
|
6
|
+
module Endicia
|
7
|
+
class Refund < Request
|
8
|
+
|
9
|
+
# Request a refund for the given tracking number(s)
|
10
|
+
#
|
11
|
+
# tracking_numbers can be an array of strings or a single string
|
12
|
+
#
|
13
|
+
# Returns an Endicia::RefundResponse
|
14
|
+
#
|
15
|
+
def request_refund(tracking_numbers, options = {})
|
16
|
+
# Build the options for this method with passed in values overriding defaults
|
17
|
+
options.reverse_merge!(default_options)
|
18
|
+
|
19
|
+
# If we didn't get an array of tracking numbers make it one for simplicity
|
20
|
+
tracking_numbers = tracking_numbers.is_a?(Array) ? tracking_numbers : [tracking_numbers]
|
21
|
+
|
22
|
+
# Build the XML document
|
23
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
24
|
+
xml.RefundRequest do
|
25
|
+
# Add the credentials
|
26
|
+
recursive_build_xml_nodes!(xml, @options[:credentials])
|
27
|
+
# Add the value for Test since this API is different and wants it as a node and not attribute, apparently
|
28
|
+
xml.Test(api_test_mode_value)
|
29
|
+
# Add all tracking numbers
|
30
|
+
xml.RefundList do |xml|
|
31
|
+
tracking_numbers.collect do |tracking_number|
|
32
|
+
xml.PICNumber(tracking_number)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
xml_body = builder.to_xml
|
38
|
+
|
39
|
+
# Log the XML of the request if desired
|
40
|
+
log("ENDICIA REFUND REQUEST: #{format_xml_for_logging(xml_body)}") if options[:log_requests]
|
41
|
+
|
42
|
+
params = { method: 'RefundRequest', XMLInput: URI.encode(xml_body) }
|
43
|
+
raw_response = self.class.get(els_service_url(params))
|
44
|
+
|
45
|
+
# Log the XML of the response if desired
|
46
|
+
log("ENDICIA REFUND RESPONSE: #{format_xml_for_logging(raw_response.body)}") if options[:log_responses]
|
47
|
+
|
48
|
+
Endicia::RefundResponse.new(raw_response)
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'nokogiri'
|
3
|
+
|
4
|
+
module Endicia
|
5
|
+
class RefundResponse
|
6
|
+
|
7
|
+
attr_accessor :success, # boolean: true if all tracking numbers are approved or false otherwise
|
8
|
+
# (Probably better to use tracking_numbers to check each individually)
|
9
|
+
:form_number, # Form Number for refunded label
|
10
|
+
:raw_response, # The XML response as a string
|
11
|
+
:parsed_response, # A Nokogiri document of the XML response
|
12
|
+
:tracking_numbers # An array with information about all requested tracking numbers
|
13
|
+
# Each entry is an OpenStruct with:
|
14
|
+
# pic_number: '123456789', # the tracking number you requested
|
15
|
+
# approved: true, # or false
|
16
|
+
# message: "the message" # message describing success or failure
|
17
|
+
|
18
|
+
def initialize(response)
|
19
|
+
@success = true
|
20
|
+
@tracking_numbers = []
|
21
|
+
@raw_response = response.body
|
22
|
+
|
23
|
+
@parsed_response = Nokogiri::XML(response.body)
|
24
|
+
refund_response = @parsed_response.xpath('/RefundResponse')
|
25
|
+
@form_number = refund_response.at('FormNumber').content
|
26
|
+
|
27
|
+
refund_list = refund_response.at('RefundList')
|
28
|
+
|
29
|
+
refund_list.xpath('//PICNumber').each do |refund_node|
|
30
|
+
approved = refund_node.at('IsApproved').content.try(:strip).try(:upcase) == "YES"
|
31
|
+
@tracking_numbers << OpenStruct.new(pic_number: refund_node.children.first.text.try(:strip),
|
32
|
+
approved: approved,
|
33
|
+
message: refund_node.at('ErrorMsg').content.try(:strip))
|
34
|
+
@success = @success && approved
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'active_support/core_ext/hash/keys'
|
3
|
+
require 'active_support/core_ext/hash/reverse_merge'
|
4
|
+
require 'active_support/core_ext/object/try'
|
5
|
+
require 'active_support/core_ext/object/blank'
|
6
|
+
|
7
|
+
# Hack fix because Endicia sends response back without protocol in xmlns uri
|
8
|
+
module HTTParty
|
9
|
+
class Request
|
10
|
+
alias_method :parse_response_without_hack, :parse_response
|
11
|
+
def parse_response(body)
|
12
|
+
parse_response_without_hack(
|
13
|
+
body.sub(/xmlns=("|')(.*envmgr.com|.*endicia.com)/i, 'xmlns=\1https://\2'))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module Endicia
|
19
|
+
class Request
|
20
|
+
|
21
|
+
include HTTParty
|
22
|
+
|
23
|
+
ENDICIA_API_HOSTS = {
|
24
|
+
test: 'https://www.envmgr.com',
|
25
|
+
sandbox: 'https://elstestserver.endicia.com',
|
26
|
+
production: 'https://LabelServer.Endicia.com',
|
27
|
+
}
|
28
|
+
|
29
|
+
# Pass a hash of options with:
|
30
|
+
# {
|
31
|
+
# credentials: {
|
32
|
+
# AccountID: '2500334',
|
33
|
+
# RequesterID: 'lxxx',
|
34
|
+
# PassPhrase: 'endicia.com',
|
35
|
+
# },
|
36
|
+
# environment: 'test' # or 'production' or 'sandbox'
|
37
|
+
# }
|
38
|
+
#
|
39
|
+
# Or if using Rails, create an endicia.yml in {Rails.root}/config/ in this format:
|
40
|
+
# development: &development
|
41
|
+
# credentials:
|
42
|
+
# AccountID: youraccountid
|
43
|
+
# RequesterID: lxxx
|
44
|
+
# PassPhrase: endicia.com
|
45
|
+
# environment: sandbox
|
46
|
+
#
|
47
|
+
# test:
|
48
|
+
# <<: *development
|
49
|
+
#
|
50
|
+
# staging:
|
51
|
+
# <<: *development
|
52
|
+
#
|
53
|
+
# production:
|
54
|
+
# credentials:
|
55
|
+
# AccountID: youraccountid
|
56
|
+
# RequesterID: lsfx
|
57
|
+
# PassPhrase: yourpassword
|
58
|
+
# environment: production
|
59
|
+
#
|
60
|
+
# If both an endicia.yml file is present and a hash of options is passed, any values in the hash take precedence
|
61
|
+
def initialize(options = {})
|
62
|
+
@options = load_options_from_config_file.deep_merge(options)
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
# Separated so you can easily stub in tests with something like the following (in rspec):
|
68
|
+
# Endicia::Request.any_instance.stub(:environment).and_return(:sandbox)
|
69
|
+
# Endicia::Label.request_label(values)
|
70
|
+
# Note: since api_url_base is memoized you want to do this before you make the first API call on that object
|
71
|
+
def environment
|
72
|
+
@options[:environment].try(:to_sym)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Most requests use this url
|
76
|
+
def api_host
|
77
|
+
if ENDICIA_API_HOSTS.key?(environment)
|
78
|
+
ENDICIA_API_HOSTS[environment]
|
79
|
+
else
|
80
|
+
raise "Invalid environment value: '#{environment}'"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def api_url_base
|
85
|
+
@api_url_base ||= "#{api_host}/LabelService/EwsLabelService.asmx"
|
86
|
+
end
|
87
|
+
|
88
|
+
# Some requests use the ELS service url. This URL is used for requests that
|
89
|
+
# can accept GET, and have params passed via URL instead of a POST body.
|
90
|
+
# Pass a hash of params to have them converted to a &key=value string and
|
91
|
+
# appended to the URL.
|
92
|
+
def els_service_url(params = {})
|
93
|
+
params = params.to_a.map { |i| "#{i[0]}=#{i[1]}"}.join('&')
|
94
|
+
"http://www.endicia.com/ELS/ELSServices.cfc?wsdl&#{params}"
|
95
|
+
end
|
96
|
+
|
97
|
+
def default_options
|
98
|
+
{
|
99
|
+
log_requests: false,
|
100
|
+
log_responses: false,
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
# Build nodes for the given xml builder recursively from a Hash
|
105
|
+
def recursive_build_xml_nodes!(xml, nodes)
|
106
|
+
nodes.each do |key, value|
|
107
|
+
node_name = key.to_s.sub(/^./,&:upcase) # convert "fooBar" to "FooBar"
|
108
|
+
case value
|
109
|
+
when Hash
|
110
|
+
xml.send(node_name) do
|
111
|
+
recursive_build_xml_nodes!(xml, value)
|
112
|
+
end
|
113
|
+
when Array
|
114
|
+
xml.send(node_name) do
|
115
|
+
value.each do |v|
|
116
|
+
recursive_build_xml_nodes!(xml, v)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
else
|
120
|
+
xml.send(node_name, value)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# The Test attribute for the root node of the XML request
|
126
|
+
# Looks like:
|
127
|
+
# <LabelRequest Test="YES" ...>
|
128
|
+
# or:
|
129
|
+
# <LabelRequest Test="NO" ...>
|
130
|
+
def api_test_mode_value
|
131
|
+
@options[:environment] == 'production' ? 'NO' : 'YES'
|
132
|
+
end
|
133
|
+
|
134
|
+
# If using Rails, load default config options from {Rails.root}/config/endicia.yml
|
135
|
+
def load_options_from_config_file
|
136
|
+
configuration_values = {}
|
137
|
+
if defined?(Rails)
|
138
|
+
config_file = File.join(Rails.root, 'config', 'endicia.yml')
|
139
|
+
if File.exist?(config_file)
|
140
|
+
configuration_values = YAML.load(ERB.new(File.read(config_file)).result)[Rails.env]
|
141
|
+
recursive_symbolize_keys!(configuration_values)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
configuration_values
|
145
|
+
end
|
146
|
+
|
147
|
+
# Used by load_options_from_config_file
|
148
|
+
def recursive_symbolize_keys! hash
|
149
|
+
hash.symbolize_keys!
|
150
|
+
hash.values.select{|v| v.is_a? Hash}.each{|h| recursive_symbolize_keys!(h)}
|
151
|
+
end
|
152
|
+
|
153
|
+
# Make a one line string with image data stripped for use in logging
|
154
|
+
def format_xml_for_logging(xml_string)
|
155
|
+
xml_string.to_s.gsub("\r\n", '').gsub("\n", '').gsub("\t", '').gsub("\s{2,}", ' ').sub(/<.*Image>.*<\/.*Image>/, '<Image>[data]</Image>')
|
156
|
+
end
|
157
|
+
|
158
|
+
# Helper for logging messages via Rails.logger if available or just puts if not using Rails
|
159
|
+
def log(message, level: :info)
|
160
|
+
if defined?(Rails)
|
161
|
+
Rails.logger.send(level, message)
|
162
|
+
else
|
163
|
+
puts message
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
data/lib/endicia_ruby.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Endicia::LabelResponse do
|
4
|
+
describe "initialize" do
|
5
|
+
let(:result) {
|
6
|
+
OpenStruct.new(
|
7
|
+
"LabelRequestResponse" => {
|
8
|
+
image_key => image_value,
|
9
|
+
"Status" => Faker::Lorem.sentence,
|
10
|
+
"TrackingNumber" => Faker::Lorem.sentence,
|
11
|
+
"FinalPostage" => Faker::Lorem.sentence,
|
12
|
+
"TransactionDateTime" => Faker::Lorem.sentence,
|
13
|
+
"TransactionId" => Faker::Lorem.sentence,
|
14
|
+
"PostmarkDate" => Faker::Lorem.sentence,
|
15
|
+
"PostageBalance" => Faker::Lorem.sentence,
|
16
|
+
"Pic" => Faker::Lorem.sentence,
|
17
|
+
"ErrorMessage" => Faker::Lorem.sentence,
|
18
|
+
"ReferenceId" => Faker::Lorem.sentence,
|
19
|
+
"ReferenceId2" => Faker::Lorem.sentence,
|
20
|
+
"ReferenceId3" => Faker::Lorem.sentence,
|
21
|
+
"ReferenceId4" => Faker::Lorem.sentence,
|
22
|
+
"RequesterId" => Faker::Lorem.sentence,
|
23
|
+
"CostCenter" => Faker::Lorem.sentence,
|
24
|
+
"RequestBody" => Faker::Lorem.sentence,
|
25
|
+
"RequestUrl" => Faker::Lorem.sentence,
|
26
|
+
"Ignored" => Faker::Lorem.sentence,
|
27
|
+
},
|
28
|
+
body: "<Image>ksadjhfalskjfhalsjfhaslfjhasdljkf</Image>"
|
29
|
+
)
|
30
|
+
}
|
31
|
+
let(:expected_image_value) { Faker::Lorem.sentence }
|
32
|
+
let(:image_key) { "Base64LabelImage" }
|
33
|
+
let(:image_value) { expected_image_value }
|
34
|
+
|
35
|
+
subject(:response) { described_class.new(result) }
|
36
|
+
|
37
|
+
it "turns the awful Enterprise keys into snake-case and sets 'em up appropriate" do
|
38
|
+
expect(response.status).to eq(result["LabelRequestResponse"]["Status"])
|
39
|
+
expect(response.tracking_number).to eq(result["LabelRequestResponse"]["TrackingNumber"])
|
40
|
+
expect(response.final_postage).to eq(result["LabelRequestResponse"]["FinalPostage"])
|
41
|
+
expect(response.transaction_date_time).to eq(result["LabelRequestResponse"]["TransactionDateTime"])
|
42
|
+
expect(response.transaction_id).to eq(result["LabelRequestResponse"]["TransactionId"])
|
43
|
+
expect(response.postmark_date).to eq(result["LabelRequestResponse"]["PostmarkDate"])
|
44
|
+
expect(response.postage_balance).to eq(result["LabelRequestResponse"]["PostageBalance"])
|
45
|
+
expect(response.pic).to eq(result["LabelRequestResponse"]["Pic"])
|
46
|
+
expect(response.error_message).to eq(result["LabelRequestResponse"]["ErrorMessage"])
|
47
|
+
expect(response.reference_id).to eq(result["LabelRequestResponse"]["ReferenceId"])
|
48
|
+
expect(response.reference_id2).to eq(result["LabelRequestResponse"]["ReferenceId2"])
|
49
|
+
expect(response.reference_id3).to eq(result["LabelRequestResponse"]["ReferenceId3"])
|
50
|
+
expect(response.reference_id4).to eq(result["LabelRequestResponse"]["ReferenceId4"])
|
51
|
+
expect(response.requester_id).to eq(result["LabelRequestResponse"]["RequesterId"])
|
52
|
+
expect(response.cost_center).to eq(result["LabelRequestResponse"]["CostCenter"])
|
53
|
+
expect(response.request_body).to eq(result["LabelRequestResponse"]["RequestBody"])
|
54
|
+
expect(response.request_url).to eq(result["LabelRequestResponse"]["RequestUrl"])
|
55
|
+
end
|
56
|
+
|
57
|
+
it "masks data in the body" do
|
58
|
+
expect(response.response_body).to eq("<Image>[data]</Image>")
|
59
|
+
end
|
60
|
+
|
61
|
+
context "for typical case" do
|
62
|
+
let(:image_key) { "Base64LabelImage" }
|
63
|
+
let(:image_value) { expected_image_value }
|
64
|
+
|
65
|
+
it "translates Base64LabelImage to 'image'" do
|
66
|
+
expect(response.image).to eq(expected_image_value)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "for customs form case" do
|
71
|
+
let(:image_key) { "Label" }
|
72
|
+
let(:image_value) {
|
73
|
+
{
|
74
|
+
"Image" => {
|
75
|
+
"__content__" => expected_image_value
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
79
|
+
it "translates Base64LabelImage to 'image'" do
|
80
|
+
expect(response.image).to eq(expected_image_value)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/spec/label_spec.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
describe Endicia::Label do
|
4
|
+
subject(:label) { described_class.new }
|
5
|
+
|
6
|
+
describe "#request_label", vcr: { record: :new_episodes } do
|
7
|
+
let(:values) {
|
8
|
+
{
|
9
|
+
ToName: Faker::Name.name,
|
10
|
+
ToCompany: Faker::Company.name,
|
11
|
+
ToPostalCode: "90210",
|
12
|
+
ToAddress1: Faker::Address.street_address,
|
13
|
+
ToAddress2: Faker::Address.secondary_address,
|
14
|
+
ToCity: Faker::Address.city,
|
15
|
+
ToState: "CA",
|
16
|
+
PartnerTransactionID: 12345,
|
17
|
+
PartnerCustomerID: 45678,
|
18
|
+
MailClass: "Priority",
|
19
|
+
MailpieceShape: "Parcel",
|
20
|
+
ToZIP4: '1234',
|
21
|
+
WeightOz: "32", # 2 pounds
|
22
|
+
MailpieceDimensions: {
|
23
|
+
Length: "12",
|
24
|
+
Width: "16",
|
25
|
+
Height: "18",
|
26
|
+
},
|
27
|
+
FromCompany: Faker::Company.name,
|
28
|
+
ReturnAddress1: Faker::Address.street_address,
|
29
|
+
ReturnAddress2: Faker::Address.secondary_address,
|
30
|
+
FromCity: Faker::Address.city,
|
31
|
+
FromState: "DC",
|
32
|
+
FromPostalCode: "20005",
|
33
|
+
FromZIP4: "1234",
|
34
|
+
FromPhone: "2025551212",
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
it "makes a request and properly parses the result" do
|
39
|
+
response = label.request_label({ nodes: values, attrs: {} }, { log_requests: true, log_responses: true })
|
40
|
+
expect(response.status).to eq("0")
|
41
|
+
expect(response.tracking_number).to_not be_blank
|
42
|
+
expect(response.final_postage).to match(/^\d+\.\d+/)
|
43
|
+
expect(response.transaction_date_time).to match(/^\d{14}$/)
|
44
|
+
expect(response.transaction_id).to_not be_blank
|
45
|
+
expect(response.postmark_date).to match(/^\d{8}$/)
|
46
|
+
expect(response.postage_balance).to match(/^\d+\.\d+/)
|
47
|
+
expect(response.pic).to_not be_blank
|
48
|
+
expect(response.error_message).to be_blank
|
49
|
+
expect(response.reference_id).to be_blank
|
50
|
+
expect(response.reference_id2).to be_blank
|
51
|
+
expect(response.reference_id3).to be_blank
|
52
|
+
expect(response.reference_id4).to be_blank
|
53
|
+
expect(response.requester_id).to_not be_blank
|
54
|
+
expect(response.cost_center).to_not be_blank
|
55
|
+
expect(response.request_url).to_not be_blank
|
56
|
+
expect(response.request_body).to_not be_blank
|
57
|
+
end
|
58
|
+
|
59
|
+
it "handles a nonsense request" do
|
60
|
+
response = label.request_label({ nodes: {}, attrs: {} }, { log_requests: true, log_responses: true })
|
61
|
+
expect(response.status).to_not eq("0")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|