scimitar 2.6.0 → 2.7.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.
@@ -13,9 +13,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
13
13
  lmt = Time.parse("2023-01-09 14:25:00 +1300")
14
14
  ids = 3.times.map { SecureRandom.uuid }.sort()
15
15
 
16
- @u1 = MockUser.create(primary_key: ids.shift(), username: '1', first_name: 'Foo', last_name: 'Ark', home_email_address: 'home_1@test.com', scim_uid: '001', created_at: lmt, updated_at: lmt + 1)
17
- @u2 = MockUser.create(primary_key: ids.shift(), username: '2', first_name: 'Foo', last_name: 'Bar', home_email_address: 'home_2@test.com', scim_uid: '002', created_at: lmt, updated_at: lmt + 2)
18
- @u3 = MockUser.create(primary_key: ids.shift(), username: '3', first_name: 'Foo', home_email_address: 'home_3@test.com', scim_uid: '003', created_at: lmt, updated_at: lmt + 3)
16
+ @u1 = MockUser.create!(primary_key: ids.shift(), username: '1', first_name: 'Foo', last_name: 'Ark', home_email_address: 'home_1@test.com', scim_uid: '001', created_at: lmt, updated_at: lmt + 1)
17
+ @u2 = MockUser.create!(primary_key: ids.shift(), username: '2', first_name: 'Foo', last_name: 'Bar', home_email_address: 'home_2@test.com', scim_uid: '002', created_at: lmt, updated_at: lmt + 2)
18
+ @u3 = MockUser.create!(primary_key: ids.shift(), username: '3', first_name: 'Foo', home_email_address: 'home_3@test.com', scim_uid: '003', created_at: lmt, updated_at: lmt + 3)
19
19
 
20
20
  @g1 = MockGroup.create!(display_name: 'Group 1')
21
21
  @g2 = MockGroup.create!(display_name: 'Group 2')
@@ -26,13 +26,17 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
26
26
 
27
27
  context '#index' do
28
28
  context 'with no items' do
29
- it 'returns empty list' do
29
+ before :each do
30
30
  MockUser.delete_all
31
+ end
31
32
 
33
+ it 'returns empty list' do
32
34
  expect_any_instance_of(MockUsersController).to receive(:index).once.and_call_original
33
35
  get '/Users', params: { format: :scim }
34
36
 
35
- expect(response.status).to eql(200)
37
+ expect(response.status ).to eql(200)
38
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
39
+
36
40
  result = JSON.parse(response.body)
37
41
 
38
42
  expect(result['totalResults']).to eql(0)
@@ -46,7 +50,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
46
50
  it 'returns all items' do
47
51
  get '/Users', params: { format: :scim }
48
52
 
49
- expect(response.status).to eql(200)
53
+ expect(response.status ).to eql(200)
54
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
55
+
50
56
  result = JSON.parse(response.body)
51
57
 
52
58
  expect(result['totalResults']).to eql(3)
@@ -64,7 +70,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
64
70
  it 'returns all items' do
65
71
  get '/Groups', params: { format: :scim }
66
72
 
67
- expect(response.status).to eql(200)
73
+ expect(response.status ).to eql(200)
74
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
75
+
68
76
  result = JSON.parse(response.body)
69
77
 
70
78
  expect(result['totalResults']).to eql(3)
@@ -84,7 +92,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
84
92
  filter: 'name.givenName eq "FOO" and name.familyName pr and emails ne "home_1@test.com"'
85
93
  }
86
94
 
87
- expect(response.status).to eql(200)
95
+ expect(response.status ).to eql(200)
96
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
97
+
88
98
  result = JSON.parse(response.body)
89
99
 
90
100
  expect(result['totalResults']).to eql(1)
@@ -103,7 +113,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
103
113
  filter: 'name.GIVENNAME eq "Foo" and name.Familyname pr and emails ne "home_1@test.com"'
104
114
  }
105
115
 
106
- expect(response.status).to eql(200)
116
+ expect(response.status ).to eql(200)
117
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
118
+
107
119
  result = JSON.parse(response.body)
108
120
 
109
121
  expect(result['totalResults']).to eql(1)
@@ -126,7 +138,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
126
138
  filter: "id eq \"#{@u3.primary_key}\""
127
139
  }
128
140
 
