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.
- checksums.yaml +4 -4
- data/LICENSE.txt +21 -0
- data/README.md +710 -0
- data/app/controllers/scimitar/active_record_backed_resources_controller.rb +53 -17
- data/app/controllers/scimitar/application_controller.rb +13 -4
- data/app/models/scimitar/complex_types/address.rb +0 -6
- data/app/models/scimitar/resource_invalid_error.rb +1 -1
- data/app/models/scimitar/resources/mixin.rb +4 -1
- data/app/models/scimitar/schema/attribute.rb +3 -3
- data/config/initializers/scimitar.rb +3 -3
- data/lib/scimitar/support/utilities.rb +51 -0
- data/lib/scimitar/version.rb +2 -2
- data/lib/scimitar.rb +1 -0
- data/spec/apps/dummy/app/controllers/custom_create_mock_users_controller.rb +25 -0
- data/spec/apps/dummy/app/controllers/custom_replace_mock_users_controller.rb +25 -0
- data/spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb +25 -0
- data/spec/apps/dummy/config/routes.rb +15 -3
- data/spec/controllers/scimitar/application_controller_spec.rb +56 -2
- data/spec/controllers/scimitar/schemas_controller_spec.rb +1 -1
- data/spec/models/scimitar/complex_types/address_spec.rb +3 -4
- data/spec/models/scimitar/resources/mixin_spec.rb +22 -0
- data/spec/requests/active_record_backed_resources_controller_spec.rb +410 -50
- data/spec/spec_helper.rb +9 -1
- metadata +34 -11
@@ -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
|
-
|
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
|
-
|
402
|
-
|
403
|
-
|
450
|
+
context 'with a block' do
|
451
|
+
it 'invokes the block' do
|
452
|
+
mock_before = MockUser.all.to_a
|
404
453
|
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
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
|
-
|
411
|
-
|
462
|
+
mock_after = MockUser.all.to_a
|
463
|
+
new_mock = (mock_after - mock_before).first
|
412
464
|
|
413
|
-
|
414
|
-
|
415
|
-
|
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
|
-
|
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
|
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"
|