scimitar 1.8.2 → 2.0.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/scimitar/active_record_backed_resources_controller.rb +20 -94
  3. data/app/controllers/scimitar/application_controller.rb +13 -41
  4. data/app/controllers/scimitar/schemas_controller.rb +0 -5
  5. data/app/models/scimitar/complex_types/address.rb +6 -0
  6. data/app/models/scimitar/engine_configuration.rb +5 -13
  7. data/app/models/scimitar/error_response.rb +0 -12
  8. data/app/models/scimitar/lists/query_parser.rb +10 -25
  9. data/app/models/scimitar/resource_invalid_error.rb +1 -1
  10. data/app/models/scimitar/resources/base.rb +4 -17
  11. data/app/models/scimitar/resources/mixin.rb +42 -539
  12. data/app/models/scimitar/schema/address.rb +0 -1
  13. data/app/models/scimitar/schema/attribute.rb +5 -14
  14. data/app/models/scimitar/schema/base.rb +1 -1
  15. data/app/models/scimitar/schema/vdtp.rb +1 -1
  16. data/app/models/scimitar/service_provider_configuration.rb +3 -14
  17. data/config/initializers/scimitar.rb +3 -28
  18. data/lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb +10 -140
  19. data/lib/scimitar/version.rb +2 -2
  20. data/lib/scimitar.rb +2 -7
  21. data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +1 -1
  22. data/spec/apps/dummy/app/models/mock_group.rb +1 -1
  23. data/spec/apps/dummy/app/models/mock_user.rb +8 -36
  24. data/spec/apps/dummy/config/application.rb +1 -0
  25. data/spec/apps/dummy/config/environments/test.rb +28 -5
  26. data/spec/apps/dummy/config/initializers/scimitar.rb +10 -61
  27. data/spec/apps/dummy/config/routes.rb +7 -28
  28. data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +1 -10
  29. data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +3 -8
  30. data/spec/apps/dummy/db/schema.rb +4 -11
  31. data/spec/controllers/scimitar/application_controller_spec.rb +3 -126
  32. data/spec/controllers/scimitar/resource_types_controller_spec.rb +2 -2
  33. data/spec/controllers/scimitar/schemas_controller_spec.rb +2 -10
  34. data/spec/models/scimitar/complex_types/address_spec.rb +4 -3
  35. data/spec/models/scimitar/complex_types/email_spec.rb +2 -0
  36. data/spec/models/scimitar/lists/query_parser_spec.rb +9 -76
  37. data/spec/models/scimitar/resources/base_spec.rb +70 -216
  38. data/spec/models/scimitar/resources/base_validation_spec.rb +2 -27
  39. data/spec/models/scimitar/resources/mixin_spec.rb +129 -1447
  40. data/spec/models/scimitar/schema/attribute_spec.rb +3 -22
  41. data/spec/models/scimitar/schema/base_spec.rb +1 -1
  42. data/spec/models/scimitar/schema/user_spec.rb +0 -10
  43. data/spec/requests/active_record_backed_resources_controller_spec.rb +68 -787
  44. data/spec/requests/application_controller_spec.rb +3 -16
  45. data/spec/spec_helper.rb +0 -8
  46. data/spec/support/hash_with_indifferent_case_insensitive_access_spec.rb +0 -108
  47. metadata +14 -25
  48. data/LICENSE.txt +0 -21
  49. data/README.md +0 -710
  50. data/lib/scimitar/support/utilities.rb +0 -51
  51. data/spec/apps/dummy/app/controllers/custom_create_mock_users_controller.rb +0 -25
  52. data/spec/apps/dummy/app/controllers/custom_replace_mock_users_controller.rb +0 -25
  53. data/spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb +0 -24
  54. data/spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb +0 -25
@@ -81,102 +81,6 @@ 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
-
180
84
  # ===========================================================================
181
85
  # Errant class definitions
182
86
  # ===========================================================================
@@ -255,74 +159,41 @@ RSpec.describe Scimitar::Resources::Mixin do
255
159
  # =========================================================================
256
160
 
257
161
  context '#to_scim' do
258
- context 'with a UUID, renamed primary key column' do
259
- it 'compiles instance attribute values into a SCIM representation, but omits do-not-return fields' 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}")
282
- json = scim.to_json()
283
- hash = JSON.parse(json)
284
-
285
- expect(hash).to eql({
286
- 'userName' => 'foo',
287
- 'name' => {'givenName'=>'Foo', 'familyName'=>'Bar'},
288
- 'active' => true,
289
- 'emails' => [{'type'=>'work', 'primary'=>true, 'value'=>'foo.bar@test.com'}, {"primary"=>false, "type"=>"home", "value"=>nil}],
290
- 'phoneNumbers'=> [{'type'=>'work', 'primary'=>false, 'value'=>'+642201234567'}],
291
- 'id' => uuid,
292
- 'externalId' => 'AA02984',
293
- 'groups' => [{'display'=>g1.display_name, 'value'=>g1.id.to_s}, {'display'=>g3.display_name, 'value'=>g3.id.to_s}],
294
- 'meta' => {'location'=>"https://test.com/mock_users/#{uuid}", 'resourceType'=>'User'},
295
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
296
-
297
- 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
298
- 'organization' => 'SOMEORG',
299
- 'department' => nil
300
- }
301
- })
302
- end
303
- end # "context 'with a UUID, renamed primary key column' do"
304
-
305
- context 'with an integer, conventionally named primary key column' do
306
- it 'compiles instance attribute values into a SCIM representation' do
307
- instance = MockGroup.new
308
- instance.id = 42
309
- instance.scim_uid = 'GG02984'
310
- instance.display_name = 'Some group'
311
-
312
- scim = instance.to_scim(location: 'https://test.com/mock_groups/42')
313
- json = scim.to_json()
314
- hash = JSON.parse(json)
315
-
316
- expect(hash).to eql({
317
- 'displayName' => 'Some group',
318
- 'id' => '42', # Note, String
319
- 'externalId' => 'GG02984',
320
- 'members' => [],
321
- 'meta' => {'location'=>'https://test.com/mock_groups/42', 'resourceType'=>'Group'},
322
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:Group']
323
- })
324
- end
325
- end # "context 'with an integer, conventionally named primary key column' do"
162
+ it 'compiles instance attribute values into a SCIM representation' do
163
+ instance = MockUser.new
164
+ instance.id = 42
165
+ instance.scim_uid = 'AA02984'
166
+ instance.username = 'foo'
167
+ instance.first_name = 'Foo'
168
+ instance.last_name = 'Bar'
169
+ instance.work_email_address = 'foo.bar@test.com'
170
+ instance.home_email_address = nil
171
+ instance.work_phone_number = '+642201234567'
172
+
173
+ g1 = MockGroup.create!(display_name: 'Group 1')
174
+ g2 = MockGroup.create!(display_name: 'Group 2')
175
+ g3 = MockGroup.create!(display_name: 'Group 3')
176
+
177
+ g1.mock_users << instance
178
+ g3.mock_users << instance
179
+
180
+ scim = instance.to_scim(location: 'https://test.com/mock_users/42')
181
+ json = scim.to_json()
182
+ hash = JSON.parse(json)
183
+
184
+ expect(hash).to eql({
185
+ 'userName' => 'foo',
186
+ 'name' => {'givenName'=>'Foo', 'familyName'=>'Bar'},
187
+ 'active' => true,
188
+ 'emails' => [{'type'=>'work', 'primary'=>true, 'value'=>'foo.bar@test.com'}, {"primary"=>false, "type"=>"home", "value"=>nil}],
189
+ 'phoneNumbers'=> [{'type'=>'work', 'primary'=>false, 'value'=>'+642201234567'}],
190
+ 'id' => '42', # Note, String
191
+ 'externalId' => 'AA02984',
192
+ 'groups' => [{'display'=>g1.display_name, 'value'=>g1.id.to_s}, {'display'=>g3.display_name, 'value'=>g3.id.to_s}],
193
+ 'meta' => {'location'=>'https://test.com/mock_users/42', 'resourceType'=>'User'},
194
+ 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
195
+ })
196
+ end
326
197
 
327
198
  context 'with optional timestamps' do
328
199
  context 'creation only' do
@@ -421,9 +292,7 @@ RSpec.describe Scimitar::Resources::Mixin do
421
292
  ],
422
293
 
423
294
  'meta' => {'location'=>'https://test.com/static_map_test', 'resourceType'=>'User'},
424
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
425
-
426
- 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {}
295
+ 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
427
296
  })
428
297
  end
429
298
  end # "context 'using static mappings' do"
@@ -450,38 +319,12 @@ RSpec.describe Scimitar::Resources::Mixin do
450
319
  ],
451
320
 
452
321
  'meta' => {'location'=>'https://test.com/dynamic_map_test', 'resourceType'=>'User'},
453
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
454
-
455
- 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {}
322
+ 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
456
323
  })
457
324
  end
458
325
  end # "context 'using dynamic lists' do"
459
326
  end # "context 'with arrays' do"
460
327
 
461
- context 'with "returned: \'never\' fields' do
462
- it 'hides appropriate top-level and nested attributes' do
463
- instance = NestedReturnedNeverTest.new(
464
- given_name: 'One',
465
- last_name: 'Two',
466
- private_given_name: 'Three',
467
- private_last_name: 'Four',
468
- simple_name: 'Five'
469
- )
470
-
471
- scim = instance.to_scim(location: 'https://test.com/never_retutrned_test')
472
- json = scim.to_json()
473
- hash = JSON.parse(json)
474
-
475
- expect(hash).to eql({
476
- 'name' => { 'givenName' => 'One' },
477
- 'simpleName' => 'Five',
478
-
479
- 'meta' => {'location'=>'https://test.com/never_retutrned_test', 'resourceType'=>'NestedReturnedNeverTestResourse'},
480
- 'schemas' => ['nested-returned-never-id']
481
- })
482
- end
483
- end # "context 'with "returned: \'never\' fields' do"
484
-
485
328
  context 'with bad definitions' do
