postmaster 1.0.0

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.
@@ -0,0 +1,20 @@
1
+ module Postmaster
2
+ class PostmasterError < StandardError
3
+ attr_reader :message
4
+ attr_reader :http_status
5
+ attr_reader :http_body
6
+ attr_reader :json_body
7
+
8
+ def initialize(message=nil, http_status=nil, http_body=nil, json_body=nil)
9
+ @message = message
10
+ @http_status = http_status
11
+ @http_body = http_body
12
+ @json_body = json_body
13
+ end
14
+
15
+ def to_s
16
+ status_string = @http_status.nil? ? "" : "(Status #{@http_status}) "
17
+ "#{status_string}#{@message}"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,21 @@
1
+ module Postmaster
2
+ module JSON
3
+ if MultiJson.respond_to?(:dump)
4
+ def self.dump(*args)
5
+ MultiJson.dump(*args)
6
+ end
7
+
8
+ def self.load(*args)
9
+ MultiJson.load(*args)
10
+ end
11
+ else
12
+ def self.dump(*args)
13
+ MultiJson.encode(*args)
14
+ end
15
+
16
+ def self.load(*args)
17
+ MultiJson.decode(*args)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ module Postmaster
2
+
3
+ class Package < APIResource
4
+ end
5
+
6
+ end
@@ -0,0 +1,172 @@
1
+ module Postmaster
2
+ class PostmasterObject
3
+ include Enumerable
4
+
5
+ # The default :id method is deprecated and isn't useful to us
6
+ if method_defined?(:id)
7
+ undef :id
8
+ end
9
+
10
+ def initialize(id=nil)
11
+ @values = {}
12
+ # This really belongs in APIResource, but not putting it there allows us
13
+ # to have a unified inspect method
14
+ @unsaved_values = Set.new
15
+ @transient_values = Set.new
16
+ self.id = id if id
17
+ end
18
+
19
+ def self.construct_from(values)
20
+ obj = self.new(values[:id])
21
+ obj.refresh_from(values)
22
+ obj
23
+ end
24
+
25
+ def to_s(*args)
26
+ Postmaster::JSON.dump(@values, :pretty => true)
27
+ end
28
+
29
+ def inspect()
30
+ id_string = (self.respond_to?(:id) && !self.id.nil?) ? " id=#{self.id}" : ""
31
+ "#<#{self.class}:0x#{self.object_id.to_s(16)}#{id_string}> JSON: " + Postmaster::JSON.dump(@values, :pretty => true)
32
+ end
33
+
34
+ def refresh_from(values)
35
+ # which keys should be converted to Postmaster_Objects
36
+ obj_keys = {
37
+ 'Postmaster::Shipment.to' => Postmaster::Address,
38
+ 'Postmaster::Shipment.from_' => Postmaster::Address,
39
+ 'Postmaster::Shipment.package' => Postmaster::Package,
40
+ 'Postmaster::AddressProposal.address' => Postmaster::Address,
41
+ #'Postmaster::Tracking.last_update' => 'DateTime',
42
+ #'Postmaster::TrackingHistory.timestamp' => 'DateTime'
43
+ }
44
+
45
+ # which keys should be converted to list of Postmaster_Objects
46
+ obj_list_keys = {
47
+ 'Postmaster::AddressValidation.addresses' => Postmaster::Address,
48
+ 'Postmaster::Shipment.packages' => Postmaster::Package,
49
+ 'Postmaster::Tracking.history' => Postmaster::TrackingHistory
50
+ }
51
+
52
+ removed = Set.new(@values.keys - values.keys)
53
+ added = Set.new(values.keys - @values.keys)
54
+ # Wipe old state before setting new. This is useful for e.g. updating a
55
+ # customer, where there is no persistent card parameter. Mark those values
56
+ # which don't persist as transient
57
+
58
+ instance_eval do
59
+ remove_accessors(removed)
60
+ add_accessors(added)
61
+ end
62
+ removed.each do |k|
63
+ @values.delete(k)
64
+ end
65
+ values.each do |k, v|
66
+ full_key = self.class.name + "." + k.to_s
67
+ if obj_keys.has_key?(full_key)
68
+ klass = obj_keys[full_key]
69
+ @values[k] = klass.construct_from(v)
70
+ elsif obj_list_keys.has_key?(full_key)
71
+ klass = obj_list_keys[full_key]
72
+ @values[k] = []
73
+ v.each do |i|
74
+ @values[k].push(klass.construct_from(i))
75
+ end
76
+ else
77
+ @values[k] = v
78
+ end
79
+ end
80
+ end
81
+
82
+ def [](k)
83
+ k = k.to_sym if k.kind_of?(String)
84
+ @values[k]
85
+ end
86
+
87
+ def []=(k, v)
88
+ k = k.to_sym if k.kind_of?(String)
89
+ send(:"#{k}=", v)
90
+ end
91
+
92
+ def has_key?(k)
93
+ k = k.to_sym if k.kind_of?(String)
94
+ @values.has_key?(k)
95
+ end
96
+
97
+ def delete(k)
98
+ k = k.to_sym if k.kind_of?(String)
99
+ @values.delete(k)
100
+ remove_accessors([k])
101
+ end
102
+
103
+ def keys
104
+ @values.keys
105
+ end
106
+
107
+ def values
108
+ @values.values
109
+ end
110
+
111
+ def to_json(*a)
112
+ Postmaster::JSON.dump(@values)
113
+ end
114
+
115
+ def to_hash
116
+ result = @values.clone
117
+ result.each do |k, v|
118
+ if v.kind_of? Postmaster::PostmasterObject
119
+ result[k] = v.to_hash
120
+ elsif v.kind_of? Array and v[0].kind_of? Postmaster::PostmasterObject
121
+ result[k] = v.map { |i| i.to_hash }
122
+ end
123
+ end
124
+ result
125
+ end
126
+
127
+ def each(&blk)
128
+ @values.each(&blk)
129
+ end
130
+
131
+ protected
132
+
133
+ def metaclass
134
+ class << self; self; end
135
+ end
136
+
137
+ def remove_accessors(keys)
138
+ metaclass.instance_eval do
139
+ keys.each do |k|
140
+ k_eq = :"#{k}="
141
+ remove_method(k) if method_defined?(k)
142
+ remove_method(k_eq) if method_defined?(k_eq)
143
+ end
144
+ end
145
+ end
146
+
147
+ def add_accessors(keys)
148
+ metaclass.instance_eval do
149
+ keys.each do |k|
150
+ k_eq = :"#{k}="
151
+ define_method(k) { @values[k] }
152
+ define_method(k_eq) do |v|
153
+ @values[k] = v
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ def method_missing(name, *args)
160
+ if name.to_s.end_with?('=')
161
+ attr = name.to_s[0...-1].to_sym
162
+ @values[attr] = args[0]
163
+ add_accessors([attr])
164
+ return
165
+ else
166
+ return @values[name] if @values.has_key?(name)
167
+ end
168
+
169
+ super
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,21 @@
1
+ module Postmaster
2
+
3
+ class Shipment < APIResource
4
+ include Postmaster::APIOperations::Create
5
+ include Postmaster::APIOperations::List
6
+
7
+ def track
8
+ response = Postmaster.request(:get, url('track'))
9
+ if response[:results].nil?
10
+ return nil
11
+ end
12
+ response[:results].map { |i| Postmaster::Tracking.construct_from(i) }
13
+ end
14
+
15
+ def void
16
+ response = Postmaster.request(:post, url('void'))
17
+ refresh_from({})
18
+ response[:message] == 'OK'
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,9 @@
1
+ module Postmaster
2
+
3
+ class Tracking < APIResource
4
+ end
5
+
6
+ class TrackingHistory < APIResource
7
+ end
8
+
9
+ end
@@ -0,0 +1,68 @@
1
+ module Postmaster
2
+ module Util
3
+
4
+ def self.objects_to_ids(h)
5
+ case h
6
+ when APIResource
7
+ h.id
8
+ when Hash
9
+ res = {}
10
+ h.each { |k, v| res[k] = objects_to_ids(v) unless v.nil? }
11
+ res
12
+ when Array
13
+ h.map { |v| objects_to_ids(v) }
14
+ else
15
+ h
16
+ end
17
+ end
18
+
19
+ def self.symbolize_names(object)
20
+ case object
21
+ when Hash
22
+ new = {}
23
+ object.each do |key, value|
24
+ key = (key.to_sym rescue key) || key
25
+ new[key] = symbolize_names(value)
26
+ end
27
+ new
28
+ when Array
29
+ object.map { |value| symbolize_names(value) }
30
+ else
31
+ object
32
+ end
33
+ end
34
+
35
+ def self.url_encode(key)
36
+ URI.escape(key.to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
37
+ end
38
+
39
+ def self.flatten_params(params, parent_key=nil)
40
+ result = []
41
+ params.each do |key, value|
42
+ calculated_key = parent_key ? "#{parent_key}[#{url_encode(key)}]" : url_encode(key)
43
+ if value.is_a?(Hash)
44
+ result += flatten_params(value, calculated_key)
45
+ elsif value.is_a?(Array)
46
+ result += flatten_params_array(value, calculated_key)
47
+ else
48
+ result << [calculated_key, value]
49
+ end
50
+ end
51
+ result
52
+ end
53
+
54
+ def self.flatten_params_array(value, calculated_key)
55
+ result = []
56
+ value.each do |elem|
57
+ if elem.is_a?(Hash)
58
+ result += flatten_params(elem, calculated_key)
59
+ elsif elem.is_a?(Array)
60
+ result += flatten_params_array(elem, calculated_key)
61
+ else
62
+ result << ["#{calculated_key}[]", elem]
63
+ end
64
+ end
65
+ result
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,3 @@
1
+ module Postmaster
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,26 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
2
+
3
+ require 'postmaster/version'
4
+
5
+ spec = Gem::Specification.new do |s|
6
+ s.name = 'postmaster'
7
+ s.version = Postmaster::VERSION
8
+ s.summary = 'Library for postmaster.io service'
9
+ s.description = 'Postmaster takes the pain out of sending shipments via UPS, Fedex, and USPS. Save money before you ship, while you ship, and after you ship. See https://postmaster.io for details.'
10
+ s.authors = ['Postmaster']
11
+ s.email = ['support@postmaster.io']
12
+ s.homepage = 'https://postmaster.io'
13
+ s.require_paths = %w{lib}
14
+
15
+ s.add_dependency('rest-client', '~> 1.4')
16
+ s.add_dependency('multi_json', '>= 1.0.4', '< 2')
17
+
18
+ s.add_development_dependency('mocha')
19
+ s.add_development_dependency('shoulda')
20
+ s.add_development_dependency('test-unit')
21
+ s.add_development_dependency('rake')
22
+
23
+ s.files = `git ls-files`.split("\n")
24
+ s.test_files = `git ls-files -- test/*`.split("\n")
25
+ s.require_paths = ['lib']
26
+ end
@@ -0,0 +1,57 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'test/unit'
3
+ require 'postmaster'
4
+ require 'rubygems'
5
+ require 'shoulda'
6
+ require 'mocha'
7
+ require 'rest-client'
8
+ require 'cgi'
9
+ require 'uri'
10
+
11
+ class TestAddressRuby < Test::Unit::TestCase
12
+ include Mocha
13
+
14
+ sample_address =
15
+
16
+ context "Address" do
17
+
18
+ should "be valid" do
19
+ result = Postmaster::AddressValidation.validate({
20
+ :company => "Asls",
21
+ :contact => "Joe Smith",
22
+ :line1 => "1110 Algarita Ave",
23
+ :city => "Austin",
24
+ :state => "TX",
25
+ :zip_code => "78704",
26
+ :country => "US",
27
+ })
28
+
29
+ assert_instance_of(Postmaster::AddressValidation, result);
30
+ assert(result.keys.include?(:status))
31
+ assert_kind_of(Array, result[:addresses])
32
+ assert(!result[:addresses].empty?)
33
+
34
+ address = result[:addresses][0]
35
+ assert_instance_of(Postmaster::Address, address);
36
+ assert(address.keys.include?(:zip_code))
37
+ assert_equal("78704", address[:zip_code])
38
+ end
39
+
40
+ should "be invalid" do
41
+ result = Postmaster::AddressValidation.validate({
42
+ :company => "Asls",
43
+ :contact => "Joe Smith",
44
+ :line1 => "007 Nowhere Ave",
45
+ :city => "Austin",
46
+ :state => "TX",
47
+ :zip_code => "00001",
48
+ :country => "US",
49
+ })
50
+
51
+ assert_instance_of(Postmaster::AddressValidation, result);
52
+ assert(result.keys.include?(:status))
53
+ assert_equal("WRONG_ADDRESS", result[:status])
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,63 @@
1
+ require 'stringio'
2
+ require 'test/unit'
3
+ require 'postmaster'
4
+ require 'mocha'
5
+ include Mocha
6
+
7
+ #monkeypatch request methods
8
+ module Postmaster
9
+ @mock_rest_client = nil
10
+
11
+ def self.mock_rest_client=(mock_client)
12
+ @mock_rest_client = mock_client
13
+ end
14
+
15
+ def self.api_key
16
+ ENV['PM_API_KEY'] || @@api_key
17
+ end
18
+
19
+ def self.api_base
20
+ ENV['PM_API_HOST'] || @@api_base
21
+ end
22
+
23
+ # remeber original execute before monkey-patching it
24
+ class << Postmaster
25
+ alias_method :original_execute_request, :execute_request
26
+ end
27
+
28
+ def self.execute_request(opts)
29
+ # send calls to mock if it's defined otherwise call real service
30
+ if @mock_rest_client
31
+ get_params = (opts[:headers] || {})[:params]
32
+ post_params = opts[:payload]
33
+ case opts[:method]
34
+ when :get then @mock_rest_client.get opts[:url], get_params, post_params
35
+ when :post then @mock_rest_client.post opts[:url], get_params, post_params
36
+ when :delete then @mock_rest_client.delete opts[:url], get_params, post_params
37
+ end
38
+ else
39
+ original_execute_request(opts)
40
+ end
41
+ end
42
+ end
43
+
44
+ def test_response(body, code=200)
45
+ # When an exception is raised, restclient clobbers method_missing. Hence we
46
+ # can't just use the stubs interface.
47
+ body = MultiJson.dump(body) if !(body.kind_of? String)
48
+ m = mock
49
+ m.instance_variable_set('@postmaster_values', { :body => body, :code => code })
50
+ def m.body; @postmaster_values[:body]; end
51
+ def m.code; @postmaster_values[:code]; end
52
+ m
53
+ end
54
+
55
+
56
+ def test_invalid_api_key_error
57
+ {
58
+ "error" => {
59
+ "type" => "invalid_request_error",
60
+ "message" => "Invalid API Key provided: invalid"
61
+ }
62
+ }
63
+ end