scimitar 1.8.1 → 1.10.0

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +27 -20
  3. data/app/controllers/scimitar/active_record_backed_resources_controller.rb +5 -4
  4. data/app/controllers/scimitar/resource_types_controller.rb +0 -2
  5. data/app/controllers/scimitar/resources_controller.rb +0 -2
  6. data/app/controllers/scimitar/schemas_controller.rb +361 -3
  7. data/app/controllers/scimitar/service_provider_configurations_controller.rb +0 -1
  8. data/app/models/scimitar/engine_configuration.rb +3 -1
  9. data/app/models/scimitar/lists/query_parser.rb +88 -3
  10. data/app/models/scimitar/resources/base.rb +48 -14
  11. data/app/models/scimitar/resources/mixin.rb +531 -71
  12. data/app/models/scimitar/schema/name.rb +2 -2
  13. data/app/models/scimitar/schema/user.rb +10 -10
  14. data/config/initializers/scimitar.rb +41 -0
  15. data/lib/scimitar/engine.rb +57 -12
  16. data/lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb +140 -10
  17. data/lib/scimitar/support/utilities.rb +60 -0
  18. data/lib/scimitar/version.rb +2 -2
  19. data/spec/apps/dummy/app/models/mock_user.rb +18 -3
  20. data/spec/apps/dummy/config/initializers/scimitar.rb +31 -2
  21. data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +1 -0
  22. data/spec/apps/dummy/db/schema.rb +1 -0
  23. data/spec/controllers/scimitar/schemas_controller_spec.rb +342 -54
  24. data/spec/models/scimitar/lists/query_parser_spec.rb +70 -0
  25. data/spec/models/scimitar/resources/base_spec.rb +20 -12
  26. data/spec/models/scimitar/resources/base_validation_spec.rb +16 -3
  27. data/spec/models/scimitar/resources/mixin_spec.rb +754 -122
  28. data/spec/models/scimitar/schema/user_spec.rb +2 -2
  29. data/spec/requests/active_record_backed_resources_controller_spec.rb +312 -5
  30. data/spec/requests/engine_spec.rb +75 -0
  31. data/spec/spec_helper.rb +1 -1
  32. data/spec/support/hash_with_indifferent_case_insensitive_access_spec.rb +108 -0
  33. metadata +22 -22
@@ -81,6 +81,102 @@ RSpec.describe Scimitar::Resources::Mixin do
81
81
  include Scimitar::Resources::Mixin
82
82
  end
83
83
 
84
+ # A simple schema containing two attributes that looks very like complex
85
+ # type "name", except shorter and with "familyName" never returned.
86
+ #
87
+ NestedReturnedNeverTestNameSchema = Class.new(Scimitar::Schema::Base) do
88
+ def self.id
89
+ 'nested-returned-never-name-id'
90
+ end
91
+
92
+ def self.scim_attributes
93
+ @scim_attributes ||= [
94
+ Scimitar::Schema::Attribute.new(name: 'familyName', type: 'string', required: true, returned: 'never'),
95
+ Scimitar::Schema::Attribute.new(name: 'givenName', type: 'string', required: true)
96
+ ]
97
+ end
98
+ end
99
+
100
+ # A complex type that uses the above schema, giving us the ability to define
101
+ # an attribute using this complex type, with therefore the *nested* attribute
102
+ # "familyName" being never returned.
103
+ #
104
+ NestedReturnedNeverTestNameType = Class.new(Scimitar::ComplexTypes::Base) do
105
+ set_schema NestedReturnedNeverTestNameSchema
106
+ end
107
+
108
+ # A test schema that uses the above type, the standard name type (but that
109
+ # *entire* top-level attribute is never returned) and a simple String item.
110
+ #
111
+ NestedReturnedNeverTestSchema = Class.new(Scimitar::Schema::Base) do
112
+ def self.id
113
+ 'nested-returned-never-id'
114
+ end
115
+
116
+ def self.scim_attributes
117
+ [
118
+ Scimitar::Schema::Attribute.new(
119
+ name: 'name', complexType: NestedReturnedNeverTestNameType
120
+ ),
121
+ Scimitar::Schema::Attribute.new(
122
+ name: 'privateName', complexType: Scimitar::ComplexTypes::Name, returned: 'never'
123
+ ),
124
+ Scimitar::Schema::Attribute.new(
125
+ name: 'simpleName', type: 'string'
126
+ )
127
+ ]
128
+ end
129
+ end
130
+
131
+ # Define a resource that is based upon the above schema.
132
+ #
133
+ NestedReturnedNeverTestResourse = Class.new(Scimitar::Resources::Base) do
134
+ set_schema NestedReturnedNeverTestSchema
135
+ end
136
+
137
+ # Create a testable model that is our internal representation of the above
138
+ # resource.
139
+ #
140
+ class NestedReturnedNeverTest
141
+ include ActiveModel::Model
142
+
143
+ def self.scim_resource_type
144
+ return NestedReturnedNeverTestResourse
145
+ end
146
+
147
+ attr_accessor :given_name,
148
+ :last_name,
149
+ :private_given_name,
150
+ :private_last_name,
151
+ :simple_name
152
+
153
+ def self.scim_attributes_map
154
+ return {
155
+ name: {
156
+ givenName: :given_name,
157
+ familyName: :last_name
158
+ },
159
+
160
+ privateName: {
161
+ givenName: :private_given_name,
162
+ familyName: :private_last_name
163
+ },
164
+
165
+ simpleName: :simple_name
166
+ }
167
+ end
168
+
169
+ def self.scim_mutable_attributes
170
+ return nil
171
+ end
172
+
173
+ def self.scim_queryable_attributes
174
+ return nil
175
+ end
176
+
177
+ include Scimitar::Resources::Mixin
178
+ end
179
+
84
180
  # ===========================================================================
85
181
  # Errant class definitions
86
182
  # ===========================================================================
@@ -159,6 +255,52 @@ RSpec.describe Scimitar::Resources::Mixin do
159
255
  # =========================================================================
160
256
 
161
257
  context '#to_scim' do
258
+ context 'with list of requested attributes' do
259
+ it 'compiles instance attribute values into a SCIM representation, including only the requested attributes' do
260
+ uuid = SecureRandom.uuid
261
+
262
+ instance = MockUser.new
263
+ instance.primary_key = uuid
264
+ instance.scim_uid = 'AA02984'
265
+ instance.username = 'foo'
266
+ instance.password = 'correcthorsebatterystaple'
267
+ instance.first_name = 'Foo'
268
+ instance.last_name = 'Bar'
269
+ instance.work_email_address = 'foo.bar@test.com'
270
+ instance.home_email_address = nil
271
+ instance.work_phone_number = '+642201234567'
272
+ instance.organization = 'SOMEORG'
273
+
274
+ g1 = MockGroup.create!(display_name: 'Group 1')
275
+ g2 = MockGroup.create!(display_name: 'Group 2')
276
+ g3 = MockGroup.create!(display_name: 'Group 3')
277
+
278
+ g1.mock_users << instance
279
+ g3.mock_users << instance
280
+
281
+ scim = instance.to_scim(location: "https://test.com/mock_users/#{uuid}", include_attributes: %w[id userName name groups.display groups.value organization])
282
+ json = scim.to_json()
283
+ hash = JSON.parse(json)
284
+
285
+ expect(hash).to eql({
286
+ 'id' => uuid,
287
+ 'userName' => 'foo',
288
+ 'name' => {'givenName'=>'Foo', 'familyName'=>'Bar'},
289
+ 'groups' => [{'display'=>g1.display_name, 'value'=>g1.id.to_s}, {'display'=>g3.display_name, 'value'=>g3.id.to_s}],
290
+ 'meta' => {'location'=>"https://test.com/mock_users/#{uuid}", 'resourceType'=>'User'},
291
+ 'schemas' => [
292
+ 'urn:ietf:params:scim:schemas:core:2.0:User',
293
+ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User',
294
+ 'urn:ietf:params:scim:schemas:extension:manager:1.0:User',
295
+ ],
296
+ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
297
+ 'organization' => 'SOMEORG',
298
+ },
299
+ 'urn:ietf:params:scim:schemas:extension:manager:1.0:User' => {},
300
+ })
301
+ end
302
+ end # "context 'with list of requested attributes' do"
303
+
162
304
  context 'with a UUID, renamed primary key column' do
