usps-imis-api 0.11.23 → 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 -204
  41. data/lib/usps/imis/command_line/options_parser.rb +0 -136
  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
@@ -2,12 +2,7 @@
2
2
 
3
3
  module Usps
4
4
  module Imis
5
- # Specific known fields mapping to facilitate updating across multiple
6
- # business objects.
7
- #
8
5
  class Mapper
9
- # List of known mapped fields
10
- #
11
6
  FIELD_MAPPING = {
12
7
  grade: %w[ABC_ASC_Individual_Demog Grade],
13
8
  edpro: %w[ABC_ASC_Individual_Demog Educ_Proficiency],
@@ -17,85 +12,19 @@ module Usps
17
12
  mm_updated: %w[ABC_ASC_Individual_Demog MMS_Updated]
18
13
  }.freeze
19
14
 
20
- # The parent +Api+ object
21
- #
22
15
  attr_reader :api
23
16
 
24
- # Tagged logger
25
- #
26
- attr_reader :logger
27
-
28
- # A new instance of +Mapper+
29
- #
30
17
  def initialize(api = nil, imis_id: nil)
31
18
  @api = api || Api.new
32
19
  @api.imis_id = imis_id if imis_id
33
- @logger ||= Imis.logger('Mapper')
34
20
  end
35
21
 
36
- # Get a member's data for a specific field by arbitrary field name
37
- #
38
- # Does not require knowing which business object / iMIS-specific field name to use
39
- #
40
- # Only available for fields defined in +FIELD_MAPPING+
41
- #
42
- # @param field_key [Symbol] Internal name of the field
43
- #
44
- # @return Value of the field
45
- #
46
- def get_field(field_key)
47
- missing_mapping!(field_key) unless FIELD_MAPPING.key?(field_key.to_sym)
48
-
49
- business_object_name, field = FIELD_MAPPING[field_key]
50
- api.on(business_object_name)[field]
51
- end
52
- alias fetch get_field
53
- alias [] get_field
54
-
55
- # Get specific fields by arbitrary field names
56
- #
57
- # @param names [Array<String>] Internal name of the fields
58
- #
59
- # @return [Array] Values of the field
60
- #
61
- def get_fields(*fields)
62
- business_objects = {}
63
-
64
- fields.map do |field_key|
65
- business_object_name, field = FIELD_MAPPING[field_key.to_sym]
66
-
67
- business_objects[business_object_name] ||= []
68
- business_objects[business_object_name] << field
69
- end
70
-
71
- business_objects.flat_map do |business_object_name, fields|
72
- api.on(business_object_name).get_fields(*fields)
73
- end
74
- end
75
- alias fetch_all get_fields
76
-
77
- # Update a member's data for a specific field by arbitrary field name
78
- #
79
- # Does not require knowing which business object / iMIS-specific field name to use
80
- #
81
- # Only available for fields defined in +FIELD_MAPPING+
82
- #
83
- # @param field_key [Symbol] Internal name of the field
84
- #
85
- # @return Value of the field
86
- #
87
- def put_field(field_key, value) = update(field_key => value)
88
- alias []= put_field
89
-
90
22
  # Update a member's data on multiple affected business objects by arbitrary field names
91
- #
92
23
  # Does not require knowing which business object / iMIS-specific field name to use
93
24
  #
94
- # Only available for fields defined in +FIELD_MAPPING+
95
- #
96
- # @param data [Hash] Conforms to pattern +{ field_key => value }+
25
+ # Only available for previously-mapped fields
97
26
  #
98
- # @return [Array<Usps::Imis::Data>] Response data from the API for each internal update request
27
+ # `data` is a hash of shape { field_key => value }
99
28
  #
100
29
  def update(data)
101
30
  updates = data.each_with_object({}) do |(field_key, value), hash|
@@ -106,35 +35,37 @@ module Usps
106
35
  end
107
36
 
108
37
  updates.map do |business_object_name, field_updates|
109
- api.on(business_object_name).put_fields(field_updates)
38
+ api.put_fields(business_object_name, field_updates)
110
39
  end
111
40
  end
112
41
 
113
- # Ruby 3.5 instance variable filter
114
- #
115
- def instance_variables_to_inspect = instance_variables - %i[@api @logger]
116
-
117
42
  private
118
43
 
