usps-imis-api 0.5.1 → 0.6.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93af4ce91eacb92695d10144b3885da6bd1e8ff31e26f8b22c8ae9404595f951
4
- data.tar.gz: 6c2df89c1531953ef37d8fd867699fc8917d2aca3db3e204cf3937bf679cf7ed
3
+ metadata.gz: 9cbe7660dffdee043ffdbe92a012879ceb439946b737f34fe9ca16d6f34e68b1
4
+ data.tar.gz: 065c486f386c7b73501dda6bf92a0214680e3ab13104388de29613d4bb4d7315
5
5
  SHA512:
6
- metadata.gz: 299a9220c4232cd0a1d45681a4f5c32800c0188e3e86f2740b7106f5ccc256f22dfe15bbafe6bd8c06152b4af9f3434c2cba5e49c632eed5a3a1540a4305ca59
7
- data.tar.gz: 2d0310cea22d9160330cb4d1438679dc17565ab9e43d32dc0db7fd2dea7315a070cbd949bc30e0cde9ecbca09302a5c1e4033c034667fffb52d44462177766a9
6
+ metadata.gz: 67e8a5cf1cb919bccaada7960f1680426491880340f68eb8ed3ed57e5922bfdd6ab98208534367679aaec6b0026c1afb8161e5df73c7fd1dff383293882239b7
7
+ data.tar.gz: 830f0e96d1528bd434a4122511ab15165d27ef42f0383713e10640906395d19742e49b5da8858021fa3a2dbd64be699c5adf2c08f4897a5c3d6b94f8bb3d270f
data/.rubocop.yml CHANGED
@@ -32,8 +32,6 @@ Layout/SpaceInsideHashLiteralBraces:
32
32
  EnforcedStyleForEmptyBraces: no_space
33
33
  Layout/SpaceInsideArrayLiteralBrackets:
34
34
  EnforcedStyle: no_space
35
- Layout/LineLength:
36
- Max: 100
37
35
 
38
36
  Lint/UnusedMethodArgument:
39
37
  Enabled: true
data/Gemfile.lock CHANGED
@@ -1,17 +1,39 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- usps-imis-api (0.5.1)
4
+ usps-imis-api (0.6.1)
5
+ activesupport (~> 8.0)
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
8
9
  specs:
10
+ activesupport (8.0.3)
11
+ base64
12
+ benchmark (>= 0.3)
13
+ bigdecimal
14
+ concurrent-ruby (~> 1.0, >= 1.3.1)
15
+ connection_pool (>= 2.2.5)
16
+ drb
17
+ i18n (>= 1.6, < 2)
18
+ logger (>= 1.4.2)
19
+ minitest (>= 5.1)
20
+ securerandom (>= 0.3)
21
+ tzinfo (~> 2.0, >= 2.0.5)
22
+ uri (>= 0.13.1)
9
23
  ast (2.4.2)
24
+ base64 (0.3.0)
25
+ benchmark (0.4.1)
26
+ bigdecimal (3.3.1)
27
+ concurrent-ruby (1.3.5)
28
+ connection_pool (2.5.4)
10
29
  date (3.4.1)
11
30
  diff-lcs (1.5.1)
12
31
  docile (1.4.1)
13
32
  dotenv (3.1.4)
33
+ drb (2.2.3)
14
34
  erb (5.0.3)
35
+ i18n (1.14.7)
36
+ concurrent-ruby (~> 1.0)
15
37
  io-console (0.8.1)
16
38
  irb (1.15.2)
17
39
  pp (>= 0.6.0)
@@ -19,6 +41,8 @@ GEM
19
41
  reline (>= 0.4.2)
20
42
  json (2.7.2)
21
43
  language_server-protocol (3.17.0.3)
44
+ logger (1.7.0)
45
+ minitest (5.26.0)
22
46
  parallel (1.26.3)
23
47
  parser (3.3.5.0)
24
48
  ast (~> 2.4.1)
@@ -67,6 +91,7 @@ GEM
67
91
  rubocop-rspec (3.1.0)
68
92
  rubocop (~> 1.61)
69
93
  ruby-progressbar (1.13.0)
94
+ securerandom (0.4.1)
70
95
  simplecov (0.22.0)
71
96
  docile (~> 1.1)
72
97
  simplecov-html (~> 0.11)
@@ -75,7 +100,10 @@ GEM
75
100
  simplecov_json_formatter (0.1.4)
76
101
  stringio (3.1.7)
77
102
  tsort (0.2.0)
