scimitar 1.7.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,83 @@ 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
+ expect(result['scimType']).to eql('invalidValue')
515
+ expect(result['detail']).to include('is reserved')
516
+ end
517
+ end # "context 'with a block' do"
416
518
  end # "context '#create' do"
417
519
 
418
520
  # ===========================================================================
@@ -423,12 +525,19 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
423
525
  attributes = { userName: '4' } # Minimum required by schema
424
526
  attributes = spec_helper_hupcase(attributes) if force_upper_case
425
527
 
528
+ # Prove that certain known pathways are called; can then unit test
529
+ # those if need be and be sure that this covers #replace actions.
530
+ #
426
531
  expect_any_instance_of(MockUsersController).to receive(:replace).once.and_call_original
532
+ expect_any_instance_of(MockUsersController).to receive(:save! ).once.and_call_original
533
+
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"