486
329
  it 'complains about non-Hash entries in mapping Arrays' do
487
330
  expect(StaticMapTest).to receive(:scim_attributes_map).and_return({
@@ -525,7 +368,6 @@ RSpec.describe Scimitar::Resources::Mixin do
525
368
  it 'ignoring read-only lists' do
526
369
  hash = {
527
370
  'userName' => 'foo',
528
- 'password' => 'staplebatteryhorsecorrect',
529
371
  'name' => {'givenName' => 'Foo', 'familyName' => 'Bar'},
530
372
  'active' => true,
531
373
  'emails' => [{'type' => 'work', 'primary' => true, 'value' => 'foo.bar@test.com'}],
@@ -534,12 +376,7 @@ RSpec.describe Scimitar::Resources::Mixin do
534
376
  'id' => '42', # Note, String
535
377
  'externalId' => 'AA02984',
536
378
  'meta' => {'location' => 'https://test.com/mock_users/42', 'resourceType' => 'User'},
537
- 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'],
538
-
539
- 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
540
- 'organization' => 'SOMEORG',
541
- 'DEPARTMENT' => 'SOMEDPT'
542
- }
379
+ 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
543
380
  }
544
381
 
545
382
  hash = spec_helper_hupcase(hash) if force_upper_case
@@ -550,14 +387,11 @@ RSpec.describe Scimitar::Resources::Mixin do
550
387
 
551
388
  expect(instance.scim_uid ).to eql('AA02984')
552
389
  expect(instance.username ).to eql('foo')
553
- expect(instance.password ).to eql('staplebatteryhorsecorrect')
554
390
  expect(instance.first_name ).to eql('Foo')
555
391
  expect(instance.last_name ).to eql('Bar')
556
392
  expect(instance.work_email_address).to eql('foo.bar@test.com')
557
393
  expect(instance.home_email_address).to be_nil
558
394
  expect(instance.work_phone_number ).to eql('+642201234567')
559
- expect(instance.organization ).to eql('SOMEORG')
560
- expect(instance.department ).to eql('SOMEDPT')
561
395
  end
562
396
 
563
397
  it 'honouring read-write lists' do
@@ -571,8 +405,8 @@ RSpec.describe Scimitar::Resources::Mixin do
571
405
  'displayName' => 'Foo Group',
572
406
  'members' => [
573
407
  {'type' => 'Group', 'value' => g1.id.to_s},
574
- {'type' => 'User', 'value' => u1.primary_key.to_s},
575
- {'type' => 'User', 'value' => u3.primary_key.to_s}
408
+ {'type' => 'User', 'value' => u1.id.to_s},
409
+ {'type' => 'User', 'value' => u3.id.to_s}
576
410
  ],
577
411
  'externalId' => 'GG01536',
578
412
  'meta' => {'location'=>'https://test.com/mock_groups/1', 'resourceType'=>'Group'},
@@ -619,10 +453,8 @@ RSpec.describe Scimitar::Resources::Mixin do
619
453
  end # "context 'using upper case' do"
620
454
 
621
455
  it 'clears things not present in input' do
622
- uuid = SecureRandom.uuid
623
-
624
456
  instance = MockUser.new
625
- instance.primary_key = uuid
457
+ instance.id = 42
626
458
  instance.scim_uid = 'AA02984'
627
459
  instance.username = 'foo'
628
460
  instance.first_name = 'Foo'
@@ -633,7 +465,7 @@ RSpec.describe Scimitar::Resources::Mixin do
633
465
 
634
466
  instance.from_scim!(scim_hash: {})
635
467
 
636
- expect(instance.primary_key ).to eql(uuid)
468
+ expect(instance.id ).to eql(42)
637
469
  expect(instance.scim_uid ).to be_nil
638
470
  expect(instance.username ).to be_nil
639
471
  expect(instance.first_name ).to be_nil
@@ -822,8 +654,7 @@ RSpec.describe Scimitar::Resources::Mixin do
822
654
  nature: 'add',
823
655
  path: path,
824
656
  value: 'foo',
825
- altering_hash: scim_hash,
826
- with_attr_map: { userName: :user_name }
657
+ altering_hash: scim_hash
827
658
  )
828
659
 
829
660
  expect(scim_hash['userName']).to eql('foo')
@@ -838,30 +669,13 @@ RSpec.describe Scimitar::Resources::Mixin do
838
669
  nature: 'add',
839
670
  path: path,
840
671
  value: 'Baz',
841
- altering_hash: scim_hash,
842
- with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
672
+ altering_hash: scim_hash
843
673
  )
844
674
 
845
675
  expect(scim_hash['name']['givenName' ]).to eql('Baz')
846
676
  expect(scim_hash['name']['familyName']).to eql('Bar')
847
677
  end
848
678
 
849
- it 'with schema extensions: overwrites' do
850
- path = [ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User', 'organization' ]
851
- scim_hash = { 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => { 'organization' => 'SOMEORG' } }.with_indifferent_case_insensitive_access()
852
-
853
- @instance.send(
854
- :from_patch_backend!,
855
- nature: 'add',
856
- path: path,
857
- value: 'OTHERORG',
858
- altering_hash: scim_hash,
859
- with_attr_map: { organization: :org_name }
860
- )
861
-
862
- expect(scim_hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']['organization' ]).to eql('OTHERORG')
863
- end
864
-
865
679
  # For 'add', filter at end-of-path is nonsensical and not
866
680
  # supported by spec or Scimitar; we only test mid-path filters.
867
681
  #
@@ -886,13 +700,7 @@ RSpec.describe Scimitar::Resources::Mixin do
886
700
  nature: 'add',
887
701
  path: path,
888
702
  value: 'added_over_original@test.com',
889
- altering_hash: scim_hash,
890
- with_attr_map: {
891
- emails: [
892
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
893
- { match: 'type', with: 'work', using: { value: :work_email } },
894
- ]
895
- }
703
+ altering_hash: scim_hash
896
704
  )
897
705
 
898
706
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -918,13 +726,7 @@ RSpec.describe Scimitar::Resources::Mixin do
918
726
  nature: 'add',
919
727
  path: path,
920
728
  value: 'added_over_original@test.com',
921
- altering_hash: scim_hash,
922
- with_attr_map: {
923
- emails: [
924
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
925
- { match: 'type', with: 'work', using: { value: :work_email } },
926
- ]
927
- }
729
+ altering_hash: scim_hash
928
730
  )
929
731
 
930
732
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -951,13 +753,7 @@ RSpec.describe Scimitar::Resources::Mixin do
951
753
  nature: 'add',
952
754
  path: path,
953
755
  value: 'added_over_original@test.com',
954
- altering_hash: scim_hash,
955
- with_attr_map: {
956
- emails: [
957
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
958
- { match: 'type', with: 'work', using: { value: :work_email } },
959
- ]
960
- }
756
+ altering_hash: scim_hash
961
757
  )
962
758
 
963
759
  expect(scim_hash['emails'][0]['value']).to eql('added_over_original@test.com')
@@ -981,13 +777,7 @@ RSpec.describe Scimitar::Resources::Mixin do
981
777
  nature: 'add',
982
778
  path: path,
983
779
  value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be)
984
- altering_hash: scim_hash,
985
- with_attr_map: {
986
- emails: [
987
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
988
- { match: 'type', with: 'work', using: { value: :work_email } },
989
- ]
990
- }
780
+ altering_hash: scim_hash
991
781
  )
992
782
 
993
783
  expect(scim_hash['emails'].size).to eql(2)
@@ -1035,12 +825,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1035
825
  nature: 'add',
1036
826
  path: ['root'],
1037
827
  value: {'members' => [{'value' => '3'}]},
1038
- altering_hash: scim_hash,
1039
- with_attr_map: {
1040
- members: [
1041
- { list: :members, using: { value: :id } }
1042
- ]
1043
- }
828
+ altering_hash: scim_hash
1044
829
  )
1045
830
 
1046
831
  expect(scim_hash['root']['members']).to match_array([{'value' => '1'}, {'value' => '2'}, {'value' => '3'}])
@@ -1058,8 +843,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1058
843
  nature: 'add',
1059
844
  path: path,
1060
845
  value: 'foo',
1061
- altering_hash: scim_hash,
1062
- with_attr_map: { userName: :user_name }
846
+ altering_hash: scim_hash
1063
847
  )
1064
848
 
1065
849
  expect(scim_hash['userName']).to eql('foo')
@@ -1074,29 +858,12 @@ RSpec.describe Scimitar::Resources::Mixin do
1074
858
  nature: 'add',
1075
859
  path: path,
1076
860
  value: 'Baz',
1077
- altering_hash: scim_hash,
1078
- with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
861
+ altering_hash: scim_hash
1079
862
  )
1080
863
 
1081
864
  expect(scim_hash['name']['givenName']).to eql('Baz')
1082
865
  end
1083
866
 