129
- expect(response.status).to eql(200)
141
+ expect(response.status ).to eql(200)
142
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
143
+
130
144
  result = JSON.parse(response.body)
131
145
 
132
146
  expect(result['totalResults']).to eql(1)
@@ -145,7 +159,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
145
159
  filter: "externalID eq \"#{@u2.scim_uid}\""
146
160
  }
147
161
 
148
- expect(response.status).to eql(200)
162
+ expect(response.status ).to eql(200)
163
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
164
+
149
165
  result = JSON.parse(response.body)
150
166
 
151
167
  expect(result['totalResults']).to eql(1)
@@ -164,7 +180,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
164
180
  filter: "Meta.LastModified eq \"#{@u3.updated_at}\""
165
181
  }
166
182
 
167
- expect(response.status).to eql(200)
183
+ expect(response.status ).to eql(200)
184
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
185
+
168
186
  result = JSON.parse(response.body)
169
187
 
170
188
  expect(result['totalResults']).to eql(1)
@@ -184,7 +202,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
184
202
  count: 2
185
203
  }
186
204
 
187
- expect(response.status).to eql(200)
205
+ expect(response.status ).to eql(200)
206
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
207
+
188
208
  result = JSON.parse(response.body)
189
209
 
190
210
  expect(result['totalResults']).to eql(3)
@@ -203,7 +223,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
203
223
  startIndex: 2
204
224
  }
205
225
 
206
- expect(response.status).to eql(200)
226
+ expect(response.status ).to eql(200)
227
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
228
+
207
229
  result = JSON.parse(response.body)
208
230
 
209
231
  expect(result['totalResults']).to eql(3)
@@ -224,8 +246,11 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
224
246
  filter: 'name.givenName'
225
247
  }
226
248
 
227
- expect(response.status).to eql(400)
249
+ expect(response.status ).to eql(400)
250
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
251
+
228
252
  result = JSON.parse(response.body)
253
+
229
254
  expect(result['scimType']).to eql('invalidFilter')
230
255
  end
231
256
  end # "context 'with bad calls' do"
@@ -239,7 +264,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
239
264
  expect_any_instance_of(MockUsersController).to receive(:show).once.and_call_original
240
265
  get "/Users/#{@u2.primary_key}", params: { format: :scim }
241
266
 
242
- expect(response.status).to eql(200)
267
+ expect(response.status ).to eql(200)
268
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
269
+
243
270
  result = JSON.parse(response.body)
244
271
 
245
272
  expect(result['id']).to eql(@u2.primary_key.to_s)
@@ -254,7 +281,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
254
281
  expect_any_instance_of(MockGroupsController).to receive(:show).once.and_call_original
255
282
  get "/Groups/#{@g2.id}", params: { format: :scim }
256
283
 
257
- expect(response.status).to eql(200)
284
+ expect(response.status ).to eql(200)
285
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
286
+
258
287
  result = JSON.parse(response.body)
259
288
 
260
289
  expect(result['id']).to eql(@g2.id.to_s) # Note - ID was converted String; not Integer
@@ -266,8 +295,11 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
266
295
  it 'renders 404' do
267
296
  get '/Users/xyz', params: { format: :scim }
268
297
 
269
- expect(response.status).to eql(404)
298
+ expect(response.status ).to eql(404)
299
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
300
+
270
301
  result = JSON.parse(response.body)
302
+
271
303
  expect(result['status']).to eql('404')
272
304
  end
273
305
  end # "context '#show' do"
@@ -283,7 +315,12 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
283
315
  attributes = { userName: '4' } # Minimum required by schema
284
316
  attributes = spec_helper_hupcase(attributes) if force_upper_case
285
317
 
318
+ # Prove that certain known pathways are called; can then unit test
319
+ # those if need be and be sure that this covers #create actions.
320
+ #
286
321
  expect_any_instance_of(MockUsersController).to receive(:create).once.and_call_original
322
+ expect_any_instance_of(MockUsersController).to receive(:save! ).once.and_call_original
323
+
287
324
  expect {
288
325
  post "/Users", params: attributes.merge(format: :scim)
289
326
  }.to change { MockUser.count }.by(1)
@@ -291,7 +328,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
291
328
  mock_after = MockUser.all.to_a
292
329
  new_mock = (mock_after - mock_before).first
293
330
 
