postmaster 1.0.0

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