163
305
  it 'compiles instance attribute values into a SCIM representation, but omits do-not-return fields' do
164
306
  uuid = SecureRandom.uuid
@@ -196,12 +338,19 @@ RSpec.describe Scimitar::Resources::Mixin do
196
338
  'externalId' => 'AA02984',
197
339
  'groups' => [{'display'=>g1.display_name, 'value'=>g1.id.to_s}, {'display'=>g3.display_name, 'value'=>g3.id.to_s}],
198
340
  'meta' => {'location'=>"https://test.com/mock_users/#{uuid}", 'resourceType'=>'User'},
199
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
200
-
341
+ 'schemas' => [
342
+ 'urn:ietf:params:scim:schemas:core:2.0:User',
343
+ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User',
344
+ 'urn:ietf:params:scim:schemas:extension:manager:1.0:User',
345
+ ],
201
346
  'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
202
347
  'organization' => 'SOMEORG',
203
- 'department' => nil
204
- }
348
+ 'department' => nil,
349
+ 'primaryEmail' => instance.work_email_address,
350
+ },
351
+ 'urn:ietf:params:scim:schemas:extension:manager:1.0:User' => {
352
+ 'manager' => nil
353
+ },
205
354
  })
206
355
  end
207
356
  end # "context 'with a UUID, renamed primary key column' do"
@@ -325,9 +474,13 @@ RSpec.describe Scimitar::Resources::Mixin do
325
474
  ],
326
475
 
327
476
  'meta' => {'location'=>'https://test.com/static_map_test', 'resourceType'=>'User'},
328
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
329
-
330
- 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {}
477
+ 'schemas' => [
478
+ 'urn:ietf:params:scim:schemas:core:2.0:User',
479
+ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User',
480
+ 'urn:ietf:params:scim:schemas:extension:manager:1.0:User',
481
+ ],
482
+ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {},
483
+ 'urn:ietf:params:scim:schemas:extension:manager:1.0:User' => {},
331
484
  })
332
485
  end
333
486
  end # "context 'using static mappings' do"
@@ -354,14 +507,42 @@ RSpec.describe Scimitar::Resources::Mixin do
354
507
  ],
355
508
 
356
509
  'meta' => {'location'=>'https://test.com/dynamic_map_test', 'resourceType'=>'User'},
357
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
358
-
359
- 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {}
510
+ 'schemas' => [
511
+ 'urn:ietf:params:scim:schemas:core:2.0:User',
512
+ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User',
513
+ 'urn:ietf:params:scim:schemas:extension:manager:1.0:User',
514
+ ],
515
+ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {},
516
+ 'urn:ietf:params:scim:schemas:extension:manager:1.0:User' => {},
360
517
  })
361
518
  end
362
519
  end # "context 'using dynamic lists' do"
363
520
  end # "context 'with arrays' do"
364
521
 
522
+ context 'with "returned: \'never\' fields' do
523
+ it 'hides appropriate top-level and nested attributes' do
524
+ instance = NestedReturnedNeverTest.new(
525
+ given_name: 'One',
526
+ last_name: 'Two',
527
+ private_given_name: 'Three',
528
+ private_last_name: 'Four',
529
+ simple_name: 'Five'
530
+ )
531
+
532
+ scim = instance.to_scim(location: 'https://test.com/never_retutrned_test')
533
+ json = scim.to_json()
534
+ hash = JSON.parse(json)
535
+
536
+ expect(hash).to eql({
537
+ 'name' => { 'givenName' => 'One' },
538
+ 'simpleName' => 'Five',
539
+
540
+ 'meta' => {'location'=>'https://test.com/never_retutrned_test', 'resourceType'=>'NestedReturnedNeverTestResourse'},
541
+ 'schemas' => ['nested-returned-never-id']
542
+ })
543
+ end
544
+ end # "context 'with "returned: \'never\' fields' do"
545
+
365
546
  context 'with bad definitions' do
366
547
  it 'complains about non-Hash entries in mapping Arrays' do
367
548
  expect(StaticMapTest).to receive(:scim_attributes_map).and_return({
@@ -702,7 +883,8 @@ RSpec.describe Scimitar::Resources::Mixin do
702
883
  nature: 'add',
703
884
  path: path,
704
885
  value: 'foo',
705
- altering_hash: scim_hash
886
+ altering_hash: scim_hash,
887
+ with_attr_map: { userName: :user_name }
706
888
  )
707
889
 
708
890
  expect(scim_hash['userName']).to eql('foo')
@@ -717,7 +899,8 @@ RSpec.describe Scimitar::Resources::Mixin do
717
899
  nature: 'add',
718
900
  path: path,
719
901
  value: 'Baz',
720
- altering_hash: scim_hash
902
+ altering_hash: scim_hash,
903
+ with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
721
904
  )
722
905
 
723
906
  expect(scim_hash['name']['givenName' ]).to eql('Baz')
@@ -733,7 +916,8 @@ RSpec.describe Scimitar::Resources::Mixin do
733
916
  nature: 'add',
734
917
  path: path,
735
918
  value: 'OTHERORG',
736
- altering_hash: scim_hash
919
+ altering_hash: scim_hash,
920
+ with_attr_map: { organization: :org_name }
737
921
  )
738
922
 
739
923
  expect(scim_hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']['organization' ]).to eql('OTHERORG')
@@ -763,7 +947,13 @@ RSpec.describe Scimitar::Resources::Mixin do
763
947
  nature: 'add',
764
948
  path: path,
765
949
  value: 'added_over_original@test.com',
766
- altering_hash: scim_hash
950
+ altering_hash: scim_hash,
951
+ with_attr_map: {
952
+ emails: [
953
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
954
+ { match: 'type', with: 'work', using: { value: :work_email } },
955
+ ]
956
+ }
767
957
  )
768
958
 
769
959
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -789,7 +979,13 @@ RSpec.describe Scimitar::Resources::Mixin do
789
979
  nature: 'add',
790
980
  path: path,
791
981
  value: 'added_over_original@test.com',
792
- altering_hash: scim_hash
982
+ altering_hash: scim_hash,
983
+ with_attr_map: {
984
+ emails: [
985
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
986
+ { match: 'type', with: 'work', using: { value: :work_email } },
987
+ ]
988
+ }
793
989
  )
794
990
 
795
991
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -816,7 +1012,13 @@ RSpec.describe Scimitar::Resources::Mixin do
816
1012
  nature: 'add',
817
1013
  path: path,
818
1014
  value: 'added_over_original@test.com',
819
- altering_hash: scim_hash
1015
+ altering_hash: scim_hash,
1016
+ with_attr_map: {
1017
+ emails: [
1018
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1019
+ { match: 'type', with: 'work', using: { value: :work_email } },
1020
+ ]
1021
+ }
820
1022
  )
821
1023
 
822
1024
  expect(scim_hash['emails'][0]['value']).to eql('added_over_original@test.com')
@@ -840,7 +1042,13 @@ RSpec.describe Scimitar::Resources::Mixin do
840
1042
  nature: 'add',
841
1043
  path: path,
842
1044
  value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be)
843
- altering_hash: scim_hash
1045
+ altering_hash: scim_hash,
1046
+ with_attr_map: {
1047
+ emails: [
1048
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1049
+ { match: 'type', with: 'work', using: { value: :work_email } },
1050
+ ]
1051
+ }
844
1052
  )