1084
- it 'with schema extensions: adds' do
1085
- path = [ 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User', 'organization' ]
1086
- scim_hash = {}.with_indifferent_case_insensitive_access()
1087
-
1088
- @instance.send(
1089
- :from_patch_backend!,
1090
- nature: 'add',
1091
- path: path,
1092
- value: 'SOMEORG',
1093
- altering_hash: scim_hash,
1094
- with_attr_map: { organization: :org_name }
1095
- )
1096
-
1097
- expect(scim_hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']['organization' ]).to eql('SOMEORG')
1098
- end
1099
-
1100
867
  context 'with filter mid-path: adds' do
1101
868
  it 'by string match' do
1102
869
  path = [ 'emails[type eq "work"]', 'value' ]
@@ -1117,13 +884,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1117
884
  nature: 'add',
1118
885
  path: path,
1119
886
  value: 'added@test.com',
1120
- altering_hash: scim_hash,
1121
- with_attr_map: {
1122
- emails: [
1123
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1124
- { match: 'type', with: 'work', using: { value: :work_email } },
1125
- ]
1126
- }
887
+ altering_hash: scim_hash
1127
888
  )
1128
889
 
1129
890
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -1148,13 +909,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1148
909
  nature: 'add',
1149
910
  path: path,
1150
911
  value: 'added@test.com',
1151
- altering_hash: scim_hash,
1152
- with_attr_map: {
1153
- emails: [
1154
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1155
- { match: 'type', with: 'work', using: { value: :work_email } },
1156
- ]
1157
- }
912
+ altering_hash: scim_hash
1158
913
  )
1159
914
 
1160
915
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -1170,13 +925,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1170
925
  nature: 'add',
1171
926
  path: path,
1172
927
  value: 'added@test.com',
1173
- altering_hash: scim_hash,
1174
- with_attr_map: {
1175
- emails: [
1176
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1177
- { match: 'type', with: 'work', using: { value: :work_email } },
1178
- ]
1179
- }
928
+ altering_hash: scim_hash
1180
929
  )
1181
930
 
1182
931
  expect(scim_hash['emails'][0]['value']).to eql('added@test.com')
@@ -1200,13 +949,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1200
949
  nature: 'add',
1201
950
  path: path,
1202
951
  value: 'added@test.com',
1203
- altering_hash: scim_hash,
1204
- with_attr_map: {
1205
- emails: [
1206
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1207
- { match: 'type', with: 'work', using: { value: :work_email } },
1208
- ]
1209
- }
952
+ altering_hash: scim_hash
1210
953
  )
1211
954
 
1212
955
  expect(scim_hash['emails'][0]['value']).to eql('added@test.com')
@@ -1223,13 +966,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1223
966
  nature: 'add',
1224
967
  path: path,
1225
968
  value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be)
1226
- altering_hash: scim_hash,
1227
- with_attr_map: {
1228
- emails: [
1229
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1230
- { match: 'type', with: 'work', using: { value: :work_email } },
1231
- ]
1232
- }
969
+ altering_hash: scim_hash
1233
970
  )
1234
971
 
1235
972
  expect(scim_hash['emails'].size).to eql(1)
@@ -1245,7 +982,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1245
982
  #
1246
983
  context 'remove' do
1247
984
  context 'when prior value already exists' do
1248
- it 'simple value: clears to "nil" in order to remove' do
985
+ it 'simple value: removes' do
1249
986
  path = [ 'userName' ]
1250
987
  scim_hash = { 'userName' => 'bar' }.with_indifferent_case_insensitive_access()
1251
988
 
@@ -1254,14 +991,13 @@ RSpec.describe Scimitar::Resources::Mixin do
1254
991
  nature: 'remove',
1255
992
  path: path,
1256
993
  value: nil,
1257
- altering_hash: scim_hash,
1258
- with_attr_map: { userName: :user_name }
994
+ altering_hash: scim_hash
1259
995
  )
1260
996
 
1261
- expect(scim_hash).to eql({ 'userName' => nil })
997
+ expect(scim_hash).to be_empty
1262
998
  end
1263
999
 
1264
- it 'nested simple value: clears to "nil" in order to remove' do
1000
+ it 'nested simple value: removes' do
1265
1001
  path = [ 'name', 'givenName' ]
1266
1002
  scim_hash = { 'name' => { 'givenName' => 'Foo', 'familyName' => 'Bar' } }.with_indifferent_case_insensitive_access()
1267
1003
 
@@ -1270,15 +1006,15 @@ RSpec.describe Scimitar::Resources::Mixin do
1270
1006
  nature: 'remove',
1271
1007
  path: path,
1272
1008
  value: nil,
1273
- altering_hash: scim_hash,
1274
- with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
1009
+ altering_hash: scim_hash
1275
1010
  )
1276
1011
 
1277
- expect(scim_hash).to eql({ 'name' => { 'givenName' => nil, 'familyName' => 'Bar' } })
1012
+ expect(scim_hash['name']).to_not have_key('givenName')
1013
+ expect(scim_hash['name']['familyName']).to eql('Bar')
1278
1014
  end
1279
1015
 
1280
1016
  context 'with filter mid-path' do
1281
- it 'by string match: clears to "nil" in order to remove' do
1017
+ it 'by string match: removes' do
1282
1018
  path = [ 'emails[type eq "work"]', 'value' ]
1283
1019
  scim_hash = {
1284
1020
  'emails' => [
@@ -1298,30 +1034,14 @@ RSpec.describe Scimitar::Resources::Mixin do
1298
1034
  nature: 'remove',
1299
1035
  path: path,
1300
1036
  value: nil,
1301
- altering_hash: scim_hash,
1302
- with_attr_map: {
1303
- emails: [
1304
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1305
- { match: 'type', with: 'work', using: { value: :work_email } },
1306
- ]
1307
- }
1037
+ altering_hash: scim_hash
1308
1038
  )
1309
1039
 
1310
- expect(scim_hash).to eql({
1311
- 'emails' => [
1312
- {
1313
- 'type' => 'home',
1314
- 'value' => 'home@test.com'
1315
- },
1316
- {
1317
- 'type' => 'work',
1318
- 'value' => nil
1319
- }
1320
- ]
1321
- })
1040
+ expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
1041
+ expect(scim_hash['emails'][1]).to_not have_key('value')
1322
1042
  end
1323
1043
 
1324
- it 'by boolean match: clears to "nil" in order to remove' do
1044
+ it 'by boolean match: removes' do
1325
1045
  path = [ 'emails[primary eq true]', 'value' ]
1326
1046
  scim_hash = {
1327
1047
  'emails' => [
@@ -1340,29 +1060,14 @@ RSpec.describe Scimitar::Resources::Mixin do
1340
1060
  nature: 'remove',
1341
1061
  path: path,
1342
1062
  value: nil,
1343
- altering_hash: scim_hash,
1344
- with_attr_map: {
1345
- emails: [
1346
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1347
- { match: 'type', with: 'work', using: { value: :work_email } },
1348
- ]
1349
- }
1063
+ altering_hash: scim_hash
1350
1064
  )
1351
1065
 
1352
- expect(scim_hash).to eql({
1353
- 'emails' => [
1354
- {
1355
- 'value' => 'home@test.com'
1356
- },
1357
- {
1358
- 'value' => nil,
1359
- 'primary' => true
1360
- }
1361
- ]
1362
- })
1066
+ expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
1067
+ expect(scim_hash['emails'][1]).to_not have_key('value')
1363
1068
  end
1364
1069
 
1365
- it 'multiple matches: clears all to "nil" in order to remove' do
1070
+ it 'multiple matches: removes all' do
1366
1071
  path = [ 'emails[type eq "work"]', 'value' ]
1367
1072
  scim_hash = {
1368
1073
  'emails' => [
@@ -1373,11 +1078,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1373
1078
  {
1374
1079
  'type' => 'work',
1375
1080
  'value' => 'work_2@test.com'
1376
- },
1377
- {
1378
- 'type' => 'home',
1379
- 'value' => 'home@test.com'
1380
- },
1081
+ }
1381
1082
  ]
1382
1083
  }.with_indifferent_case_insensitive_access()
1383
1084
 
@@ -1386,36 +1087,16 @@ RSpec.describe Scimitar::Resources::Mixin do
1386
1087
  nature: 'remove',
1387
1088
  path: path,
1388
1089
  value: nil,
1389
- altering_hash: scim_hash,
1390
- with_attr_map: {
1391
- emails: [
1392
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1393
- { match: 'type', with: 'work', using: { value: :work_email } },
1394
- ]
1395
- }
1090
+ altering_hash: scim_hash
1396
1091
  )
1397
1092
 
1398
- expect(scim_hash).to eql({
1399
- 'emails' => [
1400
- {
1401
- 'type' => 'work',
1402
- 'value' => nil
1403
- },
1404
- {
1405
- 'type' => 'work',
1406
- 'value' => nil
1407
- },
1408
- {
1409
- 'type' => 'home',
1410
- 'value' => 'home@test.com'
1411
- },
1412
- ]
1413
- })
1093
+ expect(scim_hash['emails'][0]).to_not have_key('value')
1094
+ expect(scim_hash['emails'][1]).to_not have_key('value')
1414
1095
  end
1415
1096
  end # "context 'with filter mid-path' do"
1416
1097
 
1417
1098
  context 'with filter at end of path' do
1418
- it 'by string match: clears to "nil" in order to remove' do
1099
+ it 'by string match: removes entire matching array entry' do
1419
1100
  path = [ 'emails[type eq "work"]' ]
