scimitar 2.5.0 → 2.11.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +21 -0
  3. data/README.md +721 -0
  4. data/app/controllers/scimitar/active_record_backed_resources_controller.rb +72 -18
  5. data/app/controllers/scimitar/application_controller.rb +17 -9
  6. data/app/controllers/scimitar/resource_types_controller.rb +7 -3
  7. data/app/controllers/scimitar/resources_controller.rb +0 -2
  8. data/app/controllers/scimitar/schemas_controller.rb +366 -3
  9. data/app/controllers/scimitar/service_provider_configurations_controller.rb +3 -2
  10. data/app/models/scimitar/complex_types/address.rb +0 -6
  11. data/app/models/scimitar/complex_types/base.rb +2 -2
  12. data/app/models/scimitar/engine_configuration.rb +3 -1
  13. data/app/models/scimitar/lists/query_parser.rb +97 -12
  14. data/app/models/scimitar/resource_invalid_error.rb +1 -1
  15. data/app/models/scimitar/resource_type.rb +4 -6
  16. data/app/models/scimitar/resources/base.rb +52 -8
  17. data/app/models/scimitar/resources/mixin.rb +539 -76
  18. data/app/models/scimitar/schema/attribute.rb +18 -8
  19. data/app/models/scimitar/schema/base.rb +2 -2
  20. data/app/models/scimitar/schema/name.rb +2 -2
  21. data/app/models/scimitar/schema/user.rb +10 -10
  22. data/config/initializers/scimitar.rb +49 -3
  23. data/lib/scimitar/engine.rb +57 -12
  24. data/lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb +140 -10
  25. data/lib/scimitar/support/utilities.rb +111 -0
  26. data/lib/scimitar/version.rb +2 -2
  27. data/lib/scimitar.rb +1 -0
  28. data/spec/apps/dummy/app/controllers/custom_create_mock_users_controller.rb +25 -0
  29. data/spec/apps/dummy/app/controllers/custom_replace_mock_users_controller.rb +25 -0
  30. data/spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb +24 -0
  31. data/spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb +25 -0
  32. data/spec/apps/dummy/app/models/mock_user.rb +20 -3
  33. data/spec/apps/dummy/config/application.rb +8 -0
  34. data/spec/apps/dummy/config/initializers/scimitar.rb +40 -3
  35. data/spec/apps/dummy/config/routes.rb +18 -1
  36. data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +2 -0
  37. data/spec/apps/dummy/db/schema.rb +3 -1
  38. data/spec/controllers/scimitar/application_controller_spec.rb +56 -2
  39. data/spec/controllers/scimitar/resource_types_controller_spec.rb +8 -4
  40. data/spec/controllers/scimitar/schemas_controller_spec.rb +344 -48
  41. data/spec/controllers/scimitar/service_provider_configurations_controller_spec.rb +1 -0
  42. data/spec/models/scimitar/complex_types/address_spec.rb +3 -4
  43. data/spec/models/scimitar/lists/query_parser_spec.rb +70 -0
  44. data/spec/models/scimitar/resources/base_spec.rb +55 -13
  45. data/spec/models/scimitar/resources/base_validation_spec.rb +16 -3
  46. data/spec/models/scimitar/resources/mixin_spec.rb +781 -124
  47. data/spec/models/scimitar/schema/attribute_spec.rb +22 -0
  48. data/spec/models/scimitar/schema/user_spec.rb +2 -2
  49. data/spec/requests/active_record_backed_resources_controller_spec.rb +723 -40
  50. data/spec/requests/engine_spec.rb +75 -0
  51. data/spec/spec_helper.rb +10 -2
  52. data/spec/support/hash_with_indifferent_case_insensitive_access_spec.rb +108 -0
  53. metadata +42 -34
@@ -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,14 +255,61 @@ 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
- it 'compiles instance attribute values into a SCIM representation' do
305
+ it 'compiles instance attribute values into a SCIM representation, but omits do-not-return fields' do
164
306
  uuid = SecureRandom.uuid
165
307
 
166
308
  instance = MockUser.new
167
309
  instance.primary_key = uuid
168
310
  instance.scim_uid = 'AA02984'
169
311
  instance.username = 'foo'
312
+ instance.password = 'correcthorsebatterystaple'
170
313
  instance.first_name = 'Foo'
171
314
  instance.last_name = 'Bar'
172
315
  instance.work_email_address = 'foo.bar@test.com'
@@ -195,12 +338,19 @@ RSpec.describe Scimitar::Resources::Mixin do
195
338
  'externalId' => 'AA02984',
196
339
  'groups' => [{'display'=>g1.display_name, 'value'=>g1.id.to_s}, {'display'=>g3.display_name, 'value'=>g3.id.to_s}],
197
340
  'meta' => {'location'=>"https://test.com/mock_users/#{uuid}", 'resourceType'=>'User'},
198
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
199
-
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
+ ],
200
346
  'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
201
347
  'organization' => 'SOMEORG',
202
- 'department' => nil
203
- }
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
+ },
204
354
  })
205
355
  end
206
356
  end # "context 'with a UUID, renamed primary key column' do"
@@ -324,9 +474,13 @@ RSpec.describe Scimitar::Resources::Mixin do
324
474
  ],
325
475
 
326
476
  'meta' => {'location'=>'https://test.com/static_map_test', 'resourceType'=>'User'},
327
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
328
-
329
- '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' => {},
330
484
  })
331
485
  end
332
486
  end # "context 'using static mappings' do"
