usps-imis-api 0.11.22 → 1.0.0.pre.rc.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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/main.yml +57 -0
  3. data/.gitignore +4 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +88 -0
  6. data/.ruby-version +1 -0
  7. data/.simplecov +8 -0
  8. data/Gemfile +12 -0
  9. data/Gemfile.lock +95 -0
  10. data/Rakefile +12 -0
  11. data/Readme.md +191 -19
  12. data/bin/console +21 -0
  13. data/bin/setup +8 -0
  14. data/lib/ext/hash.rb +10 -0
  15. data/lib/usps/imis/api.rb +138 -177
  16. data/lib/usps/imis/config.rb +10 -68
  17. data/lib/usps/imis/error/api.rb +26 -0
  18. data/lib/usps/imis/error/mapper.rb +9 -0
  19. data/lib/usps/imis/{errors/response_error.rb → error/response.rb} +7 -34
  20. data/lib/usps/imis/mapper.rb +21 -90
  21. data/lib/usps/imis/panel/base_panel.rb +42 -0
  22. data/lib/usps/imis/panel/education.rb +111 -0
  23. data/lib/usps/imis/panel/vsc.rb +109 -0
  24. data/lib/usps/imis/version.rb +1 -1
  25. data/lib/usps/imis.rb +17 -32
  26. data/spec/lib/usps/imis/api_spec.rb +143 -0
  27. data/spec/lib/usps/imis/config_spec.rb +33 -0
  28. data/spec/lib/usps/imis/error/api_spec.rb +17 -0
  29. data/spec/lib/usps/imis/error/response_spec.rb +107 -0
  30. data/spec/lib/usps/imis/mapper_spec.rb +31 -0
  31. data/spec/lib/usps/imis/panel/base_panel_spec.rb +32 -0
  32. data/spec/lib/usps/imis/panel/education_spec.rb +55 -0
  33. data/spec/lib/usps/imis/panel/vsc_spec.rb +38 -0
  34. data/spec/lib/usps/imis_spec.rb +11 -0
  35. data/spec/spec_helper.rb +35 -0
  36. data/usps-imis-api.gemspec +18 -0
  37. metadata +33 -94
  38. data/bin/imis +0 -8
  39. data/lib/usps/imis/business_object.rb +0 -209
  40. data/lib/usps/imis/command_line/interface.rb +0 -205
  41. data/lib/usps/imis/command_line/options_parser.rb +0 -132
  42. data/lib/usps/imis/command_line.rb +0 -15
  43. data/lib/usps/imis/data.rb +0 -76
  44. data/lib/usps/imis/error.rb +0 -55
  45. data/lib/usps/imis/errors/api_error.rb +0 -11
  46. data/lib/usps/imis/errors/command_line_error.rb +0 -11
  47. data/lib/usps/imis/errors/config_error.rb +0 -11
  48. data/lib/usps/imis/errors/locked_id_error.rb +0 -15
  49. data/lib/usps/imis/errors/mapper_error.rb +0 -29
  50. data/lib/usps/imis/errors/missing_id_error.rb +0 -15
  51. data/lib/usps/imis/errors/not_found_error.rb +0 -11
  52. data/lib/usps/imis/errors/panel_unimplemented_error.rb +0 -34
  53. data/lib/usps/imis/errors/unexpected_property_type_error.rb +0 -31
  54. data/lib/usps/imis/logger.rb +0 -19
  55. data/lib/usps/imis/logger_formatter.rb +0 -32
  56. data/lib/usps/imis/logger_helpers.rb +0 -17
  57. data/lib/usps/imis/mocks/business_object.rb +0 -47
  58. data/lib/usps/imis/mocks.rb +0 -11
  59. data/lib/usps/imis/panels/base_panel.rb +0 -158
  60. data/lib/usps/imis/panels/education.rb +0 -33
  61. data/lib/usps/imis/panels/vsc.rb +0 -32
  62. data/lib/usps/imis/panels.rb +0 -25
  63. data/lib/usps/imis/properties.rb +0 -50
  64. data/lib/usps/imis/query.rb +0 -153
  65. data/lib/usps/imis/requests.rb +0 -68
  66. data/spec/support/usps/vcr/config.rb +0 -47
  67. data/spec/support/usps/vcr/filters.rb +0 -89
  68. data/spec/support/usps/vcr.rb +0 -8
