simple_shipping 0.4.6

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.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.metrics +6 -0
  4. data/.rspec +4 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.simplecov +43 -0
  8. data/Gemfile +26 -0
  9. data/Gemfile.lock +201 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.markdown +207 -0
  12. data/Rakefile +68 -0
  13. data/VERSION +1 -0
  14. data/coverage/.resultset.json +1579 -0
  15. data/lib/colorized_text.rb +34 -0
  16. data/lib/simple_shipping.rb +27 -0
  17. data/lib/simple_shipping/abstract.rb +11 -0
  18. data/lib/simple_shipping/abstract/builder.rb +47 -0
  19. data/lib/simple_shipping/abstract/client.rb +111 -0
  20. data/lib/simple_shipping/abstract/model.rb +40 -0
  21. data/lib/simple_shipping/abstract/request.rb +27 -0
  22. data/lib/simple_shipping/abstract/response.rb +26 -0
  23. data/lib/simple_shipping/address.rb +22 -0
  24. data/lib/simple_shipping/contact.rb +24 -0
  25. data/lib/simple_shipping/demo.rb +9 -0
  26. data/lib/simple_shipping/demo/base.rb +71 -0
  27. data/lib/simple_shipping/demo/fedex.rb +46 -0
  28. data/lib/simple_shipping/demo/ups.rb +68 -0
  29. data/lib/simple_shipping/exceptions.rb +40 -0
  30. data/lib/simple_shipping/fedex.rb +14 -0
  31. data/lib/simple_shipping/fedex/client.rb +41 -0
  32. data/lib/simple_shipping/fedex/package_builder.rb +24 -0
  33. data/lib/simple_shipping/fedex/party_builder.rb +50 -0
  34. data/lib/simple_shipping/fedex/request.rb +54 -0
  35. data/lib/simple_shipping/fedex/response.rb +5 -0
  36. data/lib/simple_shipping/fedex/shipment_builder.rb +123 -0
  37. data/lib/simple_shipping/fedex/shipment_request.rb +14 -0
  38. data/lib/simple_shipping/fedex/shipment_response.rb +12 -0
  39. data/lib/simple_shipping/package.rb +43 -0
  40. data/lib/simple_shipping/party.rb +21 -0
  41. data/lib/simple_shipping/shipment.rb +42 -0
  42. data/lib/simple_shipping/ups.rb +24 -0
  43. data/lib/simple_shipping/ups/client.rb +46 -0
  44. data/lib/simple_shipping/ups/package_builder.rb +101 -0
  45. data/lib/simple_shipping/ups/party_builder.rb +38 -0
  46. data/lib/simple_shipping/ups/request.rb +27 -0
  47. data/lib/simple_shipping/ups/response.rb +63 -0
  48. data/lib/simple_shipping/ups/ship_accept_request.rb +21 -0
  49. data/lib/simple_shipping/ups/ship_accept_response.rb +6 -0
  50. data/lib/simple_shipping/ups/ship_client.rb +70 -0
  51. data/lib/simple_shipping/ups/ship_confirm_request.rb +22 -0
  52. data/lib/simple_shipping/ups/ship_confirm_response.rb +6 -0
  53. data/lib/simple_shipping/ups/shipment_builder.rb +66 -0
  54. data/lib/simple_shipping/ups/shipment_request.rb +22 -0
  55. data/lib/simple_shipping/ups/shipment_response.rb +6 -0
  56. data/lib/simple_shipping/ups/void_client.rb +50 -0
  57. data/lib/simple_shipping/ups/void_request.rb +42 -0
  58. data/lib/simple_shipping/ups/void_response.rb +6 -0
  59. data/lib/tasks/demo.rake +58 -0
  60. data/script/ups_certification.rb +140 -0
  61. data/simple_shipping.gemspec +168 -0
  62. data/spec/fixtures/fedex_shipment_request.soap.xml.erb +85 -0
  63. data/spec/fixtures/fedex_shipment_response.soap.xml.erb +182 -0
  64. data/spec/fixtures/ups_shipment_request.soap.xml.erb +88 -0
  65. data/spec/fixtures/ups_shipment_response.soap.xml.erb +58 -0
  66. data/spec/fixtures/ups_shipment_response_with_faked_label_data.soap.xml.erb +54 -0
  67. data/spec/fixtures/ups_void_request.soap.xml.erb +29 -0
  68. data/spec/fixtures/ups_void_response.soap.xml.erb +21 -0
  69. data/spec/lib/simple_shipping/address_spec.rb +19 -0
  70. data/spec/lib/simple_shipping/contact_spec.rb +28 -0
  71. data/spec/lib/simple_shipping/exceptions_spec.rb +58 -0
  72. data/spec/lib/simple_shipping/fedex/package_builder_spec.rb +5 -0
  73. data/spec/lib/simple_shipping/fedex/party_builder_spec.rb +5 -0
  74. data/spec/lib/simple_shipping/fedex/response/shipment_reponse_spec.rb +5 -0
  75. data/spec/lib/simple_shipping/fedex/response_spec.rb +5 -0
  76. data/spec/lib/simple_shipping/fedex/shipment_builder_spec.rb +23 -0
  77. data/spec/lib/simple_shipping/package_spec.rb +32 -0
  78. data/spec/lib/simple_shipping/party_spec.rb +18 -0
  79. data/spec/lib/simple_shipping/shipment_spec.rb +35 -0
  80. data/spec/lib/simple_shipping/ups/package_builder_spec.rb +26 -0
  81. data/spec/lib/simple_shipping/ups/party_builder_spec.rb +47 -0
  82. data/spec/lib/simple_shipping/ups/response/shipment_response_spec.rb +5 -0
  83. data/spec/lib/simple_shipping/ups/response_spec.rb +33 -0
  84. data/spec/lib/simple_shipping/ups/shipment_builder_spec.rb +19 -0
  85. data/spec/requests/fedex_spec.rb +47 -0
  86. data/spec/requests/ups_spec.rb +75 -0
  87. data/spec/spec_helper.rb +47 -0
  88. data/spec/support/custom_matchers/basic_matcher.rb +13 -0
  89. data/spec/support/custom_matchers/have_attribute_matcher.rb +22 -0
  90. data/spec/support/custom_matchers/have_default_value_matcher.rb +26 -0
  91. data/spec/support/custom_matchers/have_errors_on_matcher.rb +23 -0
  92. data/spec/support/custom_matchers/validate_inclusion_of_matcher.rb +37 -0
  93. data/spec/support/custom_matchers/validate_presence_of_matcher.rb +24 -0
  94. data/spec/support/custom_matchers/validate_submodel_matcher.rb +44 -0
  95. data/spec/support/shared_behaviours/builders_behaviour.rb +9 -0
  96. data/spec/support/shared_behaviours/responses_behaviour.rb +10 -0
  97. data/tmp/metric_fu/_data/20131210.yml +9964 -0
  98. data/wsdl/fedex/ship_service_v10.wsdl +5566 -0
  99. data/wsdl/ups/Ship.wsdl +120 -0
  100. data/wsdl/ups/Void.wsdl +58 -0
  101. metadata +308 -0