@@ -353,14 +507,42 @@ RSpec.describe Scimitar::Resources::Mixin do
353
507
  ],
354
508
 
355
509
  'meta' => {'location'=>'https://test.com/dynamic_map_test', 'resourceType'=>'User'},
356
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
357
-
358
- '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' => {},
359
517
  })
360
518
  end
361
519
  end # "context 'using dynamic lists' do"
362
520
  end # "context 'with arrays' do"
363
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
+
364
546
  context 'with bad definitions' do
365
547
  it 'complains about non-Hash entries in mapping Arrays' do
366
548
  expect(StaticMapTest).to receive(:scim_attributes_map).and_return({
@@ -373,7 +555,7 @@ RSpec.describe Scimitar::Resources::Mixin do
373
555
 
374
556
  expect do
375
557
  scim = instance.to_scim(location: 'https://test.com/static_map_test')
376
- end.to raise_error(RuntimeError) { |e| expect(e.message).to include('Array contains someting other than mapping Hash(es)') }
558
+ end.to raise_error(RuntimeError) { |e| expect(e.message).to include('Array contains something other than mapping Hash(es)') }
377
559
  end
378
560
 
379
561
  it 'complains about bad Hash entries in mapping Arrays' do
@@ -404,6 +586,7 @@ RSpec.describe Scimitar::Resources::Mixin do
404
586
  it 'ignoring read-only lists' do
405
587
  hash = {
406
588
  'userName' => 'foo',
589
+ 'password' => 'staplebatteryhorsecorrect',
407
590
  'name' => {'givenName' => 'Foo', 'familyName' => 'Bar'},
408
591
  'active' => true,
409
592
  'emails' => [{'type' => 'work', 'primary' => true, 'value' => 'foo.bar@test.com'}],
@@ -428,6 +611,7 @@ RSpec.describe Scimitar::Resources::Mixin do
428
611
 
429
612
  expect(instance.scim_uid ).to eql('AA02984')
430
613
  expect(instance.username ).to eql('foo')
614
+ expect(instance.password ).to eql('staplebatteryhorsecorrect')
431
615
  expect(instance.first_name ).to eql('Foo')
432
616
  expect(instance.last_name ).to eql('Bar')
433
617
  expect(instance.work_email_address).to eql('foo.bar@test.com')
@@ -699,7 +883,8 @@ RSpec.describe Scimitar::Resources::Mixin do
699
883
  nature: 'add',
700
884
  path: path,
701
885
  value: 'foo',
702
- altering_hash: scim_hash
886
+ altering_hash: scim_hash,
887
+ with_attr_map: { userName: :user_name }
703
888
  )
704
889
 
705
890
  expect(scim_hash['userName']).to eql('foo')
@@ -714,7 +899,8 @@ RSpec.describe Scimitar::Resources::Mixin do
714
899
  nature: 'add',
715
900
  path: path,
716
901
  value: 'Baz',
717
- altering_hash: scim_hash
902
+ altering_hash: scim_hash,
903
+ with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
718
904
  )
719
905
 
720
906
  expect(scim_hash['name']['givenName' ]).to eql('Baz')
@@ -730,7 +916,8 @@ RSpec.describe Scimitar::Resources::Mixin do
730
916
  nature: 'add',
731
917
  path: path,
732
918
  value: 'OTHERORG',
733
- altering_hash: scim_hash
919
+ altering_hash: scim_hash,
920
+ with_attr_map: { organization: :org_name }
734
921
  )
735
922
 
736
923
  expect(scim_hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']['organization' ]).to eql('OTHERORG')
@@ -760,7 +947,13 @@ RSpec.describe Scimitar::Resources::Mixin do
760
947
  nature: 'add',
761
948
  path: path,
762
949
  value: 'added_over_original@test.com',
763
- 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
+ }
764
957
  )
765
958
 
766
959
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -786,7 +979,13 @@ RSpec.describe Scimitar::Resources::Mixin do
786
979
  nature: 'add',
787
980
  path: path,
788
981
  value: 'added_over_original@test.com',
789
- 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
+ }
790
989
  )
791
990
 
792
991
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -813,7 +1012,13 @@ RSpec.describe Scimitar::Resources::Mixin do
813
1012
  nature: 'add',
814
1013
  path: path,
815
1014
  value: 'added_over_original@test.com',
816
- 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
+ }
817
1022
  )
818
1023
 
819
1024
  expect(scim_hash['emails'][0]['value']).to eql('added_over_original@test.com')
@@ -837,7 +1042,13 @@ RSpec.describe Scimitar::Resources::Mixin do
837
1042
  nature: 'add',
838
1043
  path: path,
839
1044
  value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be)
840
- 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
+ }
841
1052
  )
842
1053
 
843
1054
  expect(scim_hash['emails'].size).to eql(2)
@@ -885,7 +1096,12 @@ RSpec.describe Scimitar::Resources::Mixin do
885
1096
  nature: 'add',
886
1097
  path: ['root'],
887
1098
  value: {'members' => [{'value' => '3'}]},
888
- altering_hash: scim_hash
1099
+ altering_hash: scim_hash,
1100
+ with_attr_map: {
1101
+ members: [
1102
+ { list: :members, using: { value: :id } }
1103
+ ]
1104
+ }
889
1105
  )
890
1106
 
891
1107
  expect(scim_hash['root']['members']).to match_array([{'value' => '1'}, {'value' => '2'}, {'value' => '3'}])