294
- expect(response.status).to eql(201)
331
+ expect(response.status ).to eql(201)
332
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
333
+
295
334
  result = JSON.parse(response.body)
296
335
 
297
336
  expect(result['id']).to eql(new_mock.primary_key.to_s)
@@ -332,7 +371,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
332
371
  mock_after = MockUser.all.to_a
333
372
  new_mock = (mock_after - mock_before).first
334
373
 
335
- expect(response.status).to eql(201)
374
+ expect(response.status ).to eql(201)
375
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
376
+
336
377
  result = JSON.parse(response.body)
337
378
 
338
379
  expect(result['id']).to eql(new_mock.id.to_s)
@@ -363,8 +404,11 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
363
404
  }
364
405
  }.to_not change { MockUser.count }
365
406
 
366
- expect(response.status).to eql(409)
407
+ expect(response.status ).to eql(409)
408
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
409
+
367
410
  result = JSON.parse(response.body)
411
+
368
412
  expect(result['scimType']).to eql('uniqueness')
369
413
  expect(result['detail']).to include('already been taken')
370
414
  end
@@ -377,8 +421,11 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
377
421
  }
378
422
  }.to_not change { MockUser.count }
379
423
 
380
- expect(response.status).to eql(400)
424
+ expect(response.status ).to eql(400)
425
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
426
+
381
427
  result = JSON.parse(response.body)
428
+
382
429
  expect(result['scimType']).to eql('invalidValue')
383
430
  expect(result['detail']).to include('is required')
384
431
  end
@@ -391,28 +438,84 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
391
438
  }
392
439
  }.to_not change { MockUser.count }
393
440
 
394
- expect(response.status).to eql(400)
441
+ expect(response.status ).to eql(400)
442
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
443
+
395
444
  result = JSON.parse(response.body)
396
445
 
397
446
  expect(result['scimType']).to eql('invalidValue')
398
447
  expect(result['detail']).to include('is reserved')
399
448
  end
400
449
 
401
- it 'invokes a block if given one' do
402
- mock_before = MockUser.all.to_a
403
- attributes = { userName: '5' } # Minimum required by schema
450
+ context 'with a block' do
451
+ it 'invokes the block' do
452
+ mock_before = MockUser.all.to_a
404
453
 
405
- expect_any_instance_of(CustomSaveMockUsersController).to receive(:create).once.and_call_original
406
- expect {
407
- post "/CustomSaveUsers", params: attributes.merge(format: :scim)
408
- }.to change { MockUser.count }.by(1)
454
+ expect_any_instance_of(CustomCreateMockUsersController).to receive(:create).once.and_call_original
455
+ expect {
456
+ post "/CustomCreateUsers", params: {
457
+ format: :scim,
458
+ userName: '4' # Minimum required by schema
459
+ }
460
+ }.to change { MockUser.count }.by(1)
409
461
 
410
- mock_after = MockUser.all.to_a
411
- new_mock = (mock_after - mock_before).first
462
+ mock_after = MockUser.all.to_a
463
+ new_mock = (mock_after - mock_before).first
412
464
 
413
- expect(response.status).to eql(201)
414
- expect(new_mock.username).to eql(CustomSaveMockUsersController::CUSTOM_SAVE_BLOCK_USERNAME_INDICATOR)
415
- end
465
+ expect(response.status ).to eql(201)
466
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
467
+
468
+ result = JSON.parse(response.body)
469
+
470
+ expect(result['id']).to eql(new_mock.id.to_s)
471
+ expect(result['meta']['resourceType']).to eql('User')
472
+ expect(new_mock.first_name).to eql(CustomCreateMockUsersController::OVERRIDDEN_NAME)
473
+ end
474
+
475
+ it 'returns 409 for duplicates (by Rails validation)' do
476
+ existing_user = MockUser.create!(
477
+ username: '4',
478
+ first_name: 'Will Be Overridden',
479
+ last_name: 'Baz',
480
+ home_email_address: 'random@test.com',
481
+ scim_uid: '999'
482
+ )
483
+
484
+ expect_any_instance_of(CustomCreateMockUsersController).to receive(:create).once.and_call_original
485
+ expect {
486
+ post "/CustomCreateUsers", params: {
487
+ format: :scim,
488
+ userName: '4' # Already exists
489
+ }
490
+ }.to_not change { MockUser.count }
491
+
492
+ expect(response.status ).to eql(409)
493
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
494
+
495
+ result = JSON.parse(response.body)
496
+
497
+ expect(result['scimType']).to eql('uniqueness')
498
+ expect(result['detail']).to include('already been taken')
499
+ end
500
+
501
+ it 'notes Rails validation failures' do
502
+ expect_any_instance_of(CustomCreateMockUsersController).to receive(:create).once.and_call_original
503
+ expect {
504
+ post "/CustomCreateUsers", params: {
505
+ format: :scim,
506
+ userName: MockUser::INVALID_USERNAME
507
+ }
508
+ }.to_not change { MockUser.count }
509
+
510
+ expect(response.status ).to eql(400)
511
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
512
+
513
+ result = JSON.parse(response.body)
514
+
515
+ expect(result['scimType']).to eql('invalidValue')
516
+ expect(result['detail']).to include('is reserved')
517
+ end
518
+ end # "context 'with a block' do"
416
519
  end # "context '#create' do"