103
+ tzinfo (2.0.6)
104
+ concurrent-ruby (~> 1.0)
78
105
  unicode-display_width (2.6.0)
106
+ uri (1.0.4)
79
107
 
80
108
  PLATFORMS
81
109
  arm64-darwin-23
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.4.0'
16
+ gem 'usps-imis-api', '~> 0.6.1'
17
17
  ```
18
18
 
19
19
  ## Setup
@@ -25,9 +25,11 @@ require 'dotenv/load' # Optionally load environment variables from `.env` file
25
25
  require 'usps/imis'
26
26
 
27
27
  Usps::Imis.configure do |config|
28
- config.environment = :development # Rails.env
29
- config.imis_id_query_name = ENV['IMIS_ID_QUERY_NAME']
28
+ # This will default to `Rails.env` if available.
29
+ config.environment = :development
30
30
 
31
+ # These options will default to the listed `ENV` variable if available.
32
+ config.imis_id_query_name = ENV['IMIS_ID_QUERY_NAME']
31
33
  config.username = ENV['IMIS_USERNAME']
32
34
  config.password = ENV['IMIS_PASSWORD']
33
35
  end
@@ -82,7 +84,7 @@ To fetch member data, run e.g.:
82
84
  ```ruby
83
85
  api.imis_id = 31092
84
86
 
85
- data = api.get('ABC_ASC_Individual_Demog')
87
+ data = api.on('ABC_ASC_Individual_Demog').get
86
88
  ```
87
89
 
88
90
  ### PUT Fields
@@ -93,7 +95,7 @@ To update member data, run e.g.:
93
95
  api.imis_id = 31092
94
96
 
95
97
  data = { 'MMS_Updated' => Time.now.strftime('%Y-%m-%dT%H:%M:%S'), 'TotMMS' => new_total }
96
- update = api.put_fields('ABC_ASC_Individual_Demog', data)
98
+ update = api.on('ABC_ASC_Individual_Demog').put_fields(data)
97
99
  ```
98
100
 
99
101
  This method fetches the current data structure, and filters it down to just what you want to
@@ -106,7 +108,7 @@ To update member data, run e.g.:
106
108
  ```ruby
107
109
  api.imis_id = 31092
108
110
 
109
- update = api.put('ABC_ASC_Individual_Demog', complete_imis_object)
111
+ update = api.on('ABC_ASC_Individual_Demog').put(complete_imis_object)
110
112
  ```
111
113
 
112
114
  This method requires a complete iMIS data structure.
@@ -116,7 +118,7 @@ This method requires a complete iMIS data structure.
116
118
  To create new member data, run e.g.:
117
119
 
118
120
  ```ruby
119
- created = api.post('ABC_ASC_Individual_Demog', complete_imis_object)
121
+ created = api.on('ABC_ASC_Individual_Demog').post(complete_imis_object)
120
122
  ```
121
123
 
122
124
  This method requires a complete iMIS data structure.
@@ -128,7 +130,7 @@ To remove member data, run e.g.:
128
130
  ```ruby
129
131
  api.imis_id = 31092
130
132
 
131
- api.delete('ABC_ASC_Individual_Demog')
133
+ api.on('ABC_ASC_Individual_Demog').delete
132
134
  ```
133
135
 
134
136
  This returns a blank string on success.
@@ -202,9 +204,11 @@ previous value.
202
204
 
203
205
  ```ruby
204
206
  api.with(31092) do
205
- # These three requests are identical:
207
+ # These four requests are identical:
208
+
209
+ on('ABC_ASC_Individual_Demog') { put('TotMMS' => 15) }
206
210
 
207
- put('ABC_ASC_Individual_Demog', { 'TotMMS' => 15 })
211
+ on('ABC_ASC_Individual_Demog').put('TotMMS' => 15)
208
212
 
209
213
  mapper.update(mm: 15)
210
214
 
@@ -220,10 +224,7 @@ end
220
224
 
221
225
  ## Exception Handling
222
226
 
223
- Exception and error response handling will be added later.
224
-
225
- To print exception information to STDERR when raising, set the environment
226
- variable `IMIS_ERROR_LOG_TO_STDERR=true`.
227
+ All internal exceptions inherit from `Usps::Imis::ApiError`.
227
228
 
228
229
  ## Automated Testing and Linting
229
230
 
@@ -239,6 +240,8 @@ Linting is available by running:
239
240
  bundle exec rubocop
240
241
  ```
241
242
 
243
+ 100% branch coverage is enforced on the test suite.
244
+
242
245
  ### GitHub Actions