@@ -903,7 +1119,8 @@ RSpec.describe Scimitar::Resources::Mixin do
903
1119
  nature: 'add',
904
1120
  path: path,
905
1121
  value: 'foo',
906
- altering_hash: scim_hash
1122
+ altering_hash: scim_hash,
1123
+ with_attr_map: { userName: :user_name }
907
1124
  )
908
1125
 
909
1126
  expect(scim_hash['userName']).to eql('foo')
@@ -918,7 +1135,8 @@ RSpec.describe Scimitar::Resources::Mixin do
918
1135
  nature: 'add',
919
1136
  path: path,
920
1137
  value: 'Baz',
921
- altering_hash: scim_hash
1138
+ altering_hash: scim_hash,
1139
+ with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
922
1140
  )
923
1141
 
924
1142
  expect(scim_hash['name']['givenName']).to eql('Baz')
@@ -933,7 +1151,8 @@ RSpec.describe Scimitar::Resources::Mixin do
933
1151
  nature: 'add',
934
1152
  path: path,
935
1153
  value: 'SOMEORG',
936
- altering_hash: scim_hash
1154
+ altering_hash: scim_hash,
1155
+ with_attr_map: { organization: :org_name }
937
1156
  )
938
1157
 
939
1158
  expect(scim_hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']['organization' ]).to eql('SOMEORG')
@@ -959,7 +1178,13 @@ RSpec.describe Scimitar::Resources::Mixin do
959
1178
  nature: 'add',
960
1179
  path: path,
961
1180
  value: 'added@test.com',
962
- 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
+ }
963
1188
  )
964
1189
 
965
1190
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -984,7 +1209,13 @@ RSpec.describe Scimitar::Resources::Mixin do
984
1209
  nature: 'add',
985
1210
  path: path,
986
1211
  value: 'added@test.com',
987
- 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
+ }
988
1219
  )
989
1220
 
990
1221
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -1000,7 +1231,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1000
1231
  nature: 'add',
1001
1232
  path: path,
1002
1233
  value: 'added@test.com',
1003
- 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
+ }
1004
1241
  )
1005
1242
 
1006
1243
  expect(scim_hash['emails'][0]['value']).to eql('added@test.com')
@@ -1024,7 +1261,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1024
1261
  nature: 'add',
1025
1262
  path: path,
1026
1263
  value: 'added@test.com',
1027
- 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
+ }
1028
1271
  )
1029
1272
 
1030
1273
  expect(scim_hash['emails'][0]['value']).to eql('added@test.com')
@@ -1041,7 +1284,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1041
1284
  nature: 'add',
1042
1285
  path: path,
1043
1286
  value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be)
1044
- 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
+ }
1045
1294
  )
1046
1295
 
1047
1296
  expect(scim_hash['emails'].size).to eql(1)
@@ -1057,7 +1306,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1057
1306
  #
1058
1307
  context 'remove' do
1059
1308
  context 'when prior value already exists' do
1060
- it 'simple value: removes' do
1309
+ it 'simple value: clears to "nil" in order to remove' do
1061
1310
  path = [ 'userName' ]
1062
1311
  scim_hash = { 'userName' => 'bar' }.with_indifferent_case_insensitive_access()
1063
1312
 
@@ -1066,13 +1315,14 @@ RSpec.describe Scimitar::Resources::Mixin do
1066
1315
  nature: 'remove',
1067
1316
  path: path,
1068
1317
  value: nil,
1069
- altering_hash: scim_hash
1318
+ altering_hash: scim_hash,
1319
+ with_attr_map: { userName: :user_name }
1070
1320
  )
1071
1321
 
1072
- expect(scim_hash).to be_empty
1322
+ expect(scim_hash).to eql({ 'userName' => nil })
1073
1323
  end
1074
1324
 
1075
- it 'nested simple value: removes' do
1325
+ it 'nested simple value: clears to "nil" in order to remove' do
1076
1326
  path = [ 'name', 'givenName' ]
1077
1327
  scim_hash = { 'name' => { 'givenName' => 'Foo', 'familyName' => 'Bar' } }.with_indifferent_case_insensitive_access()
1078
1328
 
@@ -1081,15 +1331,15 @@ RSpec.describe Scimitar::Resources::Mixin do
1081
1331
  nature: 'remove',
1082
1332
  path: path,
1083
1333
  value: nil,
1084
- altering_hash: scim_hash
1334
+ altering_hash: scim_hash,
1335
+ with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
1085
1336
  )
1086
1337
 
1087
- expect(scim_hash['name']).to_not have_key('givenName')
1088
- expect(scim_hash['name']['familyName']).to eql('Bar')
1338
+ expect(scim_hash).to eql({ 'name' => { 'givenName' => nil, 'familyName' => 'Bar' } })
1089
1339
  end
1090
1340
 
1091
1341
  context 'with filter mid-path' do
1092
- it 'by string match: removes' do
1342
+ it 'by string match: clears to "nil" in order to remove' do
1093
1343
  path = [ 'emails[type eq "work"]', 'value' ]
1094
1344
  scim_hash = {
1095
1345
  'emails' => [
@@ -1109,14 +1359,30 @@ RSpec.describe Scimitar::Resources::Mixin do
1109
1359
  nature: 'remove',
1110
1360
  path: path,
1111
1361
  value: nil,
1112
- 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
+ }
1113
1369
  )
1114
1370
 
1115
- expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
1116
- 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
+ })
1117
1383
  end
1118
1384
 
