scimitar 1.0.3 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -15,6 +15,19 @@ RSpec.describe Scimitar::Resources::User do
15
15
  expect(user.as_json['name']['errors']).to be_nil
16
16
  end
17
17
 
18
+ it 'treats attributes as case-insensitive' do
19
+ user = described_class.new(name: Scimitar::ComplexTypes::Name.new(
20
+ FAMILYNAME: 'Smith',
21
+ GIVENNAME: 'John',
22
+ FORMATTED: 'John Smith'
23
+ ))
24
+
25
+ expect(user.name.familyName).to eql('Smith')
26
+ expect(user.name.givenName).to eql('John')
27
+ expect(user.name.formatted).to eql('John Smith')
28
+ expect(user.as_json['name']['errors']).to be_nil
29
+ end
30
+
18
31
  it 'validates that the provided name matches the name schema' do
19
32
  user = described_class.new(name: Scimitar::ComplexTypes::Email.new(
20
33
  value: 'john@smoth.com',
@@ -146,36 +146,35 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
146
146
 
147
147
  context '#create' do
148
148
  context 'creates an item' do
149
- it 'with minimal parameters' do
150
- mock_before = MockUser.all.to_a
149
+ shared_examples 'a creator' do | force_upper_case: |
150
+ it 'with minimal parameters' do
151
+ mock_before = MockUser.all.to_a
151
152
 
152
- expect_any_instance_of(MockUsersController).to receive(:create).once.and_call_original
153
- expect {
154
- post "/Users", params: {
155
- format: :scim,
156
- userName: '4' # Minimum required by schema
157
- }
158
- }.to change { MockUser.count }.by(1)
153
+ attributes = { userName: '4' } # Minimum required by schema
154
+ attributes = spec_helper_hupcase(attributes) if force_upper_case
159
155
 
160
- mock_after = MockUser.all.to_a
161
- new_mock = (mock_after - mock_before).first
156
+ expect_any_instance_of(MockUsersController).to receive(:create).once.and_call_original
157
+ expect {
158
+ post "/Users", params: attributes.merge(format: :scim)
159
+ }.to change { MockUser.count }.by(1)
162
160
 
163
- expect(response.status).to eql(201)
164
- result = JSON.parse(response.body)
161
+ mock_after = MockUser.all.to_a
162
+ new_mock = (mock_after - mock_before).first
165
163
 
166
- expect(result['id']).to eql(new_mock.id.to_s)
167
- expect(result['meta']['resourceType']).to eql('User')
168
- expect(new_mock.username).to eql('4')
169
- end
164
+ expect(response.status).to eql(201)
165
+ result = JSON.parse(response.body)
170
166
 
171
- # A bit of extra coverage just for general confidence.
172
- #
173
- it 'with more comprehensive parameters' do
174
- mock_before = MockUser.all.to_a
167
+ expect(result['id']).to eql(new_mock.id.to_s)
168
+ expect(result['meta']['resourceType']).to eql('User')
169
+ expect(new_mock.username).to eql('4')
170
+ end
175
171
 
176
- expect {
177
- post "/Users", params: {
178
- format: :scim,
172
+ # A bit of extra coverage just for general confidence.
173
+ #
174
+ it 'with more comprehensive parameters' do
175
+ mock_before = MockUser.all.to_a
176
+
177
+ attributes = {
179
178
  userName: '4',
180
179
  name: {
181
180
  givenName: 'Given',
@@ -192,22 +191,36 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
192
191
  }
193
192
  ]
194
193
  }
195
- }.to change { MockUser.count }.by(1)
196
194
 
197
- mock_after = MockUser.all.to_a
198
- new_mock = (mock_after - mock_before).first
195
+ attributes = spec_helper_hupcase(attributes) if force_upper_case
199
196
 
200
- expect(response.status).to eql(201)
201
- result = JSON.parse(response.body)
197
+ expect {
198
+ post "/Users", params: attributes.merge(format: :scim)
199
+ }.to change { MockUser.count }.by(1)
202
200
 
203
- expect(result['id']).to eql(new_mock.id.to_s)
204
- expect(result['meta']['resourceType']).to eql('User')
205
- expect(new_mock.username).to eql('4')
206
- expect(new_mock.first_name).to eql('Given')
207
- expect(new_mock.last_name).to eql('Family')
208
- expect(new_mock.home_email_address).to eql('home_4@test.com')
209
- expect(new_mock.work_email_address).to eql('work_4@test.com')
210
- end
201
+ mock_after = MockUser.all.to_a
202
+ new_mock = (mock_after - mock_before).first
203
+
204
+ expect(response.status).to eql(201)
205
+ result = JSON.parse(response.body)
206
+
207
+ expect(result['id']).to eql(new_mock.id.to_s)
208
+ expect(result['meta']['resourceType']).to eql('User')
209
+ expect(new_mock.username).to eql('4')
210
+ expect(new_mock.first_name).to eql('Given')
211
+ expect(new_mock.last_name).to eql('Family')
212
+ expect(new_mock.home_email_address).to eql('home_4@test.com')
213
+ expect(new_mock.work_email_address).to eql('work_4@test.com')
214
+ end
215
+ end # "shared_examples 'a creator' do | force_upper_case: |"
216
+
217
+ context 'using schema-matched case' do
218
+ it_behaves_like 'a creator', force_upper_case: false
219
+ end # "context 'using schema-matched case' do"
220
+
221
+ context 'using upper case' do
222
+ it_behaves_like 'a creator', force_upper_case: true
223
+ end # "context 'using upper case' do"
211
224
  end
212
225
 
213
226
  it 'returns 409 for duplicates (by Rails validation)' do
@@ -258,28 +271,38 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
258
271
  # ===========================================================================
259
272
 
260
273
  context '#replace' do
261
- it 'replaces all attributes in an instance' do
262
- expect_any_instance_of(MockUsersController).to receive(:replace).once.and_call_original
263
- expect {
264
- put "/Users/#{@u2.id}", params: {
265
- format: :scim,
266
- userName: '4' # Minimum required by schema
267
- }
268
- }.to_not change { MockUser.count }
274
+ shared_examples 'a replacer' do | force_upper_case: |
275
+ it 'which replaces all attributes in an instance' do
276
+ attributes = { userName: '4' } # Minimum required by schema
277
+ attributes = spec_helper_hupcase(attributes) if force_upper_case
269
278
 
270
- expect(response.status).to eql(200)
271
- result = JSON.parse(response.body)
279
+ expect_any_instance_of(MockUsersController).to receive(:replace).once.and_call_original
280
+ expect {
281
+ put "/Users/#{@u2.id}", params: attributes.merge(format: :scim)
282
+ }.to_not change { MockUser.count }
272
283
 
273
- expect(result['id']).to eql(@u2.id.to_s)
274
- expect(result['meta']['resourceType']).to eql('User')
284
+ expect(response.status).to eql(200)
285
+ result = JSON.parse(response.body)
275
286
 
276
- @u2.reload
287
+ expect(result['id']).to eql(@u2.id.to_s)
288
+ expect(result['meta']['resourceType']).to eql('User')
277
289
 
278
- expect(@u2.username).to eql('4')
279
- expect(@u2.first_name).to be_nil
280
- expect(@u2.last_name).to be_nil
281
- expect(@u2.home_email_address).to be_nil
282
- end
290
+ @u2.reload
291
+
292
+ expect(@u2.username).to eql('4')
293
+ expect(@u2.first_name).to be_nil
294
+ expect(@u2.last_name).to be_nil
295
+ expect(@u2.home_email_address).to be_nil
296
+ end
297
+ end # "shared_examples 'a replacer' do | force_upper_case: |"
298
+
299
+ context 'using schema-matched case' do
300
+ it_behaves_like 'a replacer', force_upper_case: false
301
+ end # "context 'using schema-matched case' do"
302
+
303
+ context 'using upper case' do
304
+ it_behaves_like 'a replacer', force_upper_case: true
305
+ end # "context 'using upper case' do"
283
306
 
284
307
  it 'notes schema validation failures' do
285
308
  expect {
@@ -341,11 +364,9 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
341
364
  # ===========================================================================
342
365
 
343
366
  context '#update' do
344
- it 'patches specific attributes' do
345
- expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
346
- expect {
347
- patch "/Users/#{@u2.id}", params: {
348
- format: :scim,
367
+ shared_examples 'an updater' do | force_upper_case: |
368
+ it 'which patches specific attributes' do
369
+ payload = {
349
370
  Operations: [
350
371
  {
351
372
  op: 'add',
@@ -359,33 +380,36 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
359
380
  }
360
381
  ]
361
382
  }
362
- }.to_not change { MockUser.count }
363
383
 
364
- expect(response.status).to eql(200)
365
- result = JSON.parse(response.body)
384
+ payload = spec_helper_hupcase(payload) if force_upper_case
366
385
 
367
- expect(result['id']).to eql(@u2.id.to_s)
368
- expect(result['meta']['resourceType']).to eql('User')
386
+ expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
387
+ expect {
388
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
389
+ }.to_not change { MockUser.count }
369
390
 
370
- @u2.reload
391
+ expect(response.status).to eql(200)
392
+ result = JSON.parse(response.body)
371
393
 
372
- expect(@u2.username).to eql('4')
373
- expect(@u2.first_name).to eql('Foo')
374
- expect(@u2.last_name).to eql('Bar')
375
- expect(@u2.home_email_address).to eql('home_2@test.com')
376
- expect(@u2.work_email_address).to eql('work_4@test.com')
377
- end
394
+ expect(result['id']).to eql(@u2.id.to_s)
395
+ expect(result['meta']['resourceType']).to eql('User')
378
396
 
379
- context 'clears attributes' do
380
- before :each do
381
- @u2.update!(work_email_address: 'work_2@test.com')
397
+ @u2.reload
398
+
399
+ expect(@u2.username).to eql('4')
400
+ expect(@u2.first_name).to eql('Foo')
401
+ expect(@u2.last_name).to eql('Bar')
402
+ expect(@u2.home_email_address).to eql('home_2@test.com')
403
+ expect(@u2.work_email_address).to eql('work_4@test.com')
382
404
  end
383
405
 
384
- it 'with simple paths' do
385
- expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
386
- expect {
387
- patch "/Users/#{@u2.id}", params: {
388
- format: :scim,
406
+ context 'which clears attributes' do
407
+ before :each do
408
+ @u2.update!(work_email_address: 'work_2@test.com')
409
+ end
410
+
411
+ it 'with simple paths' do
412
+ payload = {
389
413
  Operations: [
390
414
  {
391
415
  op: 'remove',
@@ -393,28 +417,31 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
393
417
  }
394
418
  ]
395
419
  }
396
- }.to_not change { MockUser.count }
397
420
 
398
- expect(response.status).to eql(200)
399
- result = JSON.parse(response.body)
421
+ payload = spec_helper_hupcase(payload) if force_upper_case
400
422
 
401
- expect(result['id']).to eql(@u2.id.to_s)
402
- expect(result['meta']['resourceType']).to eql('User')
423
+ expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
424
+ expect {
425
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
426
+ }.to_not change { MockUser.count }
403
427
 
404
- @u2.reload
428
+ expect(response.status).to eql(200)
429
+ result = JSON.parse(response.body)
405
430
 
406
- expect(@u2.username).to eql('2')
407
- expect(@u2.first_name).to be_nil
408
- expect(@u2.last_name).to eql('Bar')
409
- expect(@u2.home_email_address).to eql('home_2@test.com')
410
- expect(@u2.work_email_address).to eql('work_2@test.com')
411
- end
431
+ expect(result['id']).to eql(@u2.id.to_s)
432
+ expect(result['meta']['resourceType']).to eql('User')
412
433
 
413
- it 'by array entry filter match' do
414
- expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
415
- expect {
416
- patch "/Users/#{@u2.id}", params: {
417
- format: :scim,
434
+ @u2.reload
435
+
436
+ expect(@u2.username).to eql('2')
437
+ expect(@u2.first_name).to be_nil
438
+ expect(@u2.last_name).to eql('Bar')
439
+ expect(@u2.home_email_address).to eql('home_2@test.com')
440
+ expect(@u2.work_email_address).to eql('work_2@test.com')
441
+ end
442
+
443
+ it 'by array entry filter match' do
444
+ payload = {
418
445
  Operations: [
419
446
  {
420
447
  op: 'remove',
@@ -422,28 +449,31 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
422
449
  }
423
450
  ]
424
451
  }
425
- }.to_not change { MockUser.count }
426
452
 
427
- expect(response.status).to eql(200)
428
- result = JSON.parse(response.body)
453
+ payload = spec_helper_hupcase(payload) if force_upper_case
429
454
 
430
- expect(result['id']).to eql(@u2.id.to_s)
431
- expect(result['meta']['resourceType']).to eql('User')
455
+ expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
456
+ expect {
457
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
458
+ }.to_not change { MockUser.count }
432
459
 
433
- @u2.reload
460
+ expect(response.status).to eql(200)
461
+ result = JSON.parse(response.body)
434
462
 
435
- expect(@u2.username).to eql('2')
436
- expect(@u2.first_name).to eql('Foo')
437
- expect(@u2.last_name).to eql('Bar')
438
- expect(@u2.home_email_address).to eql('home_2@test.com')
439
- expect(@u2.work_email_address).to be_nil
440
- end
463
+ expect(result['id']).to eql(@u2.id.to_s)
464
+ expect(result['meta']['resourceType']).to eql('User')
441
465
 
442
- it 'by whole collection' do
443
- expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
444
- expect {
445
- patch "/Users/#{@u2.id}", params: {
446
- format: :scim,
466
+ @u2.reload
467
+
468
+ expect(@u2.username).to eql('2')
469
+ expect(@u2.first_name).to eql('Foo')
470
+ expect(@u2.last_name).to eql('Bar')
471
+ expect(@u2.home_email_address).to eql('home_2@test.com')
472
+ expect(@u2.work_email_address).to be_nil
473
+ end
474
+
475
+ it 'by whole collection' do
476
+ payload = {
447
477
  Operations: [
448
478
  {
449
479
  op: 'remove',
@@ -451,23 +481,38 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
451
481
  }
452
482
  ]
453
483
  }
454
- }.to_not change { MockUser.count }
455
484
 
456
- expect(response.status).to eql(200)
457
- result = JSON.parse(response.body)
485
+ payload = spec_helper_hupcase(payload) if force_upper_case
458
486
 
459
- expect(result['id']).to eql(@u2.id.to_s)
460
- expect(result['meta']['resourceType']).to eql('User')
487
+ expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
488
+ expect {
489
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
490
+ }.to_not change { MockUser.count }
461
491
 
462
- @u2.reload
492
+ expect(response.status).to eql(200)
493
+ result = JSON.parse(response.body)
463
494
 
464
- expect(@u2.username).to eql('2')
465
- expect(@u2.first_name).to eql('Foo')
466
- expect(@u2.last_name).to eql('Bar')
467
- expect(@u2.home_email_address).to be_nil
468
- expect(@u2.work_email_address).to be_nil
469
- end
470
- end # "context 'clears attributes' do"
495
+ expect(result['id']).to eql(@u2.id.to_s)
496
+ expect(result['meta']['resourceType']).to eql('User')
497
+
498
+ @u2.reload
499
+
500
+ expect(@u2.username).to eql('2')
501
+ expect(@u2.first_name).to eql('Foo')
502
+ expect(@u2.last_name).to eql('Bar')
503
+ expect(@u2.home_email_address).to be_nil
504
+ expect(@u2.work_email_address).to be_nil
505
+ end
506
+ end # "context 'which clears attributes' do"
507
+ end # "shared_examples 'an updater' do | force_upper_case: |"
508
+
509
+ context 'using schema-matched case' do
510
+ it_behaves_like 'an updater', force_upper_case: false
511
+ end # "context 'using schema-matched case' do"
512
+
513
+ context 'using upper case' do
514
+ it_behaves_like 'an updater', force_upper_case: true
515
+ end # "context 'using upper case' do"
471
516
 
472
517
  it 'notes Rails validation failures' do
473
518
  expect {
@@ -10,11 +10,36 @@ RSpec.describe Scimitar::Engine do
10
10
  # "params" given here as a String, expecting the engine's custom parser to
11
11
  # decode it for us.
12
12
  #
13
- it 'decodes JSON', type: :model do
13
+ it 'decodes simple JSON', type: :model do
14
14
  post '/Users.scim', params: '{"userName": "foo"}', headers: { 'CONTENT_TYPE' => 'application/scim+json' }
15
15
 
16
16
  expect(response.status).to eql(201)
17
17
  expect(JSON.parse(response.body)['userName']).to eql('foo')
18
18
  end
19
+
20
+ it 'decodes nested JSON', type: :model do
21
+ post '/Users.scim', params: '{"userName": "foo", "name": {"givenName": "bar", "familyName": "baz"}}', headers: { 'CONTENT_TYPE' => 'application/scim+json' }
22
+
23
+ expect(response.status).to eql(201)
24
+ expect(JSON.parse(response.body)['userName']).to eql('foo')
25
+ expect(JSON.parse(response.body)['name']['givenName']).to eql('bar')
26
+ expect(JSON.parse(response.body)['name']['familyName']).to eql('baz')
27
+ end
28
+
29
+ it 'is case insensitive at the top level', type: :model do
30
+ post '/Users.scim', params: '{"USERNAME": "foo"}', headers: { 'CONTENT_TYPE' => 'application/scim+json' }
31
+
32
+ expect(response.status).to eql(201)
33
+ expect(JSON.parse(response.body)['userName']).to eql('foo')
34
+ end
35
+
36
+ it 'is case insensitive in nested levels', type: :model do
37
+ post '/Users.scim', params: '{"USERNAME": "foo", "NAME": {"GIVENNAME": "bar", "FAMILYNAME": "baz"}}', headers: { 'CONTENT_TYPE' => 'application/scim+json' }
38
+
39
+ expect(response.status).to eql(201)
40
+ expect(JSON.parse(response.body)['userName']).to eql('foo')
41
+ expect(JSON.parse(response.body)['name']['givenName']).to eql('bar')
42
+ expect(JSON.parse(response.body)['name']['familyName']).to eql('baz')
43
+ end
19
44
  end # "context 'parameter parser' do"
20
45
  end
data/spec/spec_helper.rb CHANGED
@@ -64,3 +64,30 @@ def spec_helper_capture_stdout( &block )
64
64
 
65
65
  return result
66
66
  end
67
+
68
+ # Recursively transform the keys of any given Hash or any Hashes in a given
69
+ # Array into uppercase form, retaining Symbol or String keys. Returns the
70
+ # transformed duplicate structure.
71
+ #
72
+ # Only Hashes or Hash entries within an Array are converted. Other data is left
73
+ # alone. The original input item is not modified.
74
+ #
75
+ # IMPORTANT: HashWithIndifferentAccess or similar subclasses are not supported.
76
+ #
77
+ # +item+:: Hash or Array that might contain some Hashes.
78
+ #
79
+ def spec_helper_hupcase(item)
80
+ if item.is_a?(Hash)
81
+ rehash = item.transform_keys(&:upcase)
82
+ rehash.each do | key, value |
83
+ rehash[key] = spec_helper_hupcase(value)
84
+ end
85
+ rehash
86
+ elsif item.is_a?(Array)
87
+ item.map do | array_entry |
88
+ spec_helper_hupcase(array_entry)
89
+ end
90
+ else
91
+ item
92
+ end
93
+ end