243
246
 
244
247
  Testing and linting are automatically run on every push.
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'
@@ -52,7 +50,7 @@ module Usps
52
50
  def imis_id=(id)
53
51
  raise Error::ApiError, 'Cannot change iMIS ID while locked' if lock_imis_id
54
52
 
55
- @imis_id = id.to_i.to_s
53
+ @imis_id = id&.to_i&.to_s
56
54
  end
57
55
 
58
56
  # Convert a member's certificate number into an iMIS ID number
@@ -76,6 +74,8 @@ module Usps
76
74
  #
77
75
  # While in this block, changes to the value of +imis_id+ are not allowed
78
76
  #
77
+ # If no block is given, this sets the iMIS ID and returns self.
78
+ #
79
79
  # @param id [Integer, String] iMIS ID to select for requests within the block
80
80
  #
81
81
  # @example
@@ -86,107 +86,68 @@ module Usps
86
86
  def with(id, &)
87
87
  old_id = imis_id
88
88
  self.imis_id = id
89
+ return self unless block_given?
89
90
 
90
91
  @lock_imis_id = true
91
92
  instance_eval(&)
92
93
  ensure
93
- @lock_imis_id = false
94
- self.imis_id = old_id
94
+ if block_given?
95
+ @lock_imis_id = false
96
+ self.imis_id = old_id
97
+ end
95
98
  end
96
99
 
97
- # Get a business object for the current member
100
+ # Run an IQA Query
98
101
  #
99
- # @param business_object_name [String] Name of the business object
100
- # @param url_id [String] Override the ID param of the URL (e.g. used for Panels)
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 }+
101
104
  #
102
105
  # @return [Hash] Response data from the API
103
106
  #
104
- def get(business_object_name, url_id: nil)
105
- uri = uri_for(business_object_name, url_id:)
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))
106
111
  request = Net::HTTP::Get.new(uri)
107
112
  result = submit(uri, authorize(request))
108
113
  JSON.parse(result.body)
109
114
  end
110
115
 
111
- # Update only specific fields on a business object for the current member
116
+ # An instance of +BusinessObject+, using this instance as its parent +Api+
112
117
  #
113
118
  # @param business_object_name [String] Name of the business object
114
- # @param fields [Hash] Conforms to pattern +{ field_key => value }+
115
119
  # @param url_id [String] Override the ID param of the URL (e.g. used for Panels)
116
120
  #
117
- # @return [Hash] Response data from the API
118
- #
119
- def put_fields(business_object_name, fields, url_id: nil)
120
- updated = filter_fields(business_object_name, fields)
121
- 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:)
122
123
  end
123
124
 
124
- # Update a business object for the current member
125
- #
126
- # @param business_object_name [String] Name of the business object
127
- # @param body [Hash] Full raw API object data
128
- # @param url_id [String] Override the ID param of the URL (e.g. used for Panels)
125
+ # Run requests as DSL, with specific +BusinessObject+ only maintained for this scope
129
126
  #
130
- # @return [Hash] Response data from the API
131
- #
132
- def put(business_object_name, body, url_id: nil)
133
- uri = uri_for(business_object_name, url_id:)
134
- request = Net::HTTP::Put.new(uri)
135
- request.body = JSON.dump(body)
136
- result = submit(uri, authorize(request))
137
- JSON.parse(result.body)
138
- end
139
-
140
- # Create a business object for the current member
127
+ # If no block is given, this returns the specified +BusinessObject+.
141
128
  #
142
129
  # @param business_object_name [String] Name of the business object
143
- # @param body [Hash] Full raw API object data
144
130
  # @param url_id [String] Override the ID param of the URL (e.g. used for Panels)
145
131
  #
146
- # @return [Hash] Response data from the API
147
- #
148
- def post(business_object_name, body, url_id: nil)
149
- uri = uri_for(business_object_name, url_id:)
150
- request = Net::HTTP::Post.new(uri)
151
- request.body = JSON.dump(body)
152
- result = submit(uri, authorize(request))
153
- JSON.parse(result.body)
154
- 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?
155
135
 
156
- # Remove a business object for the current member
157
- #
158
- # @param business_object_name [String] Name of the business object
159
- # @param url_id [String] Override the ID param of the URL (e.g. used for Panels)
160
- #
161
- # @return [String] Error response body from the API, or empty string on success
162
- #
163
- def delete(business_object_name, url_id: nil)
164
- uri = uri_for(business_object_name, url_id:)
165
- request = Net::HTTP::Delete.new(uri)
166
- result = submit(uri, authorize(request))
167
- result.body
136
+ result = nil
137
+ object.tap { |obj| result = obj.instance_eval(&) }
138
+ result
168
139
  end