119
- def map_update(field_key)
120
- missing_mapping!(field_key) unless FIELD_MAPPING.key?(field_key.to_sym)
121
-
122
- business_object_name, field = FIELD_MAPPING[field_key.to_sym]
123
- yield(business_object_name, field)
44
+ def map_update(field_name)
45
+ if FIELD_MAPPING.key?(field_name.to_sym)
46
+ business_object_name, field = FIELD_MAPPING[field_name.to_sym]
47
+ yield(business_object_name, field)
48
+ else
49
+ missing_mapping(field_name)
50
+ end
124
51
  end
125
52
 
126
- def missing_mapping!(field_key)
53
+ def missing_mapping(field_name)
127
54
  unless ENV['TESTING']
128
55
  # :nocov:
129
- "Mapper does not know how to handle field \"#{field_key}\".\n\n" \
130
- 'You can use api.put_fields(business_object_name, { field_name => value }) ' \
131
- "if you know the business object and iMIS-specific field name.\n\n"
132
- .split("\n")
133
- .each { logger.warn(it) }
56
+ warn(
57
+ "Mapper does not know how to handle field \"#{field_name}\".\n\n" \
58
+ 'You can use api.put_fields(business_object_name, { field_name => value }) ' \
59
+ "if you know the business object and iMIS-specific field name.\n\n"
60
+ )
134
61
  # :nocov:
135
62
  end
136
63
 
137
- raise Errors::MapperError.new({ field_key: })
64
+ raise(
65
+ Error::Mapper,
66
+ "Unrecognized field: \"#{field_name}\". " \
67
+ 'Please report what data you are attempting to work with to ITCom leadership.'
68
+ )
138
69
  end
139
70
  end