417
520
 
418
521
  # ===========================================================================
@@ -423,12 +526,18 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
423
526
  attributes = { userName: '4' } # Minimum required by schema
424
527
  attributes = spec_helper_hupcase(attributes) if force_upper_case
425
528
 
529
+ # Prove that certain known pathways are called; can then unit test
530
+ # those if need be and be sure that this covers #replace actions.
531
+ #
426
532
  expect_any_instance_of(MockUsersController).to receive(:replace).once.and_call_original
533
+ expect_any_instance_of(MockUsersController).to receive(:save! ).once.and_call_original
427
534
  expect {
428
535
  put "/Users/#{@u2.primary_key}", params: attributes.merge(format: :scim)
429
536
  }.to_not change { MockUser.count }
430
537
 
431
- expect(response.status).to eql(200)
538
+ expect(response.status ).to eql(200)
539
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
540
+
432
541
  result = JSON.parse(response.body)
433
542
 
434
543
  expect(result['id']).to eql(@u2.primary_key.to_s)
@@ -459,8 +568,11 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
459
568
  }
460
569
  }.to_not change { MockUser.count }
461
570
 
462
- expect(response.status).to eql(400)
571
+ expect(response.status ).to eql(400)
572
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
573
+
463
574
  result = JSON.parse(response.body)
575
+
464
576
  expect(result['scimType']).to eql('invalidValue')
465
577
  expect(result['detail']).to include('is required')
466
578
 
@@ -474,13 +586,15 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
474
586
 
475
587
  it 'notes Rails validation failures' do
476
588
  expect {
477
- post "/Users", params: {
589
+ put "/Users/#{@u2.primary_key}", params: {
478
590
  format: :scim,
479
591
  userName: MockUser::INVALID_USERNAME
480
592
  }
481
593
  }.to_not change { MockUser.count }
482
594
 
483
- expect(response.status).to eql(400)
595
+ expect(response.status ).to eql(400)
596
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
597
+
484
598
  result = JSON.parse(response.body)
485
599
 
486
600
  expect(result['scimType']).to eql('invalidValue')
@@ -502,10 +616,65 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
502
616
  }
503
617
  }.to_not change { MockUser.count }
504
618
 
505
- expect(response.status).to eql(404)
619
+ expect(response.status ).to eql(404)
620
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
621
+
506
622
  result = JSON.parse(response.body)
623
+
507
624
  expect(result['status']).to eql('404')
508
625
  end
