simple_shipping 0.4.6

Sign up to get free protection for your applications and to get access to all the features.
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