1119
- it 'by boolean match: removes' do
1385
+ it 'by boolean match: clears to "nil" in order to remove' do
1120
1386
  path = [ 'emails[primary eq true]', 'value' ]
1121
1387
  scim_hash = {
1122
1388
  'emails' => [
@@ -1135,14 +1401,29 @@ RSpec.describe Scimitar::Resources::Mixin do
1135
1401
  nature: 'remove',
1136
1402
  path: path,
1137
1403
  value: nil,
1138
- 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
+ }
1139
1411
  )
1140
1412
 
1141
- expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
1142
- 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
+ })
1143
1424
  end
1144
1425
 
1145
- it 'multiple matches: removes all' do
1426
+ it 'multiple matches: clears all to "nil" in order to remove' do
1146
1427
  path = [ 'emails[type eq "work"]', 'value' ]
1147
1428
  scim_hash = {
1148
1429
  'emails' => [
@@ -1153,7 +1434,11 @@ RSpec.describe Scimitar::Resources::Mixin do
1153
1434
  {
1154
1435
  'type' => 'work',
1155
1436
  'value' => 'work_2@test.com'
1156
- }
1437
+ },
1438
+ {
1439
+ 'type' => 'home',
1440
+ 'value' => 'home@test.com'
1441
+ },
1157
1442
  ]
1158
1443
  }.with_indifferent_case_insensitive_access()
1159
1444
 
@@ -1162,16 +1447,36 @@ RSpec.describe Scimitar::Resources::Mixin do
1162
1447
  nature: 'remove',
1163
1448
  path: path,
1164
1449
  value: nil,
1165
- 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
+ }
1166
1457
  )
1167
1458
 
1168
- expect(scim_hash['emails'][0]).to_not have_key('value')
1169
- 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
+ })
1170
1475
  end
1171
1476
  end # "context 'with filter mid-path' do"
1172
1477
 
1173
1478
  context 'with filter at end of path' do
1174
- it 'by string match: removes entire matching array entry' do
1479
+ it 'by string match: clears to "nil" in order to remove' do
1175
1480
  path = [ 'emails[type eq "work"]' ]
1176
1481
  scim_hash = {
1177
1482
  'emails' => [
@@ -1191,23 +1496,39 @@ RSpec.describe Scimitar::Resources::Mixin do
1191
1496
  nature: 'remove',
1192
1497
  path: path,
1193
1498
  value: nil,
1194
- 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
+ }
1195
1506
  )
1196
1507
 
1197
- expect(scim_hash['emails'].size).to eql(1)
1198
- 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
+ })
1199
1520
  end
1200
1521
 
1201
- it 'by boolean match: removes entire matching array entry' do
1522
+ it 'by boolean match: clears to "nil" in order to remove' do
1202
1523
  path = [ 'emails[primary eq true]' ]
1203
1524
  scim_hash = {
1204
1525
  'emails' => [
1205
1526
  {
1206
- 'value' => 'home@test.com'
1527
+ 'value' => 'home@test.com',
1528
+ 'primary' => true
1207
1529
  },
1208
1530
  {
1209
- 'value' => 'work@test.com',
1210
- 'primary' => true
1531
+ 'value' => 'work@test.com'
1211
1532
  }
1212
1533
  ]
1213
1534
  }.with_indifferent_case_insensitive_access()
@@ -1217,14 +1538,29 @@ RSpec.describe Scimitar::Resources::Mixin do
1217
1538
  nature: 'remove',
1218
1539
  path: path,
1219
1540
  value: nil,
1220
- 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
+ }
1221
1548
  )
1222
1549
 
1223
- expect(scim_hash['emails'].size).to eql(1)
1224
- 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
+ })
1225
1561
  end
1226
1562
 
1227
- it 'multiple matches: removes all matching array entries' do
1563
+ it 'multiple matches: clears all to "nil" in order to remove' do
1228
1564
  path = [ 'emails[type eq "work"]' ]
1229
1565
  scim_hash = {
1230
1566
  'emails' => [
@@ -1248,21 +1584,45 @@ RSpec.describe Scimitar::Resources::Mixin do
1248
1584
  nature: 'remove',
1249
1585
  path: path,
1250
1586
  value: nil,
1251
- 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
+ }
1252
1594
  )
1253
1595
 
1254
- expect(scim_hash['emails'].size).to eql(1)
1255
- 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
+ })
1256
1612
  end
1257
1613
  end # "context 'with filter at end of path' do"
1258
1614
 
1259
- it 'whole array: removes' do
1615
+ it 'whole array: clears mapped values to "nil" to remove them' do
1260
1616
  path = [ 'emails' ]
1261
1617
  scim_hash = {
1262
1618
  'emails' => [
1263
1619
  {
1264
1620
  'type' => 'home',
1265
1621
  'value' => 'home@test.com'
1622
+ },
1623
+ {
1624
+ 'type' => 'work',
1625
+ 'value' => 'work@test.com'
1266
1626
  }
1267
1627
  ]
1268
1628
  }.with_indifferent_case_insensitive_access()
@@ -1272,10 +1632,27 @@ RSpec.describe Scimitar::Resources::Mixin do
1272
1632
  nature: 'remove',
1273
1633
  path: path,
1274
1634
  value: nil,
1275
- 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
+ }
1276
1642
  )
1277
1643
 
1278
- 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
+ })
1279
1656
  end
1280
1657
 
1281
1658
  # What we expect:
@@ -1321,7 +1698,12 @@ RSpec.describe Scimitar::Resources::Mixin do
1321
1698
  nature: 'remove',