845
1053
 
846
1054
  expect(scim_hash['emails'].size).to eql(2)
@@ -888,7 +1096,12 @@ RSpec.describe Scimitar::Resources::Mixin do
888
1096
  nature: 'add',
889
1097
  path: ['root'],
890
1098
  value: {'members' => [{'value' => '3'}]},
891
- altering_hash: scim_hash
1099
+ altering_hash: scim_hash,
1100
+ with_attr_map: {
1101
+ members: [
1102
+ { list: :members, using: { value: :id } }
1103
+ ]
1104
+ }
892
1105
  )
893
1106
 
894
1107
  expect(scim_hash['root']['members']).to match_array([{'value' => '1'}, {'value' => '2'}, {'value' => '3'}])
@@ -906,7 +1119,8 @@ RSpec.describe Scimitar::Resources::Mixin do
906
1119
  nature: 'add',
907
1120
  path: path,
908
1121
  value: 'foo',
909
- altering_hash: scim_hash
1122
+ altering_hash: scim_hash,
1123
+ with_attr_map: { userName: :user_name }
910
1124
  )
911
1125
 
912
1126
  expect(scim_hash['userName']).to eql('foo')
@@ -921,7 +1135,8 @@ RSpec.describe Scimitar::Resources::Mixin do
921
1135
  nature: 'add',
922
1136
  path: path,
923
1137
  value: 'Baz',
924
- altering_hash: scim_hash
1138
+ altering_hash: scim_hash,
1139
+ with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
925
1140
  )
926
1141
 
927
1142
  expect(scim_hash['name']['givenName']).to eql('Baz')
@@ -936,7 +1151,8 @@ RSpec.describe Scimitar::Resources::Mixin do
936
1151
  nature: 'add',
937
1152
  path: path,
938
1153
  value: 'SOMEORG',
939
- altering_hash: scim_hash
1154
+ altering_hash: scim_hash,
1155
+ with_attr_map: { organization: :org_name }
940
1156
  )
941
1157
 
942
1158
  expect(scim_hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']['organization' ]).to eql('SOMEORG')
@@ -962,7 +1178,13 @@ RSpec.describe Scimitar::Resources::Mixin do
962
1178
  nature: 'add',
963
1179
  path: path,
964
1180
  value: 'added@test.com',
965
- altering_hash: scim_hash
1181
+ altering_hash: scim_hash,
1182
+ with_attr_map: {
1183
+ emails: [
1184
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1185
+ { match: 'type', with: 'work', using: { value: :work_email } },
1186
+ ]
1187
+ }
966
1188
  )
967
1189
 
968
1190
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -987,7 +1209,13 @@ RSpec.describe Scimitar::Resources::Mixin do
987
1209
  nature: 'add',
988
1210
  path: path,
989
1211
  value: 'added@test.com',
990
- altering_hash: scim_hash
1212
+ altering_hash: scim_hash,
1213
+ with_attr_map: {
1214
+ emails: [
1215
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1216
+ { match: 'type', with: 'work', using: { value: :work_email } },
1217
+ ]
1218
+ }
991
1219
  )
992
1220
 
993
1221
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -1003,7 +1231,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1003
1231
  nature: 'add',
1004
1232
  path: path,
1005
1233
  value: 'added@test.com',
1006
- altering_hash: scim_hash
1234
+ altering_hash: scim_hash,
1235
+ with_attr_map: {
1236
+ emails: [
1237
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1238
+ { match: 'type', with: 'work', using: { value: :work_email } },
1239
+ ]
1240
+ }
1007
1241
  )
1008
1242
 
1009
1243
  expect(scim_hash['emails'][0]['value']).to eql('added@test.com')
@@ -1027,7 +1261,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1027
1261
  nature: 'add',
1028
1262
  path: path,
1029
1263
  value: 'added@test.com',
1030
- altering_hash: scim_hash
1264
+ altering_hash: scim_hash,
1265
+ with_attr_map: {
1266
+ emails: [
1267
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1268
+ { match: 'type', with: 'work', using: { value: :work_email } },
1269
+ ]
1270
+ }
1031
1271
  )
1032
1272
 
1033
1273
  expect(scim_hash['emails'][0]['value']).to eql('added@test.com')
@@ -1044,7 +1284,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1044
1284
  nature: 'add',
1045
1285
  path: path,
1046
1286
  value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be)
1047
- altering_hash: scim_hash
1287
+ altering_hash: scim_hash,
1288
+ with_attr_map: {
1289
+ emails: [
1290
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1291
+ { match: 'type', with: 'work', using: { value: :work_email } },
1292
+ ]
1293
+ }
1048
1294
  )
1049
1295
 
1050
1296
  expect(scim_hash['emails'].size).to eql(1)
@@ -1060,7 +1306,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1060
1306
  #
1061
1307
  context 'remove' do
1062
1308
  context 'when prior value already exists' do
1063
- it 'simple value: removes' do
1309
+ it 'simple value: clears to "nil" in order to remove' do
1064
1310
  path = [ 'userName' ]
1065
1311
  scim_hash = { 'userName' => 'bar' }.with_indifferent_case_insensitive_access()
1066
1312
 
@@ -1069,13 +1315,14 @@ RSpec.describe Scimitar::Resources::Mixin do
1069
1315
  nature: 'remove',
1070
1316
  path: path,
1071
1317
  value: nil,
1072
- altering_hash: scim_hash
1318
+ altering_hash: scim_hash,
1319
+ with_attr_map: { userName: :user_name }
1073
1320
  )
1074
1321
 
1075
- expect(scim_hash).to be_empty
1322
+ expect(scim_hash).to eql({ 'userName' => nil })
1076
1323
  end
1077
1324
 
1078
- it 'nested simple value: removes' do
1325
+ it 'nested simple value: clears to "nil" in order to remove' do
1079
1326
  path = [ 'name', 'givenName' ]
1080
1327
  scim_hash = { 'name' => { 'givenName' => 'Foo', 'familyName' => 'Bar' } }.with_indifferent_case_insensitive_access()
1081
1328
 
@@ -1084,15 +1331,15 @@ RSpec.describe Scimitar::Resources::Mixin do
1084
1331
  nature: 'remove',
1085
1332
  path: path,
1086
1333
  value: nil,
1087
- altering_hash: scim_hash
1334
+ altering_hash: scim_hash,
1335
+ with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
1088
1336
  )
1089
1337
 
1090
- expect(scim_hash['name']).to_not have_key('givenName')
1091
- expect(scim_hash['name']['familyName']).to eql('Bar')
1338
+ expect(scim_hash).to eql({ 'name' => { 'givenName' => nil, 'familyName' => 'Bar' } })
1092
1339
  end
1093
1340
 
1094
1341
  context 'with filter mid-path' do
1095
- it 'by string match: removes' do
1342
+ it 'by string match: clears to "nil" in order to remove' do
1096
1343
  path = [ 'emails[type eq "work"]', 'value' ]