1420
1101
  scim_hash = {
1421
1102
  'emails' => [
@@ -1435,39 +1116,23 @@ RSpec.describe Scimitar::Resources::Mixin do
1435
1116
  nature: 'remove',
1436
1117
  path: path,
1437
1118
  value: nil,
1438
- altering_hash: scim_hash,
1439
- with_attr_map: {
1440
- emails: [
1441
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1442
- { match: 'type', with: 'work', using: { value: :work_email } },
1443
- ]
1444
- }
1119
+ altering_hash: scim_hash
1445
1120
  )
1446
1121
 
1447
- expect(scim_hash).to eql({
1448
- 'emails' => [
1449
- {
1450
- 'type' => 'home',
1451
- 'value' => 'home@test.com'
1452
- },
1453
- {
1454
- 'type' => 'work',
1455
- 'value' => nil
1456
- }
1457
- ]
1458
- })
1122
+ expect(scim_hash['emails'].size).to eql(1)
1123
+ expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
1459
1124
  end
1460
1125
 
1461
- it 'by boolean match: clears to "nil" in order to remove' do
1126
+ it 'by boolean match: removes entire matching array entry' do
1462
1127
  path = [ 'emails[primary eq true]' ]
1463
1128
  scim_hash = {
1464
1129
  'emails' => [
1465
1130
  {
1466
- 'value' => 'home@test.com',
1467
- 'primary' => true
1131
+ 'value' => 'home@test.com'
1468
1132
  },
1469
1133
  {
1470
- 'value' => 'work@test.com'
1134
+ 'value' => 'work@test.com',
1135
+ 'primary' => true
1471
1136
  }
1472
1137
  ]
1473
1138
  }.with_indifferent_case_insensitive_access()
@@ -1477,29 +1142,14 @@ RSpec.describe Scimitar::Resources::Mixin do
1477
1142
  nature: 'remove',
1478
1143
  path: path,
1479
1144
  value: nil,
1480
- altering_hash: scim_hash,
1481
- with_attr_map: {
1482
- emails: [
1483
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1484
- { match: 'type', with: 'work', using: { value: :work_email } },
1485
- ]
1486
- }
1145
+ altering_hash: scim_hash
1487
1146
  )
1488
1147
 
1489
- expect(scim_hash).to eql({
1490
- 'emails' => [
1491
- {
1492
- 'value' => nil,
1493
- 'primary' => true
1494
- },
1495
- {
1496
- 'value' => 'work@test.com'
1497
- }
1498
- ]
1499
- })
1148
+ expect(scim_hash['emails'].size).to eql(1)
1149
+ expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
1500
1150
  end
1501
1151
 
1502
- it 'multiple matches: clears all to "nil" in order to remove' do
1152
+ it 'multiple matches: removes all matching array entries' do
1503
1153
  path = [ 'emails[type eq "work"]' ]
1504
1154
  scim_hash = {
1505
1155
  'emails' => [
@@ -1523,45 +1173,21 @@ RSpec.describe Scimitar::Resources::Mixin do
1523
1173
  nature: 'remove',
1524
1174
  path: path,
1525
1175
  value: nil,
1526
- altering_hash: scim_hash,
1527
- with_attr_map: {
1528
- emails: [
1529
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1530
- { match: 'type', with: 'work', using: { value: :work_email } },
1531
- ]
1532
- }
1176
+ altering_hash: scim_hash
1533
1177
  )
1534
1178
 
1535
- expect(scim_hash).to eql({
1536
- 'emails' => [
1537
- {
1538
- 'type' => 'work',
1539
- 'value' => nil
1540
- },
1541
- {
1542
- 'type' => 'work',
1543
- 'value' => nil
1544
- },
1545
- {
1546
- 'type' => 'home',
1547
- 'value' => 'home@test.com'
1548
- },
1549
- ]
1550
- })
1179
+ expect(scim_hash['emails'].size).to eql(1)
1180
+ expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
1551
1181
  end
1552
1182
  end # "context 'with filter at end of path' do"
1553
1183
 
1554
- it 'whole array: clears mapped values to "nil" to remove them' do
1184
+ it 'whole array: removes' do
1555
1185
  path = [ 'emails' ]
1556
1186
  scim_hash = {
1557
1187
  'emails' => [
1558
1188
  {
1559
1189
  'type' => 'home',
1560
1190
  'value' => 'home@test.com'
1561
- },
1562
- {
1563
- 'type' => 'work',
1564
- 'value' => 'work@test.com'
1565
1191
  }
1566
1192
  ]
1567
1193
  }.with_indifferent_case_insensitive_access()
@@ -1571,741 +1197,11 @@ RSpec.describe Scimitar::Resources::Mixin do
1571
1197
  nature: 'remove',
1572
1198
  path: path,
1573
1199
  value: nil,
1574
- altering_hash: scim_hash,
1575
- with_attr_map: {
1576
- emails: [
1577
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1578
- { match: 'type', with: 'work', using: { value: :work_email } },
1579
- ]
1580
- }
1200
+ altering_hash: scim_hash
1581
1201
  )
1582
1202
 
1583
- expect(scim_hash).to eql({
1584
- 'emails' => [
1585
- {
1586
- 'type' => 'home',
1587
- 'value' => nil
1588
- },
1589
- {
1590
- 'type' => 'work',
1591
- 'value' => nil
1592
- }
1593
- ]
1594
- })
1203
+ expect(scim_hash).to_not have_key('emails')
1595
1204
  end