1322
1699
  path: path,
1323
1700
  value: value,
1324
- 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
+ }
1325
1707
  )
1326
1708
 
1327
1709
  expect(scim_hash).to eql({
@@ -1373,7 +1755,12 @@ RSpec.describe Scimitar::Resources::Mixin do
1373
1755
  nature: 'remove',
1374
1756
  path: path,
1375
1757
  value: value,
1376
- 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
+ }
1377
1764
  )
1378
1765
 
1379
1766
  expect(scim_hash).to eql({
@@ -1407,7 +1794,12 @@ RSpec.describe Scimitar::Resources::Mixin do
1407
1794
  nature: 'remove',
1408
1795
  path: path,
1409
1796
  value: value,
1410
- 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
+ }
1411
1803
  )
1412
1804
 
1413
1805
  expect(scim_hash).to eql({
@@ -1435,7 +1827,12 @@ RSpec.describe Scimitar::Resources::Mixin do
1435
1827
  nature: 'remove',
1436
1828
  path: path,
1437
1829
  value: value,
1438
- 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
+ }
1439
1836
  )
1440
1837
 
1441
1838
  expect(scim_hash).to eql({
@@ -1463,7 +1860,12 @@ RSpec.describe Scimitar::Resources::Mixin do
1463
1860
  nature: 'remove',
1464
1861
  path: path,
1465
1862
  value: value,
1466
- 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
+ }
1467
1869
  )
1468
1870
 
1469
1871
  # The 'value' mismatched, so the user was not removed.
@@ -1499,7 +1901,12 @@ RSpec.describe Scimitar::Resources::Mixin do
1499
1901
  nature: 'remove',
1500
1902
  path: path,
1501
1903
  value: value,
1502
- 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
+ }
1503
1910
  )
1504
1911
 
1505
1912
  # Type 'Group' mismatches 'User', so the user was not
@@ -1517,7 +1924,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1517
1924
  })
1518
1925
  end
1519
1926
 
1520
- it 'matches keys case-insensitive' do
1927
+ it 'matches keys case-insensitive (but preserves case in response)' do
1521
1928
  path = [ 'members' ]
1522
1929
  value = [ { '$ref' => nil, 'VALUe' => 'f648f8d5ea4e4cd38e9c' } ]
1523
1930
  scim_hash = {
@@ -1536,12 +1943,17 @@ RSpec.describe Scimitar::Resources::Mixin do
1536
1943
  nature: 'remove',
1537
1944
  path: path,
1538
1945
  value: value,
1539
- 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
+ }
1540
1952
  )
1541
1953
 
1542
1954
  expect(scim_hash).to eql({
1543
1955
  'displayname' => 'Mock group',
1544
- 'members' => []
1956
+ 'memBERS' => []
1545
1957
  })
1546
1958
  end
1547
1959
 
@@ -1549,7 +1961,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1549
1961
  path = [ 'members' ]
1550
1962
  value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c', 'type' => 'USER' } ]
1551
1963
  scim_hash = {
1552
- 'displayname' => 'Mock group',
1964
+ 'displayName' => 'Mock group',
1553
1965
  'members' => [
1554
1966
  {
1555
1967
  'value' => 'f648f8d5ea4e4cd38e9c',
@@ -1564,13 +1976,18 @@ RSpec.describe Scimitar::Resources::Mixin do
1564
1976
  nature: 'remove',
1565
1977
  path: path,
1566
1978
  value: value,
1567
- 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
+ }
1568
1985
  )
1569
1986
 
1570
- # USER mismatchs User, so the user was not removed.
1987
+ # USER mismatches User, so the user was not removed.
1571
1988
  #
1572
1989
  expect(scim_hash).to eql({
1573
- 'displayname' => 'Mock group',
1990
+ 'displayName' => 'Mock group',
1574
1991
  'members' => [
1575
1992
  {
1576
1993
  'value' => 'f648f8d5ea4e4cd38e9c',
@@ -1583,7 +2000,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1583
2000
  end # "context 'removing a user from a group' do"
1584
2001
 
1585
2002
  context 'generic use' do
1586
- it 'removes matched items' do
2003
+ it 'clears static map matched items to "nil" in order to remove' do
1587
2004
  path = [ 'emails' ]
1588
2005
  value = [ { 'type' => 'work' } ]
1589
2006
  scim_hash = {
@@ -1604,7 +2021,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1604
2021
  nature: 'remove',
1605
2022
  path: path,
1606
2023
  value: value,
1607
- 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
+ }
1608
2031
  )
1609
2032
 
1610
2033
  expect(scim_hash).to eql({
@@ -1612,6 +2035,10 @@ RSpec.describe Scimitar::Resources::Mixin do
1612
2035
  {
1613
2036
  'type' => 'home',
1614
2037
  'value' => 'home@test.com'
2038
+ },
2039
+ {
2040
+ 'type' => 'work',
2041
+ 'value' => nil
1615
2042
  }
1616
2043
  ]
1617
2044
  })
@@ -1638,7 +2065,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1638
2065
  nature: 'remove',
1639
2066
  path: path,
1640
2067
  value: value,
1641
- 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
+ }
1642
2075
  )
1643
2076
 
1644
2077
  expect(scim_hash).to eql({
@@ -1670,6 +2103,10 @@ RSpec.describe Scimitar::Resources::Mixin do
1670
2103
  {
1671
2104
  'active' => false,
1672
2105
  'value' => '42'
2106
+ },
2107
+ {
2108
+ 'active' => 'hello',
2109
+ 'value' => 'world'
1673
2110
  }
1674
2111
  ]
1675
2112
  }.with_indifferent_case_insensitive_access()
@@ -1679,10 +2116,28 @@ RSpec.describe Scimitar::Resources::Mixin do
1679
2116
  nature: 'remove',
1680
2117
  path: path,
1681
2118
  value: value,
1682
- 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
+ }
1683
2131
  )