626
+
627
+ context 'with a block' do
628
+ it 'invokes the block' do
629
+ attributes = { userName: '4' } # Minimum required by schema
630
+
631
+ expect_any_instance_of(CustomReplaceMockUsersController).to receive(:replace).once.and_call_original
632
+ expect {
633
+ put "/CustomReplaceUsers/#{@u2.primary_key}", params: {
634
+ format: :scim,
635
+ userName: '4'
636
+ }
637
+ }.to_not change { MockUser.count }
638
+
639
+ expect(response.status ).to eql(200)
640
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
641
+
642
+ result = JSON.parse(response.body)
643
+
644
+ expect(result['id']).to eql(@u2.primary_key.to_s)
645
+ expect(result['meta']['resourceType']).to eql('User')
646
+
647
+ @u2.reload
648
+
649
+ expect(@u2.username ).to eql('4')
650
+ expect(@u2.first_name).to eql(CustomReplaceMockUsersController::OVERRIDDEN_NAME)
651
+ end
652
+
653
+ it 'notes Rails validation failures' do
654
+ expect_any_instance_of(CustomReplaceMockUsersController).to receive(:replace).once.and_call_original
655
+ expect {
656
+ put "/CustomReplaceUsers/#{@u2.primary_key}", params: {
657
+ format: :scim,
658
+ userName: MockUser::INVALID_USERNAME
659
+ }
660
+ }.to_not change { MockUser.count }
661
+
662
+ expect(response.status ).to eql(400)
663
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
664
+
665
+ result = JSON.parse(response.body)
666
+
667
+ expect(result['scimType']).to eql('invalidValue')
668
+ expect(result['detail']).to include('is reserved')
669
+
670
+ @u2.reload
671
+
672
+ expect(@u2.username).to eql('2')
673
+ expect(@u2.first_name).to eql('Foo')
674
+ expect(@u2.last_name).to eql('Bar')
675
+ expect(@u2.home_email_address).to eql('home_2@test.com')
676
+ end
677
+ end # "context 'with a block' do"
509
678
  end # "context '#replace' do"
510
679
 
511
680
  # ===========================================================================
@@ -530,12 +699,19 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
530
699
 
531
700
  payload = spec_helper_hupcase(payload) if force_upper_case
532
701
 
702
+ # Prove that certain known pathways are called; can then unit test
703
+ # those if need be and be sure that this covers #update actions.
704
+ #
533
705
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
706
+ expect_any_instance_of(MockUsersController).to receive(:save! ).once.and_call_original
707
+
534
708
  expect {
535
709
  patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
536
710
  }.to_not change { MockUser.count }
537
711
 
538
- expect(response.status).to eql(200)
712
+ expect(response.status ).to eql(200)
713
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
714
+
539
715
  result = JSON.parse(response.body)
540
716
 
541
717
  expect(result['id']).to eql(@u2.primary_key.to_s)
@@ -572,7 +748,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
572
748
  patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
573
749
  }.to_not change { MockUser.count }
574
750
 
575
- expect(response.status).to eql(200)
751
+ expect(response.status ).to eql(200)
752
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
753
+
576
754
  result = JSON.parse(response.body)
577
755
 
578
756
  expect(result['id']).to eql(@u2.primary_key.to_s)
@@ -604,7 +782,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
604
782
  patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
605
783
  }.to_not change { MockUser.count }
606
784
 
607
- expect(response.status).to eql(200)
785
+ expect(response.status ).to eql(200)
786
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
787
+
608
788
  result = JSON.parse(response.body)
609
789
 
610
790
  expect(result['id']).to eql(@u2.primary_key.to_s)
@@ -636,7 +816,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
636
816
  patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
637
817
  }.to_not change { MockUser.count }
638
818
 
639
- expect(response.status).to eql(200)
819
+ expect(response.status ).to eql(200)
820
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
821
+
640
822
  result = JSON.parse(response.body)
641
823
 
642
824
  expect(result['id']).to eql(@u2.primary_key.to_s)
@@ -675,7 +857,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
675
857
  }
676
858
  }.to_not change { MockUser.count }
677
859
 
678
- expect(response.status).to eql(400)
860
+ expect(response.status ).to eql(400)
861
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
862
+
679
863
  result = JSON.parse(response.body)
680
864
 
681
865
  expect(result['scimType']).to eql('invalidValue')
@@ -703,8 +887,11 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
703
887
  }
704
888
  }.to_not change { MockUser.count }
705
889
 
706
- expect(response.status).to eql(404)
890
+ expect(response.status ).to eql(404)
891
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
892
+
707
893
  result = JSON.parse(response.body)
894
+
708
895
  expect(result['status']).to eql('404')
709
896
  end
710
897
 
@@ -741,7 +928,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
741
928
 
742
929
  get "/Groups/#{@g1.id}", params: { format: :scim }
743
930
 
744
- expect(response.status).to eql(200)
931
+ expect(response.status ).to eql(200)
932
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
933
+
745
934
  result = JSON.parse(response.body)
746
935
 
747
936
  expect(result['members']).to be_empty
@@ -768,7 +957,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
768
957
 
769
958
  get "/Groups/#{@g1.id}", params: { format: :scim }