140
71
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ module Panel
6
+ class BasePanel
7
+ attr_reader :api
8
+
9
+ def initialize(api = nil, imis_id: nil)
10
+ @api = api || Api.new
11
+ @api.imis_id = imis_id if imis_id
12
+ end
13
+
14
+ def get(ordinal)
15
+ api.get(business_object, url_id: "~#{api.imis_id}|#{ordinal}")
16
+ end
17
+
18
+ def create(data)
19
+ api.post(business_object, payload(data), url_id: '')
20
+ end
21
+
22
+ def update(data)
23
+ api.put(business_object, payload(data), url_id: "~#{api.imis_id}|#{data[:ordinal]}")
24
+ end
25
+
26
+ def destroy(ordinal)
27
+ api.delete(business_object, url_id: "~#{api.imis_id}|#{ordinal}")
28
+ end
29
+
30
+ private
31
+
32
+ def business_object
33
+ raise Error::Api, "#{self.class.name} must implement #business_object"
34
+ end
35
+
36
+ def payload(_data)
37
+ raise Error::Api, "#{self.class.name} must implement #payload(data)"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ module Panel
6
+ class Education < BasePanel
7
+ private
8
+
9
+ def business_object
10
+ 'ABC_ASC_Educ'
11
+ end
12
+
13
+ # rubocop:disable Metrics/MethodLength
14
+ def payload(data)
15
+ identity_type =
16
+ 'System.Collections.ObjectModel.Collection`1[[System.String, mscorlib]], mscorlib'
17
+
18
+ {
19
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericEntityData, Asi.Contracts',
20
+ 'EntityTypeName' => 'ABC_ASC_EDUC',
21
+ 'PrimaryParentEntityTypeName' => 'Party',
22
+ 'Identity' => {
23
+ '$type' => 'Asi.Soa.Core.DataContracts.IdentityData, Asi.Contracts',
24
+ 'EntityTypeName' => 'ABC_ASC_EDUC',
25
+ 'IdentityElements' => {
26
+ '$type' => identity_type,
27
+ '$values' => [api.imis_id, data[:ordinal]&.to_s].compact
28
+ }
29
+ },
30
+ 'PrimaryParentIdentity' => {
31
+ '$type' => 'Asi.Soa.Core.DataContracts.IdentityData, Asi.Contracts',
32
+ 'EntityTypeName' => 'Party',
33
+ 'IdentityElements' => {
34
+ '$type' => identity_type,
35
+ '$values' => [api.imis_id]
36
+ }
37
+ },
38
+ 'Properties' => {
39
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyDataCollection, Asi.Contracts',
40
+ '$values' => [
41
+ {
42
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
43
+ 'Name' => 'ID',
44
+ 'Value' => api.imis_id
45
+ },
46
+ (
47
+ if data[:ordinal]
48
+ {
49
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
50
+ 'Name' => 'Ordinal',
51
+ 'Value' => {
52
+ '$type' => 'System.Int32',
53
+ '$value' => data[:ordinal]
54
+ }
55
+ }
56
+ end
57
+ ),
58
+ {
59
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
60
+ 'Name' => 'ABC_EDUC_THRU_DATE',
61
+ 'Value' => data[:thru_date] || '0001-01-01T00:00:00'
62
+ },
63
+ {
64
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
65
+ 'Name' => 'ABC_ECertificate',
66
+ 'Value' => data[:certificate]
67
+ },
68
+ {
69
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
70
+ 'Name' => 'ABC_Educ_Description',
71
+ 'Value' => data[:description]
72
+ },
73
+ {
74
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
75
+ 'Name' => 'ABC_Educ_Effective_Date',
76
+ 'Value' => data[:effective_date]
77
+ },
78
+ {
79
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
80
+ 'Name' => 'ABC_Educ_Source_System',
81
+ 'Value' => data[:source]
82
+ },
83
+ {
84
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
85
+ 'Name' => 'ABC_Educ_Transaction_Date',
86
+ 'Value' => Time.now.strftime('%Y-%m-%dT%H:%I:%S')
87
+ },
88
+ {
89
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
90
+ 'Name' => 'ABC_Other_Code',
91
+ 'Value' => data[:code]
92
+ },
93
+ {
94
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
95
+ 'Name' => 'ABC_Product_Code',
96
+ 'Value' => data[:type_code]
97
+ },
98
+ {
99
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
100
+ 'Name' => 'ABC_TYPE',
101
+ 'Value' => data[:abc_type_code] || 'EDUC'
102
+ }
103
+ ].compact
104
+ }
105
+ }
106
+ end
107
+ # rubocop:enable Metrics/MethodLength
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Usps
4
+ module Imis
5
+ module Panel
6
+ class Vsc < BasePanel
7
+ private
8
+
9
+ def business_object
10
+ 'ABC_ASC_Vessel_Safety_Checks'
11
+ end
12
+
13
+ # rubocop:disable Metrics/MethodLength
14
+ def payload(data)
15
+ identity_type =
16
+ 'System.Collections.ObjectModel.Collection`1[[System.String, mscorlib]], mscorlib'
17
+
18
+ {
19
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericEntityData, Asi.Contracts',
20
+ 'EntityTypeName' => 'ABC_ASC_Vessel_Safety_Checks',
21
+ 'PrimaryParentEntityTypeName' => 'Party',
22
+ 'Identity' => {
23
+ '$type' => 'Asi.Soa.Core.DataContracts.IdentityData, Asi.Contracts',
24
+ 'EntityTypeName' => 'ABC_ASC_Vessel_Safety_Checks',
25
+ 'IdentityElements' => {
26
+ '$type' => identity_type,
27
+ '$values' => [api.imis_id, data[:ordinal]&.to_s].compact
28
+ }
29
+ },
30
+ 'PrimaryParentIdentity' => {
31
+ '$type' => 'Asi.Soa.Core.DataContracts.IdentityData, Asi.Contracts',
32
+ 'EntityTypeName' => 'Party',
33
+ 'IdentityElements' => {
34
+ '$type' => identity_type,
35
+ '$values' => [api.imis_id]
36
+ }
37
+ },
38
+ 'Properties' => {
39
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyDataCollection, Asi.Contracts',
40
+ '$values' => [
41
+ {
42
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
43
+ 'Name' => 'ID',
44
+ 'Value' => api.imis_id
45
+ },
46
+ (
47
+ if data[:ordinal]
48
+ {
49
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
50
+ 'Name' => 'Ordinal',
51
+ 'Value' => {
52
+ '$type' => 'System.Int32',
53
+ '$value' => data[:ordinal]
54
+ }
55
+ }
56
+ end
57
+ ),
58
+ {
59
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
60
+ 'Name' => 'Source_System',
61
+ 'Value' => 'Manual ITCom Entry'
62
+ },
63
+ {
64
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
65
+ 'Name' => 'ABC_ECertificate',
66
+ 'Value' => data[:certificate]
67
+ },
68
+ {
69
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
70
+ 'Name' => 'Activity_Type',
71
+ 'Value' => 'VSC'
72
+ },
73
+ {
74
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
75
+ 'Name' => 'Description',
76
+ 'Value' => 'Vessel Safety Checks'
77
+ },
78
+ {
79
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
80
+ 'Name' => 'Effective_Date',
81
+ 'Value' => "#{data[:year]}-12-01T00:00:00"
82
+ },
83
+ {
84
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
85
+ 'Name' => 'Quantity',
86
+ 'Value' => {
87
+ '$type' => 'System.Int32',
88
+ '$value' => data[:count]
89
+ }
90
+ },
91
+ {
92
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
93
+ 'Name' => 'Thru_Date',
94
+ 'Value' => "#{data[:year]}-12-31T00:00:00"
95
+ },
96
+ {
97
+ '$type' => 'Asi.Soa.Core.DataContracts.GenericPropertyData, Asi.Contracts',
98
+ 'Name' => 'Transaction_Date',
99
+ 'Value' => Time.now.strftime('%Y-%m-%dT%H:%M:%S')
100
+ }
101
+ ].compact
102
+ }
103
+ }
104
+ end
105
+ # rubocop:enable Metrics/MethodLength
106
+ end
107
+ end
108
+ end
109
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Usps
4
4
  module Imis