1596
-
1597
- # What we expect:
1598
- #
1599
- # https://tools.ietf.org/html/rfc7644#section-3.5.2.2
1600
- # https://docs.snowflake.com/en/user-guide/scim-intro.html#patch-scim-v2-groups-id
1601
- #
1602
- # ...vs accounting for the unusual payloads we sometimes get,
1603
- # tested here.
1604
- #
1605
- context 'special cases' do
1606
-
1607
- # https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups#update-group-remove-members
1608
- #
1609
- context 'Microsoft-style payload' do
1610
- context 'removing a user from a group' do
1611
- it 'removes identified user' do
1612
- path = [ 'members' ]
1613
- value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' } ]
1614
- scim_hash = {
1615
- 'displayname' => 'Mock group',
1616
- 'members' => [
1617
- {
1618
- 'value' => '50ca93d04ab0c2de4772',
1619
- 'display' => 'Ingrid Smith',
1620
- 'type' => 'User'
1621
- },
1622
- {
1623
- 'value' => 'f648f8d5ea4e4cd38e9c',
1624
- 'display' => 'Fred Smith',
1625
- 'type' => 'User'
1626
- },
1627
- {
1628
- 'value' => 'a774d480e8112101375b',
1629
- 'display' => 'Taylor Smith',
1630
- 'type' => 'User'
1631
- }
1632
- ]
1633
- }.with_indifferent_case_insensitive_access()
1634
-
1635
- @instance.send(
1636
- :from_patch_backend!,
1637
- nature: 'remove',
1638
- path: path,
1639
- value: value,
1640
- altering_hash: scim_hash,
1641
- with_attr_map: {
1642
- members: [
1643
- { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1644
- ]
1645
- }
1646
- )
1647
-
1648
- expect(scim_hash).to eql({
1649
- 'displayname' => 'Mock group',
1650
- 'members' => [
1651
- {
1652
- 'value' => '50ca93d04ab0c2de4772',
1653
- 'display' => 'Ingrid Smith',
1654
- 'type' => 'User'
1655
- },
1656
- {
1657
- 'value' => 'a774d480e8112101375b',
1658
- 'display' => 'Taylor Smith',
1659
- 'type' => 'User'
1660
- }
1661
- ]
1662
- })
1663
- end
1664
-
1665
- it 'removes multiple identified users' do
1666
- path = [ 'members' ]
1667
- value = [
1668
- { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' },
1669
- { '$ref' => nil, 'value' => '50ca93d04ab0c2de4772' }
1670
- ]
1671
- scim_hash = {
1672
- 'displayname' => 'Mock group',
1673
- 'members' => [
1674
- {
1675
- 'value' => '50ca93d04ab0c2de4772',
1676
- 'display' => 'Ingrid Smith',
1677
- 'type' => 'User'
1678
- },
1679
- {
1680
- 'value' => 'f648f8d5ea4e4cd38e9c',
1681
- 'display' => 'Fred Smith',
1682
- 'type' => 'User'
1683
- },
1684
- {
1685
- 'value' => 'a774d480e8112101375b',
1686
- 'display' => 'Taylor Smith',
1687
- 'type' => 'User'
1688
- }
1689
- ]
1690
- }.with_indifferent_case_insensitive_access()
1691
-
1692
- @instance.send(
1693
- :from_patch_backend!,
1694
- nature: 'remove',
1695
- path: path,
1696
- value: value,
1697
- altering_hash: scim_hash,
1698
- with_attr_map: {
1699
- members: [
1700
- { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1701
- ]
1702
- }
1703
- )
1704
-
1705
- expect(scim_hash).to eql({
1706
- 'displayname' => 'Mock group',
1707
- 'members' => [
1708
- {
1709
- 'value' => 'a774d480e8112101375b',
1710
- 'display' => 'Taylor Smith',
1711
- 'type' => 'User'
1712
- }
1713
- ]
1714
- })
1715
- end
1716
-
1717
- it 'removes all users individually without error' do
1718
- path = [ 'members' ]
1719
- value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' } ]
1720
- scim_hash = {
1721
- 'displayname' => 'Mock group',
1722
- 'members' => [
1723
- {
1724
- 'value' => 'f648f8d5ea4e4cd38e9c',
1725
- 'display' => 'Fred Smith',
1726
- 'type' => 'User'
1727
- }
1728
- ]
1729
- }.with_indifferent_case_insensitive_access()
1730
-
1731
- @instance.send(
1732
- :from_patch_backend!,
1733
- nature: 'remove',
1734
- path: path,
1735
- value: value,
1736
- altering_hash: scim_hash,
1737
- with_attr_map: {
1738
- members: [
1739
- { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1740
- ]
1741
- }
1742
- )
1743
-
1744
- expect(scim_hash).to eql({
1745
- 'displayname' => 'Mock group',
1746
- 'members' => []
1747
- })
1748
- end
1749
-
1750
- it 'can match on multiple attributes' do
1751
- path = [ 'members' ]
1752
- value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c', 'type' => 'User' } ]
1753
- scim_hash = {
1754
- 'displayname' => 'Mock group',
1755
- 'members' => [
1756
- {
1757
- 'value' => 'f648f8d5ea4e4cd38e9c',
1758
- 'display' => 'Fred Smith',
1759
- 'type' => 'User'
1760
- }
1761
- ]
1762
- }.with_indifferent_case_insensitive_access()
1763
-
1764
- @instance.send(
1765
- :from_patch_backend!,
1766
- nature: 'remove',
1767
- path: path,
1768
- value: value,
1769
- altering_hash: scim_hash,
1770
- with_attr_map: {
1771
- members: [
1772
- { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1773
- ]
1774
- }
1775
- )
1776
-
1777
- expect(scim_hash).to eql({
1778
- 'displayname' => 'Mock group',
1779
- 'members' => []
1780
- })
1781
- end
1782
-
1783
- it 'ignores unrecognised users' do
1784
- path = [ 'members' ]
1785
- value = [ { '$ref' => nil, 'value' => '11b054a9c85216ed9356' } ]
1786
- scim_hash = {
1787
- 'displayname' => 'Mock group',
1788
- 'members' => [
1789
- {
1790
- 'value' => 'f648f8d5ea4e4cd38e9c',
1791
- 'display' => 'Fred Smith',
1792
- 'type' => 'User'
1793
- }
1794
- ]
1795
- }.with_indifferent_case_insensitive_access()
1796
-
1797
- @instance.send(
1798
- :from_patch_backend!,
1799
- nature: 'remove',
1800
- path: path,
1801
- value: value,
1802
- altering_hash: scim_hash,
1803
- with_attr_map: {
1804
- members: [
1805
- { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1806
- ]
1807
- }
1808
- )
1809
-
1810
- # The 'value' mismatched, so the user was not removed.
1811
- #
1812
- expect(scim_hash).to eql({
1813
- 'displayname' => 'Mock group',
1814
- 'members' => [
1815
- {
1816
- 'value' => 'f648f8d5ea4e4cd38e9c',
1817
- 'display' => 'Fred Smith',
1818
- 'type' => 'User'
1819
- }
1820
- ]
1821
- })
1822
- end
1823
-
1824
- it 'ignores a mismatch on (for example) "type"' do
1825
- path = [ 'members' ]
1826
- value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c', 'type' => 'Group' } ]
1827
- scim_hash = {
1828
- 'displayname' => 'Mock group',
1829
- 'members' => [
1830
- {
1831
- 'value' => 'f648f8d5ea4e4cd38e9c',
1832
- 'display' => 'Fred Smith',
1833
- 'type' => 'User'
1834
- }
1835
- ]
1836
- }.with_indifferent_case_insensitive_access()
1837
-
1838
- @instance.send(
1839
- :from_patch_backend!,
1840
- nature: 'remove',
1841
- path: path,
1842
- value: value,
1843
- altering_hash: scim_hash,
1844
- with_attr_map: {
1845
- members: [
1846
- { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1847
- ]
1848
- }
1849
- )
1850
-
1851
- # Type 'Group' mismatches 'User', so the user was not
1852
- # removed.
1853
- #
1854
- expect(scim_hash).to eql({
1855
- 'displayname' => 'Mock group',
1856
- 'members' => [
1857
- {
1858
- 'value' => 'f648f8d5ea4e4cd38e9c',
1859
- 'display' => 'Fred Smith',
1860
- 'type' => 'User'
1861
- }
1862
- ]
1863
- })
1864
- end
1865
-
1866
- it 'matches keys case-insensitive (but preserves case in response)' do
1867
- path = [ 'members' ]
1868
- value = [ { '$ref' => nil, 'VALUe' => 'f648f8d5ea4e4cd38e9c' } ]
1869
- scim_hash = {
1870
- 'displayname' => 'Mock group',
1871
- 'memBERS' => [
1872
- {
1873
- 'vaLUe' => 'f648f8d5ea4e4cd38e9c',
1874
- 'display' => 'Fred Smith',
1875
- 'type' => 'User'
1876
- }
1877
- ]
1878
- }.with_indifferent_case_insensitive_access()
1879
-
1880
- @instance.send(
1881
- :from_patch_backend!,
1882
- nature: 'remove',
1883
- path: path,
1884
- value: value,
1885
- altering_hash: scim_hash,
1886
- with_attr_map: {
1887
- members: [
1888
- { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1889
- ]
1890
- }
1891
- )
1892
-
1893
- expect(scim_hash).to eql({
1894
- 'displayname' => 'Mock group',
1895
- 'memBERS' => []
1896
- })
1897
- end
1898
-
1899
- it 'matches values case-sensitive' do
1900
- path = [ 'members' ]
1901
- value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c', 'type' => 'USER' } ]
1902
- scim_hash = {
1903
- 'displayName' => 'Mock group',
1904
- 'members' => [
1905
- {
1906
- 'value' => 'f648f8d5ea4e4cd38e9c',
1907
- 'display' => 'Fred Smith',
1908
- 'type' => 'User'
1909
- }
1910
- ]
1911
- }.with_indifferent_case_insensitive_access()
1912
-
1913
- @instance.send(
1914
- :from_patch_backend!,
1915
- nature: 'remove',
1916
- path: path,
1917
- value: value,
1918
- altering_hash: scim_hash,
1919
- with_attr_map: {
1920
- members: [
1921
- { list: :members, using: { value: :id, display: :full_name, type: 'User' } }
1922
- ]
1923
- }
1924
- )
1925
-
1926
- # USER mismatches User, so the user was not removed.
1927
- #
1928
- expect(scim_hash).to eql({
1929
- 'displayName' => 'Mock group',
1930
- 'members' => [
1931
- {
1932
- 'value' => 'f648f8d5ea4e4cd38e9c',
1933
- 'display' => 'Fred Smith',
1934
- 'type' => 'User'
1935
- }
1936
- ]
1937
- })
1938
- end
1939
- end # "context 'removing a user from a group' do"
1940
-
1941
- context 'generic use' do
1942
- it 'clears static map matched items to "nil" in order to remove' do
1943
- path = [ 'emails' ]
1944
- value = [ { 'type' => 'work' } ]
1945
- scim_hash = {
1946
- 'emails' => [
1947
- {
1948
- 'type' => 'home',
1949
- 'value' => 'home@test.com'
1950
- },
1951
- {
1952
- 'type' => 'work',
1953
- 'value' => 'work@test.com'
1954
- }
1955
- ]
1956
- }.with_indifferent_case_insensitive_access()
1957
-
1958
- @instance.send(
1959
- :from_patch_backend!,
1960
- nature: 'remove',
1961
- path: path,
1962
- value: value,
1963
- altering_hash: scim_hash,
1964
- with_attr_map: {
1965
- emails: [
1966
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
1967
- { match: 'type', with: 'work', using: { value: :work_email } },
1968
- ]
1969
- }
1970
- )
1971
-
1972
- expect(scim_hash).to eql({
1973
- 'emails' => [
1974
- {
1975
- 'type' => 'home',
1976
- 'value' => 'home@test.com'
1977
- },
1978
- {
1979
- 'type' => 'work',
1980
- 'value' => nil
1981
- }
1982
- ]
1983
- })
1984
- end
1985
-
1986
- it 'ignores unmatched items' do
1987
- path = [ 'emails' ]
1988
- value = [ { 'type' => 'missing' } ]
1989
- scim_hash = {
1990
- 'emails' => [
1991
- {
1992
- 'type' => 'home',
1993
- 'value' => 'home@test.com'
1994
- },
1995
- {
1996
- 'type' => 'work',
1997
- 'value' => 'work@test.com'
1998
- }
1999
- ]
2000
- }.with_indifferent_case_insensitive_access()
2001
-
2002
- @instance.send(
2003
- :from_patch_backend!,
2004
- nature: 'remove',
2005
- path: path,
2006
- value: value,
2007
- altering_hash: scim_hash,
2008
- with_attr_map: {
2009
- emails: [
2010
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2011
- { match: 'type', with: 'work', using: { value: :work_email } },
2012
- ]
2013
- }
2014
- )
2015
-
2016
- expect(scim_hash).to eql({
2017
- 'emails' => [
2018
- {
2019
- 'type' => 'home',
2020
- 'value' => 'home@test.com'
2021
- },
2022
- {
2023
- 'type' => 'work',
2024
- 'value' => 'work@test.com'
2025
- }
2026
- ]
2027
- })
2028
- end
2029
-
2030
- it 'compares string forms' do
2031
- path = [ 'test' ]
2032
- value = [
2033
- { 'active' => true, 'value' => '12' },
2034
- { 'active' => 'false', 'value' => 42 }
2035
- ]
2036
- scim_hash = {
2037
- 'test' => [
2038
- {
2039
- 'active' => 'true',
2040
- 'value' => 12
2041
- },
2042
- {
2043
- 'active' => false,
2044
- 'value' => '42'
2045
- },
2046
- {
2047
- 'active' => 'hello',
2048
- 'value' => 'world'
2049
- }
2050
- ]
2051
- }.with_indifferent_case_insensitive_access()
2052
-
2053
- @instance.send(
2054
- :from_patch_backend!,
2055
- nature: 'remove',
2056
- path: path,
2057
- value: value,
2058
- altering_hash: scim_hash,
2059
- with_attr_map: {
2060
- test: [
2061
- {
2062
- list: :test,
2063
- using: {
2064
- active: :active,
2065
- value: :value
2066
- }
2067
- }
2068
- ]
2069
- }
2070
- )
2071
-
2072
- expect(scim_hash).to eql({
2073
- 'test' => [
2074
- {
2075
- 'active' => 'hello',
2076
- 'value' => 'world'
2077
- }
2078
- ]
2079
- })
2080
- end
2081
-
2082
- it 'handles a singular to-remove value rather than an array' do
2083
- path = [ 'emails' ]
2084
- value = { 'type' => 'work' }
2085
- scim_hash = {
2086
- 'emails' => [
2087
- {
2088
- 'type' => 'home',
2089
- 'value' => 'home@test.com'
2090
- },
2091
- {
2092
- 'type' => 'work',
2093
- 'value' => 'work@test.com'
2094
- }
2095
- ]
2096
- }.with_indifferent_case_insensitive_access()
2097
-
2098
- @instance.send(
2099
- :from_patch_backend!,
2100
- nature: 'remove',
2101
- path: path,
2102
- value: value,
2103
- altering_hash: scim_hash,
2104
- with_attr_map: {
2105
- emails: [
2106
- { match: 'type', with: 'home', using: { value: :home_email } },
2107
- { match: 'type', with: 'work', using: { value: :work_email } },
2108
- ]
2109
- }
2110
- )
2111
-
2112
- expect(scim_hash).to eql({
2113
- 'emails' => [
2114
- {
2115
- 'type' => 'home',
2116
- 'value' => 'home@test.com'
2117
- },
2118
- {
2119
- 'type' => 'work',
2120
- 'value' => nil
2121
- }
2122
- ]
2123
- })
2124
- end
2125
-
2126
- it 'handles simple values rather than object (Hash) values' do
2127
- path = [ 'test' ]
2128
- value = 42
2129
- scim_hash = {
2130
- 'test' => [
2131
- '21',
2132
- '42',
2133
- '15'
2134
- ]
2135
- }.with_indifferent_case_insensitive_access()
2136
-
2137
- @instance.send(
2138
- :from_patch_backend!,
2139
- nature: 'remove',
2140
- path: path,
2141
- value: value,
2142
- altering_hash: scim_hash,
2143
- with_attr_map: {
2144
- test: []
2145
- }
2146
- )
2147
-
2148
- expect(scim_hash).to eql({
2149
- 'test' => [
2150
- '21',
2151
- '15'
2152
- ]
2153
- })
2154
- end
2155
- end
2156
- end # "context 'Microsoft-style payload' do"
2157
-
2158
- # https://help.salesforce.com/s/articleView?id=sf.identity_scim_manage_groups.htm&type=5
2159
- #
2160
- context 'Salesforce-style payload' do
2161
- it 'removes identified user' do
2162
- path = [ 'members' ]
2163
- value = { 'members' => [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' } ] }
2164
- scim_hash = {
2165
- 'displayname' => 'Mock group',
2166
- 'members' => [
2167
- {
2168
- 'value' => '50ca93d04ab0c2de4772',
2169
- 'display' => 'Ingrid Smith',
2170
- 'type' => 'User'
2171
- },
2172
- {
2173
- 'value' => 'f648f8d5ea4e4cd38e9c',
2174
- 'display' => 'Fred Smith',
2175
- 'type' => 'User'
2176
- }
2177
- ]
2178
- }.with_indifferent_case_insensitive_access()
2179
-
2180
- @instance.send(
2181
- :from_patch_backend!,
2182
- nature: 'remove',
2183
- path: path,
2184
- value: value,
2185
- altering_hash: scim_hash,
2186
- with_attr_map: {
2187
- displayName: :name,
2188
- members: [
2189
- list: :members,
2190
- using: {
2191
- value: :id,
2192
- display: :full_name,
2193
- type: 'User'
2194
- }
2195
- ]
2196
- }
2197
- )
2198
-
2199
- expect(scim_hash).to eql({
2200
- 'displayname' => 'Mock group',
2201
- 'members' => [
2202
- {
2203
- 'value' => '50ca93d04ab0c2de4772',
2204
- 'display' => 'Ingrid Smith',
2205
- 'type' => 'User'
2206
- }
2207
- ]
2208
- })
2209
- end
2210
-
2211
- it 'matches the "members" key case-insensitive' do
2212
- path = [ 'members' ]
2213
- value = { 'MEMBERS' => [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' } ] }
2214
- scim_hash = {
2215
- 'displayname' => 'Mock group',
2216
- 'members' => [
2217
- {
2218
- 'value' => 'f648f8d5ea4e4cd38e9c',
2219
- 'display' => 'Fred Smith',
2220
- 'type' => 'User'
2221
- },
2222
- {
2223
- 'value' => 'a774d480e8112101375b',
2224
- 'display' => 'Taylor Smith',
2225
- 'type' => 'User'
2226
- }
2227
- ]
2228
- }.with_indifferent_case_insensitive_access()
2229
-
2230
- @instance.send(
2231
- :from_patch_backend!,
2232
- nature: 'remove',
2233
- path: path,
2234
- value: value,
2235
- altering_hash: scim_hash,
2236
- with_attr_map: {
2237
- displayName: :name,
2238
- members: [
2239
- list: :members,
2240
- using: {
2241
- value: :id,
2242
- display: :full_name,
2243
- type: 'User'
2244
- }
2245
- ]
2246
- }
2247
- )
2248
-
2249
- expect(scim_hash).to eql({
2250
- 'displayname' => 'Mock group',
2251
- 'members' => [
2252
- {
2253
- 'value' => 'a774d480e8112101375b',
2254
- 'display' => 'Taylor Smith',
2255
- 'type' => 'User'
2256
- }
2257
- ]
2258
- })
2259
- end
2260
-
2261
- it 'ignores unrecognised users' do
2262
- path = [ 'members' ]
2263
- value = { 'members' => [ { '$ref' => nil, 'value' => '11b054a9c85216ed9356' } ] }
2264
- scim_hash = {
2265
- 'displayname' => 'Mock group',
2266
- 'members' => [
2267
- {
2268
- 'value' => 'f648f8d5ea4e4cd38e9c',
2269
- 'display' => 'Fred Smith',
2270
- 'type' => 'User'
2271
- }
2272
- ]
2273
- }.with_indifferent_case_insensitive_access()
2274
-
2275
- @instance.send(
2276
- :from_patch_backend!,
2277
- nature: 'remove',
2278
- path: path,
2279
- value: value,
2280
- altering_hash: scim_hash,
2281
- with_attr_map: {
2282
- displayName: :name,
2283
- members: [
2284
- list: :members,
2285
- using: {
2286
- value: :id,
2287
- display: :full_name,
2288
- type: 'User'
2289
- }
2290
- ]
2291
- }
2292
- )
2293
-
2294
- # The 'value' mismatched, so the user was not removed.
2295
- #
2296
- expect(scim_hash).to eql({
2297
- 'displayname' => 'Mock group',
2298
- 'members' => [
2299
- {
2300
- 'value' => 'f648f8d5ea4e4cd38e9c',
2301
- 'display' => 'Fred Smith',
2302
- 'type' => 'User'
2303
- }
2304
- ]
2305
- })
2306
- end
2307
- end # "context 'Salesforce-style payload' do"
2308
- end # "context 'special cases' do"
2309
1205
  end # context 'when prior value already exists' do
2310
1206
 
2311
1207
  context 'when value is not present' do
@@ -2318,8 +1214,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2318
1214
  nature: 'remove',
2319
1215
  path: path,
2320
1216
  value: nil,
2321
- altering_hash: scim_hash,
2322
- with_attr_map: { userName: :user_name }
1217
+ altering_hash: scim_hash
2323
1218
  )
2324
1219
 
2325
1220
  expect(scim_hash).to be_empty
@@ -2334,8 +1229,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2334
1229
  nature: 'remove',
2335
1230
  path: path,
2336
1231
  value: nil,
2337
- altering_hash: scim_hash,
2338
- with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
1232
+ altering_hash: scim_hash
2339
1233
  )
2340
1234
 
2341
1235
  expect(scim_hash['name']).to_not have_key('givenName')
@@ -2359,13 +1253,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2359
1253
  nature: 'remove',
2360
1254
  path: path,
2361
1255
  value: nil,
2362
- altering_hash: scim_hash,
2363
- with_attr_map: {
2364
- emails: [
2365
- { match: 'type', with: 'home', using: { value: :home_email } },
2366
- { match: 'type', with: 'work', using: { value: :work_email } },
2367
- ]
2368
- }
1256
+ altering_hash: scim_hash
2369
1257
  )
2370
1258
 
2371
1259
  expect(scim_hash['emails'].size).to eql(1)
@@ -2387,13 +1275,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2387
1275
  nature: 'remove',
2388
1276
  path: path,
2389
1277
  value: nil,
2390
- altering_hash: scim_hash,
2391
- with_attr_map: {
2392
- emails: [
2393
- { match: 'type', with: 'home', using: { value: :home_email } },
2394
- { match: 'type', with: 'work', using: { value: :work_email } },
2395
- ]
2396
- }
1278
+ altering_hash: scim_hash
2397
1279
  )
2398
1280
 
2399
1281
  expect(scim_hash['emails'].size).to eql(1)
@@ -2416,13 +1298,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2416
1298
  nature: 'remove',
2417
1299
  path: path,
2418
1300
  value: nil,
2419
- altering_hash: scim_hash,
2420
- with_attr_map: {
2421
- emails: [
2422
- { match: 'type', with: 'home', using: { value: :home_email } },
2423
- { match: 'type', with: 'work', using: { value: :work_email } },
2424
- ]
2425
- }
1301
+ altering_hash: scim_hash
2426
1302
  )
2427
1303
 
2428
1304
  expect(scim_hash['emails'].size).to eql(1)
@@ -2440,13 +1316,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2440
1316
  nature: 'remove',
2441
1317
  path: path,
2442
1318
  value: nil,
2443
- altering_hash: scim_hash,
2444
- with_attr_map: {
2445
- emails: [
2446
- { match: 'type', with: 'home', using: { value: :home_email } },
2447
- { match: 'type', with: 'work', using: { value: :work_email } },
2448
- ]
2449
- }
1319
+ altering_hash: scim_hash
2450
1320
  )
2451
1321
 
2452
1322
  expect(scim_hash).to be_empty
@@ -2468,13 +1338,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2468
1338
  nature: 'remove',
2469
1339
  path: path,
2470
1340
  value: nil,
2471
- altering_hash: scim_hash,
2472
- with_attr_map: {
2473
- emails: [
2474
- { match: 'type', with: 'home', using: { value: :home_email } },
2475
- { match: 'type', with: 'work', using: { value: :work_email } },
2476
- ]
2477
- }
1341
+ altering_hash: scim_hash
2478
1342
  )
2479
1343
 
2480
1344
  expect(scim_hash['emails'].size).to eql(1)
@@ -2491,13 +1355,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2491
1355
  nature: 'remove',
2492
1356
  path: path,
2493
1357
  value: nil,
2494
- altering_hash: scim_hash,
2495
- with_attr_map: {
2496
- emails: [
2497
- { match: 'type', with: 'home', using: { value: :home_email } },
2498
- { match: 'type', with: 'work', using: { value: :work_email } },
2499
- ]
2500
- }
1358
+ altering_hash: scim_hash
2501
1359
  )
2502
1360
 
2503
1361
  expect(scim_hash).to_not have_key('emails')
@@ -2523,8 +1381,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2523
1381
  nature: 'replace',
2524
1382
  path: path,
2525
1383
  value: 'foo',
2526
- altering_hash: scim_hash,
2527
- with_attr_map: { userName: :user_name }
1384
+ altering_hash: scim_hash
2528
1385
  )
2529
1386
 
2530
1387
  expect(scim_hash['userName']).to eql('foo')
@@ -2539,8 +1396,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2539
1396
  nature: 'replace',
2540
1397
  path: path,
2541
1398
  value: 'Baz',
2542
- altering_hash: scim_hash,
2543
- with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
1399
+ altering_hash: scim_hash
2544
1400
  )
2545
1401
 
2546
1402
  expect(scim_hash['name']['givenName' ]).to eql('Baz')
@@ -2568,13 +1424,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2568
1424
  nature: 'replace',
2569
1425
  path: path,
2570
1426
  value: 'added_over_original@test.com',
2571
- altering_hash: scim_hash,
2572
- with_attr_map: {
2573
- emails: [
2574
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2575
- { match: 'type', with: 'work', using: { value: :work_email } },
2576
- ]
2577
- }
1427
+ altering_hash: scim_hash
2578
1428
  )
2579
1429
 
2580
1430
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -2600,13 +1450,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2600
1450
  nature: 'replace',
2601
1451
  path: path,
2602
1452
  value: 'added_over_original@test.com',
2603
- altering_hash: scim_hash,
2604
- with_attr_map: {
2605
- emails: [
2606
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2607
- { match: 'type', with: 'work', using: { value: :work_email } },
2608
- ]
2609
- }
1453
+ altering_hash: scim_hash
2610
1454
  )