1097
1344
  scim_hash = {
1098
1345
  'emails' => [
@@ -1112,14 +1359,30 @@ RSpec.describe Scimitar::Resources::Mixin do
1112
1359
  nature: 'remove',
1113
1360
  path: path,
1114
1361
  value: nil,
1115
- altering_hash: scim_hash
1362
+ altering_hash: scim_hash,
1363
+ with_attr_map: {
1364
+ emails: [
1365
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1366
+ { match: 'type', with: 'work', using: { value: :work_email } },
1367
+ ]
1368
+ }
1116
1369
  )
1117
1370
 
1118
- expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
1119
- expect(scim_hash['emails'][1]).to_not have_key('value')
1371
+ expect(scim_hash).to eql({
1372
+ 'emails' => [
1373
+ {
1374
+ 'type' => 'home',
1375
+ 'value' => 'home@test.com'
1376
+ },
1377
+ {
1378
+ 'type' => 'work',
1379
+ 'value' => nil
1380
+ }
1381
+ ]
1382
+ })
1120
1383
  end
1121
1384
 
1122
- it 'by boolean match: removes' do
1385
+ it 'by boolean match: clears to "nil" in order to remove' do
1123
1386
  path = [ 'emails[primary eq true]', 'value' ]
1124
1387
  scim_hash = {
1125
1388
  'emails' => [
@@ -1138,14 +1401,29 @@ RSpec.describe Scimitar::Resources::Mixin do
1138
1401
  nature: 'remove',
1139
1402
  path: path,
1140
1403
  value: nil,
1141
- altering_hash: scim_hash
1404
+ altering_hash: scim_hash,
1405
+ with_attr_map: {
1406
+ emails: [
1407
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1408
+ { match: 'type', with: 'work', using: { value: :work_email } },
1409
+ ]
1410
+ }
1142
1411
  )
1143
1412
 
1144
- expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
1145
- expect(scim_hash['emails'][1]).to_not have_key('value')
1413
+ expect(scim_hash).to eql({
1414
+ 'emails' => [
1415
+ {
1416
+ 'value' => 'home@test.com'
1417
+ },
1418
+ {
1419
+ 'value' => nil,
1420
+ 'primary' => true
1421
+ }
1422
+ ]
1423
+ })
1146
1424
  end
1147
1425
 
1148
- it 'multiple matches: removes all' do
1426
+ it 'multiple matches: clears all to "nil" in order to remove' do
1149
1427
  path = [ 'emails[type eq "work"]', 'value' ]
1150
1428
  scim_hash = {
1151
1429
  'emails' => [
@@ -1156,7 +1434,11 @@ RSpec.describe Scimitar::Resources::Mixin do
1156
1434
  {
1157
1435
  'type' => 'work',
1158
1436
  'value' => 'work_2@test.com'
1159
- }
1437
+ },
1438
+ {
1439
+ 'type' => 'home',
1440
+ 'value' => 'home@test.com'
1441
+ },
1160
1442
  ]
1161
1443
  }.with_indifferent_case_insensitive_access()
1162
1444
 
@@ -1165,16 +1447,36 @@ RSpec.describe Scimitar::Resources::Mixin do
1165
1447
  nature: 'remove',
1166
1448
  path: path,
1167
1449
  value: nil,
1168
- altering_hash: scim_hash
1450
+ altering_hash: scim_hash,
1451
+ with_attr_map: {
1452
+ emails: [
1453
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1454
+ { match: 'type', with: 'work', using: { value: :work_email } },
1455
+ ]
1456
+ }
1169
1457
  )
1170
1458
 
1171
- expect(scim_hash['emails'][0]).to_not have_key('value')
1172
- expect(scim_hash['emails'][1]).to_not have_key('value')
1459
+ expect(scim_hash).to eql({
1460
+ 'emails' => [
1461
+ {
1462
+ 'type' => 'work',
1463
+ 'value' => nil
1464
+ },
1465
+ {
1466
+ 'type' => 'work',
1467
+ 'value' => nil
1468
+ },
1469
+ {
1470
+ 'type' => 'home',
1471
+ 'value' => 'home@test.com'
1472
+ },
1473
+ ]
1474
+ })
1173
1475
  end
1174
1476
  end # "context 'with filter mid-path' do"
1175
1477
 
1176
1478
  context 'with filter at end of path' do
1177
- it 'by string match: removes entire matching array entry' do
1479
+ it 'by string match: clears to "nil" in order to remove' do
1178
1480
  path = [ 'emails[type eq "work"]' ]
1179
1481
  scim_hash = {
1180
1482
  'emails' => [
@@ -1194,23 +1496,39 @@ RSpec.describe Scimitar::Resources::Mixin do
1194
1496
  nature: 'remove',
1195
1497
  path: path,
1196
1498
  value: nil,
1197
- altering_hash: scim_hash
1499
+ altering_hash: scim_hash,
1500
+ with_attr_map: {
1501
+ emails: [
1502
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1503
+ { match: 'type', with: 'work', using: { value: :work_email } },
1504
+ ]
1505
+ }
1198
1506
  )
1199
1507
 
1200
- expect(scim_hash['emails'].size).to eql(1)
1201
- expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
1508
+ expect(scim_hash).to eql({
1509
+ 'emails' => [
1510
+ {
1511
+ 'type' => 'home',
1512
+ 'value' => 'home@test.com'
1513
+ },
1514
+ {
1515
+ 'type' => 'work',
1516
+ 'value' => nil
1517
+ }
1518
+ ]
1519
+ })
1202
1520
  end
1203
1521
 
1204
- it 'by boolean match: removes entire matching array entry' do
1522
+ it 'by boolean match: clears to "nil" in order to remove' do
1205
1523
  path = [ 'emails[primary eq true]' ]
1206
1524
  scim_hash = {
1207
1525
  'emails' => [
1208
1526
  {
1209
- 'value' => 'home@test.com'
1527
+ 'value' => 'home@test.com',
1528
+ 'primary' => true
1210
1529
  },
1211
1530
  {
1212
- 'value' => 'work@test.com',
1213
- 'primary' => true
1531
+ 'value' => 'work@test.com'
1214
1532
  }
1215
1533
  ]
1216
1534
  }.with_indifferent_case_insensitive_access()
@@ -1220,14 +1538,29 @@ RSpec.describe Scimitar::Resources::Mixin do
1220
1538
  nature: 'remove',
1221
1539
  path: path,
1222
1540
  value: nil,
1223
- altering_hash: scim_hash
1541
+ altering_hash: scim_hash,
1542
+ with_attr_map: {
1543
+ emails: [
1544
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1545
+ { match: 'type', with: 'work', using: { value: :work_email } },
1546
+ ]
1547
+ }
1224
1548
  )
1225
1549
 
1226
- expect(scim_hash['emails'].size).to eql(1)
1227
- expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
1550
+ expect(scim_hash).to eql({
1551
+ 'emails' => [
1552
+ {
1553
+ 'value' => nil,
1554
+ 'primary' => true
1555
+ },
1556
+ {
1557
+ 'value' => 'work@test.com'
1558
+ }
1559
+ ]
1560
+ })
1228
1561
  end
1229
1562
 
1230
- it 'multiple matches: removes all matching array entries' do
1563
+ it 'multiple matches: clears all to "nil" in order to remove' do
1231
1564
  path = [ 'emails[type eq "work"]' ]
1232
1565
  scim_hash = {
1233
1566
  'emails' => [
@@ -1251,21 +1584,45 @@ RSpec.describe Scimitar::Resources::Mixin do
1251
1584
  nature: 'remove',
1252
1585
  path: path,
1253
1586
  value: nil,
1254
- altering_hash: scim_hash
1587
+ altering_hash: scim_hash,
1588
+ with_attr_map: {
1589
+ emails: [
1590
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1591
+ { match: 'type', with: 'work', using: { value: :work_email } },
1592
+ ]
1593
+ }
1255
1594
  )
1256
1595
 
1257
- expect(scim_hash['emails'].size).to eql(1)
1258
- expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
1596
+ expect(scim_hash).to eql({
1597
+ 'emails' => [
1598
+ {
1599
+ 'type' => 'work',
1600
+ 'value' => nil
1601
+ },
1602
+ {
1603
+ 'type' => 'work',
1604
+ 'value' => nil
1605
+ },
1606
+ {
1607
+ 'type' => 'home',
1608
+ 'value' => 'home@test.com'
1609
+ },
1610
+ ]
1611
+ })
1259
1612
  end
1260
1613
  end # "context 'with filter at end of path' do"
1261
1614
 
1262
- it 'whole array: removes' do
1615
+ it 'whole array: clears mapped values to "nil" to remove them' do
1263
1616
  path = [ 'emails' ]
1264
1617
  scim_hash = {
1265
1618
  'emails' => [
1266
1619
  {
1267
1620
  'type' => 'home',
1268
1621
  'value' => 'home@test.com'
1622
+ },
1623
+ {
1624
+ 'type' => 'work',
1625
+ 'value' => 'work@test.com'
1269
1626
  }
1270
1627
  ]
1271
1628
  }.with_indifferent_case_insensitive_access()
@@ -1275,10 +1632,27 @@ RSpec.describe Scimitar::Resources::Mixin do
1275
1632
  nature: 'remove',
1276
1633
  path: path,
1277
1634
  value: nil,
1278
- altering_hash: scim_hash
1635
+ altering_hash: scim_hash,
1636
+ with_attr_map: {
1637
+ emails: [
1638
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1639
+ { match: 'type', with: 'work', using: { value: :work_email } },
1640
+ ]
1641
+ }
1279
1642
  )