770
959
 
771
- expect(response.status).to eql(200)
960
+ expect(response.status ).to eql(200)
961
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
962
+
772
963
  result = JSON.parse(response.body)
773
964
 
774
965
  expect(result['members'].map { |m| m['value'] }.sort()).to eql(expected_remaining_user_ids)
@@ -843,12 +1034,178 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
843
1034
  it_behaves_like 'a user remover'
844
1035
  end # context 'and using a Salesforce variant payload' do
845
1036
  end # "context 'when removing users from groups' do"
1037
+
1038
+ context 'with a block' do
1039
+ it 'invokes the block' do
1040
+ payload = {
1041
+ format: :scim,
1042
+ Operations: [
1043
+ {
1044
+ op: 'add',
1045
+ path: 'userName',
1046
+ value: '4'
1047
+ },
1048
+ {
1049
+ op: 'replace',
1050
+ path: 'emails[type eq "work"]',
1051
+ value: { type: 'work', value: 'work_4@test.com' }
1052
+ }
1053
+ ]
1054
+ }
1055
+
1056
+ expect_any_instance_of(CustomUpdateMockUsersController).to receive(:update).once.and_call_original
1057
+ expect {
1058
+ patch "/CustomUpdateUsers/#{@u2.primary_key}", params: payload
1059
+ }.to_not change { MockUser.count }
1060
+
1061
+ expect(response.status ).to eql(200)
1062
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
1063
+
1064
+ result = JSON.parse(response.body)
1065
+
1066
+ expect(result['id']).to eql(@u2.primary_key.to_s)
1067
+ expect(result['meta']['resourceType']).to eql('User')
1068
+
1069
+ @u2.reload
1070
+
1071
+ expect(@u2.username ).to eql('4')
1072
+ expect(@u2.first_name ).to eql(CustomUpdateMockUsersController::OVERRIDDEN_NAME)
1073
+ expect(@u2.work_email_address).to eql('work_4@test.com')
1074
+ end
1075
+
1076
+ it 'notes Rails validation failures' do
1077
+ expect_any_instance_of(CustomUpdateMockUsersController).to receive(:update).once.and_call_original
1078
+ expect {
1079
+ patch "/CustomUpdateUsers/#{@u2.primary_key}", params: {
1080
+ format: :scim,
1081
+ Operations: [
1082
+ {
1083
+ op: 'add',
1084
+ path: 'userName',
1085
+ value: MockUser::INVALID_USERNAME
1086
+ }
1087
+ ]
1088
+ }
1089
+ }.to_not change { MockUser.count }
1090
+
1091
+ expect(response.status ).to eql(400)
1092
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
1093
+
1094
+ result = JSON.parse(response.body)
1095
+
1096
+ expect(result['scimType']).to eql('invalidValue')
1097
+ expect(result['detail']).to include('is reserved')
1098
+
1099
+ @u2.reload
1100
+
1101
+ expect(@u2.username).to eql('2')
1102
+ expect(@u2.first_name).to eql('Foo')
1103
+ expect(@u2.last_name).to eql('Bar')
1104
+ expect(@u2.home_email_address).to eql('home_2@test.com')
1105
+ end
1106
+ end # "context 'with a block' do"
846
1107
  end # "context '#update' do"
847
1108
 
