scimitar 1.8.1 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
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