1280
1643
 
1281
- expect(scim_hash).to_not have_key('emails')
1644
+ expect(scim_hash).to eql({
1645
+ 'emails' => [
1646
+ {
1647
+ 'type' => 'home',
1648
+ 'value' => nil
1649
+ },
1650
+ {
1651
+ 'type' => 'work',
1652
+ 'value' => nil
1653
+ }
1654
+ ]
1655
+ })
1282
1656
  end
1283
1657
 
1284
1658
  # What we expect:
@@ -1324,7 +1698,12 @@ RSpec.describe Scimitar::Resources::Mixin do
1324
1698
  nature: 'remove',
1325
1699
  path: path,
1326
1700
  value: value,
1327
- altering_hash: scim_hash
1701
+ altering_hash: scim_hash,
1702
+ with_attr_map: {
1703
+ members: [
1704
+ { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1705
+ ]
1706
+ }
1328
1707
  )
1329
1708
 
1330
1709
  expect(scim_hash).to eql({
@@ -1376,7 +1755,12 @@ RSpec.describe Scimitar::Resources::Mixin do
1376
1755
  nature: 'remove',
1377
1756
  path: path,
1378
1757
  value: value,
1379
- altering_hash: scim_hash
1758
+ altering_hash: scim_hash,
1759
+ with_attr_map: {
1760
+ members: [
1761
+ { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1762
+ ]
1763
+ }
1380
1764
  )
1381
1765
 
1382
1766
  expect(scim_hash).to eql({
@@ -1410,7 +1794,12 @@ RSpec.describe Scimitar::Resources::Mixin do
1410
1794
  nature: 'remove',
1411
1795
  path: path,
1412
1796
  value: value,
1413
- altering_hash: scim_hash
1797
+ altering_hash: scim_hash,
1798
+ with_attr_map: {
1799
+ members: [
1800
+ { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1801
+ ]
1802
+ }
1414
1803
  )
1415
1804
 
1416
1805
  expect(scim_hash).to eql({
@@ -1438,7 +1827,12 @@ RSpec.describe Scimitar::Resources::Mixin do
1438
1827
  nature: 'remove',
1439
1828
  path: path,
1440
1829
  value: value,
1441
- altering_hash: scim_hash
1830
+ altering_hash: scim_hash,
1831
+ with_attr_map: {
1832
+ members: [
1833
+ { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1834
+ ]
1835
+ }
1442
1836
  )
1443
1837
 
1444
1838
  expect(scim_hash).to eql({
@@ -1466,7 +1860,12 @@ RSpec.describe Scimitar::Resources::Mixin do
1466
1860
  nature: 'remove',
1467
1861
  path: path,
1468
1862
  value: value,
1469
- altering_hash: scim_hash
1863
+ altering_hash: scim_hash,
1864
+ with_attr_map: {
1865
+ members: [
1866
+ { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1867
+ ]
1868
+ }
1470
1869
  )
1471
1870
 
1472
1871
  # The 'value' mismatched, so the user was not removed.
@@ -1502,7 +1901,12 @@ RSpec.describe Scimitar::Resources::Mixin do
1502
1901
  nature: 'remove',
1503
1902
  path: path,
1504
1903
  value: value,
1505
- altering_hash: scim_hash
1904
+ altering_hash: scim_hash,
1905
+ with_attr_map: {
1906
+ members: [
1907
+ { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1908
+ ]
1909
+ }
1506
1910
  )
1507
1911
 
1508
1912
  # Type 'Group' mismatches 'User', so the user was not
@@ -1520,7 +1924,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1520
1924
  })
1521
1925
  end
1522
1926
 
1523
- it 'matches keys case-insensitive' do
1927
+ it 'matches keys case-insensitive (but preserves case in response)' do
1524
1928
  path = [ 'members' ]
1525
1929
  value = [ { '$ref' => nil, 'VALUe' => 'f648f8d5ea4e4cd38e9c' } ]
1526
1930
  scim_hash = {
@@ -1539,12 +1943,17 @@ RSpec.describe Scimitar::Resources::Mixin do
1539
1943
  nature: 'remove',
1540
1944
  path: path,
1541
1945
  value: value,
1542
- altering_hash: scim_hash
1946
+ altering_hash: scim_hash,
1947
+ with_attr_map: {
1948
+ members: [
1949
+ { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1950
+ ]
1951
+ }
1543
1952
  )
1544
1953
 
1545
1954
  expect(scim_hash).to eql({
1546
1955
  'displayname' => 'Mock group',
1547
- 'members' => []
1956
+ 'memBERS' => []
1548
1957
  })
1549
1958
  end
1550
1959
 
@@ -1552,7 +1961,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1552
1961
  path = [ 'members' ]
1553
1962
  value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c', 'type' => 'USER' } ]
1554
1963
  scim_hash = {
1555
- 'displayname' => 'Mock group',
1964
+ 'displayName' => 'Mock group',
1556
1965
  'members' => [
1557
1966
  {
1558
1967
  'value' => 'f648f8d5ea4e4cd38e9c',
@@ -1567,13 +1976,18 @@ RSpec.describe Scimitar::Resources::Mixin do
1567
1976
  nature: 'remove',
1568
1977
  path: path,
1569
1978
  value: value,
1570
- altering_hash: scim_hash
1979
+ altering_hash: scim_hash,
1980
+ with_attr_map: {
1981
+ members: [
1982
+ { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1983
+ ]
1984
+ }
1571
1985
  )
1572
1986
 
1573
- # USER mismatchs User, so the user was not removed.
1987
+ # USER mismatches User, so the user was not removed.
1574
1988
  #
1575
1989
  expect(scim_hash).to eql({
1576
- 'displayname' => 'Mock group',
1990
+ 'displayName' => 'Mock group',
1577
1991
  'members' => [
1578
1992
  {
1579
1993
  'value' => 'f648f8d5ea4e4cd38e9c',
@@ -1586,7 +2000,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1586
2000
  end # "context 'removing a user from a group' do"
1587
2001
 
1588
2002
  context 'generic use' do
1589
- it 'removes matched items' do
2003
+ it 'clears static map matched items to "nil" in order to remove' do
1590
2004
  path = [ 'emails' ]
1591
2005
  value = [ { 'type' => 'work' } ]
1592
2006
  scim_hash = {
@@ -1607,7 +2021,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1607
2021
  nature: 'remove',
1608
2022
  path: path,
1609
2023
  value: value,
1610
- altering_hash: scim_hash
2024
+ altering_hash: scim_hash,
2025
+ with_attr_map: {
2026
+ emails: [
2027
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2028
+ { match: 'type', with: 'work', using: { value: :work_email } },
2029
+ ]
2030
+ }
1611
2031
  )
1612
2032
 
1613
2033
  expect(scim_hash).to eql({
@@ -1615,6 +2035,10 @@ RSpec.describe Scimitar::Resources::Mixin do
1615
2035
  {
1616
2036
  'type' => 'home',
1617
2037
  'value' => 'home@test.com'
2038
+ },
2039
+ {
2040
+ 'type' => 'work',
2041
+ 'value' => nil
1618
2042
  }
1619
2043
  ]
1620
2044
  })
@@ -1641,7 +2065,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1641
2065
  nature: 'remove',
1642
2066
  path: path,
1643
2067
  value: value,
1644
- altering_hash: scim_hash
2068
+ altering_hash: scim_hash,
2069
+ with_attr_map: {
2070
+ emails: [
2071
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2072
+ { match: 'type', with: 'work', using: { value: :work_email } },
2073
+ ]
2074
+ }
1645
2075
  )
1646
2076
 
1647
2077
  expect(scim_hash).to eql({
@@ -1673,6 +2103,10 @@ RSpec.describe Scimitar::Resources::Mixin do
1673
2103
  {
1674
2104
  'active' => false,
1675
2105
  'value' => '42'
2106
+ },
2107
+ {
2108
+ 'active' => 'hello',
2109
+ 'value' => 'world'
1676
2110
  }
1677
2111
  ]
1678
2112
  }.with_indifferent_case_insensitive_access()
@@ -1682,10 +2116,28 @@ RSpec.describe Scimitar::Resources::Mixin do
1682
2116
  nature: 'remove',
1683
2117
  path: path,
1684
2118
  value: value,
1685
- altering_hash: scim_hash
2119
+ altering_hash: scim_hash,
2120
+ with_attr_map: {
2121
+ test: [
2122
+ {
2123
+ list: :test,
2124
+ using: {
2125
+ active: :active,
2126
+ value: :value
2127
+ }
2128
+ }
2129
+ ]
2130
+ }
1686
2131
  )
1687
2132
 
1688
- expect(scim_hash).to eql({'test' => []})
2133
+ expect(scim_hash).to eql({
2134
+ 'test' => [
2135
+ {
2136
+ 'active' => 'hello',
2137
+ 'value' => 'world'
2138
+ }
2139
+ ]
2140
+ })
1689
2141
  end
1690
2142
 
1691
2143
  it 'handles a singular to-remove value rather than an array' do
@@ -1709,7 +2161,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1709
2161
  nature: 'remove',
1710
2162
  path: path,
1711
2163
  value: value,
1712
- altering_hash: scim_hash
2164
+ altering_hash: scim_hash,
2165
+ with_attr_map: {
2166
+ emails: [
2167
+ { match: 'type', with: 'home', using: { value: :home_email } },
2168
+ { match: 'type', with: 'work', using: { value: :work_email } },
2169
+ ]
2170
+ }
1713
2171
  )
1714
2172
 
1715
2173
  expect(scim_hash).to eql({
@@ -1717,6 +2175,10 @@ RSpec.describe Scimitar::Resources::Mixin do
1717
2175
  {
1718
2176
  'type' => 'home',
1719
2177
  'value' => 'home@test.com'
2178
+ },
2179
+ {
2180
+ 'type' => 'work',
2181
+ 'value' => nil
1720
2182
  }
1721
2183
  ]
1722
2184
  })
@@ -1738,7 +2200,10 @@ RSpec.describe Scimitar::Resources::Mixin do
1738
2200
  nature: 'remove',
1739
2201
  path: path,
1740
2202
  value: value,
1741
- altering_hash: scim_hash
2203
+ altering_hash: scim_hash,
2204
+ with_attr_map: {
2205
+ test: []
2206
+ }
1742
2207
  )
1743
2208
 
1744
2209
  expect(scim_hash).to eql({
@@ -1778,7 +2243,18 @@ RSpec.describe Scimitar::Resources::Mixin do
1778
2243
  nature: 'remove',
1779
2244
  path: path,
1780
2245
  value: value,
1781
- altering_hash: scim_hash
2246
+ altering_hash: scim_hash,
2247
+ with_attr_map: {
2248
+ displayName: :name,
2249
+ members: [
2250
+ list: :members,
2251
+ using: {
2252
+ value: :id,
2253
+ display: :full_name,
2254
+ type: 'User'
2255
+ }
2256
+ ]
2257
+ }
1782
2258
  )
1783
2259
 
1784
2260
  expect(scim_hash).to eql({
@@ -1817,7 +2293,18 @@ RSpec.describe Scimitar::Resources::Mixin do
1817
2293
  nature: 'remove',
1818
2294
  path: path,
1819
2295
  value: value,
1820
- altering_hash: scim_hash
2296
+ altering_hash: scim_hash,
2297
+ with_attr_map: {
2298
+ displayName: :name,
2299
+ members: [
2300
+ list: :members,
2301
+ using: {
2302
+ value: :id,
2303
+ display: :full_name,
2304
+ type: 'User'
2305
+ }
2306
+ ]
2307
+ }
1821
2308
  )
1822
2309
 
1823
2310
  expect(scim_hash).to eql({
@@ -1851,7 +2338,18 @@ RSpec.describe Scimitar::Resources::Mixin do
1851
2338
  nature: 'remove',
1852
2339
  path: path,
1853
2340
  value: value,
1854
- altering_hash: scim_hash
2341
+ altering_hash: scim_hash,
2342
+ with_attr_map: {
2343
+ displayName: :name,
2344
+ members: [
2345
+ list: :members,
2346
+ using: {
2347
+ value: :id,
2348
+ display: :full_name,
2349
+ type: 'User'
2350
+ }
2351
+ ]
2352
+ }
1855
2353
  )
1856
2354
 
1857
2355
  # The 'value' mismatched, so the user was not removed.
@@ -1881,7 +2379,8 @@ RSpec.describe Scimitar::Resources::Mixin do
1881
2379
  nature: 'remove',
1882
2380
  path: path,
1883
2381
  value: nil,
1884
- altering_hash: scim_hash
2382
+ altering_hash: scim_hash,
2383
+ with_attr_map: { userName: :user_name }
1885
2384
  )
1886
2385
 
1887
2386
  expect(scim_hash).to be_empty
@@ -1896,7 +2395,8 @@ RSpec.describe Scimitar::Resources::Mixin do
1896
2395
  nature: 'remove',
1897
2396
  path: path,
1898
2397
  value: nil,
1899
- altering_hash: scim_hash
2398
+ altering_hash: scim_hash,
2399
+ with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
1900
2400
  )
1901
2401
 
1902
2402
  expect(scim_hash['name']).to_not have_key('givenName')
@@ -1920,7 +2420,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1920
2420
  nature: 'remove',
1921
2421
  path: path,
1922
2422
  value: nil,
1923
- altering_hash: scim_hash
2423
+ altering_hash: scim_hash,
2424
+ with_attr_map: {
2425
+ emails: [
2426
+ { match: 'type', with: 'home', using: { value: :home_email } },
2427
+ { match: 'type', with: 'work', using: { value: :work_email } },
2428
+ ]
2429
+ }
1924
2430
  )
1925
2431
 
1926
2432
  expect(scim_hash['emails'].size).to eql(1)
@@ -1942,7 +2448,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1942
2448
  nature: 'remove',
1943
2449
  path: path,
1944
2450
  value: nil,
1945
- altering_hash: scim_hash
2451
+ altering_hash: scim_hash,
2452
+ with_attr_map: {
2453
+ emails: [
2454
+ { match: 'type', with: 'home', using: { value: :home_email } },
2455
+ { match: 'type', with: 'work', using: { value: :work_email } },
2456
+ ]
2457
+ }
1946
2458
  )
1947
2459
 
1948
2460
  expect(scim_hash['emails'].size).to eql(1)
@@ -1965,7 +2477,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1965
2477
  nature: 'remove',
1966
2478
  path: path,
1967
2479
  value: nil,
1968
- altering_hash: scim_hash
2480
+ altering_hash: scim_hash,
2481
+ with_attr_map: {
2482
+ emails: [
2483
+ { match: 'type', with: 'home', using: { value: :home_email } },
2484
+ { match: 'type', with: 'work', using: { value: :work_email } },
2485
+ ]
2486
+ }
1969
2487
  )
1970
2488
 
1971
2489
  expect(scim_hash['emails'].size).to eql(1)
@@ -1983,7 +2501,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1983
2501
  nature: 'remove',
1984
2502
  path: path,
1985
2503
  value: nil,
1986
- altering_hash: scim_hash
2504
+ altering_hash: scim_hash,
2505
+ with_attr_map: {
2506
+ emails: [
2507
+ { match: 'type', with: 'home', using: { value: :home_email } },
2508
+ { match: 'type', with: 'work', using: { value: :work_email } },
2509
+ ]
2510
+ }
1987
2511
  )
1988
2512
 
1989
2513
  expect(scim_hash).to be_empty
@@ -2005,7 +2529,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2005
2529
  nature: 'remove',
2006
2530
  path: path,
2007
2531
  value: nil,
2008
- altering_hash: scim_hash
2532
+ altering_hash: scim_hash,
2533
+ with_attr_map: {
2534
+ emails: [
2535
+ { match: 'type', with: 'home', using: { value: :home_email } },
2536
+ { match: 'type', with: 'work', using: { value: :work_email } },
2537
+ ]
2538
+ }
2009
2539
  )
2010
2540
 
2011
2541
  expect(scim_hash['emails'].size).to eql(1)
@@ -2022,7 +2552,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2022
2552
  nature: 'remove',
2023
2553
  path: path,
2024
2554
  value: nil,
2025
- altering_hash: scim_hash
2555
+ altering_hash: scim_hash,
2556
+ with_attr_map: {
2557
+ emails: [
2558
+ { match: 'type', with: 'home', using: { value: :home_email } },
2559
+ { match: 'type', with: 'work', using: { value: :work_email } },
2560
+ ]
2561
+ }
2026
2562
  )
2027
2563
 
2028
2564
  expect(scim_hash).to_not have_key('emails')
@@ -2048,7 +2584,8 @@ RSpec.describe Scimitar::Resources::Mixin do
2048
2584
  nature: 'replace',
2049
2585
  path: path,
2050
2586
  value: 'foo',
2051
- altering_hash: scim_hash
2587
+ altering_hash: scim_hash,
2588
+ with_attr_map: { userName: :user_name }
2052
2589
  )
2053
2590
 
2054
2591
  expect(scim_hash['userName']).to eql('foo')
@@ -2063,7 +2600,8 @@ RSpec.describe Scimitar::Resources::Mixin do
2063
2600
  nature: 'replace',
2064
2601
  path: path,
2065
2602
  value: 'Baz',
2066
- altering_hash: scim_hash
2603
+ altering_hash: scim_hash,
2604
+ with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
2067
2605
  )
2068
2606
 
2069
2607
  expect(scim_hash['name']['givenName' ]).to eql('Baz')
@@ -2091,7 +2629,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2091
2629
  nature: 'replace',
2092
2630
  path: path,
2093
2631
  value: 'added_over_original@test.com',
2094
- altering_hash: scim_hash
2632
+ altering_hash: scim_hash,
2633
+ with_attr_map: {
2634
+ emails: [
2635
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2636
+ { match: 'type', with: 'work', using: { value: :work_email } },
2637
+ ]
2638
+ }
2095
2639
  )