1109
+ # ===========================================================================
1110
+ # In-passing parts of tests above show that #create, #replace and #update all
1111
+ # route through #save!, so now add some unit tests for that and for exception
1112
+ # handling overrides invoked via #save!.
1113
+ # ===========================================================================
1114
+
1115
+ context 'overriding #save!' do
1116
+ it 'invokes a block if given one' do
1117
+ mock_before = MockUser.all.to_a
1118
+ attributes = { userName: '5' } # Minimum required by schema
1119
+
1120
+ expect_any_instance_of(CustomSaveMockUsersController).to receive(:create).once.and_call_original
1121
+ expect {
1122
+ post "/CustomSaveUsers", params: attributes.merge(format: :scim)
1123
+ }.to change { MockUser.count }.by(1)
1124
+
1125
+ mock_after = MockUser.all.to_a
1126
+ new_mock = (mock_after - mock_before).first
1127
+
1128
+ expect(response.status ).to eql(201)
1129
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
1130
+
1131
+ expect(new_mock.username).to eql(CustomSaveMockUsersController::CUSTOM_SAVE_BLOCK_USERNAME_INDICATOR)
1132
+ end
1133
+ end # "context 'overriding #save!' do
1134
+
1135
+ context 'custom on-save exceptions' do
1136
+ MockUsersController.new.send(:scimitar_rescuable_exceptions).each do | exception_class |
1137
+ it "handles out-of-box exception #{exception_class}" do
1138
+ expect_any_instance_of(MockUsersController).to receive(:create).once.and_call_original
1139
+ expect_any_instance_of(MockUsersController).to receive(:save! ).once.and_call_original
1140
+
1141
+ expect_any_instance_of(MockUser).to receive(:save!).once { raise exception_class }
1142
+
1143
+ expect {
1144
+ post "/Users", params: { format: :scim, userName: SecureRandom.uuid }
1145
+ }.to_not change { MockUser.count }
1146
+
1147
+ expected_status, expected_prefix = if exception_class == ActiveRecord::RecordNotUnique
1148
+ [409, 'Operation failed due to a uniqueness constraint: ']
1149
+ else
1150
+ [400, 'Operation failed since record has become invalid: ']
1151
+ end
1152
+
1153
+ expect(response.status ).to eql(expected_status)
1154
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
1155
+
1156
+ result = JSON.parse(response.body)
1157
+
1158
+ # Check basic SCIM error rendering - good enough given other tests
1159
+ # elsewhere. Exact message varies by exception.
1160
+ #
1161
+ expect(result['detail']).to start_with(expected_prefix)
1162
+ end
1163
+ end
1164
+
1165
+ it 'handles custom exceptions' do
1166
+ exception_class = RuntimeError # (for testing only; usually, this would provoke a 500 response)
1167
+
1168
+ expect_any_instance_of(MockUsersController).to receive(:create).once.and_call_original
1169
+ expect_any_instance_of(MockUsersController).to receive(:save! ).once.and_call_original
1170
+
1171
+ expect_any_instance_of(MockUsersController).to receive(:scimitar_rescuable_exceptions).once { [ exception_class ] }
1172
+ expect_any_instance_of(MockUser ).to receive(:save! ).once { raise exception_class }
1173
+
1174
+ expect {
1175
+ post "/Users", params: { format: :scim, userName: SecureRandom.uuid }
1176
+ }.to_not change { MockUser.count }
1177
+
1178
+ expect(response.status ).to eql(400)
1179
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
1180
+
1181
+ result = JSON.parse(response.body)
1182
+
1183
+ expect(result['detail']).to start_with('Operation failed since record has become invalid: ')
1184
+ end
1185
+
1186
+ it 'reports other exceptions as 500s' do
1187
+ expect_any_instance_of(MockUsersController).to receive(:create).once.and_call_original
1188
+ expect_any_instance_of(MockUsersController).to receive(:save! ).once.and_call_original
1189
+
1190
+ expect_any_instance_of(MockUser).to receive(:save!).once { raise RuntimeError }
1191
+
1192
+ expect {
1193
+ post "/Users", params: { format: :scim, userName: SecureRandom.uuid }
1194
+ }.to_not change { MockUser.count }
1195
+
1196
+ expect(response.status ).to eql(500)
1197
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
1198
+
1199
+ result = JSON.parse(response.body)
1200
+
1201
+ expect(result['detail']).to eql('RuntimeError')
1202
+ end
1203
+ end
1204
+
848
1205
  # ===========================================================================
849
1206
 
850
1207
  context '#destroy' do
851
- it 'deletes an item if given no blok' do
1208
+ it 'deletes an item if given no block' do
852
1209
  expect_any_instance_of(MockUsersController).to receive(:destroy).once.and_call_original
853
1210
  expect_any_instance_of(MockUser).to receive(:destroy!).once.and_call_original
854
1211
  expect {
@@ -879,8 +1236,11 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
879
1236
  delete '/Users/xyz', params: { format: :scim }
880
1237
  }.to_not change { MockUser.count }
881
1238
 
882
- expect(response.status).to eql(404)
1239
+ expect(response.status ).to eql(404)
1240
+ expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
1241
+
883
1242
  result = JSON.parse(response.body)
1243
+
884
1244
  expect(result['status']).to eql('404')
885
1245
  end
886
1246
  end # "context '#destroy' do"