169
140
 
170
- # Run an IQA Query
171
- #
172
- # @param query_name [String] Full path of the query in IQA, e.g. +$/_ABC/Fiander/iMIS_ID+
173
- # @query_params [Hash] Conforms to pattern +{ param_name => param_value }+
141
+ # An instance of +Mapper+, using this instance as its parent +Api+
174
142
  #
175
- # @return [Hash] Response data from the API
176
- #
177
- def query(query_name, query_params = {})
178
- query_params[:QueryName] = query_name
179
- path = "#{QUERY_PATH}?#{query_params.to_query}"
180
- uri = URI(File.join(imis_hostname, path))
181
- request = Net::HTTP::Get.new(uri)
182
- result = submit(uri, authorize(request))
183
- JSON.parse(result.body)
143
+ def mapper
144
+ @mapper ||= Mapper.new(self)
184
145
  end
185
146
 
186
- # An instance of Mapper, using this instance as its parent +Api+
147
+ # Convenience alias for updating mapped fields
187
148
  #
188
- def mapper
189
- @mapper ||= Mapper.new(self)
149
+ def update(data)
150
+ mapper.update(data)
190
151
  end
191
152
 
192
153
  # Convenience accessor for available Panel objects, each using this instance as its parent
@@ -199,56 +160,16 @@ module Usps
199
160
  )
200
161
  end
201
162
 
202
- # Convenience alias for updating mapped fields
203
- #
204
- def update(data)
205
- mapper.update(data)
206
- end
207
-
208
163
  # Ruby 3.5 instance variable filter
209
164
  #
210
165
  def instance_variables_to_inspect = %i[@token_expiration @imis_id]
211
166
 
212
167
  private
213
168
 
214
- def client(uri)
215
- Net::HTTP.new(uri.host, uri.port).tap do |http|
216
- http.use_ssl = true
217
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
218
- end
219
- end
220
-
221
- def imis_hostname
222
- Imis.configuration.hostname
223
- end
224
-
225
- # Authorize a request prior to submitting
226
- #
227
- # If the current token is missing/expired, request a new one
228
- #
229
- def authorize(request)
230
- authenticate if token_expiration < Time.now
231
- request.tap { |r| r.add_field('Authorization', "Bearer #{token}") }
232
- end
233
-
234
- # Construct a business object API endpoint address
235
- #
236
- def uri_for(business_object_name, url_id: nil)
237
- url_id ||= imis_id
238
- url_id = CGI.escape(url_id)
239
- URI(File.join(imis_hostname, "#{API_PATH}/#{business_object_name}/#{url_id}"))
240
- end
241
-
242
- def submit(uri, request)
243
- client(uri).request(request).tap do |result|
244
- raise Error::ResponseError.from(result) unless result.is_a?(Net::HTTPSuccess)
245
- end
246
- end
247
-
248
169
  # Authenticate to the iMIS API, and store the access token and expiration time
249
170
  #
250
171
  def authenticate
251
- uri = URI(File.join(imis_hostname, AUTHENTICATION_PATH))
172
+ uri = URI(File.join(Imis.configuration.hostname, AUTHENTICATION_PATH))
252
173
  req = Net::HTTP::Post.new(uri)