2611
1455
 
2612
1456
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -2633,13 +1477,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2633
1477
  nature: 'replace',
2634
1478
  path: path,
2635
1479
  value: 'added_over_original@test.com',
2636
- altering_hash: scim_hash,
2637
- with_attr_map: {
2638
- emails: [
2639
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2640
- { match: 'type', with: 'work', using: { value: :work_email } },
2641
- ]
2642
- }
1480
+ altering_hash: scim_hash
2643
1481
  )
2644
1482
 
2645
1483
  expect(scim_hash['emails'][0]['value']).to eql('added_over_original@test.com')
@@ -2668,13 +1506,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2668
1506
  nature: 'replace',
2669
1507
  path: path,
2670
1508
  value: {'type' => 'home', 'primary' => true, 'value' => 'home@test.com'},
2671
- altering_hash: scim_hash,
2672
- with_attr_map: {
2673
- emails: [
2674
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2675
- { match: 'type', with: 'work', using: { value: :work_email } },
2676
- ]
2677
- }
1509
+ altering_hash: scim_hash
2678
1510
  )
2679
1511
 
2680
1512
  expect(scim_hash['emails'].size).to eql(2)
@@ -2708,13 +1540,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2708
1540
  nature: 'replace',
2709
1541
  path: path,
2710
1542
  value: {'type' => 'workinate', 'value' => 'replaced@test.com'},