5
- VERSION = '0.11.23'
5
+ VERSION = '1.0.0-rc.1'
6
6
  end
7
7
  end
data/lib/usps/imis.rb CHANGED
@@ -3,59 +3,44 @@
3
3
  # Core dependencies
4
4
  require 'net/https'
5
5
  require 'json'
6
- require 'yaml'
7
6
  require 'time'
8
7
  require 'cgi'
9
8
 
10
- require 'active_support/core_ext/string/inflections'
11
- require 'active_support/core_ext/object/to_query'
12
- require 'active_support/core_ext/enumerable'
13
- require 'active_support/string_inquirer'
14
- require 'logger'
15
- require 'active_support/isolated_execution_state' # Fix costant loading issue with TaggedLogging
16
- require 'active_support/tagged_logging'
17
-
18
- require 'dotenv/load'
9
+ # Extensions
10
+ # :nocov:
11
+ require 'ext/hash' unless defined?(Rails)
12
+ # :nocov:
19
13
 
20
14
  # Internal requires
21
15
  require_relative 'imis/config'
22
- require_relative 'imis/error'
16
+ require_relative 'imis/error/api'
17
+ require_relative 'imis/error/mapper'
18
+ require_relative 'imis/error/response'
23
19
  require_relative 'imis/api'
24
- require_relative 'imis/properties'
25
- require_relative 'imis/panels'
26
- require_relative 'imis/mocks'
27
- require_relative 'imis/version'
20
+ require_relative 'imis/mapper'
21
+ require_relative 'imis/panel/base_panel'
22
+ require_relative 'imis/panel/vsc'
23
+ require_relative 'imis/panel/education'
28
24
 
29
25
  module Usps
30
- # API wrapper for interacting with iMIS
31
- #
32
26
  module Imis
33
27
  class << self
34
- # Accessor for configuration values
35
- #
36
- # @return The configuration object
37
- #
38
28
  def configuration
39
29
  @configuration ||= Config.new
40
30
  end
41
- alias config configuration
42
31
 
43
- # Used to define a block of configuration settings
44
- #
45
- # @return The updated configuration object
46
- #
47
32
  def configure
48
33
  yield(configuration) if block_given?
49
34
  configuration
50
35
  end
51
36
 
52
- # Logger (with optional nested tags) to write to
53
- #
54
- def logger(*) = configuration.logger.tagged(*)
37
+ # def mock!(value = true)
38
+ # @mock = value
39
+ # end
55
40
 
56
- # Gem version
57
- #
58
- def version = Gem::Version.new(VERSION)
41
+ # def mock
42
+ # @mock || false
43
+ # end
59
44
  end
60
45
  end
61
46
  end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Usps::Imis::Api do