@@ -0,0 +1,34 @@
1
+ # Colorizes text with ASCII colors.
2
+ #
3
+ # @example
4
+ # include ColorizedText
5
+ #
6
+ # puts green "OK" # => green output
7
+ # puts bold "Running... # => bold output
8
+ # puts bold green "OK!!!" # => bold green output
9
+ module ColorizedText
10
+ # Colorize text using ASCII color code
11
+ def colorize(text, code)
12
+ "\033[#{code}m#{text}\033[0m"
13
+ end
14
+
15
+ # :nodoc:
16
+ def yellow(text)
17
+ colorize(text, 33)
18
+ end
19
+
20
+ # :nodoc:
21
+ def green(text)
22
+ colorize(text, 32)
23
+ end
24
+
25
+ # :nodoc:
26
+ def red(text)
27
+ colorize(text, 31)
28
+ end
29
+
30
+ # :nodoc:
31
+ def bold(text)
32
+ colorize(text, 1)
33
+ end
34
+ end
@@ -0,0 +1,27 @@
1
+ require 'active_support/core_ext'
2
+ require 'active_model'
3
+ require 'savon'
4
+ require 'ostruct'
5
+
6
+
7
+ # Namespace for SimpleShipping library.
8
+ module SimpleShipping
9
+ extend ActiveSupport::Autoload
10
+
11
+ # Path to the directory with WDSL files.
12
+ WSDL_DIR = File.expand_path("../../wsdl", __FILE__)
13
+
14
+ autoload :Abstract
15
+ autoload :Address
16
+ autoload :Contact
17
+ autoload :Demo
18
+ autoload :Package
19
+ autoload :Party
20
+ autoload :Shipment
21
+
22
+ autoload :Ups
23
+ autoload :Fedex
24
+ end
25
+
26
+
27
+ require 'simple_shipping/exceptions'
@@ -0,0 +1,11 @@
1
+ # Namespace for the abstract classes that define common interface for all
2
+ # shipping providers.
3
+ module SimpleShipping::Abstract
4
+ extend ActiveSupport::Autoload
5
+
6
+ autoload :Response
7
+ autoload :Model
8
+ autoload :Builder
9
+ autoload :Client
10
+ autoload :Request
11
+ end
@@ -0,0 +1,47 @@
1
+ module SimpleShipping
2
+ # Kind of an abstract class which should be used to create model builders.
3
+ # Model builder "knows" how to represent its model for a its service.
4
+ # This class provides only common skeleton for subclasses.
5
+ class Abstract::Builder
6
+ class_attribute :default_opts
7
+
8
+ # == Parameters:
9
+ # model - kind of {Abstract::Model}
10
+ # opts - hash of options. Every builder can have its own specific set of options
11
+ # == Returns:
12
+ # Hash which can be used by Savon to build a part of SOAP request.
13
+ def self.build(model, opts = {})
14
+ raise(ValidationError.new(model)) unless model.valid?
15
+
16
+ builder = self.new(model, opts)
17
+ builder.validate
18
+ builder.build
19
+ end
20
+
21
+ # Allows to set default option values is subclasses.
22
+ def self.set_default_opts(opts = {})
23
+ self.default_opts = opts
24
+ end
25
+
26
+ # Should be implemented by subclasses. But by default returns empty hash.
27
+ def build; {}; end
28
+
29
+ # Should be implemented by subclass if subclass needs to do some validation.
30
+ def validate; end
31
+
32
+ def initialize(model = nil, opts = {})
33
+ self.default_opts ||= {}
34
+ @opts = default_opts.merge(opts)
35
+ @model = model
36
+ end
37
+ private :initialize
38
+
39
+ # Raises {ValidationError} if option has invalid value.
40
+ def validate_inclusion_of(option, enumeration)
41
+ unless enumeration.has_key?(@opts[option])
42
+ raise ValidationError.new("#{option} has an unavailable value(#{@opts[option]}). Available values are #{enumeration.keys.inspect}")
43
+ end
44
+ end
45
+ private :validate_inclusion_of
46
+ end
47
+ end
@@ -0,0 +1,111 @@
1
+ module SimpleShipping
2
+ # Abstract class which provides common interfaces for the next concrete clients:
3
+ # * {Fedex::Client}
4
+ # * {Ups::Client}
5
+ class Abstract::Client
6
+ class_attribute :required_credentials,
7
+ :wsdl_document,
8
+ :production_address,
9
+ :testing_address
10
+
11
+ # Set credentials which should be validated.
12
+ def self.set_required_credentials(*args)
13
+ self.required_credentials = args
14
+ end
15
+
16
+ # Set the WSDL document used by Savon.
17
+ def self.set_wsdl_document(wsdl_path)
18
+ self.wsdl_document = wsdl_path
19
+ end
20
+
21
+ # Set the production endpoint.
22
+ #
23
+ # @param address [String]
24
+ def self.set_production_address(address)
25
+ self.production_address = address
26
+ end
27
+
28
+ # Set the testing endpoint.
29
+ #
30
+ # @param address [String]
31
+ def self.set_testing_address(address)
32
+ self.testing_address = address
33
+ end
34
+
35
+ # Create an instance of a client.
36
+ # == Parameters:
37
+ # * credentials - a hash with credentials.
38
+ def initialize(options)
39
+ @options = options.dup
40
+ @live = options.delete(:live)
41
+ @debug = options.delete(:debug)
42
+ @debug_path = options.delete(:debug_path)
43
+ credentials = options.delete(:credentials)
44
+
45
+ validate_credentials(credentials)
46
+ @credentials = OpenStruct.new(credentials)
47
+
48
+ @client = Savon.client(client_options(options))
49
+ end
50
+
51
+ # @param [Hash] options Savon client options
52
+ # @return [Hash{Symbol => Object}] Savon client options
53
+ def client_options(options = {})
54
+ endpoint = @live ? self.class.production_address : self.class.testing_address
55
+
56
+ options.symbolize_keys.reverse_merge(
57
+ :wsdl => wsdl_document,
58
+ :endpoint => endpoint
59
+ )
60
+ end
61
+ protected :client_options
62
+
63
+
64
+ # Validate that all required credentials are passed.
65
+ def validate_credentials(credentials)
66
+ credentials.assert_valid_keys(required_credentials)
67
+ missing = required_credentials - credentials.keys
68
+ raise(Error.new "The next credentials are missing for #{self}: #{missing.join(', ')}") unless missing.empty?
69
+ end
70
+ private :validate_credentials
71
+
72
+ # Build the {Shipment shipment} model.
73
+ def create_shipment(shipper, recipient, package, opts = {})
74
+ shipment = SimpleShipping::Shipment.new(
75
+ :shipper => shipper,
76
+ :recipient => recipient,
77
+ :package => package)
78
+ shipment.payor = opts[:payor] if opts[:payor]
79
+ shipment
80
+ end
81
+ private :create_shipment
82
+
83
+ # Write the request information to request.xml.
84
+ #
85
+ # @param soap [Savon::HTTPRequest]
86
+ def log_request(soap)
87
+ log_soap("request", soap)
88
+ end
89
+ private :log_request
90
+
91
+ # Write the response information to response.xml.
92
+ #
93
+ # @param [Savon::Response] soap
94
+ def log_response(soap)
95
+ log_soap("response", soap)
96
+ end
97
+ private :log_response
98
+
99
+ # Write the request/response to .xml file.
100
+ #
101
+ # @param name [String] file name without .xml
102
+ # @param soap [Savon::HTTPRequest, Savon::Response]
103
+ def log_soap(name, soap)
104
+ if @debug
105
+ path = File.join(@debug_path, "#{name}.xml")
106
+ File.open(path, 'w') {|f| f.write soap.to_xml}
107
+ end
108
+ end
109
+ private :log_soap
110
+ end
111
+ end
@@ -0,0 +1,40 @@
1
+ module SimpleShipping
2
+ # Base class for all simple shipping models.
3
+ class Abstract::Model
4
+ include ActiveModel::Validations
5
+
6
+ # hash with default attribute values
7
+ class_attribute :default_values
8
+
9
+ # Define the default values of the attributes which should be set when the model is created.
10
+ def self.set_default_values(values = {})
11
+ self.default_values = values
12
+ end
13
+
14
+ # Add a validation callback to validate the submodel. Submodel is a model
15
+ # which belongs to current model.
16
+ # == Parameters:
17
+ # * name - name of attribute which is submodel
18
+ # * opts - hash with only on key :as. It should points to class of submodel.
19
+ def self.validates_submodel(name, opts = {})
20
+ validate do
21
+ klass = opts[:as] || raise(":as option should be passed")
22
+ submodel = send(name)
23
+ if !submodel.instance_of?(klass)
24
+ errors.add(name.to_sym, "must be an instance of #{klass.inspect}")
25
+ elsif submodel.invalid?
26
+ errors.add(name.to_sym, "is invalid")
27
+ end
28
+ end
29
+ end
30
+
31
+ # Create a new model and set the default and passed values.
32
+ def initialize(values = {})
33
+ values.reverse_merge!(default_values || {})
34
+ values.each do |attribute, value|
35
+ raise("Undefined attribute `#{attribute}` for #{self}") unless respond_to?(attribute)
36
+ send("#{attribute}=", value)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ module SimpleShipping
2
+ # Base class for request builders. Every service has its own implementation.
3
+ class Abstract::Request
4
+ attr_reader :credentials
5
+ attr_reader :type
6
+
7
+ def initialize(credentials)
8
+ @credentials = credentials
9
+ end
10
+
11
+ # Wrap the Savon response with specific response for shipment provider.
12
+ #
13
+ # @param savon_response [Savon::Response]
14
+ #
15
+ # @return [SimpleShipping::Abstract::Response]
16
+ def response(savon_response)
17
+ response_class.new(savon_response)
18
+ end
19
+
20
+ # Response class to wrap Savon response.
21
+ #
22
+ # @return [Class]
23
+ def response_class
24
+ Response
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ module SimpleShipping
2
+ # Represents a response returned by the remote service for a request initiated
3
+ # by {SimpleShipping::Abstract::Client client}.
4
+ #
5
+ # An abstract class which provides a common interface.
6
+ # In the real world, you will deal with its subclasses:
7
+ # * {SimpleShipping::Fedex::Response}
8
+ # * {SimpleShipping::Ups::Response}
9
+ #
10
+ # == Example:
11
+ # response = client.request(shipper, recipient, package)
12
+ # response.response # => #<Savon::SOAP::Response ...>
13
+ class Abstract::Response
14
+ attr_reader :response
15
+
16
+ def initialize(savon_resp = nil)
17
+ @response = savon_resp
18
+ end
19
+
20
+ # Fetch the value of an XML attribute at the path specified as an array.
21
+ # of node names
22
+ def value_of(*path)
23
+ @response.to_array(*path).first
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ module SimpleShipping
2
+ # Represents an address information of {SimpleShipping::Party party}.
3
+ # == Attributes:
4
+ # * _country_code_
5
+ # * _state_code_
6
+ # * _city_
7
+ # * _street_line_
8
+ # * _street_line_2_
9
+ # * _street_line_3_
10
+ # * _postal_code_
11
+ class Address < Abstract::Model
12
+ attr_accessor :country_code,
13
+ :state_code,
14
+ :city,
15
+ :street_line,
16
+ :street_line_2,
17
+ :street_line_3,
18
+ :postal_code
19
+
20
+ validates_presence_of :country_code, :state_code, :city, :street_line, :postal_code
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ module SimpleShipping
2
+ # Represents the contact information of the {SimpleShipping::Party party} who takes
3
+ # part in shipment process.
4
+ #
5
+ # == Attributes
6
+ # * _person_name_ (optional if company_name is provided)
7
+ # * _company_name_ (optional if person_name is provided)
8
+ # * _phone_number_
9
+ # * _email_ (optional)
10
+ class Contact < Abstract::Model
11
+ attr_accessor :person_name,
12
+ :company_name,
13
+ :phone_number,
14
+ :email
15
+
16
+ validates_presence_of :phone_number
17
+ validate :validate_name
18
+
19
+ # Validate presence of person or company name.
20
+ def validate_name
21
+ errors.add(:abstract, "person_name or company_name must be present") unless (person_name || company_name)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,9 @@
1
+ # Namespace for demo rake tasks used to test real remote requests by sending
2
+ # test requests to verify credentials. Not intended for runtime use.
3
+ class SimpleShipping::Demo
4
+ extend ActiveSupport::Autoload
5
+
6
+ autoload :Fedex
7
+ autoload :Ups
8
+ autoload :Base
9
+ end
@@ -0,0 +1,71 @@
1
+ # Base class for UPS and FedEx demos.
2
+ class SimpleShipping::Demo::Base
3
+ attr_reader :options
4
+
5
+ # Build the shipper address with random attributes.
6
+ #
7
+ # @return [SimpleShipping::Address]
8
+ def shipper_address
9
+ @shipper_address ||= SimpleShipping::Address.new(
10
+ :country_code => 'US',
11
+ :state_code => 'TX',
12
+ :city => 'Texas',
13
+ :street_line => 'SN2000 Test Meter 8',
14
+ :postal_code => '73301'
15
+ )
16
+ end
17
+
18
+ # Build the shipper contact object.
19
+ #
20
+ # @return [SimpleShipping::Contact]
21
+ def shipper_contact
22
+ @shipper_contact ||= SimpleShipping::Contact.new(
23
+ :person_name => 'Mister Someone',
24
+ :phone_number => '1234567890'
25
+ )
26
+ end
27
+
28
+ # Build the shipper object.
29
+ #
30
+ # @return [SimpleShipping::Party]
31
+ def shipper
32
+ @shipper ||= SimpleShipping::Party.new(
33
+ :address => shipper_address,
34
+ :contact => shipper_contact,
35
+ :account_number => options[:account_number]
36
+ )
37
+ end
38
+
39
+ # Build the recipient address with random attributes.
40
+ #
41
+ # @return [SimpleShipping::Address]
42
+ def recipient_address
43
+ @recipient_address ||= SimpleShipping::Address.new(
44
+ :country_code => 'US',
45
+ :state_code => 'MN',
46
+ :city => 'Minneapolis',
47
+ :street_line => 'Nightmare Avenue 13',
48
+ :postal_code => '55411'
49
+ )
50
+ end
51
+
52
+ # Build the recipient contact.
53
+ #
54
+ # @return [SimpleShipping::Contact]
55
+ def recipient_contact
56
+ @recipient_contact ||= SimpleShipping::Contact.new(
57
+ :person_name => "John Recipient Smith",
58
+ :phone_number => "1234567890"
59
+ )
60
+ end
61
+
62
+ # Build the recipient object.
63
+ #
64
+ # @return [SimpleShipping::Party]
65
+ def recipient
66
+ @recipient ||= SimpleShipping::Party.new(
67
+ :address => recipient_address,
68
+ :contact => recipient_contact
69
+ )
70
+ end
71
+ end