usps-imis-api 1.0.0.pre.rc.2 → 1.0.0.pre.rc.4
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/Readme.md +10 -8
- data/lib/usps/imis/api.rb +52 -145
- data/lib/usps/imis/business_object.rb +131 -0
- data/lib/usps/imis/config.rb +1 -1
- data/lib/usps/imis/error/{api.rb → api_error.rb} +2 -2
- data/lib/usps/imis/error/{mapper.rb → mapper_error.rb} +1 -1
- data/lib/usps/imis/error/{response.rb → response_error.rb} +3 -3
- data/lib/usps/imis/mapper.rb +2 -2
- data/lib/usps/imis/panel/base_panel.rb +8 -6
- data/lib/usps/imis/requests.rb +31 -0
- data/lib/usps/imis/version.rb +1 -1
- data/lib/usps/imis.rb +5 -3
- data/spec/lib/usps/imis/api_spec.rb +53 -38
- data/spec/lib/usps/imis/business_object_spec.rb +40 -0
- data/spec/lib/usps/imis/config_spec.rb +1 -1
- data/spec/lib/usps/imis/error/{api_spec.rb → api_error_spec.rb} +1 -1
- data/spec/lib/usps/imis/error/{response_spec.rb → response_error_spec.rb} +4 -4
- data/spec/lib/usps/imis/mapper_spec.rb +1 -1
- data/spec/lib/usps/imis/panel/base_panel_spec.rb +2 -2
- metadata +9 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bb187f7e4a8497da991378eec6a9893c5c2a5e637b808a9225ab72ebd5ee385d
|
|
4
|
+
data.tar.gz: 0d09118f25fce28b94ddbc1257e9670fe3454e34df094885bc9cf35cbb1a86a0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f50698bc529a6d66d3c69e201d696cdfde1f24c7bf414ab1f655ebdf3b21e1bd512b8e00476ffa62e3ded1a50d72e82586b8f11c002291c5a8038ad5f4cdd2d6
|
|
7
|
+
data.tar.gz: '09e2849e666c312d0a974efc14b5133ac6e2f0ef181a6715f83ae420b6ba48f6a1566cc749a1dcbce614989c9e5b9fa343ba4dd6a56f1ecd47a9a14b7572617a'
|
data/Gemfile.lock
CHANGED
data/Readme.md
CHANGED
|
@@ -13,7 +13,7 @@ gem install usps-imis-api
|
|
|
13
13
|
or add this line to your Gemfile:
|
|
14
14
|
|
|
15
15
|
```ruby
|
|
16
|
-
gem 'usps-imis-api', '>= 0.
|
|
16
|
+
gem 'usps-imis-api', '>= 0.6.0'
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## Setup
|
|
@@ -82,7 +82,7 @@ To fetch member data, run e.g.:
|
|
|
82
82
|
```ruby
|
|
83
83
|
api.imis_id = 31092
|
|
84
84
|
|
|
85
|
-
data = api.
|
|
85
|
+
data = api.on('ABC_ASC_Individual_Demog').get
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
### PUT Fields
|
|
@@ -93,7 +93,7 @@ To update member data, run e.g.:
|
|
|
93
93
|
api.imis_id = 31092
|
|
94
94
|
|
|
95
95
|
data = { 'MMS_Updated' => Time.now.strftime('%Y-%m-%dT%H:%M:%S'), 'TotMMS' => new_total }
|
|
96
|
-
update = api.
|
|
96
|
+
update = api.on('ABC_ASC_Individual_Demog').put_fields(data)
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
This method fetches the current data structure, and filters it down to just what you want to
|
|
@@ -106,7 +106,7 @@ To update member data, run e.g.:
|
|
|
106
106
|
```ruby
|
|
107
107
|
api.imis_id = 31092
|
|
108
108
|
|
|
109
|
-
update = api.
|
|
109
|
+
update = api.on('ABC_ASC_Individual_Demog').put(complete_imis_object)
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
This method requires a complete iMIS data structure.
|
|
@@ -116,7 +116,7 @@ This method requires a complete iMIS data structure.
|
|
|
116
116
|
To create new member data, run e.g.:
|
|
117
117
|
|
|
118
118
|
```ruby
|
|
119
|
-
created = api.
|
|
119
|
+
created = api.on('ABC_ASC_Individual_Demog').post(complete_imis_object)
|
|
120
120
|
```
|
|
121
121
|
|
|
122
122
|
This method requires a complete iMIS data structure.
|
|
@@ -128,7 +128,7 @@ To remove member data, run e.g.:
|
|
|
128
128
|
```ruby
|
|
129
129
|
api.imis_id = 31092
|
|
130
130
|
|
|
131
|
-
api.
|
|
131
|
+
api.on('ABC_ASC_Individual_Demog').delete
|
|
132
132
|
```
|
|
133
133
|
|
|
134
134
|
This returns a blank string on success.
|
|
@@ -202,9 +202,11 @@ previous value.
|
|
|
202
202
|
|
|
203
203
|
```ruby
|
|
204
204
|
api.with(31092) do
|
|
205
|
-
# These
|
|
205
|
+
# These four requests are identical:
|
|
206
206
|
|
|
207
|
-
|
|
207
|
+
on('ABC_ASC_Individual_Demog') { put('TotMMS' => 15) }
|
|
208
|
+
|
|
209
|
+
on('ABC_ASC_Individual_Demog').put('TotMMS' => 15)
|
|
208
210
|
|
|
209
211
|
mapper.update(mm: 15)
|
|
210
212
|
|
data/lib/usps/imis/api.rb
CHANGED
|
@@ -5,14 +5,12 @@ module Usps
|
|
|
5
5
|
# The core API wrapper
|
|
6
6
|
#
|
|
7
7
|
class Api
|
|
8
|
+
include Requests
|
|
9
|
+
|
|
8
10
|
# Endpoint for (re-)authentication requests
|
|
9
11
|
#
|
|
10
12
|
AUTHENTICATION_PATH = 'Token'
|
|
11
13
|
|
|
12
|
-
# Endpoint for general API requests
|
|
13
|
-
#
|
|
14
|
-
API_PATH = 'api'
|
|
15
|
-
|
|
16
14
|
# Endpoint for IQA query requests
|
|
17
15
|
#
|
|
18
16
|
QUERY_PATH = 'api/Query'
|
|
@@ -31,6 +29,10 @@ module Usps
|
|
|
31
29
|
#
|
|
32
30
|
attr_reader :imis_id
|
|
33
31
|
|
|
32
|
+
# Whether to lock changes to the selected iMIS ID
|
|
33
|
+
#
|
|
34
|
+
attr_reader :lock_imis_id
|
|
35
|
+
|
|
34
36
|
# A new instance of +Api+
|
|
35
37
|
#
|
|
36
38
|
# @param skip_authentication [bool] Skip authentication on initialization (used for tests)
|
|
@@ -46,7 +48,9 @@ module Usps
|
|
|
46
48
|
# @param id [Integer, String] iMIS ID to select for future requests
|
|
47
49
|
#
|
|
48
50
|
def imis_id=(id)
|
|
49
|
-
|
|
51
|
+
raise Error::ApiError, 'Cannot change iMIS ID while locked' if lock_imis_id
|
|
52
|
+
|
|
53
|
+
@imis_id = id&.to_i&.to_s
|
|
50
54
|
end
|
|
51
55
|
|
|
52
56
|
# Convert a member's certificate number into an iMIS ID number
|
|
@@ -56,15 +60,21 @@ module Usps
|
|
|
56
60
|
# @return [String] Corresponding iMIS ID
|
|
57
61
|
#
|
|
58
62
|
def imis_id_for(certificate)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
raise Error::ApiError, 'Cannot change iMIS ID while locked' if lock_imis_id
|
|
64
|
+
|
|
65
|
+
begin
|
|
66
|
+
result = query(Imis.configuration.imis_id_query_name, { certificate: })
|
|
67
|
+
@imis_id = result['Items']['$values'][0]['ID']
|
|
68
|
+
rescue StandardError
|
|
69
|
+
raise Error::ApiError, 'Member not found'
|
|
70
|
+
end
|
|
63
71
|
end
|
|
64
72
|
|
|
65
73
|
# Run requests as DSL, with specific iMIS ID only maintained for this scope
|
|
66
74
|
#
|
|
67
|
-
#
|
|
75
|
+
# While in this block, changes to the value of +imis_id+ are not allowed
|
|
76
|
+
#
|
|
77
|
+
# If no block is given, this sets the iMIS ID and returns self.
|
|
68
78
|
#
|
|
69
79
|
# @param id [Integer, String] iMIS ID to select for requests within the block
|
|
70
80
|
#
|
|
@@ -76,104 +86,68 @@ module Usps
|
|
|
76
86
|
def with(id, &)
|
|
77
87
|
old_id = imis_id
|
|
78
88
|
self.imis_id = id
|
|
89
|
+
return self unless block_given?
|
|
90
|
+
|
|
91
|
+
@lock_imis_id = true
|
|
79
92
|
instance_eval(&)
|
|
80
93
|
ensure
|
|
81
|
-
|
|
94
|
+
if block_given?
|
|
95
|
+
@lock_imis_id = false
|
|
96
|
+
self.imis_id = old_id
|
|
97
|
+
end
|
|
82
98
|
end
|
|
83
99
|
|
|
84
|
-
#
|
|
100
|
+
# Run an IQA Query
|
|
85
101
|
#
|
|
86
|
-
# @param
|
|
87
|
-
# @
|
|
102
|
+
# @param query_name [String] Full path of the query in IQA, e.g. +$/_ABC/Fiander/iMIS_ID+
|
|
103
|
+
# @query_params [Hash] Conforms to pattern +{ param_name => param_value }+
|
|
88
104
|
#
|
|
89
105
|
# @return [Hash] Response data from the API
|
|
90
106
|
#
|
|
91
|
-
def
|
|
92
|
-
|
|
107
|
+
def query(query_name, query_params = {})
|
|
108
|
+
query_params[:QueryName] = query_name
|
|
109
|
+
path = "#{QUERY_PATH}?#{query_params.to_query}"
|
|
110
|
+
uri = URI(File.join(Imis.configuration.hostname, path))
|
|
93
111
|
request = Net::HTTP::Get.new(uri)
|
|
94
112
|
result = submit(uri, authorize(request))
|
|
95
113
|
JSON.parse(result.body)
|
|
96
114
|
end
|
|
97
115
|
|
|
98
|
-
#
|
|
116
|
+
# An instance of +BusinessObject+, using this instance as its parent +Api+
|
|
99
117
|
#
|
|
100
118
|
# @param business_object_name [String] Name of the business object
|
|
101
|
-
# @param fields [Hash] Conforms to pattern +{ field_key => value }+
|
|
102
119
|
# @param url_id [String] Override the ID param of the URL (e.g. used for Panels)
|
|
103
120
|
#
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def put_fields(business_object_name, fields, url_id: nil)
|
|
107
|
-
updated = filter_fields(business_object_name, fields)
|
|
108
|
-
put(business_object_name, updated, url_id:)
|
|
121
|
+
def business_object(business_object_name, url_id: nil)
|
|
122
|
+
BusinessObject.new(self, business_object_name, url_id:)
|
|
109
123
|
end
|
|
110
124
|
|
|
111
|
-
#
|
|
125
|
+
# Run requests as DSL, with specific +BusinessObject+ only maintained for this scope
|
|
112
126
|
#
|
|
113
|
-
#
|
|
114
|
-
# @param body [Hash] Full raw API object data
|
|
115
|
-
# @param url_id [String] Override the ID param of the URL (e.g. used for Panels)
|
|
116
|
-
#
|
|
117
|
-
# @return [Hash] Response data from the API
|
|
118
|
-
#
|
|
119
|
-
def put(business_object_name, body, url_id: nil)
|
|
120
|
-
uri = uri_for(business_object_name, url_id:)
|
|
121
|
-
request = Net::HTTP::Put.new(uri)
|
|
122
|
-
request.body = JSON.dump(body)
|
|
123
|
-
result = submit(uri, authorize(request))
|
|
124
|
-
JSON.parse(result.body)
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# Create a business object for the current member
|
|
127
|
+
# If no block is given, this returns the specified +BusinessObject+.
|
|
128
128
|
#
|
|
129
129
|
# @param business_object_name [String] Name of the business object
|
|
130
|
-
# @param body [Hash] Full raw API object data
|
|
131
130
|
# @param url_id [String] Override the ID param of the URL (e.g. used for Panels)
|
|
132
131
|
#
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
uri = uri_for(business_object_name, url_id:)
|
|
137
|
-
request = Net::HTTP::Post.new(uri)
|
|
138
|
-
request.body = JSON.dump(body)
|
|
139
|
-
result = submit(uri, authorize(request))
|
|
140
|
-
JSON.parse(result.body)
|
|
141
|
-
end
|
|
132
|
+
def on(business_object_name, url_id: nil, &)
|
|
133
|
+
object = business_object(business_object_name, url_id:)
|
|
134
|
+
return object unless block_given?
|
|
142
135
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
# @param url_id [String] Override the ID param of the URL (e.g. used for Panels)
|
|
147
|
-
#
|
|
148
|
-
# @return [String] Error response body from the API, or empty string on success
|
|
149
|
-
#
|
|
150
|
-
def delete(business_object_name, url_id: nil)
|
|
151
|
-
uri = uri_for(business_object_name, url_id:)
|
|
152
|
-
request = Net::HTTP::Delete.new(uri)
|
|
153
|
-
result = submit(uri, authorize(request))
|
|
154
|
-
result.body
|
|
136
|
+
result = nil
|
|
137
|
+
object.tap { |obj| result = obj.instance_eval(&) }
|
|
138
|
+
result
|
|
155
139
|
end
|
|
156
140
|
|
|
157
|
-
#
|
|
158
|
-
#
|
|
159
|
-
# @param query_name [String] Full path of the query in IQA, e.g. +$/_ABC/Fiander/iMIS_ID+
|
|
160
|
-
# @query_params [Hash] Conforms to pattern +{ param_name => param_value }+
|
|
141
|
+
# An instance of +Mapper+, using this instance as its parent +Api+
|
|
161
142
|
#
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def query(query_name, query_params = {})
|
|
165
|
-
query_params[:QueryName] = query_name
|
|
166
|
-
path = "#{QUERY_PATH}?#{query_params.to_query}"
|
|
167
|
-
uri = URI(File.join(imis_hostname, path))
|
|
168
|
-
request = Net::HTTP::Get.new(uri)
|
|
169
|
-
result = submit(uri, authorize(request))
|
|
170
|
-
JSON.parse(result.body)
|
|
143
|
+
def mapper
|
|
144
|
+
@mapper ||= Mapper.new(self)
|
|
171
145
|
end
|
|
172
146
|
|
|
173
|
-
#
|
|
147
|
+
# Convenience alias for updating mapped fields
|
|
174
148
|
#
|
|
175
|
-
def
|
|
176
|
-
|
|
149
|
+
def update(data)
|
|
150
|
+
mapper.update(data)
|
|
177
151
|
end
|
|
178
152
|
|
|
179
153
|
# Convenience accessor for available Panel objects, each using this instance as its parent
|
|
@@ -186,56 +160,16 @@ module Usps
|
|
|
186
160
|
)
|
|
187
161
|
end
|
|
188
162
|
|
|
189
|
-
# Convenience alias for updating mapped fields
|
|
190
|
-
#
|
|
191
|
-
def update(data)
|
|
192
|
-
mapper.update(data)
|
|
193
|
-
end
|
|
194
|
-
|
|
195
163
|
# Ruby 3.5 instance variable filter
|
|
196
164
|
#
|
|
197
165
|
def instance_variables_to_inspect = %i[@token_expiration @imis_id]
|
|
198
166
|
|
|
199
167
|
private
|
|
200
168
|
|
|
201
|
-
def client(uri)
|
|
202
|
-
Net::HTTP.new(uri.host, uri.port).tap do |http|
|
|
203
|
-
http.use_ssl = true
|
|
204
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
def imis_hostname
|
|
209
|
-
Imis.configuration.hostname
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
# Authorize a request prior to submitting
|
|
213
|
-
#
|
|
214
|
-
# If the current token is missing/expired, request a new one
|
|
215
|
-
#
|
|
216
|
-
def authorize(request)
|
|
217
|
-
authenticate if token_expiration < Time.now
|
|
218
|
-
request.tap { |r| r.add_field('Authorization', "Bearer #{token}") }
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
# Construct a business object API endpoint address
|
|
222
|
-
#
|
|
223
|
-
def uri_for(business_object_name, url_id: nil)
|
|
224
|
-
url_id ||= imis_id
|
|
225
|
-
url_id = CGI.escape(url_id)
|
|
226
|
-
URI(File.join(imis_hostname, "#{API_PATH}/#{business_object_name}/#{url_id}"))
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
def submit(uri, request)
|
|
230
|
-
client(uri).request(request).tap do |result|
|
|
231
|
-
raise Error::Response.from(result) unless result.is_a?(Net::HTTPSuccess)
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
|
|
235
169
|
# Authenticate to the iMIS API, and store the access token and expiration time
|
|
236
170
|
#
|
|
237
171
|
def authenticate
|
|
238
|
-
uri = URI(File.join(
|
|
172
|
+
uri = URI(File.join(Imis.configuration.hostname, AUTHENTICATION_PATH))
|
|
239
173
|
req = Net::HTTP::Post.new(uri)
|
|
240
174
|
authentication_data = {
|
|
241
175
|
grant_type: 'password',
|
|
@@ -249,33 +183,6 @@ module Usps
|
|
|
249
183
|
@token = json['access_token']
|
|
250
184
|
@token_expiration = Time.parse(json['.expires'])
|
|
251
185
|
end
|
|
252
|
-
|
|
253
|
-
# Manually assemble the matching data structure, with fields in the correct order
|
|
254
|
-
#
|
|
255
|
-
def filter_fields(business_object_name, fields)
|
|
256
|
-
existing = get(business_object_name)
|
|
257
|
-
|
|
258
|
-
JSON.parse(JSON.dump(existing)).tap do |updated|
|
|
259
|
-
# The first property is always the iMIS ID again
|
|
260
|
-
updated['Properties']['$values'] = [existing['Properties']['$values'][0]]
|
|
261
|
-
|
|
262
|
-
# Iterate through all existing fields
|
|
263
|
-
existing['Properties']['$values'].each do |value|
|
|
264
|
-
next unless fields.keys.include?(value['Name'])
|
|
265
|
-
|
|
266
|
-
# Strings are not wrapped in the type definition structure
|
|
267
|
-
new_value = fields[value['Name']]
|
|
268
|
-
if new_value.is_a?(String)
|
|
269
|
-
value['Value'] = new_value
|
|
270
|
-
else
|
|
271
|
-
value['Value']['$value'] = new_value
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
# Add the completed field with the updated value
|
|
275
|
-
updated['Properties']['$values'] << value
|
|
276
|
-
end
|
|
277
|
-
end
|
|
278
|
-
end
|
|
279
186
|
end
|
|
280
187
|
end
|
|
281
188
|
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Usps
|
|
4
|
+
module Imis
|
|
5
|
+
# DEV
|
|
6
|
+
class BusinessObject
|
|
7
|
+
include Requests
|
|
8
|
+
|
|
9
|
+
# Endpoint for general API requests
|
|
10
|
+
#
|
|
11
|
+
API_PATH = 'api'
|
|
12
|
+
|
|
13
|
+
# The parent +Api+ object
|
|
14
|
+
#
|
|
15
|
+
attr_reader :api
|
|
16
|
+
|
|
17
|
+
# Name of the iMIS Business Object
|
|
18
|
+
#
|
|
19
|
+
attr_reader :business_object_name
|
|
20
|
+
|
|
21
|
+
# Override ID param of the URL (e.g. used for Panels)
|
|
22
|
+
#
|
|
23
|
+
attr_reader :url_id
|
|
24
|
+
|
|
25
|
+
# A new instance of +BusinessObject+
|
|
26
|
+
#
|
|
27
|
+
def initialize(api, business_object_name, url_id: nil)
|
|
28
|
+
@api = api
|
|
29
|
+
@business_object_name = business_object_name
|
|
30
|
+
@url_id = url_id
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Get a business object for the current member
|
|
34
|
+
#
|
|
35
|
+
# @return [Hash] Response data from the API
|
|
36
|
+
#
|
|
37
|
+
def get
|
|
38
|
+
request = Net::HTTP::Get.new(uri)
|
|
39
|
+
result = submit(uri, authorize(request))
|
|
40
|
+
JSON.parse(result.body)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Update only specific fields on a business object for the current member
|
|
44
|
+
#
|
|
45
|
+
# @param fields [Hash] Conforms to pattern +{ field_key => value }+
|
|
46
|
+
#
|
|
47
|
+
# @return [Hash] Response data from the API
|
|
48
|
+
#
|
|
49
|
+
def put_fields(fields)
|
|
50
|
+
updated = filter_fields(fields)
|
|
51
|
+
put(updated)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Update a business object for the current member
|
|
55
|
+
#
|
|
56
|
+
# @param body [Hash] Full raw API object data
|
|
57
|
+
#
|
|
58
|
+
# @return [Hash] Response data from the API
|
|
59
|
+
#
|
|
60
|
+
def put(body)
|
|
61
|
+
request = Net::HTTP::Put.new(uri)
|
|
62
|
+
request.body = JSON.dump(body)
|
|
63
|
+
result = submit(uri, authorize(request))
|
|
64
|
+
JSON.parse(result.body)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Create a business object for the current member
|
|
68
|
+
#
|
|
69
|
+
# @param body [Hash] Full raw API object data
|
|
70
|
+
#
|
|
71
|
+
# @return [Hash] Response data from the API
|
|
72
|
+
#
|
|
73
|
+
def post(body)
|
|
74
|
+
request = Net::HTTP::Post.new(uri)
|
|
75
|
+
request.body = JSON.dump(body)
|
|
76
|
+
result = submit(uri, authorize(request))
|
|
77
|
+
JSON.parse(result.body)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Remove a business object for the current member
|
|
81
|
+
#
|
|
82
|
+
# @return [String] Error response body from the API, or empty string on success
|
|
83
|
+
#
|
|
84
|
+
def delete
|
|
85
|
+
request = Net::HTTP::Delete.new(uri)
|
|
86
|
+
result = submit(uri, authorize(request))
|
|
87
|
+
result.body
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def token = api.token
|
|
93
|
+
def token_expiration = api.token_expiration
|
|
94
|
+
|
|
95
|
+
# Construct a business object API endpoint address
|
|
96
|
+
#
|
|
97
|
+
def uri
|
|
98
|
+
id_for_url = url_id ? CGI.escape(url_id) : api.imis_id
|
|
99
|
+
full_path = "#{API_PATH}/#{business_object_name}/#{id_for_url}"
|
|
100
|
+
URI(File.join(Imis.configuration.hostname, full_path))
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Manually assemble the matching data structure, with fields in the correct order
|
|
104
|
+
#
|
|
105
|
+
def filter_fields(fields)
|
|
106
|
+
existing = get
|
|
107
|
+
|
|
108
|
+
JSON.parse(JSON.dump(existing)).tap do |updated|
|
|
109
|
+
# The first property is always the iMIS ID again
|
|
110
|
+
updated['Properties']['$values'] = [existing['Properties']['$values'][0]]
|
|
111
|
+
|
|
112
|
+
# Iterate through all existing fields
|
|
113
|
+
existing['Properties']['$values'].each do |value|
|
|
114
|
+
next unless fields.keys.include?(value['Name'])
|
|
115
|
+
|
|
116
|
+
# Strings are not wrapped in the type definition structure
|
|
117
|
+
new_value = fields[value['Name']]
|
|
118
|
+
if new_value.is_a?(String)
|
|
119
|
+
value['Value'] = new_value
|
|
120
|
+
else
|
|
121
|
+
value['Value']['$value'] = new_value
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Add the completed field with the updated value
|
|
125
|
+
updated['Properties']['$values'] << value
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
data/lib/usps/imis/config.rb
CHANGED
|
@@ -5,12 +5,12 @@ module Usps
|
|
|
5
5
|
module Error
|
|
6
6
|
# Base error class for all internal exceptions
|
|
7
7
|
#
|
|
8
|
-
class
|
|
8
|
+
class ApiError < StandardError
|
|
9
9
|
# Additional call-specific metadata to pass through to Bugsnag
|
|
10
10
|
#
|
|
11
11
|
attr_accessor :metadata
|
|
12
12
|
|
|
13
|
-
# A new instance of +
|
|
13
|
+
# A new instance of +ApiError+
|
|
14
14
|
#
|
|
15
15
|
# @param message [String] The base exception message
|
|
16
16
|
# @param metadata [Hash] Additional call-specific metadata to pass through to Bugsnag
|
|
@@ -5,7 +5,7 @@ module Usps
|
|
|
5
5
|
module Error
|
|
6
6
|
# Exception raised due to receiving an error response from the API
|
|
7
7
|
#
|
|
8
|
-
class
|
|
8
|
+
class ResponseError < ApiError
|
|
9
9
|
# [Net::HTTPResponse] The response received from the API
|
|
10
10
|
#
|
|
11
11
|
attr_reader :response
|
|
@@ -14,7 +14,7 @@ module Usps
|
|
|
14
14
|
#
|
|
15
15
|
attr_accessor :metadata
|
|
16
16
|
|
|
17
|
-
# Create a new instance of +
|
|
17
|
+
# Create a new instance of +ResponseError+ from an API response
|
|
18
18
|
#
|
|
19
19
|
# @param response [Net::HTTPResponse] The response received from the API
|
|
20
20
|
#
|
|
@@ -22,7 +22,7 @@ module Usps
|
|
|
22
22
|
new(nil, response)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
# Create a new instance of +
|
|
25
|
+
# Create a new instance of +ResponseError+
|
|
26
26
|
#
|
|
27
27
|
# @param _message Ignored
|
|
28
28
|
# @param response [Net::HTTPResponse] The response received from the API
|
data/lib/usps/imis/mapper.rb
CHANGED
|
@@ -47,7 +47,7 @@ module Usps
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
updates.map do |business_object_name, field_updates|
|
|
50
|
-
api.put_fields(
|
|
50
|
+
api.business_object(business_object_name).put_fields(field_updates)
|
|
51
51
|
end
|
|
52
52
|
end
|
|
53
53
|
|
|
@@ -74,7 +74,7 @@ module Usps
|
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
raise(
|
|
77
|
-
Error::
|
|
77
|
+
Error::MapperError,
|
|
78
78
|
"Unrecognized field: \"#{field_name}\". " \
|
|
79
79
|
'Please report what data you are attempting to work with to ITCom leadership.'
|
|
80
80
|
)
|
|
@@ -20,7 +20,7 @@ module Usps
|
|
|
20
20
|
# @param ordinal [Integer] The ordinal identifier for the desired object
|
|
21
21
|
#
|
|
22
22
|
def get(ordinal)
|
|
23
|
-
api.
|
|
23
|
+
api.business_object(business_object, url_id: "~#{api.imis_id}|#{ordinal}").get
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
# Create a new object in the Panel
|
|
@@ -28,7 +28,7 @@ module Usps
|
|
|
28
28
|
# @param data [Hash] The record data for the desired object
|
|
29
29
|
#
|
|
30
30
|
def create(data)
|
|
31
|
-
api.
|
|
31
|
+
api.business_object(business_object, url_id: '').post(payload(data))
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
# Update an existing object in the Panel
|
|
@@ -37,7 +37,9 @@ module Usps
|
|
|
37
37
|
# +ordinal+ identifier
|
|
38
38
|
#
|
|
39
39
|
def update(data)
|
|
40
|
-
api
|
|
40
|
+
api
|
|
41
|
+
.business_object(business_object, url_id: "~#{api.imis_id}|#{data[:ordinal]}")
|
|
42
|
+
.put(payload(data))
|
|
41
43
|
end
|
|
42
44
|
|
|
43
45
|
# Remove a specific object from the Panel
|
|
@@ -45,17 +47,17 @@ module Usps
|
|
|
45
47
|
# @param ordinal [Integer] The ordinal identifier for the desired object
|
|
46
48
|
#
|
|
47
49
|
def destroy(ordinal)
|
|
48
|
-
api.
|
|
50
|
+
api.business_object(business_object, url_id: "~#{api.imis_id}|#{ordinal}").delete
|
|
49
51
|
end
|
|
50
52
|
|
|
51
53
|
private
|
|
52
54
|
|
|
53
55
|
def business_object
|
|
54
|
-
raise Error::
|
|
56
|
+
raise Error::ApiError, "#{self.class.name} must implement #business_object"
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
def payload(_data)
|
|
58
|
-
raise Error::
|
|
60
|
+
raise Error::ApiError, "#{self.class.name} must implement #payload(data)"
|
|
59
61
|
end
|
|
60
62
|
end
|
|
61
63
|
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Usps
|
|
4
|
+
module Imis
|
|
5
|
+
module Requests
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def client(uri)
|
|
9
|
+
Net::HTTP.new(uri.host, uri.port).tap do |http|
|
|
10
|
+
http.use_ssl = true
|
|
11
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Authorize a request prior to submitting
|
|
16
|
+
#
|
|
17
|
+
# If the current token is missing/expired, request a new one
|
|
18
|
+
#
|
|
19
|
+
def authorize(request)
|
|
20
|
+
authenticate if token_expiration < Time.now
|
|
21
|
+
request.tap { |r| r.add_field('Authorization', "Bearer #{token}") }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def submit(uri, request)
|
|
25
|
+
client(uri).request(request).tap do |result|
|
|
26
|
+
raise Error::ResponseError.from(result) unless result.is_a?(Net::HTTPSuccess)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/usps/imis/version.rb
CHANGED
data/lib/usps/imis.rb
CHANGED
|
@@ -13,9 +13,11 @@ require 'ext/hash' unless defined?(Rails)
|
|
|
13
13
|
|
|
14
14
|
# Internal requires
|
|
15
15
|
require_relative 'imis/config'
|
|
16
|
-
require_relative 'imis/error/
|
|
17
|
-
require_relative 'imis/error/
|
|
18
|
-
require_relative 'imis/error/
|
|
16
|
+
require_relative 'imis/error/api_error'
|
|
17
|
+
require_relative 'imis/error/mapper_error'
|
|
18
|
+
require_relative 'imis/error/response_error'
|
|
19
|
+
require_relative 'imis/requests'
|
|
20
|
+
require_relative 'imis/business_object'
|
|
19
21
|
require_relative 'imis/api'
|
|
20
22
|
require_relative 'imis/mapper'
|
|
21
23
|
require_relative 'imis/panel/base_panel'
|
|
@@ -31,7 +31,7 @@ describe Usps::Imis::Api do
|
|
|
31
31
|
|
|
32
32
|
it 'wraps errors' do
|
|
33
33
|
expect { api.imis_id_for('E231625') }.to raise_error(
|
|
34
|
-
Usps::Imis::Error::
|
|
34
|
+
Usps::Imis::Error::ApiError, 'Member not found'
|
|
35
35
|
)
|
|
36
36
|
end
|
|
37
37
|
end
|
|
@@ -41,13 +41,15 @@ describe Usps::Imis::Api do
|
|
|
41
41
|
before { api.imis_id = 31092 }
|
|
42
42
|
|
|
43
43
|
it 'sends an update' do
|
|
44
|
-
expect(api.
|
|
44
|
+
expect(api.business_object('ABC_ASC_Individual_Demog').put_fields('TotMMS' => 15)).to(
|
|
45
|
+
be_a(Hash)
|
|
46
|
+
)
|
|
45
47
|
end
|
|
46
48
|
|
|
47
49
|
context 'when receiving a response error' do
|
|
48
50
|
let(:warning_text) do
|
|
49
51
|
<<~WARNING.chomp
|
|
50
|
-
Usps::Imis::Error::
|
|
52
|
+
Usps::Imis::Error::ResponseError: [INTERNAL_SERVER_ERROR] The iMIS API returned an error.
|
|
51
53
|
Something went wrong
|
|
52
54
|
WARNING
|
|
53
55
|
end
|
|
@@ -61,8 +63,8 @@ describe Usps::Imis::Api do
|
|
|
61
63
|
end
|
|
62
64
|
|
|
63
65
|
it 'wraps the error' do
|
|
64
|
-
expect { api.
|
|
65
|
-
Usps::Imis::Error::
|
|
66
|
+
expect { api.business_object('ABC_ASC_Individual_Demog').put_fields('TotMMS' => 15) }.to(
|
|
67
|
+
raise_error(Usps::Imis::Error::ApiError, warning_text)
|
|
66
68
|
)
|
|
67
69
|
end
|
|
68
70
|
end
|
|
@@ -71,7 +73,7 @@ describe Usps::Imis::Api do
|
|
|
71
73
|
describe '#with' do
|
|
72
74
|
it 'sends an update from put' do
|
|
73
75
|
expect(
|
|
74
|
-
api.with(31092) {
|
|
76
|
+
api.with(31092) { business_object('ABC_ASC_Individual_Demog').put_fields('TotMMS' => 15) }
|
|
75
77
|
).to be_a(Hash)
|
|
76
78
|
end
|
|
77
79
|
|
|
@@ -82,6 +84,51 @@ describe Usps::Imis::Api do
|
|
|
82
84
|
it 'uses a panel correctly' do
|
|
83
85
|
expect(api.with(6374) { panels.vsc.get(1433) }).to be_a(Hash)
|
|
84
86
|
end
|
|
87
|
+
|
|
88
|
+
it 'blocks calling imis_id=' do
|
|
89
|
+
expect do
|
|
90
|
+
api.with(31092) { self.imis_id = 31092 }
|
|
91
|
+
end.to raise_error(Usps::Imis::Error::ApiError, 'Cannot change iMIS ID while locked')
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'blocks calling imis_id_for' do
|
|
95
|
+
expect do
|
|
96
|
+
api.with(31092) { imis_id_for('E231625') }
|
|
97
|
+
end.to raise_error(Usps::Imis::Error::ApiError, 'Cannot change iMIS ID while locked')
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
describe '#on' do
|
|
102
|
+
it 'returns a BusinessObject without a block' do
|
|
103
|
+
expect(api.on('ABC_ASC_Individual_Demog')).to be_a(Usps::Imis::BusinessObject)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it 'sends an update from put', :aggregate_failures do
|
|
107
|
+
result = api.with(31092) do
|
|
108
|
+
on('ABC_ASC_Individual_Demog') { put_fields({ 'TotMMS' => 15 }) }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
expect(result).to be_a(Hash)
|
|
112
|
+
expect(api.imis_id).to be_nil
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'chains .with().on() to a single block', :aggregate_failures do
|
|
116
|
+
result = api.with(31092).on('ABC_ASC_Individual_Demog') do
|
|
117
|
+
put_fields({ 'TotMMS' => 15 })
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
expect(result).to be_a(Hash)
|
|
121
|
+
expect(api.imis_id).to eq('31092')
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it 'nests on and with', :aggregate_failures do
|
|
125
|
+
result = api.on('ABC_ASC_Individual_Demog') do |object|
|
|
126
|
+
api.with(31092) { object.put_fields({ 'TotMMS' => 15 }) }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
expect(result).to be_a(Hash)
|
|
130
|
+
expect(api.imis_id).to be_nil
|
|
131
|
+
end
|
|
85
132
|
end
|
|
86
133
|
|
|
87
134
|
describe '#inspect' do
|
|
@@ -108,36 +155,4 @@ describe Usps::Imis::Api do
|
|
|
108
155
|
expect(api).to have_received(:authenticate)
|
|
109
156
|
end
|
|
110
157
|
end
|
|
111
|
-
|
|
112
|
-
describe '#filter_fields' do
|
|
113
|
-
let(:expected) do
|
|
114
|
-
{
|
|
115
|
-
'Properties' => {
|
|
116
|
-
'$values' => [
|
|
117
|
-
{ 'Name' => 'Stub iMIS ID', 'Value' => { '$value' => '31092' } },
|
|
118
|
-
{ 'Name' => 'Stub Integer', 'Value' => { '$value' => 43 } },
|
|
119
|
-
{ 'Name' => 'Stub String', 'Value' => 'other' }
|
|
120
|
-
]
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
before do
|
|
126
|
-
allow(api).to receive(:get).and_return({
|
|
127
|
-
'Properties' => {
|
|
128
|
-
'$values' => [
|
|
129
|
-
{ 'Name' => 'Stub iMIS ID', 'Value' => { '$value' => '31092' } },
|
|
130
|
-
{ 'Name' => 'Stub Integer', 'Value' => { '$value' => 42 } },
|
|
131
|
-
{ 'Name' => 'Stub String', 'Value' => 'something' }
|
|
132
|
-
]
|
|
133
|
-
}
|
|
134
|
-
})
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
it 'formats fields correctly' do
|
|
138
|
-
updated = api.send(:filter_fields, 'Stub', { 'Stub Integer' => 43, 'Stub String' => 'other' })
|
|
139
|
-
|
|
140
|
-
expect(updated).to eq(expected)
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
158
|
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Usps::Imis::BusinessObject do
|
|
6
|
+
let(:business_object) { described_class.new(api, 'Stub') }
|
|
7
|
+
let(:api) { Usps::Imis::Api.new }
|
|
8
|
+
|
|
9
|
+
describe '#filter_fields' do
|
|
10
|
+
let(:expected) do
|
|
11
|
+
{
|
|
12
|
+
'Properties' => {
|
|
13
|
+
'$values' => [
|
|
14
|
+
{ 'Name' => 'Stub iMIS ID', 'Value' => { '$value' => '31092' } },
|
|
15
|
+
{ 'Name' => 'Stub Integer', 'Value' => { '$value' => 43 } },
|
|
16
|
+
{ 'Name' => 'Stub String', 'Value' => 'other' }
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
before do
|
|
23
|
+
allow(business_object).to receive(:get).and_return({
|
|
24
|
+
'Properties' => {
|
|
25
|
+
'$values' => [
|
|
26
|
+
{ 'Name' => 'Stub iMIS ID', 'Value' => { '$value' => '31092' } },
|
|
27
|
+
{ 'Name' => 'Stub Integer', 'Value' => { '$value' => 42 } },
|
|
28
|
+
{ 'Name' => 'Stub String', 'Value' => 'something' }
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'formats fields correctly' do
|
|
35
|
+
updated = business_object.send(:filter_fields, 'Stub Integer' => 43, 'Stub String' => 'other')
|
|
36
|
+
|
|
37
|
+
expect(updated).to eq(expected)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -25,7 +25,7 @@ describe Usps::Imis::Config do
|
|
|
25
25
|
|
|
26
26
|
it 'raises an error' do
|
|
27
27
|
expect { config.hostname }.to raise_error(
|
|
28
|
-
Usps::Imis::Error::
|
|
28
|
+
Usps::Imis::Error::ApiError, 'Unexpected API environment: nothing'
|
|
29
29
|
)
|
|
30
30
|
end
|
|
31
31
|
end
|
|
@@ -4,7 +4,7 @@ require 'spec_helper'
|
|
|
4
4
|
|
|
5
5
|
ApiResponseStub = Struct.new(:code, :body)
|
|
6
6
|
|
|
7
|
-
describe Usps::Imis::Error::
|
|
7
|
+
describe Usps::Imis::Error::ResponseError do
|
|
8
8
|
let(:error) { described_class.from(response) }
|
|
9
9
|
|
|
10
10
|
describe 'error codes' do
|
|
@@ -61,7 +61,7 @@ describe Usps::Imis::Error::Response do
|
|
|
61
61
|
let(:response) { ApiResponseStub.new('500', 'Body of the API response error') }
|
|
62
62
|
let(:warning_text) do
|
|
63
63
|
<<~WARNING.chomp
|
|
64
|
-
Usps::Imis::Error::
|
|
64
|
+
Usps::Imis::Error::ResponseError: [INTERNAL_SERVER_ERROR] The iMIS API returned an error.
|
|
65
65
|
Body of the API response error
|
|
66
66
|
WARNING
|
|
67
67
|
end
|
|
@@ -78,7 +78,7 @@ describe Usps::Imis::Error::Response do
|
|
|
78
78
|
let(:response) { ApiResponseStub.new('500', response_body) }
|
|
79
79
|
let(:warning_text) do
|
|
80
80
|
<<~WARNING.chomp
|
|
81
|
-
Usps::Imis::Error::
|
|
81
|
+
Usps::Imis::Error::ResponseError: [INTERNAL_SERVER_ERROR] The iMIS API returned an error.
|
|
82
82
|
description
|
|
83
83
|
WARNING
|
|
84
84
|
end
|
|
@@ -95,7 +95,7 @@ describe Usps::Imis::Error::Response do
|
|
|
95
95
|
let(:response) { ApiResponseStub.new('500', response_body) }
|
|
96
96
|
let(:warning_text) do
|
|
97
97
|
<<~WARNING.chomp
|
|
98
|
-
Usps::Imis::Error::
|
|
98
|
+
Usps::Imis::Error::ResponseError: [INTERNAL_SERVER_ERROR] The iMIS API returned an error.
|
|
99
99
|
#{response_body}
|
|
100
100
|
WARNING
|
|
101
101
|
end
|
|
@@ -22,7 +22,7 @@ describe Usps::Imis::Mapper do
|
|
|
22
22
|
|
|
23
23
|
it 'raises for unmapped updates' do
|
|
24
24
|
expect { api.mapper.update(something: 'anything') }.to raise_error(
|
|
25
|
-
Usps::Imis::Error::
|
|
25
|
+
Usps::Imis::Error::MapperError,
|
|
26
26
|
'Unrecognized field: "something". ' \
|
|
27
27
|
'Please report what data you are attempting to work with to ITCom leadership.'
|
|
28
28
|
)
|
|
@@ -19,13 +19,13 @@ end
|
|
|
19
19
|
describe Usps::Imis::Panel::BasePanel do
|
|
20
20
|
it 'requires #business_object to be defined' do
|
|
21
21
|
expect { Usps::Imis::Panel::InvalidPanel.new.get(1) }.to raise_error(
|
|
22
|
-
Usps::Imis::Error::
|
|
22
|
+
Usps::Imis::Error::ApiError, 'Usps::Imis::Panel::InvalidPanel must implement #business_object'
|
|
23
23
|
)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
it 'requires #payload(data) to be defined' do
|
|
27
27
|
expect { Usps::Imis::Panel::InvalidPanelWithBusinessObject.new.create({}) }.to raise_error(
|
|
28
|
-
Usps::Imis::Error::
|
|
28
|
+
Usps::Imis::Error::ApiError,
|
|
29
29
|
'Usps::Imis::Panel::InvalidPanelWithBusinessObject must implement #payload(data)'
|
|
30
30
|
)
|
|
31
31
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: usps-imis-api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.0.pre.rc.
|
|
4
|
+
version: 1.0.0.pre.rc.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Julian Fiander
|
|
@@ -30,19 +30,22 @@ files:
|
|
|
30
30
|
- lib/ext/hash.rb
|
|
31
31
|
- lib/usps/imis.rb
|
|
32
32
|
- lib/usps/imis/api.rb
|
|
33
|
+
- lib/usps/imis/business_object.rb
|
|
33
34
|
- lib/usps/imis/config.rb
|
|
34
|
-
- lib/usps/imis/error/
|
|
35
|
-
- lib/usps/imis/error/
|
|
36
|
-
- lib/usps/imis/error/
|
|
35
|
+
- lib/usps/imis/error/api_error.rb
|
|
36
|
+
- lib/usps/imis/error/mapper_error.rb
|
|
37
|
+
- lib/usps/imis/error/response_error.rb
|
|
37
38
|
- lib/usps/imis/mapper.rb
|
|
38
39
|
- lib/usps/imis/panel/base_panel.rb
|
|
39
40
|
- lib/usps/imis/panel/education.rb
|
|
40
41
|
- lib/usps/imis/panel/vsc.rb
|
|
42
|
+
- lib/usps/imis/requests.rb
|
|
41
43
|
- lib/usps/imis/version.rb
|
|
42
44
|
- spec/lib/usps/imis/api_spec.rb
|
|
45
|
+
- spec/lib/usps/imis/business_object_spec.rb
|
|
43
46
|
- spec/lib/usps/imis/config_spec.rb
|
|
44
|
-
- spec/lib/usps/imis/error/
|
|
45
|
-
- spec/lib/usps/imis/error/
|
|
47
|
+
- spec/lib/usps/imis/error/api_error_spec.rb
|
|
48
|
+
- spec/lib/usps/imis/error/response_error_spec.rb
|
|
46
49
|
- spec/lib/usps/imis/mapper_spec.rb
|
|
47
50
|
- spec/lib/usps/imis/panel/base_panel_spec.rb
|
|
48
51
|
- spec/lib/usps/imis/panel/education_spec.rb
|