1684
2132
 
1685
- expect(scim_hash).to eql({'test' => []})
2133
+ expect(scim_hash).to eql({
2134
+ 'test' => [
2135
+ {
2136
+ 'active' => 'hello',
2137
+ 'value' => 'world'
2138
+ }
2139
+ ]
2140
+ })
1686
2141
  end
1687
2142
 
1688
2143
  it 'handles a singular to-remove value rather than an array' do
@@ -1706,7 +2161,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1706
2161
  nature: 'remove',
1707
2162
  path: path,
1708
2163
  value: value,
1709
- 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
+ }
1710
2171
  )
1711
2172
 
1712
2173
  expect(scim_hash).to eql({
@@ -1714,6 +2175,10 @@ RSpec.describe Scimitar::Resources::Mixin do
1714
2175
  {
1715
2176
  'type' => 'home',
1716
2177
  'value' => 'home@test.com'
2178
+ },
2179
+ {
2180
+ 'type' => 'work',
2181
+ 'value' => nil
1717
2182
  }
1718
2183
  ]
1719
2184
  })
@@ -1735,7 +2200,10 @@ RSpec.describe Scimitar::Resources::Mixin do
1735
2200
  nature: 'remove',
1736
2201
  path: path,
1737
2202
  value: value,
1738
- altering_hash: scim_hash
2203
+ altering_hash: scim_hash,
2204
+ with_attr_map: {
2205
+ test: []
2206
+ }
1739
2207
  )
1740
2208
 
1741
2209
  expect(scim_hash).to eql({
@@ -1775,7 +2243,18 @@ RSpec.describe Scimitar::Resources::Mixin do
1775
2243
  nature: 'remove',
1776
2244
  path: path,
1777
2245
  value: value,
1778
- 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
+ }
1779
2258
  )
1780
2259
 
1781
2260
  expect(scim_hash).to eql({
@@ -1814,7 +2293,18 @@ RSpec.describe Scimitar::Resources::Mixin do
1814
2293
  nature: 'remove',
1815
2294
  path: path,
1816
2295
  value: value,
1817
- 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
+ }
1818
2308
  )
1819
2309
 
1820
2310
  expect(scim_hash).to eql({
@@ -1848,7 +2338,18 @@ RSpec.describe Scimitar::Resources::Mixin do
1848
2338
  nature: 'remove',
1849
2339
  path: path,
1850
2340
  value: value,
1851
- 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
+ }
1852
2353
  )
1853
2354
 
1854
2355
  # The 'value' mismatched, so the user was not removed.
@@ -1878,7 +2379,8 @@ RSpec.describe Scimitar::Resources::Mixin do
1878
2379
  nature: 'remove',
1879
2380
  path: path,
1880
2381
  value: nil,
1881
- altering_hash: scim_hash
2382
+ altering_hash: scim_hash,
2383
+ with_attr_map: { userName: :user_name }
1882
2384
  )
1883
2385
 
1884
2386
  expect(scim_hash).to be_empty
@@ -1893,7 +2395,8 @@ RSpec.describe Scimitar::Resources::Mixin do
1893
2395
  nature: 'remove',
1894
2396
  path: path,
1895
2397
  value: nil,
1896
- altering_hash: scim_hash
2398
+ altering_hash: scim_hash,
2399
+ with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
1897
2400
  )
1898
2401
 
1899
2402
  expect(scim_hash['name']).to_not have_key('givenName')
@@ -1917,7 +2420,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1917
2420
  nature: 'remove',
1918
2421
  path: path,
1919
2422
  value: nil,
1920
- 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
+ }
1921
2430
  )
1922
2431
 
1923
2432
  expect(scim_hash['emails'].size).to eql(1)
@@ -1939,7 +2448,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1939
2448
  nature: 'remove',
1940
2449
  path: path,
1941
2450
  value: nil,
1942
- 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
+ }
1943
2458
  )
1944
2459
 
1945
2460
  expect(scim_hash['emails'].size).to eql(1)
@@ -1962,7 +2477,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1962
2477
  nature: 'remove',
1963
2478
  path: path,
1964
2479
  value: nil,
1965
- 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
+ }
1966
2487
  )
1967
2488
 
1968
2489
  expect(scim_hash['emails'].size).to eql(1)
@@ -1980,7 +2501,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1980
2501
  nature: 'remove',
1981
2502
  path: path,
1982
2503
  value: nil,
1983
- 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
+ }
1984
2511
  )
1985
2512
 
1986
2513
  expect(scim_hash).to be_empty
@@ -2002,7 +2529,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2002
2529
  nature: 'remove',
2003
2530
  path: path,
2004
2531
  value: nil,
2005
- 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
+ }
2006
2539
  )
2007
2540
 
2008
2541
  expect(scim_hash['emails'].size).to eql(1)
@@ -2019,7 +2552,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2019
2552
  nature: 'remove',
2020
2553
  path: path,
2021
2554
  value: nil,
2022
- 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
+ }
2023
2562
  )
2024
2563
 