2096
2640
 
2097
2641
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -2117,7 +2661,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2117
2661
  nature: 'replace',
2118
2662
  path: path,
2119
2663
  value: 'added_over_original@test.com',
2120
- altering_hash: scim_hash
2664
+ altering_hash: scim_hash,
2665
+ with_attr_map: {
2666
+ emails: [
2667
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2668
+ { match: 'type', with: 'work', using: { value: :work_email } },
2669
+ ]
2670
+ }
2121
2671
  )
2122
2672
 
2123
2673
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -2144,7 +2694,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2144
2694
  nature: 'replace',
2145
2695
  path: path,
2146
2696
  value: 'added_over_original@test.com',
2147
- altering_hash: scim_hash
2697
+ altering_hash: scim_hash,
2698
+ with_attr_map: {
2699
+ emails: [
2700
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2701
+ { match: 'type', with: 'work', using: { value: :work_email } },
2702
+ ]
2703
+ }
2148
2704
  )
2149
2705
 
2150
2706
  expect(scim_hash['emails'][0]['value']).to eql('added_over_original@test.com')
@@ -2173,7 +2729,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2173
2729
  nature: 'replace',
2174
2730
  path: path,
2175
2731
  value: {'type' => 'home', 'primary' => true, 'value' => 'home@test.com'},