253
174
  authentication_data = {
254
175
  grant_type: 'password',
@@ -262,33 +183,6 @@ module Usps
262
183
  @token = json['access_token']
263
184
  @token_expiration = Time.parse(json['.expires'])
264
185
  end
265
-
266
- # Manually assemble the matching data structure, with fields in the correct order
267
- #
268
- def filter_fields(business_object_name, fields)
269
- existing = get(business_object_name)
270
-
271
- JSON.parse(JSON.dump(existing)).tap do |updated|
272
- # The first property is always the iMIS ID again
273
- updated['Properties']['$values'] = [existing['Properties']['$values'][0]]
274
-
275
- # Iterate through all existing fields
276
- existing['Properties']['$values'].each do |value|
277
- next unless fields.keys.include?(value['Name'])
278
-
279
- # Strings are not wrapped in the type definition structure
280
- new_value = fields[value['Name']]
281
- if new_value.is_a?(String)
282
- value['Value'] = new_value
283
- else
284
- value['Value']['$value'] = new_value
285
- end
286
-
287
- # Add the completed field with the updated value
288
- updated['Properties']['$values'] << value
289
- end
290
- end
291
- end
292
186
  end
293
187
  end
294
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
@@ -8,25 +8,31 @@ module Usps
8
8
  IMIS_ROOT_URL_PROD = 'https://portal.americasboatingclub.org'
9
9
  IMIS_ROOT_URL_DEV = 'https://abcdev.imiscloud.com'
10
10
 
11
- attr_accessor :environment, :imis_id_query_name, :username, :password
11
+ attr_accessor :imis_id_query_name, :username, :password
12
+ attr_reader :environment
12
13
 
13
14
  def initialize
15
+ @environment = defined?(Rails) ? Rails.env : ActiveSupport::StringInquirer.new('development')
16
+ @imis_id_query_name = ENV.fetch('IMIS_ID_QUERY_NAME', nil)
17
+ @username = ENV.fetch('IMIS_USERNAME', nil)
18
+ @password = ENV.fetch('IMIS_PASSWORD', nil)
19
+
14
20
  yield self if block_given?
15
21
  end
16
22
 
23
+ def environment=(env)
24
+ @environment = ActiveSupport::StringInquirer.new(env.to_s)
25
+ end
26
+
17
27
  # Environment-specific API endpoint hostname
18
28
  #
19
29
  # @return The API hostname for the current environment
20
30
  #
21
31
  def hostname
22
- case environment.to_sym
23
- when :production
24
- IMIS_ROOT_URL_PROD
25
- when :development
26
- IMIS_ROOT_URL_DEV
27
- else
28
- raise Error::ApiError, "Unexpected API environment: #{environment}"
29
- end
32
+ return IMIS_ROOT_URL_PROD if environment.production?
33
+ return IMIS_ROOT_URL_DEV if environment.development?
34
+
35
+ raise Error::ApiError, "Unexpected API environment: #{environment}"
30
36
  end
31
37
 
32
38
  # Ruby 3.5 instance variable filter
@@ -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(business_object_name, field_updates)
50
+ api.business_object(business_object_name).put_fields(field_updates)
51
51
  end
52
52
  end
53
53
 
@@ -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.get(business_object, url_id: "~#{api.imis_id}|#{ordinal}")
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.post(business_object, payload(data), url_id: '')
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.put(business_object, payload(data), url_id: "~#{api.imis_id}|#{data[:ordinal]}")
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,7 +47,7 @@ module Usps
45
47
  # @param ordinal [Integer] The ordinal identifier for the desired object
46
48
  #
47
49
  def destroy(ordinal)
48
- api.delete(business_object, url_id: "~#{api.imis_id}|#{ordinal}")
50
+ api.business_object(business_object, url_id: "~#{api.imis_id}|#{ordinal}").delete
49
51
  end
50
52
 
51
53
  private
@@ -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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Usps
4
4
  module Imis
5
- VERSION = '0.5.1'
5
+ VERSION = '0.6.1'
6
6
  end
7
7
  end
data/lib/usps/imis.rb CHANGED
@@ -16,6 +16,8 @@ require_relative 'imis/config'
16
16
  require_relative 'imis/error/api_error'
17
17
  require_relative 'imis/error/mapper_error'
18
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'
@@ -41,7 +41,9 @@ describe Usps::Imis::Api do
41
41
  before { api.imis_id = 31092 }
42
42
 
43
43
  it 'sends an update' do
44
- expect(api.put_fields('ABC_ASC_Individual_Demog', { 'TotMMS' => 15 })).to be_a(Hash)
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
@@ -61,8 +63,8 @@ describe Usps::Imis::Api do
61
63
  end
62
64
 
63
65
  it 'wraps the error' do
64
- expect { api.put_fields('ABC_ASC_Individual_Demog', { 'TotMMS' => 15 }) }.to raise_error(
65
- Usps::Imis::Error::ApiError, warning_text
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) { put_fields('ABC_ASC_Individual_Demog', { 'TotMMS' => 15 }) }
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
 
@@ -96,6 +98,39 @@ describe Usps::Imis::Api do
96
98
  end
97
99
  end
98
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
132
+ end
133
+
99
134
  describe '#inspect' do
100
135
  it 'is configured to exclude the token instance variable' do
101
136
  expect(api.instance_variables_to_inspect).not_to include(:@token)
@@ -120,36 +155,4 @@ describe Usps::Imis::Api do
120
155
  expect(api).to have_received(:authenticate)
121
156
  end
122
157
  end
123
-
124
- describe '#filter_fields' do
125
- let(:expected) do
126
- {
127
- 'Properties' => {
128
- '$values' => [
129
- { 'Name' => 'Stub iMIS ID', 'Value' => { '$value' => '31092' } },
130
- { 'Name' => 'Stub Integer', 'Value' => { '$value' => 43 } },
131
- { 'Name' => 'Stub String', 'Value' => 'other' }
132
- ]
133
- }
134
- }
135
- end
136
-
137
- before do
138
- allow(api).to receive(:get).and_return({
139
- 'Properties' => {
140
- '$values' => [
141
- { 'Name' => 'Stub iMIS ID', 'Value' => { '$value' => '31092' } },
142
- { 'Name' => 'Stub Integer', 'Value' => { '$value' => 42 } },
143
- { 'Name' => 'Stub String', 'Value' => 'something' }
144
- ]
145
- }
146
- })
147
- end
148
-
149
- it 'formats fields correctly' do
150
- updated = api.send(:filter_fields, 'Stub', { 'Stub Integer' => 43, 'Stub String' => 'other' })
151
-
152
- expect(updated).to eq(expected)
153
- end
154
- end
155
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
@@ -5,10 +5,36 @@ require 'spec_helper'
5
5
  describe Usps::Imis::Config do
6
6
  let(:config) { described_class.new }
7
7
 
8
- it 'sets values on initialize' do
9
- config = described_class.new { |c| c.environment = 'test' }
8
+ describe 'environment' do
9
+ subject { config.environment }
10
10
 
11
- expect(config.environment).to eq('test')
11
+ let(:config) { described_class.new }
12
+
13
+ context 'when not specified' do
14
+ it { is_expected.to be_development }
15
+ end
16
+
17
+ context 'when specified' do
18
+ before { config.environment = 'something' }
19
+
20
+ it { is_expected.to be_something }
21
+ end
22
+
23
+ context 'when specified on initialize' do
24
+ subject { config.environment }
25
+
26
+ let(:config) do
27
+ described_class.new { |c| c.environment = 'test' }
28
+ end
29
+
30
+ it { is_expected.to be_test }
31
+ end
32
+
33
+ context 'when in Rails' do
34
+ before { stub_const('Rails', Struct.new(:env).new(ActiveSupport::StringInquirer.new('qa'))) }
35
+
36
+ it { is_expected.to be_qa }
37
+ end
12
38
  end
13
39
 
14
40
  describe '#hostname' do
data/spec/spec_helper.rb CHANGED
@@ -7,6 +7,7 @@ SimpleCov.minimum_coverage(line: 100, branch: 100)
7
7
 
8
8
  require 'dotenv/load'
9
9
  require 'usps/imis'
10
+ require 'active_support/string_inquirer'
10
11
 
11
12
  ENV['TESTING'] = 'true'
12
13
 
@@ -15,4 +15,6 @@ Gem::Specification.new do |s|
15
15
  s.metadata['rubygems_mfa_required'] = 'true'
16
16
 
17
17
  s.required_ruby_version = '>= 3.4'
18
+
19
+ s.add_dependency 'activesupport', '~> 8.0'
18
20
  end
metadata CHANGED
@@ -1,14 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: usps-imis-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julian Fiander
8
8
  bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies: []
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activesupport
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '8.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '8.0'
12
26
  description: A wrapper for the iMIS API.
13
27
  email: jsfiander@gmail.com
14
28
  executables: []
@@ -30,6 +44,7 @@ files:
30
44
  - lib/ext/hash.rb
31
45
  - lib/usps/imis.rb
32
46
  - lib/usps/imis/api.rb
47
+ - lib/usps/imis/business_object.rb
33
48
  - lib/usps/imis/config.rb
34
49
  - lib/usps/imis/error/api_error.rb
35
50
  - lib/usps/imis/error/mapper_error.rb
@@ -38,8 +53,10 @@ files:
38
53
  - lib/usps/imis/panel/base_panel.rb
39
54
  - lib/usps/imis/panel/education.rb
40
55
  - lib/usps/imis/panel/vsc.rb
56
+ - lib/usps/imis/requests.rb
41
57
  - lib/usps/imis/version.rb
42
58
  - spec/lib/usps/imis/api_spec.rb
59
+ - spec/lib/usps/imis/business_object_spec.rb
43
60
  - spec/lib/usps/imis/config_spec.rb
44
61
  - spec/lib/usps/imis/error/api_error_spec.rb
45
62
  - spec/lib/usps/imis/error/response_error_spec.rb