6
+ let(:api) { described_class.new }
7
+
8
+ describe '#initialize' do
9
+ it 'skips authentication' do
10
+ # rubocop:disable RSpec/AnyInstance
11
+ expect_any_instance_of(described_class).not_to receive(:authenticate)
12
+ # rubocop:enable RSpec/AnyInstance
13
+
14
+ described_class.new(skip_authentication: true)
15
+ end
16
+
17
+ it 'stores the initial imis_id' do
18
+ api = described_class.new(imis_id: 42)
19
+
20
+ expect(api.imis_id).to eq('42')
21
+ end
22
+ end
23
+
24
+ describe '#imis_id_for' do
25
+ it 'gets the iMIS ID' do
26
+ expect(api.imis_id_for('E231625')).to eq('31092')
27
+ end
28
+
29
+ context 'with a query error' do
30
+ before { allow(api).to receive(:query).and_raise('Stub') }
31
+
32
+ it 'wraps errors' do
33
+ expect { api.imis_id_for('E231625') }.to raise_error(
34
+ Usps::Imis::Error::Api, 'Member not found'
35
+ )
36
+ end
37
+ end
38
+ end
39
+
40
+ describe '#put' do
41
+ before { api.imis_id = 31092 }
42
+
43
+ it 'sends an update' do
44
+ expect(api.put_fields('ABC_ASC_Individual_Demog', { 'TotMMS' => 15 })).to be_a(Hash)
45
+ end
46
+
47
+ context 'when receiving a response error' do
48
+ let(:warning_text) do
49
+ <<~WARNING.chomp
50
+ Usps::Imis::Error::Response: [INTERNAL_SERVER_ERROR] The iMIS API returned an error.
51
+ Something went wrong
52
+ WARNING
53
+ end
54
+
55
+ before do
56
+ error = Struct.new(:code, :body).new('500', 'Something went wrong')
57
+
58
+ # rubocop:disable RSpec/AnyInstance
59
+ allow_any_instance_of(Net::HTTP).to receive(:request).and_return(error)
60
+ # rubocop:enable RSpec/AnyInstance
61
+ end
62
+
63
+ it 'wraps the error' do
64
+ expect { api.put_fields('ABC_ASC_Individual_Demog', { 'TotMMS' => 15 }) }.to raise_error(
65
+ Usps::Imis::Error::Api, warning_text
66
+ )
67
+ end
68
+ end
69
+ end
70
+
71
+ describe '#with' do
72
+ it 'sends an update from put' do
73
+ expect(
74
+ api.with(31092) { put_fields('ABC_ASC_Individual_Demog', { 'TotMMS' => 15 }) }
75
+ ).to be_a(Hash)
76
+ end
77
+
78
+ it 'sends an update from update' do
79
+ expect(api.with(31092) { update(mm: 15) }.first).to be_a(Hash)
80
+ end
81
+
82
+ it 'uses a panel correctly' do
83
+ expect(api.with(6374) { panels.vsc.get(1433) }).to be_a(Hash)
84
+ end
85
+ end
86
+
87
+ describe '#inspect' do
88
+ it 'is configured to exclude the token instance variable' do
89
+ expect(api.instance_variables_to_inspect).not_to include(:@token)
90
+ end
91
+
92
+ it 'does not show the token instance variable' do
93
+ pending 'Requires Ruby 3.5' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new(3.5)
94
+
95
+ expect(api.inspect).not_to match(/ @token="/)
96
+ end
97
+ end
98
+
99
+ describe '#authorize' do
100
+ before { allow(api).to receive(:authenticate) }
101
+
102
+ it 'automatically refreshes an expired token' do
103
+ api.instance_variable_set(:@token_expiration, Time.now - 60)
104
+
105
+ request = Net::HTTP::Put.new('/')
106
+ api.send(:authorize, request)
107
+
108
+ expect(api).to have_received(:authenticate)
109
+ end
110
+ 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
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Usps::Imis::Config do
6
+ let(:config) { described_class.new }
7
+
8
+ it 'sets values on initialize' do
9
+ config = described_class.new { |c| c.environment = 'test' }
10
+
11
+ expect(config.environment).to eq('test')
12
+ end
13
+
14
+ describe '#hostname' do
15
+ context 'with production environment' do
16
+ before { config.environment = 'production' }
17
+
18
+ it 'returns the production hostname' do
19
+ expect(config.hostname).to eq(described_class::IMIS_ROOT_URL_PROD)
20
+ end
21
+ end
22
+
23
+ context 'with unrecognized environment' do
24
+ before { config.environment = 'nothing' }
25
+
26
+ it 'raises an error' do
27
+ expect { config.hostname }.to raise_error(
28
+ Usps::Imis::Error::Api, 'Unexpected API environment: nothing'
29
+ )
30
+ end
31
+ end
32
+ end
33
+ end