2176
- altering_hash: scim_hash
2732
+ altering_hash: scim_hash,
2733
+ with_attr_map: {
2734
+ emails: [
2735
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2736
+ { match: 'type', with: 'work', using: { value: :work_email } },
2737
+ ]
2738
+ }
2177
2739
  )
2178
2740
 
2179
2741
  expect(scim_hash['emails'].size).to eql(2)
@@ -2207,7 +2769,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2207
2769
  nature: 'replace',
2208
2770
  path: path,
2209
2771
  value: {'type' => 'workinate', 'value' => 'replaced@test.com'},
2210
- altering_hash: scim_hash
2772
+ altering_hash: scim_hash,
2773
+ with_attr_map: {
2774
+ emails: [
2775
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2776
+ { match: 'type', with: 'work', using: { value: :work_email } },
2777
+ ]
2778
+ }
2211
2779
  )
2212
2780
 
2213
2781
  expect(scim_hash['emails'].size).to eql(3)
@@ -2236,7 +2804,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2236
2804
  nature: 'replace',
2237
2805
  path: path,
2238
2806
  value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be)
2239
- altering_hash: scim_hash
2807
+ altering_hash: scim_hash,
2808
+ with_attr_map: {
2809
+ emails: [
2810
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2811
+ { match: 'type', with: 'work', using: { value: :work_email } },
2812
+ ]
2813
+ }
2240
2814
  )
2241
2815
 
2242
2816
  expect(scim_hash['emails'].size).to eql(1)
@@ -2255,7 +2829,8 @@ RSpec.describe Scimitar::Resources::Mixin do
2255
2829
  nature: 'replace',
2256
2830
  path: path,
2257
2831
  value: 'foo',
2258
- altering_hash: scim_hash
2832
+ altering_hash: scim_hash,
2833
+ with_attr_map: { userName: :user_name }
2259
2834
  )
2260
2835
 
2261
2836
  expect(scim_hash['userName']).to eql('foo')
@@ -2270,7 +2845,8 @@ RSpec.describe Scimitar::Resources::Mixin do
2270
2845
  nature: 'replace',
2271
2846
  path: path,
2272
2847
  value: 'Baz',
2273
- altering_hash: scim_hash
2848
+ altering_hash: scim_hash,
2849
+ with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
2274
2850
  )
2275
2851
 
2276
2852
  expect(scim_hash['name']['givenName']).to eql('Baz')
@@ -2296,7 +2872,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2296
2872
  nature: 'replace',
2297
2873
  path: path,
2298
2874
  value: 'added@test.com',
2299
- altering_hash: scim_hash
2875
+ altering_hash: scim_hash,
2876
+ with_attr_map: {
2877
+ emails: [
2878
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2879
+ { match: 'type', with: 'work', using: { value: :work_email } },
2880
+ ]
2881
+ }
2300
2882
  )
2301
2883
 
2302
2884
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -2321,7 +2903,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2321
2903
  nature: 'replace',
2322
2904
  path: path,
2323
2905
  value: 'added@test.com',
2324
- altering_hash: scim_hash
2906
+ altering_hash: scim_hash,
2907
+ with_attr_map: {
2908
+ emails: [
2909
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2910
+ { match: 'type', with: 'work', using: { value: :work_email } },
2911
+ ]
2912
+ }
2325
2913
  )
