scimitar 1.3.3 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -159,41 +159,67 @@ RSpec.describe Scimitar::Resources::Mixin do
159
159
  # =========================================================================
160
160
 
161
161
  context '#to_scim' 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
162
+ context 'with a UUID, renamed primary key column' do
163
+ it 'compiles instance attribute values into a SCIM representation' do
164
+ uuid = SecureRandom.uuid
165
+
166
+ instance = MockUser.new
167
+ instance.primary_key = uuid
168
+ instance.scim_uid = 'AA02984'
169
+ instance.username = 'foo'
170
+ instance.first_name = 'Foo'
171
+ instance.last_name = 'Bar'
172
+ instance.work_email_address = 'foo.bar@test.com'
173
+ instance.home_email_address = nil
174
+ instance.work_phone_number = '+642201234567'
175
+
176
+ g1 = MockGroup.create!(display_name: 'Group 1')
177
+ g2 = MockGroup.create!(display_name: 'Group 2')
178
+ g3 = MockGroup.create!(display_name: 'Group 3')
179
+
180
+ g1.mock_users << instance
181
+ g3.mock_users << instance
182
+
183
+ scim = instance.to_scim(location: "https://test.com/mock_users/#{uuid}")
184
+ json = scim.to_json()
185
+ hash = JSON.parse(json)
186
+
187
+ expect(hash).to eql({
188
+ 'userName' => 'foo',
189
+ 'name' => {'givenName'=>'Foo', 'familyName'=>'Bar'},
190
+ 'active' => true,
191
+ 'emails' => [{'type'=>'work', 'primary'=>true, 'value'=>'foo.bar@test.com'}, {"primary"=>false, "type"=>"home", "value"=>nil}],
192
+ 'phoneNumbers'=> [{'type'=>'work', 'primary'=>false, 'value'=>'+642201234567'}],
193
+ 'id' => uuid,
194
+ 'externalId' => 'AA02984',
195
+ 'groups' => [{'display'=>g1.display_name, 'value'=>g1.id.to_s}, {'display'=>g3.display_name, 'value'=>g3.id.to_s}],
196
+ 'meta' => {'location'=>"https://test.com/mock_users/#{uuid}", 'resourceType'=>'User'},
197
+ 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:User']
198
+ })
199
+ end
200
+ end # "context 'with a UUID, renamed primary key column' do"
201
+
202
+ context 'with an integer, conventionally named primary key column' do
203
+ it 'compiles instance attribute values into a SCIM representation' do
204
+ instance = MockGroup.new
205
+ instance.id = 42
206
+ instance.scim_uid = 'GG02984'
207
+ instance.display_name = 'Some group'
208
+
209
+ scim = instance.to_scim(location: 'https://test.com/mock_groups/42')
210
+ json = scim.to_json()
211
+ hash = JSON.parse(json)
212
+
213
+ expect(hash).to eql({
214
+ 'displayName' => 'Some group',
215
+ 'id' => '42', # Note, String
216
+ 'externalId' => 'GG02984',
217
+ 'members' => [],
218
+ 'meta' => {'location'=>'https://test.com/mock_groups/42', 'resourceType'=>'Group'},
219
+ 'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:Group']
220
+ })
221
+ end
222
+ end # "context 'with an integer, conventionally named primary key column' do"
197
223
 
198
224
  context 'with optional timestamps' do
199
225
  context 'creation only' do
@@ -405,8 +431,8 @@ RSpec.describe Scimitar::Resources::Mixin do
405
431
  'displayName' => 'Foo Group',
406
432
  'members' => [
407
433
  {'type' => 'Group', 'value' => g1.id.to_s},
408
- {'type' => 'User', 'value' => u1.id.to_s},
409
- {'type' => 'User', 'value' => u3.id.to_s}
434
+ {'type' => 'User', 'value' => u1.primary_key.to_s},
435
+ {'type' => 'User', 'value' => u3.primary_key.to_s}
410
436
  ],
411
437
  'externalId' => 'GG01536',
412
438
  'meta' => {'location'=>'https://test.com/mock_groups/1', 'resourceType'=>'Group'},
@@ -453,8 +479,10 @@ RSpec.describe Scimitar::Resources::Mixin do
453
479
  end # "context 'using upper case' do"
454
480
 
455
481
  it 'clears things not present in input' do
482
+ uuid = SecureRandom.uuid
483
+
456
484
  instance = MockUser.new