2711
- altering_hash: scim_hash,
2712
- with_attr_map: {
2713
- emails: [
2714
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2715
- { match: 'type', with: 'work', using: { value: :work_email } },
2716
- ]
2717
- }
1543
+ altering_hash: scim_hash
2718
1544
  )
2719
1545
 
2720
1546
  expect(scim_hash['emails'].size).to eql(3)
@@ -2743,13 +1569,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2743
1569
  nature: 'replace',
2744
1570
  path: path,
2745
1571
  value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be)
2746
- altering_hash: scim_hash,
2747
- with_attr_map: {
2748
- emails: [
2749
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2750
- { match: 'type', with: 'work', using: { value: :work_email } },
2751
- ]
2752
- }
1572
+ altering_hash: scim_hash
2753
1573
  )
2754
1574
 
2755
1575
  expect(scim_hash['emails'].size).to eql(1)
@@ -2768,8 +1588,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2768
1588
  nature: 'replace',
2769
1589
  path: path,
2770
1590
  value: 'foo',
2771
- altering_hash: scim_hash,
2772
- with_attr_map: { userName: :user_name }
1591
+ altering_hash: scim_hash
2773
1592
  )
2774
1593
 
2775
1594
  expect(scim_hash['userName']).to eql('foo')
@@ -2784,8 +1603,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2784
1603
  nature: 'replace',
2785
1604
  path: path,
2786
1605
  value: 'Baz',
2787
- altering_hash: scim_hash,
2788
- with_attr_map: { name: { givenName: :first_name, familyName: :last_name } }
1606
+ altering_hash: scim_hash
2789
1607
  )
2790
1608
 
2791
1609
  expect(scim_hash['name']['givenName']).to eql('Baz')
@@ -2811,13 +1629,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2811
1629
  nature: 'replace',
2812
1630
  path: path,
2813
1631
  value: 'added@test.com',
2814
- altering_hash: scim_hash,
2815
- with_attr_map: {
2816
- emails: [
2817
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2818
- { match: 'type', with: 'work', using: { value: :work_email } },
2819
- ]
2820
- }
1632
+ altering_hash: scim_hash
2821
1633
  )
2822
1634
 
2823
1635
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -2842,13 +1654,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2842
1654
  nature: 'replace',
2843
1655
  path: path,
2844
1656
  value: 'added@test.com',
2845
- altering_hash: scim_hash,
2846
- with_attr_map: {
2847
- emails: [
2848
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2849
- { match: 'type', with: 'work', using: { value: :work_email } },
2850
- ]
2851
- }
1657
+ altering_hash: scim_hash
2852
1658
  )
2853
1659
 
2854
1660
  expect(scim_hash['emails'][0]['value']).to eql('home@test.com')
@@ -2873,13 +1679,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2873
1679
  nature: 'replace',
2874
1680
  path: path,
2875
1681
  value: 'added@test.com',
2876
- altering_hash: scim_hash,
2877
- with_attr_map: {
2878
- emails: [
2879
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2880
- { match: 'type', with: 'work', using: { value: :work_email } },
2881
- ]
2882
- }
1682
+ altering_hash: scim_hash
2883
1683
  )
2884
1684
 
2885
1685
  expect(scim_hash['emails'][0]['value']).to eql('added@test.com')
@@ -2897,13 +1697,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2897
1697
  nature: 'replace',
2898
1698
  path: path,
2899
1699
  value: {'type' => 'work', 'value' => 'work@test.com'},
2900
- altering_hash: scim_hash,
2901
- with_attr_map: {
2902
- emails: [
2903
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2904
- { match: 'type', with: 'work', using: { value: :work_email } },
2905
- ]
2906
- }
1700
+ altering_hash: scim_hash
2907
1701
  )
2908
1702
 
2909
1703
  expect(scim_hash['emails'].size).to eql(1)
@@ -2927,13 +1721,7 @@ RSpec.describe Scimitar::Resources::Mixin do
2927
1721
  nature: 'replace',
2928
1722
  path: path,
2929
1723
  value: {'type' => 'work', 'value' => 'work@test.com'},
2930
- altering_hash: scim_hash,
2931
- with_attr_map: {
2932
- emails: [
2933
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2934
- { match: 'type', with: 'work', using: { value: :work_email } },
2935
- ]
2936
- }
1724
+ altering_hash: scim_hash
2937
1725
  )
2938
1726
 
2939
1727
  expect(scim_hash['emails'].size).to eql(2)
@@ -2952,41 +1740,13 @@ RSpec.describe Scimitar::Resources::Mixin do
2952
1740
  nature: 'replace',
2953
1741
  path: path,
2954
1742
  value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be)
2955
- altering_hash: scim_hash,
2956
- with_attr_map: {
2957
- emails: [
2958
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
2959
- { match: 'type', with: 'work', using: { value: :work_email } },
2960
- ]
2961
- }
1743
+ altering_hash: scim_hash
2962
1744
  )
2963
1745
 
2964
1746
  expect(scim_hash['emails'].size).to eql(1)
2965
1747
  expect(scim_hash['emails'][0]['type' ]).to eql('work')
2966
1748
  expect(scim_hash['emails'][0]['value']).to eql('work@test.com')
2967
1749
  end