2326
2914
 
2327
2915
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -2346,7 +2934,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2346
2934
  nature: 'replace',
2347
2935
  path: path,
2348
2936
  value: 'added@test.com',
2349
- altering_hash: scim_hash
2937
+ altering_hash: scim_hash,
2938
+ with_attr_map: {
2939
+ emails: [
2940
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2941
+ { match: 'type', with: 'work', using: { value: :work_email } },
2942
+ ]
2943
+ }
2350
2944
  )
2351
2945
 
2352
2946
  expect(scim_hash['emails'][0]['value']).to eql('added@test.com')
@@ -2364,7 +2958,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2364
2958
  nature: 'replace',
2365
2959
  path: path,
2366
2960
  value: {'type' => 'work', 'value' => 'work@test.com'},
2367
- altering_hash: scim_hash
2961
+ altering_hash: scim_hash,
2962
+ with_attr_map: {
2963
+ emails: [
2964
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2965
+ { match: 'type', with: 'work', using: { value: :work_email } },
2966
+ ]
2967
+ }
2368
2968
  )
2369
2969
 
2370
2970
  expect(scim_hash['emails'].size).to eql(1)
@@ -2388,7 +2988,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2388
2988
  nature: 'replace',
2389
2989
  path: path,
2390
2990
  value: {'type' => 'work', 'value' => 'work@test.com'},
2391
- altering_hash: scim_hash
2991
+ altering_hash: scim_hash,
2992
+ with_attr_map: {
2993
+ emails: [
2994
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2995
+ { match: 'type', with: 'work', using: { value: :work_email } },
2996
+ ]
2997
+ }
2392
2998
  )
2393
2999
 
2394
3000
  expect(scim_hash['emails'].size).to eql(2)
@@ -2407,7 +3013,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2407
3013
  nature: 'replace',
2408
3014
  path: path,
2409
3015
  value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be)
2410
- altering_hash: scim_hash
3016
+ altering_hash: scim_hash,
3017
+ with_attr_map: {
3018
+ emails: [
3019
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
3020
+ { match: 'type', with: 'work', using: { value: :work_email } },
3021
+ ]
3022
+ }
2411
3023
  )
2412
3024
 
2413
3025
  expect(scim_hash['emails'].size).to eql(1)
@@ -2425,7 +3037,11 @@ RSpec.describe Scimitar::Resources::Mixin do
2425
3037
  nature: 'replace',
2426
3038
  path: path,
2427
3039
  value: { 'active' => false }.with_indifferent_case_insensitive_access(),
2428
- altering_hash: scim_hash
3040
+ altering_hash: scim_hash,
3041
+ with_attr_map: {
3042
+ userName: :user_name,
3043
+ active: :active
3044
+ }
2429
3045
  )
2430
3046
 
2431
3047
  expect(scim_hash['root']['userName']).to eql('bar')
@@ -2546,7 +3162,8 @@ RSpec.describe Scimitar::Resources::Mixin do
2546
3162
  nature: 'add',
2547
3163
  path: ['complex[type eq "type1"]', 'data', 'nested[nature eq "nature2"]', 'info'],
2548
3164
  value: [{ 'deeper' => 'addition' }],
2549
- altering_hash: scim_hash
3165
+ altering_hash: scim_hash,
3166
+ with_attr_map: @contrived_class.scim_attributes_map()
2550
3167
  )
2551
3168
 
2552
3169
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged
@@ -2569,7 +3186,8 @@ RSpec.describe Scimitar::Resources::Mixin do
2569
3186
  nature: 'replace',
2570
3187
  path: ['complex[type eq "type1"]', 'data', 'nested[nature eq "nature2"]', 'info'],
2571
3188
  value: [{ 'deeper' => 'addition' }],
2572
- altering_hash: scim_hash
3189
+ altering_hash: scim_hash,
3190
+ with_attr_map: @contrived_class.scim_attributes_map()
2573
3191
  )
2574
3192
 
2575
3193
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged?
@@ -2586,7 +3204,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2586
3204
  expect(scim_hash.dig('complex', 2, 'data', 'nested', 0, 'info', 0, 'deep')).to eql('nature2deep3') # Unchanged
2587
3205
  end
2588
3206
 
2589
- it 'removes across multiple deep matching points' do
3207
+ it 'removes via clearing to "nil" or empty Array across multiple deep matching points' do
2590
3208
  scim_hash = @original_hash.deep_dup().with_indifferent_case_insensitive_access()
2591
3209
  contrived_instance = @contrived_class.new
2592
3210
  contrived_instance.send(
@@ -2594,16 +3212,17 @@ RSpec.describe Scimitar::Resources::Mixin do
2594
3212
  nature: 'remove',
2595
3213
  path: ['complex[type eq "type1"]', 'data', 'nested[nature eq "nature2"]', 'info'],
2596
3214
  value: nil,
2597
- altering_hash: scim_hash
3215
+ altering_hash: scim_hash,
3216
+ with_attr_map: @contrived_class.scim_attributes_map()
2598
3217
  )
2599
3218
 
2600
3219
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged
2601
- expect(scim_hash.dig('complex', 0, 'data', 'nested', 1, 'info')).to be_nil
3220
+ expect(scim_hash.dig('complex', 0, 'data', 'nested', 1, 'info')).to eql([])
2602
3221
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 2, 'nature')).to be_present
2603
- expect(scim_hash.dig('complex', 0, 'data', 'nested', 1, 'info')).to be_nil
3222
+ expect(scim_hash.dig('complex', 0, 'data', 'nested', 1, 'info')).to eql([])
2604
3223
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 2, 'nature')).to be_present
2605
3224
 
2606
- expect(scim_hash.dig('complex', 1, 'data', 'nested', 0, 'info')).to be_nil
3225
+ expect(scim_hash.dig('complex', 1, 'data', 'nested', 0, 'info')).to eql([])
2607
3226
  expect(scim_hash.dig('complex', 1, 'data', 'nested', 0, 'nature')).to be_present
2608
3227
 
2609
3228
  expect(scim_hash.dig('complex', 2, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged
@@ -2637,7 +3256,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2637
3256
  nature: 'replace',
2638
3257
  path: path,
2639
3258
  value: 'ignored',
2640
- altering_hash: scim_hash
3259
+ altering_hash: scim_hash,
3260
+ with_attr_map: {
3261
+ emails: [
3262
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
3263
+ { match: 'type', with: 'work', using: { value: :work_email } },
3264
+ ]
3265
+ }
2641
3266
  )
2642
3267
  end.to raise_error(Scimitar::ErrorResponse) { |e| expect(e.as_json['scimType']).to eql('invalidSyntax') }
2643
3268
  end
@@ -2654,7 +3279,8 @@ RSpec.describe Scimitar::Resources::Mixin do
2654
3279
  nature: 'replace',
2655
3280
  path: path,
2656
3281
  value: 'ignored',
2657
- altering_hash: scim_hash
3282
+ altering_hash: scim_hash,
3283
+ with_attr_map: { userName: :user_name }
2658
3284
  )
2659
3285
  end.to raise_error(Scimitar::ErrorResponse) { |e| expect(e.as_json['scimType']).to eql('invalidSyntax') }
2660
3286
  end
@@ -2674,7 +3300,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2674
3300
  nature: 'replace',
2675
3301
  path: path,
2676
3302
  value: 'ignored',
2677
- altering_hash: scim_hash
3303
+ altering_hash: scim_hash,
3304
+ with_attr_map: {
3305
+ emails: [
3306
+ { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
3307
+ { match: 'type', with: 'work', using: { value: :work_email } },
3308
+ ]
3309
+ }
2678
3310
  )
2679
3311
  end.to raise_error(Scimitar::ErrorResponse) { |e| expect(e.as_json['scimType']).to eql('invalidSyntax') }
2680
3312
  end