457
- instance.id = 42
485
+ instance.primary_key = uuid
458
486
  instance.scim_uid = 'AA02984'
459
487
  instance.username = 'foo'
460
488
  instance.first_name = 'Foo'
@@ -465,7 +493,7 @@ RSpec.describe Scimitar::Resources::Mixin do
465
493
 
466
494
  instance.from_scim!(scim_hash: {})
467
495
 
468
- expect(instance.id ).to eql(42)
496
+ expect(instance.primary_key ).to eql(uuid)
469
497
  expect(instance.scim_uid ).to be_nil
470
498
  expect(instance.username ).to be_nil
471
499
  expect(instance.first_name ).to be_nil
@@ -1202,6 +1230,595 @@ RSpec.describe Scimitar::Resources::Mixin do
1202
1230
 
1203
1231
  expect(scim_hash).to_not have_key('emails')
1204
1232
  end
1233
+
1234
+ # What we expect:
1235
+ #
1236
+ # https://www.rfc-editor.org/rfc/rfc7644#section-3.5.2.2
1237
+ # https://docs.snowflake.com/en/user-guide/scim-intro.html#patch-scim-v2-groups-id
1238
+ #
1239
+ # ...vs accounting for the unusual payloads we sometimes get,
1240
+ # tested here.
1241
+ #
1242
+ context 'special cases' do
1243
+
1244
+ # https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups#update-group-remove-members
1245
+ #
1246
+ context 'Microsoft-style payload' do
1247
+ context 'removing a user from a group' do
1248
+ it 'removes identified user' do
1249
+ path = [ 'members' ]
1250
+ value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' } ]
1251
+ scim_hash = {
1252
+ 'displayname' => 'Mock group',
1253
+ 'members' => [
1254
+ {
1255
+ 'value' => '50ca93d04ab0c2de4772',
1256
+ 'display' => 'Ingrid Smith',
1257
+ 'type' => 'User'
1258
+ },
1259
+ {
1260
+ 'value' => 'f648f8d5ea4e4cd38e9c',
1261
+ 'display' => 'Fred Smith',
1262
+ 'type' => 'User'
1263
+ },
1264
+ {
1265
+ 'value' => 'a774d480e8112101375b',
1266
+ 'display' => 'Taylor Smith',
1267
+ 'type' => 'User'
1268
+ }
1269
+ ]
1270
+ }.with_indifferent_case_insensitive_access()
1271
+
1272
+ @instance.send(
1273
+ :from_patch_backend!,
1274
+ nature: 'remove',
1275
+ path: path,
1276
+ value: value,
1277
+ altering_hash: scim_hash
1278
+ )
1279
+
1280
+ expect(scim_hash).to eql({
1281
+ 'displayname' => 'Mock group',
1282
+ 'members' => [
1283
+ {
1284
+ 'value' => '50ca93d04ab0c2de4772',
1285
+ 'display' => 'Ingrid Smith',
1286
+ 'type' => 'User'
1287
+ },
1288
+ {
1289
+ 'value' => 'a774d480e8112101375b',
1290
+ 'display' => 'Taylor Smith',
1291
+ 'type' => 'User'
1292
+ }
1293
+ ]
1294
+ })
1295
+ end
1296
+
1297
+ it 'removes multiple identified users' do
1298
+ path = [ 'members' ]
1299
+ value = [
1300
+ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' },
1301
+ { '$ref' => nil, 'value' => '50ca93d04ab0c2de4772' }
1302
+ ]
1303
+ scim_hash = {
1304
+ 'displayname' => 'Mock group',
1305
+ 'members' => [
1306
+ {
1307
+ 'value' => '50ca93d04ab0c2de4772',
1308
+ 'display' => 'Ingrid Smith',
1309
+ 'type' => 'User'
1310
+ },
1311
+ {
1312
+ 'value' => 'f648f8d5ea4e4cd38e9c',
1313
+ 'display' => 'Fred Smith',
1314
+ 'type' => 'User'
1315
+ },
1316
+ {
1317
+ 'value' => 'a774d480e8112101375b',
1318
+ 'display' => 'Taylor Smith',
1319
+ 'type' => 'User'
1320
+ }
1321
+ ]
1322
+ }.with_indifferent_case_insensitive_access()
1323
+
1324
+ @instance.send(
1325
+ :from_patch_backend!,
1326
+ nature: 'remove',
1327
+ path: path,
1328
+ value: value,
1329
+ altering_hash: scim_hash
1330
+ )
1331
+
1332
+ expect(scim_hash).to eql({
1333
+ 'displayname' => 'Mock group',
1334
+ 'members' => [
1335
+ {
1336
+ 'value' => 'a774d480e8112101375b',
1337
+ 'display' => 'Taylor Smith',
1338
+ 'type' => 'User'
1339
+ }
1340
+ ]
1341
+ })
1342
+ end
1343
+
1344
+ it 'removes all users individually without error' do
1345
+ path = [ 'members' ]
1346
+ value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' } ]
1347
+ scim_hash = {
1348
+ 'displayname' => 'Mock group',
1349
+ 'members' => [
1350
+ {
1351
+ 'value' => 'f648f8d5ea4e4cd38e9c',
1352
+ 'display' => 'Fred Smith',
1353
+ 'type' => 'User'
1354
+ }
1355
+ ]
1356
+ }.with_indifferent_case_insensitive_access()
1357
+
1358
+ @instance.send(
1359
+ :from_patch_backend!,
1360
+ nature: 'remove',
1361
+ path: path,
1362
+ value: value,
1363
+ altering_hash: scim_hash
1364
+ )
1365
+
1366
+ expect(scim_hash).to eql({
1367
+ 'displayname' => 'Mock group',
1368
+ 'members' => []
1369
+ })
1370
+ end
1371
+
1372
+ it 'can match on multiple attributes' do
1373
+ path = [ 'members' ]
1374
+ value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c', 'type' => 'User' } ]
1375
+ scim_hash = {
1376
+ 'displayname' => 'Mock group',
1377
+ 'members' => [
1378
+ {
1379
+ 'value' => 'f648f8d5ea4e4cd38e9c',
1380
+ 'display' => 'Fred Smith',
1381
+ 'type' => 'User'
1382
+ }
1383
+ ]
1384
+ }.with_indifferent_case_insensitive_access()
1385
+
1386
+ @instance.send(
1387
+ :from_patch_backend!,
1388
+ nature: 'remove',
1389
+ path: path,
1390
+ value: value,
1391
+ altering_hash: scim_hash
1392
+ )
1393
+
1394
+ expect(scim_hash).to eql({
1395
+ 'displayname' => 'Mock group',
1396
+ 'members' => []
1397
+ })
1398
+ end
1399
+
1400
+ it 'ignores unrecognised users' do
1401
+ path = [ 'members' ]
1402
+ value = [ { '$ref' => nil, 'value' => '11b054a9c85216ed9356' } ]
1403
+ scim_hash = {
1404
+ 'displayname' => 'Mock group',
1405
+ 'members' => [
1406
+ {
1407
+ 'value' => 'f648f8d5ea4e4cd38e9c',
1408
+ 'display' => 'Fred Smith',
1409
+ 'type' => 'User'
1410
+ }
1411
+ ]
1412
+ }.with_indifferent_case_insensitive_access()
1413
+
1414
+ @instance.send(
1415
+ :from_patch_backend!,
1416
+ nature: 'remove',
1417
+ path: path,
1418
+ value: value,
1419
+ altering_hash: scim_hash
1420
+ )
1421
+
1422
+ # The 'value' mismatched, so the user was not removed.
1423
+ #
1424
+ expect(scim_hash).to eql({
1425
+ 'displayname' => 'Mock group',
1426
+ 'members' => [
1427
+ {
1428
+ 'value' => 'f648f8d5ea4e4cd38e9c',
1429
+ 'display' => 'Fred Smith',
1430
+ 'type' => 'User'
1431
+ }
1432
+ ]
1433
+ })
1434
+ end
1435
+
1436
+ it 'ignores a mismatch on (for example) "type"' do
1437
+ path = [ 'members' ]
1438
+ value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c', 'type' => 'Group' } ]
1439
+ scim_hash = {
1440
+ 'displayname' => 'Mock group',
1441
+ 'members' => [
1442
+ {
1443
+ 'value' => 'f648f8d5ea4e4cd38e9c',
1444
+ 'display' => 'Fred Smith',
1445
+ 'type' => 'User'
1446
+ }
1447
+ ]
1448
+ }.with_indifferent_case_insensitive_access()
1449
+
1450
+ @instance.send(
1451
+ :from_patch_backend!,
1452
+ nature: 'remove',
1453
+ path: path,
1454
+ value: value,
1455
+ altering_hash: scim_hash
1456
+ )
1457
+
1458
+ # Type 'Group' mismatches 'User', so the user was not
1459
+ # removed.
1460
+ #
1461
+ expect(scim_hash).to eql({
1462
+ 'displayname' => 'Mock group',
1463
+ 'members' => [
1464
+ {
1465
+ 'value' => 'f648f8d5ea4e4cd38e9c',
1466
+ 'display' => 'Fred Smith',
1467
+ 'type' => 'User'
1468
+ }
1469
+ ]
1470
+ })
1471
+ end
1472
+
1473
+ it 'matches keys case-insensitive' do
1474
+ path = [ 'members' ]
1475
+ value = [ { '$ref' => nil, 'VALUe' => 'f648f8d5ea4e4cd38e9c' } ]
1476
+ scim_hash = {
1477
+ 'displayname' => 'Mock group',
1478
+ 'memBERS' => [
1479
+ {
1480
+ 'vaLUe' => 'f648f8d5ea4e4cd38e9c',
1481
+ 'display' => 'Fred Smith',
1482
+ 'type' => 'User'
1483
+ }
1484
+ ]
1485
+ }.with_indifferent_case_insensitive_access()
1486
+
1487
+ @instance.send(
1488
+ :from_patch_backend!,
1489
+ nature: 'remove',
1490
+ path: path,
1491
+ value: value,
1492
+ altering_hash: scim_hash
1493
+ )
1494
+
1495
+ expect(scim_hash).to eql({
1496
+ 'displayname' => 'Mock group',
1497
+ 'members' => []
1498
+ })
1499
+ end
1500
+
1501
+ it 'matches values case-sensitive' do
1502
+ path = [ 'members' ]
1503
+ value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c', 'type' => 'USER' } ]
1504
+ scim_hash = {
1505
+ 'displayname' => 'Mock group',
1506
+ 'members' => [
1507
+ {
1508
+ 'value' => 'f648f8d5ea4e4cd38e9c',
1509
+ 'display' => 'Fred Smith',
1510
+ 'type' => 'User'
1511
+ }
1512
+ ]
1513
+ }.with_indifferent_case_insensitive_access()
1514
+
1515
+ @instance.send(
1516
+ :from_patch_backend!,
1517
+ nature: 'remove',
1518
+ path: path,
1519
+ value: value,
1520
+ altering_hash: scim_hash
1521
+ )
1522
+
1523
+ # USER mismatchs User, so the user was not removed.
1524
+ #
1525
+ expect(scim_hash).to eql({
1526
+ 'displayname' => 'Mock group',
1527
+ 'members' => [
1528
+ {
1529
+ 'value' => 'f648f8d5ea4e4cd38e9c',
1530
+ 'display' => 'Fred Smith',
1531
+ 'type' => 'User'
1532
+ }
1533
+ ]
1534
+ })
1535
+ end
1536
+ end # "context 'removing a user from a group' do"
1537
+
1538
+ context 'generic use' do
1539
+ it 'removes matched items' do
1540
+ path = [ 'emails' ]
1541
+ value = [ { 'type' => 'work' } ]
1542
+ scim_hash = {
1543
+ 'emails' => [
1544
+ {
1545
+ 'type' => 'home',
1546
+ 'value' => 'home@test.com'
1547
+ },
1548
+ {
1549
+ 'type' => 'work',
1550
+ 'value' => 'work@test.com'
1551
+ }
1552
+ ]
1553
+ }.with_indifferent_case_insensitive_access()
1554
+
1555
+ @instance.send(
1556
+ :from_patch_backend!,
1557
+ nature: 'remove',
1558
+ path: path,
1559
+ value: value,
1560
+ altering_hash: scim_hash
1561
+ )
1562
+
1563
+ expect(scim_hash).to eql({
1564
+ 'emails' => [
1565
+ {
1566
+ 'type' => 'home',
1567
+ 'value' => 'home@test.com'
1568
+ }
1569
+ ]
1570
+ })
1571
+ end
1572
+
1573
+ it 'ignores unmatched items' do
1574
+ path = [ 'emails' ]
1575
+ value = [ { 'type' => 'missing' } ]
1576
+ scim_hash = {
1577
+ 'emails' => [
1578
+ {
1579
+ 'type' => 'home',
1580
+ 'value' => 'home@test.com'
1581
+ },
1582
+ {
1583
+ 'type' => 'work',
1584
+ 'value' => 'work@test.com'
1585
+ }
1586
+ ]
1587
+ }.with_indifferent_case_insensitive_access()
1588
+
1589
+ @instance.send(
1590
+ :from_patch_backend!,
1591
+ nature: 'remove',
1592
+ path: path,
1593
+ value: value,
1594
+ altering_hash: scim_hash
1595
+ )
1596
+
1597
+ expect(scim_hash).to eql({
1598
+ 'emails' => [
1599
+ {
1600
+ 'type' => 'home',
1601
+ 'value' => 'home@test.com'
1602
+ },
1603
+ {
1604
+ 'type' => 'work',
1605
+ 'value' => 'work@test.com'
1606
+ }
1607
+ ]
1608
+ })
1609
+ end
1610
+
1611
+ it 'compares string forms' do
1612
+ path = [ 'test' ]
1613
+ value = [
1614
+ { 'active' => true, 'value' => '12' },
1615
+ { 'active' => 'false', 'value' => 42 }
1616
+ ]
1617
+ scim_hash = {
1618
+ 'test' => [
1619
+ {
1620
+ 'active' => 'true',
1621
+ 'value' => 12
1622
+ },
1623
+ {
1624
+ 'active' => false,
1625
+ 'value' => '42'
1626
+ }
1627
+ ]
1628
+ }.with_indifferent_case_insensitive_access()
1629
+
1630
+ @instance.send(
1631
+ :from_patch_backend!,
1632
+ nature: 'remove',
1633
+ path: path,
1634
+ value: value,
1635
+ altering_hash: scim_hash
1636
+ )
1637
+
1638
+ expect(scim_hash).to eql({'test' => []})
1639
+ end
1640
+
1641
+ it 'handles a singular to-remove value rather than an array' do
1642
+ path = [ 'emails' ]
1643
+ value = { 'type' => 'work' }
1644
+ scim_hash = {
1645
+ 'emails' => [
1646
+ {
1647
+ 'type' => 'home',
1648
+ 'value' => 'home@test.com'
1649
+ },
1650
+ {
1651
+ 'type' => 'work',
1652
+ 'value' => 'work@test.com'
1653
+ }
1654
+ ]
1655
+ }.with_indifferent_case_insensitive_access()
1656
+
1657
+ @instance.send(
1658
+ :from_patch_backend!,
1659
+ nature: 'remove',
1660
+ path: path,
1661
+ value: value,
1662
+ altering_hash: scim_hash
1663
+ )
1664
+
1665
+ expect(scim_hash).to eql({
1666
+ 'emails' => [
1667
+ {
1668
+ 'type' => 'home',
1669
+ 'value' => 'home@test.com'
1670
+ }
1671
+ ]
1672
+ })
1673
+ end
1674
+
1675
+ it 'handles simple values rather than object (Hash) values' do
1676
+ path = [ 'test' ]
1677
+ value = 42
1678
+ scim_hash = {
1679
+ 'test' => [
1680
+ '21',
1681
+ '42',
1682
+ '15'
1683
+ ]
1684
+ }.with_indifferent_case_insensitive_access()
1685
+
1686
+ @instance.send(
1687
+ :from_patch_backend!,
1688
+ nature: 'remove',
1689
+ path: path,
1690
+ value: value,
1691
+ altering_hash: scim_hash
1692
+ )
1693
+
1694
+ expect(scim_hash).to eql({
1695
+ 'test' => [
1696
+ '21',
1697
+ '15'
1698
+ ]
1699
+ })
1700
+ end
1701
+ end
1702
+ end # "context 'Microsoft-style payload' do"
1703
+
1704
+ # https://help.salesforce.com/s/articleView?id=sf.identity_scim_manage_groups.htm&type=5
1705
+ #
1706
+ context 'Salesforce-style payload' do
1707
+ it 'removes identified user' do
1708
+ path = [ 'members' ]
1709
+ value = { 'members' => [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' } ] }
1710
+ scim_hash = {
1711
+ 'displayname' => 'Mock group',
1712
+ 'members' => [
1713
+ {
1714
+ 'value' => '50ca93d04ab0c2de4772',
1715
+ 'display' => 'Ingrid Smith',
1716
+ 'type' => 'User'
1717
+ },
1718
+ {
1719
+ 'value' => 'f648f8d5ea4e4cd38e9c',
1720
+ 'display' => 'Fred Smith',
1721
+ 'type' => 'User'
1722
+ }
1723
+ ]
1724
+ }.with_indifferent_case_insensitive_access()
1725
+
1726
+ @instance.send(
1727
+ :from_patch_backend!,
1728
+ nature: 'remove',
1729
+ path: path,
1730
+ value: value,
1731
+ altering_hash: scim_hash
1732
+ )
1733
+
1734
+ expect(scim_hash).to eql({
1735
+ 'displayname' => 'Mock group',
1736
+ 'members' => [
1737
+ {
1738
+ 'value' => '50ca93d04ab0c2de4772',
1739
+ 'display' => 'Ingrid Smith',
1740
+ 'type' => 'User'
1741
+ }
1742
+ ]
1743
+ })
1744
+ end
1745
+
1746
+ it 'matches the "members" key case-insensitive' do
1747
+ path = [ 'members' ]
1748
+ value = { 'MEMBERS' => [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c' } ] }
1749
+ scim_hash = {
1750
+ 'displayname' => 'Mock group',
1751
+ 'members' => [
1752
+ {
1753
+ 'value' => 'f648f8d5ea4e4cd38e9c',
1754
+ 'display' => 'Fred Smith',
1755
+ 'type' => 'User'
1756
+ },
1757
+ {
1758
+ 'value' => 'a774d480e8112101375b',
1759
+ 'display' => 'Taylor Smith',
1760
+ 'type' => 'User'
1761
+ }
1762
+ ]
1763
+ }.with_indifferent_case_insensitive_access()
1764
+
1765
+ @instance.send(
1766
+ :from_patch_backend!,
1767
+ nature: 'remove',
1768
+ path: path,
1769
+ value: value,
1770
+ altering_hash: scim_hash
1771
+ )
1772
+
1773
+ expect(scim_hash).to eql({
1774
+ 'displayname' => 'Mock group',
1775
+ 'members' => [
1776
+ {
1777
+ 'value' => 'a774d480e8112101375b',
1778
+ 'display' => 'Taylor Smith',
1779
+ 'type' => 'User'
1780
+ }
1781
+ ]
1782
+ })
1783
+ end
1784
+
1785
+ it 'ignores unrecognised users' do
1786
+ path = [ 'members' ]
1787
+ value = { 'members' => [ { '$ref' => nil, 'value' => '11b054a9c85216ed9356' } ] }
1788
+ scim_hash = {
1789
+ 'displayname' => 'Mock group',
1790
+ 'members' => [
1791
+ {
1792
+ 'value' => 'f648f8d5ea4e4cd38e9c',
1793
+ 'display' => 'Fred Smith',
1794
+ 'type' => 'User'
1795
+ }
1796
+ ]
1797
+ }.with_indifferent_case_insensitive_access()
1798
+
1799
+ @instance.send(
1800
+ :from_patch_backend!,
1801
+ nature: 'remove',
1802
+ path: path,
1803
+ value: value,
1804
+ altering_hash: scim_hash
1805
+ )
1806
+
1807
+ # The 'value' mismatched, so the user was not removed.
1808
+ #
1809
+ expect(scim_hash).to eql({
1810
+ 'displayname' => 'Mock group',
1811
+ 'members' => [
1812
+ {
1813
+ 'value' => 'f648f8d5ea4e4cd38e9c',
1814
+ 'display' => 'Fred Smith',
1815
+ 'type' => 'User'
1816
+ }
1817
+ ]
1818
+ })
1819
+ end
1820
+ end # "context 'Salesforce-style payload' do"
1821
+ end # "context 'special cases' do"
1205
1822
  end # context 'when prior value already exists' do
1206
1823
 
1207
1824
  context 'when value is not present' do
@@ -1926,7 +2543,7 @@ RSpec.describe Scimitar::Resources::Mixin do
1926
2543
  :from_patch_backend!,
1927
2544
  nature: 'remove',
1928
2545
  path: ['complex[type eq "type1"]', 'data', 'nested[nature eq "nature2"]', 'info'],
1929
- value: [{ 'deeper' => 'addition' }],
2546
+ value: nil,
1930
2547
  altering_hash: scim_hash
1931
2548
  )
1932
2549