scimitar 1.0.3 → 2.0.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/app/controllers/scimitar/active_record_backed_resources_controller.rb +2 -2
- data/app/models/scimitar/complex_types/base.rb +43 -1
- data/app/models/scimitar/errors.rb +1 -1
- data/app/models/scimitar/resources/base.rb +22 -4
- data/app/models/scimitar/resources/mixin.rb +34 -26
- data/app/models/scimitar/schema/attribute.rb +1 -1
- data/app/models/scimitar/schema/base.rb +6 -2
- data/config/initializers/scimitar.rb +71 -67
- data/lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb +86 -0
- data/lib/scimitar/version.rb +2 -2
- data/lib/scimitar.rb +1 -0
- data/spec/apps/dummy/config/application.rb +1 -0
- data/spec/apps/dummy/config/environments/test.rb +28 -5
- data/spec/apps/dummy/config/initializers/scimitar.rb +10 -8
- data/spec/models/scimitar/resources/base_spec.rb +108 -58
- data/spec/models/scimitar/resources/mixin_spec.rb +316 -264
- data/spec/models/scimitar/resources/user_spec.rb +17 -4
- data/spec/requests/active_record_backed_resources_controller_spec.rb +172 -127
- data/spec/requests/application_controller_spec.rb +0 -1
- data/spec/requests/engine_spec.rb +26 -1
- data/spec/spec_helper.rb +27 -0
- data/spec/spec_helper_spec.rb +30 -0
- data/spec/support/hash_with_indifferent_case_insensitive_access_spec.rb +61 -0
- metadata +58 -50
@@ -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',
|
@@ -29,25 +42,25 @@ RSpec.describe Scimitar::Resources::User do
|
|
29
42
|
let(:user) { described_class.new }
|
30
43
|
|
31
44
|
it 'adds the error when the value is a string' do
|
32
|
-
user.add_errors_from_hash(key: 'some error')
|
45
|
+
user.add_errors_from_hash(errors_hash: {key: 'some error'})
|
33
46
|
expect(user.errors.messages.to_h).to eql({key: ['some error']})
|
34
47
|
expect(user.errors.full_messages).to eql(['Key some error'])
|
35
48
|
end
|
36
49
|
|
37
50
|
it 'adds the error when the value is an array' do
|
38
|
-
user.add_errors_from_hash(key: ['error1', 'error2'])
|
51
|
+
user.add_errors_from_hash(errors_hash: {key: ['error1', 'error2']})
|
39
52
|
expect(user.errors.messages.to_h).to eql({key: ['error1', 'error2']})
|
40
53
|
expect(user.errors.full_messages).to eql(['Key error1', 'Key error2'])
|
41
54
|
end
|
42
55
|
|
43
56
|
it 'adds the error with prefix when the value is a string' do
|
44
|
-
user.add_errors_from_hash({key: 'some error'}, prefix: :pre)
|
57
|
+
user.add_errors_from_hash(errors_hash: {key: 'some error'}, prefix: :pre)
|
45
58
|
expect(user.errors.messages.to_h).to eql({:'pre.key' => ['some error']})
|
46
59
|
expect(user.errors.full_messages).to eql(['Pre key some error'])
|
47
60
|
end
|
48
61
|
|
49
62
|
it 'adds the error wity prefix when the value is an array' do
|
50
|
-
user.add_errors_from_hash({key: ['error1', 'error2']}, prefix: :pre)
|
63
|
+
user.add_errors_from_hash(errors_hash: {key: ['error1', 'error2']}, prefix: :pre)
|
51
64
|
expect(user.errors.messages.to_h).to eql({:'pre.key' => ['error1', 'error2']})
|
52
65
|
expect(user.errors.full_messages).to eql(['Pre key error1', 'Pre key error2'])
|
53
66
|
end
|
@@ -146,36 +146,35 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
146
146
|
|
147
147
|
context '#create' do
|
148
148
|
context 'creates an item' do
|
149
|
-
|
150
|
-
|
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
|
-
|
153
|
-
|
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
|
-
|
161
|
-
|
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
|
-
|
164
|
-
|
161
|
+
mock_after = MockUser.all.to_a
|
162
|
+
new_mock = (mock_after - mock_before).first
|
165
163
|
|
166
|
-
|
167
|
-
|
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
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
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
|
-
|
177
|
-
|
178
|
-
|
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
|
-
|
198
|
-
new_mock = (mock_after - mock_before).first
|
195
|
+
attributes = spec_helper_hupcase(attributes) if force_upper_case
|
199
196
|
|
200
|
-
|
201
|
-
|
197
|
+
expect {
|
198
|
+
post "/Users", params: attributes.merge(format: :scim)
|
199
|
+
}.to change { MockUser.count }.by(1)
|
202
200
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
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
|
-
|
271
|
-
|
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
|
-
|
274
|
-
|
284
|
+
expect(response.status).to eql(200)
|
285
|
+
result = JSON.parse(response.body)
|
275
286
|
|
276
|
-
|
287
|
+
expect(result['id']).to eql(@u2.id.to_s)
|
288
|
+
expect(result['meta']['resourceType']).to eql('User')
|
277
289
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
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
|
-
|
345
|
-
|
346
|
-
|
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
|
-
|
365
|
-
result = JSON.parse(response.body)
|
384
|
+
payload = spec_helper_hupcase(payload) if force_upper_case
|
366
385
|
|
367
|
-
|
368
|
-
|
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
|
-
|
391
|
+
expect(response.status).to eql(200)
|
392
|
+
result = JSON.parse(response.body)
|
371
393
|
|
372
|
-
|
373
|
-
|
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
|
-
|
380
|
-
|
381
|
-
@u2.
|
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
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
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
|
-
|
399
|
-
result = JSON.parse(response.body)
|
421
|
+
payload = spec_helper_hupcase(payload) if force_upper_case
|
400
422
|
|
401
|
-
|
402
|
-
|
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
|
-
|
428
|
+
expect(response.status).to eql(200)
|
429
|
+
result = JSON.parse(response.body)
|
405
430
|
|
406
|
-
|
407
|
-
|
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
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
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
|
-
|
428
|
-
result = JSON.parse(response.body)
|
453
|
+
payload = spec_helper_hupcase(payload) if force_upper_case
|
429
454
|
|
430
|
-
|
431
|
-
|
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
|
-
|
460
|
+
expect(response.status).to eql(200)
|
461
|
+
result = JSON.parse(response.body)
|
434
462
|
|
435
|
-
|
436
|
-
|
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
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
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
|
-
|
457
|
-
result = JSON.parse(response.body)
|
485
|
+
payload = spec_helper_hupcase(payload) if force_upper_case
|
458
486
|
|
459
|
-
|
460
|
-
|
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
|
-
|
492
|
+
expect(response.status).to eql(200)
|
493
|
+
result = JSON.parse(response.body)
|
463
494
|
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
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 {
|
@@ -23,7 +23,6 @@ RSpec.describe Scimitar::ApplicationController do
|
|
23
23
|
|
24
24
|
expect(response).to have_http_status(:bad_request)
|
25
25
|
expect(JSON.parse(response.body)['detail']).to start_with('Invalid JSON - ')
|
26
|
-
expect(JSON.parse(response.body)['detail']).to include("'not-json-12345'")
|
27
26
|
end
|
28
27
|
|
29
28
|
it 'translates Content-Type to Rails request format' do
|
@@ -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
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Self-test. Sometimes tests call into utility methods in spec_helper.rb;
|
2
|
+
# if those aren't doing what they should, then the tests might give false
|
3
|
+
# positive passes.
|
4
|
+
#
|
5
|
+
require 'spec_helper'
|
6
|
+
|
7
|
+
RSpec.describe 'spec_helper.rb self-test' do
|
8
|
+
context '#spec_helper_hupcase' do
|
9
|
+
it 'converts a flat Hash, preserving data type of keys' do
|
10
|
+
input = {:one => 1, 'two' => 2}
|
11
|
+
output = spec_helper_hupcase(input)
|
12
|
+
|
13
|
+
expect(output).to eql({:ONE => 1, 'TWO' => 2})
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'converts a nested Hash' do
|
17
|
+
input = {:one => 1, 'two' => {:tHrEe => {'fOuR' => 4}}}
|
18
|
+
output = spec_helper_hupcase(input)
|
19
|
+
|
20
|
+
expect(output).to eql({:ONE => 1, 'TWO' => {:THREE => {'FOUR' => 4}}})
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'converts an Array with Hashes' do
|
24
|
+
input = {:one => 1, 'two' => [true, 42, {:tHrEe => {'fOuR' => 4}}]}
|
25
|
+
output = spec_helper_hupcase(input)
|
26
|
+
|
27
|
+
expect(output).to eql({:ONE => 1, 'TWO' => [true, 42, {:THREE => {'FOUR' => 4}}]})
|
28
|
+
end
|
29
|
+
end # "context '#spec_helper_hupcase' do"
|
30
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Scimitar::Support::HashWithIndifferentCaseInsensitiveAccess do
|
4
|
+
shared_examples 'an indifferent access, case insensitive Hash' do
|
5
|
+
context 'where keys set as strings' do
|
6
|
+
it 'can be retrieved via any case' do
|
7
|
+
subject()['foo'] = 1
|
8
|
+
|
9
|
+
expect(subject()['FOO']).to eql(1)
|
10
|
+
expect(subject()[:FoO ]).to eql(1)
|
11
|
+
expect(subject()['bar']).to be_nil
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'can be checked for via any case' do
|
15
|
+
subject()['foo'] = 1
|
16
|
+
|
17
|
+
expect(subject()).to have_key('FOO')
|
18
|
+
expect(subject()).to have_key(:FoO )
|
19
|
+
expect(subject()).to_not have_key('bar')
|
20
|
+
end
|
21
|
+
end # "context 'where keys set as strings' do"
|
22
|
+
|
23
|
+
context 'where keys set as symbols' do
|
24
|
+
it 'retrieves via any case' do
|
25
|
+
subject()[:foo] = 1
|
26
|
+
|
27
|
+
expect(subject()['FOO']).to eql(1)
|
28
|
+
expect(subject()[:FoO ]).to eql(1)
|
29
|
+
expect(subject()['bar']).to be_nil
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'enquires via any case' do
|
33
|
+
subject()[:foo] = 1
|
34
|
+
|
35
|
+
expect(subject()).to have_key('FOO')
|
36
|
+
expect(subject()).to have_key(:FoO )
|
37
|
+
expect(subject()).to_not have_key('bar')
|
38
|
+
end
|
39
|
+
end # "context 'where keys set as symbols' do"
|
40
|
+
end # "shared_examples 'an indifferent access, case insensitive Hash' do"
|
41
|
+
|
42
|
+
context 'when created directly' do
|
43
|
+
subject do
|
44
|
+
described_class.new()
|
45
|
+
end
|
46
|
+
|
47
|
+
it_behaves_like 'an indifferent access, case insensitive Hash'
|
48
|
+
end # "context 'when created directly' do"
|
49
|
+
|
50
|
+
context 'when created through conversion' do
|
51
|
+
subject do
|
52
|
+
{ 'test' => 2 }.with_indifferent_access.with_indifferent_case_insensitive_access()
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'includes the original data' do
|
56
|
+
expect(subject()[:TEST]).to eql(2)
|
57
|
+
end
|
58
|
+
|
59
|
+
it_behaves_like 'an indifferent access, case insensitive Hash'
|
60
|
+
end # "context 'converting hashes' do"
|
61
|
+
end
|