2025
2564
  expect(scim_hash).to_not have_key('emails')
@@ -2045,7 +2584,8 @@ RSpec.describe Scimitar::Resources::Mixin do
2045
2584
  nature: 'replace',
2046
2585
  path: path,
2047
2586
  value: 'foo',
2048
- altering_hash: scim_hash
2587
+ altering_hash: scim_hash,
2588
+ with_attr_map: { userName: :user_name }
2049
2589
  )
2050
2590
 
2051
2591
  expect(scim_hash['userName']).to eql('foo')
@@ -2060,7 +2600,8 @@ RSpec.describe Scimitar::Resources::Mixin do
2060
2600
  nature: 'replace',
2061
2601
  path: path,
2062
2602
  value: 'Baz',
2063
- altering_hash: scim_hash
2603
+ altering_hash: scim_hash,
2604
+ with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
2064
2605
  )
2065
2606
 
2066
2607
  expect(scim_hash['name']['givenName' ]).to eql('Baz')
@@ -2088,7 +2629,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2088
2629
  nature: 'replace',
2089
2630
  path: path,
2090
2631
  value: 'added_over_original@test.com',
2091
- 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
+ }
2092
2639
  )
2093
2640
 
2094
2641
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -2114,7 +2661,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2114
2661
  nature: 'replace',
2115
2662
  path: path,
2116
2663
  value: 'added_over_original@test.com',
2117
- 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
+ }
2118
2671
  )
2119
2672
 
2120
2673
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -2141,7 +2694,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2141
2694
  nature: 'replace',
2142
2695
  path: path,
2143
2696
  value: 'added_over_original@test.com',
2144
- 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
+ }
2145
2704
  )
2146
2705
 
2147
2706
  expect(scim_hash['emails'][0]['value']).to eql('added_over_original@test.com')
@@ -2170,7 +2729,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2170
2729
  nature: 'replace',
2171
2730
  path: path,
2172
2731
  value: {'type' => 'home', 'primary' => true, 'value' => 'home@test.com'},
2173
- 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
+ }
2174
2739
  )
2175
2740
 
2176
2741
  expect(scim_hash['emails'].size).to eql(2)
@@ -2204,7 +2769,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2204
2769
  nature: 'replace',
2205
2770
  path: path,
2206
2771
  value: {'type' => 'workinate', 'value' => 'replaced@test.com'},
2207
- 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
+ }
2208
2779
  )
2209
2780
 
2210
2781
  expect(scim_hash['emails'].size).to eql(3)
@@ -2233,7 +2804,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2233
2804
  nature: 'replace',
2234
2805
  path: path,
2235
2806
  value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be)
2236
- 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
+ }
2237
2814
  )
2238
2815
 
2239
2816
  expect(scim_hash['emails'].size).to eql(1)
@@ -2252,7 +2829,8 @@ RSpec.describe Scimitar::Resources::Mixin do
2252
2829
  nature: 'replace',
2253
2830
  path: path,
2254
2831
  value: 'foo',
2255
- altering_hash: scim_hash
2832
+ altering_hash: scim_hash,
2833
+ with_attr_map: { userName: :user_name }
2256
2834
  )
2257
2835
 
2258
2836
  expect(scim_hash['userName']).to eql('foo')
@@ -2267,7 +2845,8 @@ RSpec.describe Scimitar::Resources::Mixin do
2267
2845
  nature: 'replace',
2268
2846
  path: path,
2269
2847
  value: 'Baz',
2270
- altering_hash: scim_hash
2848
+ altering_hash: scim_hash,
2849
+ with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
2271
2850
  )
2272
2851
 
2273
2852
  expect(scim_hash['name']['givenName']).to eql('Baz')
@@ -2293,7 +2872,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2293
2872
  nature: 'replace',
2294
2873
  path: path,
2295
2874
  value: 'added@test.com',
2296
- 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
+ }
2297
2882
  )
2298
2883
 
2299
2884
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -2318,7 +2903,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2318
2903
  nature: 'replace',
2319
2904
  path: path,
2320
2905
  value: 'added@test.com',
2321
- 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
+ }
2322
2913
  )
2323
2914
 
2324
2915
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -2343,7 +2934,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2343
2934
  nature: 'replace',
2344
2935
  path: path,
2345
2936
  value: 'added@test.com',
2346
- 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
+ }
2347
2944
  )
2348
2945
 
2349
2946
  expect(scim_hash['emails'][0]['value']).to eql('added@test.com')
@@ -2361,7 +2958,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2361
2958
  nature: 'replace',
2362
2959
  path: path,
2363
2960
  value: {'type' => 'work', 'value' => 'work@test.com'},
2364
- 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
+ }
2365
2968
  )
2366
2969
 
2367
2970
  expect(scim_hash['emails'].size).to eql(1)
@@ -2385,7 +2988,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2385
2988
  nature: 'replace',
2386
2989
  path: path,
2387
2990
  value: {'type' => 'work', 'value' => 'work@test.com'},
2388
- 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
+ }
2389
2998
  )
2390
2999
 
2391
3000
  expect(scim_hash['emails'].size).to eql(2)
@@ -2404,7 +3013,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2404
3013
  nature: 'replace',
2405
3014
  path: path,
2406
3015
  value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be)
2407
- 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
+ }
2408
3023
  )
2409
3024
 
2410
3025
  expect(scim_hash['emails'].size).to eql(1)
@@ -2422,7 +3037,11 @@ RSpec.describe Scimitar::Resources::Mixin do
2422
3037
  nature: 'replace',