2968
-
2969
- context 'when prior value already exists, and no path' do
2970
- it 'simple value: overwrites' do
2971
- path = [ 'root' ]
2972
- scim_hash = { 'root' => { 'userName' => 'bar', 'active' => true } }.with_indifferent_case_insensitive_access()
2973
-
2974
- @instance.send(
2975
- :from_patch_backend!,
2976
- nature: 'replace',
2977
- path: path,
2978
- value: { 'active' => false }.with_indifferent_case_insensitive_access(),
2979
- altering_hash: scim_hash,
2980
- with_attr_map: {
2981
- userName: :user_name,
2982
- active: :active
2983
- }
2984
- )
2985
-
2986
- expect(scim_hash['root']['userName']).to eql('bar')
2987
- expect(scim_hash['root']['active']).to eql(false)
2988
- end
2989
- end
2990
1750
  end # context 'when value is not present' do
2991
1751
  end # "context 'replace' do"
2992
1752
 
@@ -3101,8 +1861,7 @@ RSpec.describe Scimitar::Resources::Mixin do
3101
1861
  nature: 'add',
3102
1862
  path: ['complex[type eq "type1"]', 'data', 'nested[nature eq "nature2"]', 'info'],
3103
1863
  value: [{ 'deeper' => 'addition' }],
3104
- altering_hash: scim_hash,
3105
- with_attr_map: @contrived_class.scim_attributes_map()
1864
+ altering_hash: scim_hash
3106
1865
  )
3107
1866
 
3108
1867
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged
@@ -3125,8 +1884,7 @@ RSpec.describe Scimitar::Resources::Mixin do
3125
1884
  nature: 'replace',
3126
1885
  path: ['complex[type eq "type1"]', 'data', 'nested[nature eq "nature2"]', 'info'],
3127
1886
  value: [{ 'deeper' => 'addition' }],
3128
- altering_hash: scim_hash,
3129
- with_attr_map: @contrived_class.scim_attributes_map()
1887
+ altering_hash: scim_hash
3130
1888
  )
3131
1889
 
3132
1890
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged?
@@ -3143,25 +1901,24 @@ RSpec.describe Scimitar::Resources::Mixin do
3143
1901
  expect(scim_hash.dig('complex', 2, 'data', 'nested', 0, 'info', 0, 'deep')).to eql('nature2deep3') # Unchanged
3144
1902
  end
3145
1903
 
3146
- it 'removes via clearing to "nil" or empty Array across multiple deep matching points' do
1904
+ it 'removes across multiple deep matching points' do
3147
1905
  scim_hash = @original_hash.deep_dup().with_indifferent_case_insensitive_access()
3148
1906
  contrived_instance = @contrived_class.new
3149
1907
  contrived_instance.send(
3150
1908
  :from_patch_backend!,
3151
1909
  nature: 'remove',
3152
1910
  path: ['complex[type eq "type1"]', 'data', 'nested[nature eq "nature2"]', 'info'],
3153
- value: nil,
3154
- altering_hash: scim_hash,
3155
- with_attr_map: @contrived_class.scim_attributes_map()
1911
+ value: [{ 'deeper' => 'addition' }],
1912
+ altering_hash: scim_hash
3156
1913
  )
3157
1914
 
3158
1915
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged
3159
- expect(scim_hash.dig('complex', 0, 'data', 'nested', 1, 'info')).to eql([])
1916
+ expect(scim_hash.dig('complex', 0, 'data', 'nested', 1, 'info')).to be_nil
3160
1917
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 2, 'nature')).to be_present
3161
- expect(scim_hash.dig('complex', 0, 'data', 'nested', 1, 'info')).to eql([])
1918
+ expect(scim_hash.dig('complex', 0, 'data', 'nested', 1, 'info')).to be_nil
3162
1919
  expect(scim_hash.dig('complex', 0, 'data', 'nested', 2, 'nature')).to be_present
3163
1920
 
3164
- expect(scim_hash.dig('complex', 1, 'data', 'nested', 0, 'info')).to eql([])
1921
+ expect(scim_hash.dig('complex', 1, 'data', 'nested', 0, 'info')).to be_nil
3165
1922
  expect(scim_hash.dig('complex', 1, 'data', 'nested', 0, 'nature')).to be_present
3166
1923
 
3167
1924
  expect(scim_hash.dig('complex', 2, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged
@@ -3195,13 +1952,7 @@ RSpec.describe Scimitar::Resources::Mixin do
3195
1952
  nature: 'replace',
3196
1953
  path: path,
3197
1954
  value: 'ignored',
3198
- altering_hash: scim_hash,
3199
- with_attr_map: {
3200
- emails: [
3201
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
3202
- { match: 'type', with: 'work', using: { value: :work_email } },
3203
- ]
3204
- }
1955
+ altering_hash: scim_hash
3205
1956
  )
3206
1957
  end.to raise_error(Scimitar::ErrorResponse) { |e| expect(e.as_json['scimType']).to eql('invalidSyntax') }
3207
1958
  end
@@ -3218,8 +1969,7 @@ RSpec.describe Scimitar::Resources::Mixin do
3218
1969
  nature: 'replace',
3219
1970
  path: path,
3220
1971
  value: 'ignored',
3221
- altering_hash: scim_hash,
3222
- with_attr_map: { userName: :user_name }
1972
+ altering_hash: scim_hash
3223
1973
  )
3224
1974
  end.to raise_error(Scimitar::ErrorResponse) { |e| expect(e.as_json['scimType']).to eql('invalidSyntax') }
3225
1975
  end
@@ -3239,13 +1989,7 @@ RSpec.describe Scimitar::Resources::Mixin do
3239
1989
  nature: 'replace',
3240
1990
  path: path,
3241
1991
  value: 'ignored',
3242
- altering_hash: scim_hash,
3243
- with_attr_map: {
3244
- emails: [
3245
- { match: 'type', with: 'home', using: { value: :home_email, primary: true } },
3246
- { match: 'type', with: 'work', using: { value: :work_email } },
3247
- ]
3248
- }
1992
+ altering_hash: scim_hash
3249
1993
  )
3250
1994
  end.to raise_error(Scimitar::ErrorResponse) { |e| expect(e.as_json['scimType']).to eql('invalidSyntax') }
3251
1995
  end
@@ -3259,14 +2003,6 @@ RSpec.describe Scimitar::Resources::Mixin do
3259
2003
  #
3260
2004
  context 'public interface' do
3261
2005
  shared_examples 'a patcher' do | force_upper_case: |
3262
- it 'gives the user a comprehensible error when operations are missing' do
3263
- patch = { 'schemas' => ['urn:ietf:params:scim:api:messages:2.0:PatchOp'] }
3264
-
3265
- expect do
3266
- @instance.from_scim_patch!(patch_hash: patch)
3267
- end.to raise_error Scimitar::InvalidSyntaxError, "Missing PATCH \"operations\""
3268
- end
3269
-
3270
2006
  it 'which updates simple values' do
3271
2007
  @instance.update!(username: 'foo')
3272
2008
 
@@ -3288,28 +2024,6 @@ RSpec.describe Scimitar::Resources::Mixin do
3288
2024
  expect(@instance.username).to eql('1234')
3289
2025
  end
3290
2026
 
3291
- it 'which updates nested values using root syntax' do
3292
- @instance.update!(first_name: 'Foo', last_name: 'Bar')
3293
-
3294
- path = 'name.givenName'
3295
- path = path.upcase if force_upper_case
3296
-
3297
- patch = {
3298
- 'schemas' => ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
3299
- 'Operations' => [
3300
- {
3301
- 'op' => 'replace',
3302
- 'value' => {
3303
- path => 'Baz'
3304
- }
3305
- }
3306
- ]
3307
- }
3308
-
3309
- @instance.from_scim_patch!(patch_hash: patch)
3310
- expect(@instance.first_name).to eql('Baz')
3311
- end
3312
-
3313
2027
  it 'which updates nested values' do
3314
2028
  @instance.update!(first_name: 'Foo', last_name: 'Bar')
3315
2029
 
@@ -3331,38 +2045,6 @@ RSpec.describe Scimitar::Resources::Mixin do
3331
2045
  expect(@instance.first_name).to eql('Baz')
3332
2046
  end
3333
2047
 
3334
- # Note odd ":" separating schema ID from first attribute, although
3335
- # the nature of JSON rendering / other payloads might lead you to
3336
- # expect a "." as with any other path component.
3337
- #
3338
- # Note the ":" separating the schema ID (URN) from the attribute.
3339
- # The nature of JSON rendering / other payloads might lead you to
3340
- # expect a "." as with any complex types, but that's not the case;
3341
- # see https://tools.ietf.org/html/rfc7644#section-3.10, or
3342
- # https://tools.ietf.org/html/rfc7644#section-3.5.2 of which in
3343
- # particular, https://tools.ietf.org/html/rfc7644#page-35.
3344
- #
3345
- it 'which updates attributes defined by extension schema' do
3346
- @instance.update!(department: 'SOMEDPT')
3347
-
3348
- path = 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:department'
3349
- path = path.upcase if force_upper_case
3350
-
3351
- patch = {
3352
- 'schemas' => ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
3353
- 'Operations' => [
3354
- {
3355
- 'op' => 'replace',
3356
- 'path' => path,
3357
- 'value' => 'OTHERDPT'
3358
- }
3359
- ]
3360
- }
3361
-
3362
- @instance.from_scim_patch!(patch_hash: patch)
3363
- expect(@instance.department).to eql('OTHERDPT')
3364
- end
3365
-
3366
2048
  it 'which updates with filter match' do
3367
2049
  @instance.update!(work_email_address: 'work@test.com', home_email_address: 'home@test.com')
3368
2050