@@ -1,153 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Usps
4
- module Imis
5
- # API wrapper for IQA and Business Object Queries
6
- #
7
- class Query
8
- include Enumerable
9
- include Requests
10
-
11
- # Endpoint for IQA query requests
12
- #
13
- IQA_PATH = 'api/Query'
14
-
15
- # The parent +Api+ object
16
- #
17
- attr_reader :api
18
-
19
- # Name of the Query to run
20
- #
21
- attr_reader :query_name
22
-
23
- # Parameters for the Query
24
- #
25
- attr_reader :query_params
26
-
27
- # Current page size for paging through the Query
28
- #
29
- attr_accessor :page_size
30
-
31
- # Current offset for paging through the Query
32
- #
33
- attr_accessor :offset
34
-
35
- # Count of records processed
36
- #
37
- attr_reader :count
38
-
39
- # Whether the current query has a next page
40
- #
41
- attr_reader :next_page
42
-
43
- # Tagged logger
44
- #
45
- attr_reader :logger
46
-
47
- # A new instance of +Query+
48
- #
49
- # @param api [Api] Parent to use for making requests
50
- # @param query_name [String] Full path of the query in IQA, e.g. +$/_ABC/Fiander/iMIS_ID+
51
- # @param page_size [Integer] Number of records to return on each request page
52
- # @param offset [Integer] Offset index of records to return on next request page
53
- # @param query_params [Hash] Conforms to pattern +{ param_name => param_value }+
54
- #
55
- def initialize(api, query_name, page_size: 100, offset: nil, **query_params)
56
- @api = api
57
- @query_name = query_name
58
- @query_params = query_params
59
- @page_size = page_size
60
- @offset = offset
61
- @count = 0
62
- @logger ||= Imis.logger('Query', query_type)
63
-
64
- logger.tagged('Name').debug query_name
65
- logger.tagged('Params').json query_params
66
- logger.tagged('URI').debug uri
67
- logger.tagged('Page Size').debug page_size
68
- logger.tagged('Offset').debug offset.to_i
69
- end
70
-
71
- # Iterate through all results from the query
72
- #
73
- def each(&)
74
- logger.info 'Running'
75
-
76
- items = []
77
- find_each { items << it }
78
- items.each(&)
79
- end
80
-
81
- # Iterate through all results from the query, fetching one page at a time
82
- #
83
- def find_each(&)
84
- reset!
85
- page.each(&) while page?
86
- nil
87
- end
88
-
89
- # Fetch a filtered query page, and update the current offset
90
- #
91
- def page = fetch_next['Items']['$values'].map { iqa? ? it.except('$type') : Imis::Data[it] }
92
-
93
- # Fetch the next raw query page, and update the current offset
94
- #
95
- def fetch_next
96
- return unless page?
97
-
98
- logger.info "Fetching #{query_type} Query page"
99
-
100
- result = fetch
101
-
102
- @count += result['Count'] || 0
103
- total = result['TotalCount']
104
- logger.info "#{@count} / #{total} #{'item'.pluralize(total)}"
105
- logger.debug 'Query page data:'
106
- logger.json result
107
-
108
- @offset = result['NextOffset']
109
- @next_page = result['HasNext']
110
-
111
- result
112
- end
113
-
114
- # Fetch a raw query page
115
- #
116
- def fetch = JSON.parse(submit(uri, authorize(http_get)).body)
117
-
118
- # Reset query paging progress
119
- #
120
- def reset!
121
- return if next_page.nil?
122
-
123
- logger.debug 'Resetting progress'
124
-
125
- @count = 0
126
- @offset = 0
127
- @next_page = nil
128
- end
129
-
130
- # Ruby 3.5 instance variable filter
131
- #
132
- def instance_variables_to_inspect = instance_variables - %i[@api @logger]
133
-
134
- private
135
-
136
- # Only skip if explicitly set
137
- def page? = next_page.nil? || next_page
138
-
139
- def iqa? = query_name.match?(/^\$/)
140
- def query_type = iqa? ? :IQA : :'Business Object'
141
- def path_params = query_params.merge({ Offset: offset, Limit: page_size }.compact)
142
- def uri = URI(File.join(Imis.configuration.hostname, path))
143
-
144
- def path
145
- if iqa?
146
- "#{IQA_PATH}?#{path_params.merge(QueryName: query_name).to_query}"
147
- else
148
- "#{Imis::BusinessObject::API_PATH}/#{query_name}?#{path_params.to_query}"
149
- end
150
- end
151
- end
152
- end
153
- end
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Usps
4
- module Imis
5
- # @private
6
- #
7
- module Requests
8
- private
9
-
10
- def logger = raise("#{self.class.name} must implement #logger")
11
-
12
- def token = api.token
13
- def token_expiration = api.token_expiration
14
- def authenticate = api.send(:authenticate)
15
-
16
- def http_get = Net::HTTP::Get.new(uri)
17
- def http_put = Net::HTTP::Put.new(uri)
18
- def http_post = Net::HTTP::Post.new(uri(id: ''))
19
- def http_delete = Net::HTTP::Delete.new(uri)
20
-
21
- def client(uri)
22
- Net::HTTP.new(uri.host, uri.port).tap do |http|
23
- http.use_ssl = true
24
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
25
- end
26
- end
27
-
28
- # Authorize a request prior to submitting
29
- #
30
- # If the current token is missing/expired, request a new one
31
- #
32
- def authorize(request)
33
- if token_expiration.nil? || token_expiration < Time.now
34
- logger.debug 'Token expired: re-authenticating with iMIS'
35
- authenticate
36
- end
37
- request.tap { it.add_field('Authorization', "Bearer #{token}") }
38
- end
39
-
40
- def submit(uri, request)
41
- logger.info 'Submitting request to iMIS'
42
- logger.tagged('Request') do
43
- logger.debug "#{request.class.name.demodulize.upcase} #{uri}"
44
- logger.multiline sanitized_request_body(request)
45
-
46
- client(uri).request(request).tap do |result|
47
- raise Errors::ResponseError.from(result) unless result.is_a?(Net::HTTPSuccess)
48
-
49
- logger.info 'Request succeeded'
50
- end
51
- end
52
- end
53
-
54
- def sanitized_request_body(request)
55
- return '*** empty request body ***' if request.body.nil?
56
-
57
- body = request.body.dup
58
-
59
- Imis.config.filtered_parameters.each do |parameter|
60
- body.gsub!(Imis.config.public_send(parameter), '[FILTERED]')
61
- body.gsub!(CGI.escape(Imis.config.public_send(parameter)), '[FILTERED]')
62
- end
63
-
64
- body
65
- end
66
- end
67
- end
68
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Usps
4
- module Vcr
5
- module Config
6
- class << self
7
- def configure!
8
- WebMock.disable_net_connect!
9
-
10
- VCR.configure do |config|
11
- config.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
12
- config.hook_into :webmock
13
- default_options(config)
14
- config.configure_rspec_metadata!
15
- apply_filters(config)
16
-
17
- yield(config) if block_given?
18
- end
19
- end
20
-
21
- def vcr_record_ordered
22
- ENV['VCR'] == 'all' ? :defined : :random
23
- end
24
-
25
- private
26
-
27
- def default_options(config)
28
- config.default_cassette_options = {
29
- record: ENV['VCR'] ? ENV['VCR'].to_sym : :once,
30
- match_requests_on: %i[method uri]
31
- }
32
- end
33
-
34
- def apply_filters(config)
35
- Filters.apply!(
36
- config,
37
- *%i[
38
- username password access_token bearer_token
39
- ignore_response_headers cf_ray cookie date
40
- issued expires
41
- ]
42
- )
43
- end
44
- end
45
- end
46
- end
47
- end
@@ -1,89 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Usps
4
- module Vcr
5
- module Filters
6
- class << self
7
- def apply!(config, *filters)
8
- filters.each { public_send(it, config) }
9
- end
10
-
11
- def username(config)
12
- value = Usps::Imis.config.username || '<USERNAME>'
13
- config.filter_sensitive_data('<USERNAME>') { value }
14
- config.filter_sensitive_data('<USERNAME>') { CGI.escape(value) }
15
- config.filter_sensitive_data('<USERNAME>') { value.upcase }
16
- config.filter_sensitive_data('<USERNAME>') { CGI.escape(value).upcase }
17
- end
18
-
19
- def password(config)
20
- value = Usps::Imis.config.password || '<PASSWORD>'
21
- config.filter_sensitive_data('<PASSWORD>') { value }
22
- config.filter_sensitive_data('<PASSWORD>') { CGI.escape(value) }
23
- end
24
-
25
- def access_token(config)
26
- filter_json_field(config, 'access_token', '<ACCESS_TOKEN>')
27
- end
28
-
29
- def bearer_token(config)
30
- config.filter_sensitive_data('<BEARER_TOKEN>') do |interaction|
31
- if interaction.request.headers['Authorization']
32
- authorization = interaction.request.headers['Authorization'].first
33
- if (match = authorization.match(/^Bearer\s+([^,\s]+)/))
34
- match.captures.first
35
- end
36
- end
37
- end
38
- end
39
-
40
- def ignore_response_headers(config)
41
- config.before_record do |interaction|
42
- interaction.response.headers.delete('Report-To')
43
- interaction.response.headers.delete('Content-Security-Policy-Report-Only')
44
- end
45
- end
46
-
47
- def cf_ray(config)
48
- config.filter_sensitive_data('<CF_RAY>') do |interaction|
49
- interaction.response.headers['Cf-Ray']&.first
50
- end
51
- end
52
-
53
- def cookie(config)
54
- config.filter_sensitive_data('<COOKIE>') do |interaction|
55
- interaction.response.headers['Set-Cookie']&.first
56
- end
57
- end
58
-
59
- def date(config)
60
- config.filter_sensitive_data('<DATE>') do |interaction|
61
- interaction.response.headers['Date']&.first
62
- end
63
- end
64
-
65
- def issued(config)
66
- filter_json_field(config, '.issued', '<ISSUED>')
67
- end
68
-
69
- def expires(config)
70
- filter_json_field(config, '.expires', '<EXPIRES>')
71
- end
72
-
73
- private
74
-
75
- def filter_json_field(config, field_name, placeholder)
76
- config.filter_sensitive_data(placeholder) do |interaction|
77
- if interaction.response.headers['Content-Type']&.first&.include?('application/json')
78
- begin
79
- JSON.parse(interaction.response.body)[field_name]
80
- rescue JSON::ParserError
81
- nil
82
- end
83
- end
84
- end
85
- end
86
- end
87
- end
88
- end
89
- end
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'vcr'
4
- require 'webmock/rspec'
5
- require 'usps/imis'
6
-
7
- require_relative 'vcr/filters'
8
- require_relative 'vcr/config'