2423
3038
  path: path,
2424
3039
  value: { 'active' => false }.with_indifferent_case_insensitive_access(),
2425
- altering_hash: scim_hash
3040
+ altering_hash: scim_hash,
3041
+ with_attr_map: {
3042
+ userName: :user_name,
3043
+ active: :active
3044
+ }
2426
3045
  )
2427
3046
 
2428
3047
  expect(scim_hash['root']['userName']).to eql('bar')
@@ -2543,7 +3162,8 @@ RSpec.describe Scimitar::Resources::Mixin do
2543
3162
  nature: 'add',
2544
3163
  path: ['complex[type eq "type1"]', 'data', 'nested[nature eq "nature2"]', 'info'],
2545
3164
  value: [{ 'deeper' => 'addition' }],
2546
- altering_hash: scim_hash
3165
+ altering_hash: scim_hash,
3166
+ with_attr_map: @contrived_class.scim_attributes_map()
2547
3167
  )
2548
3168
 
2549
3169
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged
@@ -2566,7 +3186,8 @@ RSpec.describe Scimitar::Resources::Mixin do
2566
3186
  nature: 'replace',
2567
3187
  path: ['complex[type eq "type1"]', 'data', 'nested[nature eq "nature2"]', 'info'],
2568
3188
  value: [{ 'deeper' => 'addition' }],
2569
- altering_hash: scim_hash
3189
+ altering_hash: scim_hash,
3190
+ with_attr_map: @contrived_class.scim_attributes_map()
2570
3191
  )
2571
3192
 
2572
3193
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged?
@@ -2583,7 +3204,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2583
3204
  expect(scim_hash.dig('complex', 2, 'data', 'nested', 0, 'info', 0, 'deep')).to eql('nature2deep3') # Unchanged
2584
3205
  end
2585
3206
 
2586
- it 'removes across multiple deep matching points' do
3207
+ it 'removes via clearing to "nil" or empty Array across multiple deep matching points' do
2587
3208
  scim_hash = @original_hash.deep_dup().with_indifferent_case_insensitive_access()
2588
3209
  contrived_instance = @contrived_class.new
2589
3210
  contrived_instance.send(
@@ -2591,16 +3212,17 @@ RSpec.describe Scimitar::Resources::Mixin do
2591
3212
  nature: 'remove',
2592
3213
  path: ['complex[type eq "type1"]', 'data', 'nested[nature eq "nature2"]', 'info'],
2593
3214
  value: nil,
2594
- altering_hash: scim_hash
3215
+ altering_hash: scim_hash,
3216
+ with_attr_map: @contrived_class.scim_attributes_map()
2595
3217
  )
2596
3218
 
2597
3219
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged
2598
- 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([])
2599
3221
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 2, 'nature')).to be_present
2600
- 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([])
2601
3223
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 2, 'nature')).to be_present
2602
3224
 
2603
- 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([])
2604
3226
  expect(scim_hash.dig('complex', 1, 'data', 'nested', 0, 'nature')).to be_present
2605
3227
 
2606
3228
  expect(scim_hash.dig('complex', 2, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged
@@ -2634,7 +3256,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2634
3256
  nature: 'replace',
2635
3257
  path: path,
2636
3258
  value: 'ignored',
2637
- 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
+ }
2638
3266
  )
2639
3267
  end.to raise_error(Scimitar::ErrorResponse) { |e| expect(e.as_json['scimType']).to eql('invalidSyntax') }
2640
3268
  end
@@ -2651,7 +3279,8 @@ RSpec.describe Scimitar::Resources::Mixin do
2651
3279
  nature: 'replace',
2652
3280
  path: path,
2653
3281
  value: 'ignored',
2654
- altering_hash: scim_hash
3282
+ altering_hash: scim_hash,
3283
+ with_attr_map: { userName: :user_name }
2655
3284
  )
2656
3285
  end.to raise_error(Scimitar::ErrorResponse) { |e| expect(e.as_json['scimType']).to eql('invalidSyntax') }
2657
3286
  end
@@ -2671,7 +3300,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2671
3300
  nature: 'replace',
2672
3301
  path: path,
2673
3302
  value: 'ignored',
2674
- 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
+ }
2675
3310
  )
2676
3311
  end.to raise_error(Scimitar::ErrorResponse) { |e| expect(e.as_json['scimType']).to eql('invalidSyntax') }
2677
3312
  end
@@ -2714,6 +3349,28 @@ RSpec.describe Scimitar::Resources::Mixin do
2714
3349
  expect(@instance.username).to eql('1234')
2715
3350
  end
2716
3351
 
3352
+ it 'which updates nested values using root syntax' do
3353
+ @instance.update!(first_name: 'Foo', last_name: 'Bar')
3354
+
3355
+ path = 'name.givenName'
3356
+ path = path.upcase if force_upper_case
3357
+
3358
+ patch = {
3359
+ 'schemas' => ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
3360
+ 'Operations' => [
3361
+ {
3362
+ 'op' => 'replace',
3363
+ 'value' => {
3364
+ path => 'Baz'
3365
+ }
3366
+ }
3367
+ ]
3368
+ }
3369
+
3370
+ @instance.from_scim_patch!(patch_hash: patch)
3371
+ expect(@instance.first_name).to eql('Baz')
3372
+ end
3373
+
2717
3374
  it 'which updates nested values' do
2718
3375
  @instance.update!(first_name: 'Foo', last_name: 'Bar')
2719
3376