scimitar 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +16 -0
  3. data/app/controllers/scimitar/active_record_backed_resources_controller.rb +180 -0
  4. data/app/controllers/scimitar/application_controller.rb +129 -0
  5. data/app/controllers/scimitar/resource_types_controller.rb +28 -0
  6. data/app/controllers/scimitar/resources_controller.rb +203 -0
  7. data/app/controllers/scimitar/schemas_controller.rb +16 -0
  8. data/app/controllers/scimitar/service_provider_configurations_controller.rb +8 -0
  9. data/app/models/scimitar/authentication_error.rb +9 -0
  10. data/app/models/scimitar/authentication_scheme.rb +18 -0
  11. data/app/models/scimitar/bulk.rb +8 -0
  12. data/app/models/scimitar/complex_types/address.rb +18 -0
  13. data/app/models/scimitar/complex_types/base.rb +41 -0
  14. data/app/models/scimitar/complex_types/email.rb +12 -0
  15. data/app/models/scimitar/complex_types/entitlement.rb +12 -0
  16. data/app/models/scimitar/complex_types/ims.rb +12 -0
  17. data/app/models/scimitar/complex_types/name.rb +12 -0
  18. data/app/models/scimitar/complex_types/phone_number.rb +12 -0
  19. data/app/models/scimitar/complex_types/photo.rb +12 -0
  20. data/app/models/scimitar/complex_types/reference_group.rb +12 -0
  21. data/app/models/scimitar/complex_types/reference_member.rb +12 -0
  22. data/app/models/scimitar/complex_types/role.rb +12 -0
  23. data/app/models/scimitar/complex_types/x509_certificate.rb +12 -0
  24. data/app/models/scimitar/engine_configuration.rb +24 -0
  25. data/app/models/scimitar/error_response.rb +20 -0
  26. data/app/models/scimitar/errors.rb +14 -0
  27. data/app/models/scimitar/filter.rb +11 -0
  28. data/app/models/scimitar/filter_error.rb +22 -0
  29. data/app/models/scimitar/invalid_syntax_error.rb +9 -0
  30. data/app/models/scimitar/lists/count.rb +64 -0
  31. data/app/models/scimitar/lists/query_parser.rb +730 -0
  32. data/app/models/scimitar/meta.rb +7 -0
  33. data/app/models/scimitar/not_found_error.rb +10 -0
  34. data/app/models/scimitar/resource_invalid_error.rb +9 -0
  35. data/app/models/scimitar/resource_type.rb +29 -0
  36. data/app/models/scimitar/resources/base.rb +159 -0
  37. data/app/models/scimitar/resources/group.rb +13 -0
  38. data/app/models/scimitar/resources/mixin.rb +964 -0
  39. data/app/models/scimitar/resources/user.rb +13 -0
  40. data/app/models/scimitar/schema/address.rb +24 -0
  41. data/app/models/scimitar/schema/attribute.rb +123 -0
  42. data/app/models/scimitar/schema/base.rb +86 -0
  43. data/app/models/scimitar/schema/derived_attributes.rb +24 -0
  44. data/app/models/scimitar/schema/email.rb +10 -0
  45. data/app/models/scimitar/schema/entitlement.rb +10 -0
  46. data/app/models/scimitar/schema/group.rb +27 -0
  47. data/app/models/scimitar/schema/ims.rb +10 -0
  48. data/app/models/scimitar/schema/name.rb +20 -0
  49. data/app/models/scimitar/schema/phone_number.rb +10 -0
  50. data/app/models/scimitar/schema/photo.rb +10 -0
  51. data/app/models/scimitar/schema/reference_group.rb +23 -0
  52. data/app/models/scimitar/schema/reference_member.rb +21 -0
  53. data/app/models/scimitar/schema/role.rb +10 -0
  54. data/app/models/scimitar/schema/user.rb +52 -0
  55. data/app/models/scimitar/schema/vdtp.rb +18 -0
  56. data/app/models/scimitar/schema/x509_certificate.rb +22 -0
  57. data/app/models/scimitar/service_provider_configuration.rb +49 -0
  58. data/app/models/scimitar/supportable.rb +14 -0
  59. data/app/views/layouts/scimitar/application.html.erb +14 -0
  60. data/config/initializers/scimitar.rb +82 -0
  61. data/config/routes.rb +6 -0
  62. data/lib/scimitar.rb +23 -0
  63. data/lib/scimitar/engine.rb +63 -0
  64. data/lib/scimitar/version.rb +13 -0
  65. data/spec/apps/dummy/app/controllers/custom_destroy_mock_users_controller.rb +24 -0
  66. data/spec/apps/dummy/app/controllers/custom_request_verifiers_controller.rb +30 -0
  67. data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +13 -0
  68. data/spec/apps/dummy/app/controllers/mock_users_controller.rb +13 -0
  69. data/spec/apps/dummy/app/models/mock_group.rb +83 -0
  70. data/spec/apps/dummy/app/models/mock_user.rb +104 -0
  71. data/spec/apps/dummy/config/application.rb +17 -0
  72. data/spec/apps/dummy/config/boot.rb +2 -0
  73. data/spec/apps/dummy/config/environment.rb +2 -0
  74. data/spec/apps/dummy/config/environments/test.rb +15 -0
  75. data/spec/apps/dummy/config/initializers/cookies_serializer.rb +3 -0
  76. data/spec/apps/dummy/config/initializers/scimitar.rb +14 -0
  77. data/spec/apps/dummy/config/initializers/session_store.rb +3 -0
  78. data/spec/apps/dummy/config/routes.rb +24 -0
  79. data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +15 -0
  80. data/spec/apps/dummy/db/migrate/20210308020313_create_mock_groups.rb +10 -0
  81. data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +8 -0
  82. data/spec/apps/dummy/db/schema.rb +42 -0
  83. data/spec/controllers/scimitar/application_controller_spec.rb +173 -0
  84. data/spec/controllers/scimitar/resource_types_controller_spec.rb +94 -0
  85. data/spec/controllers/scimitar/resources_controller_spec.rb +247 -0
  86. data/spec/controllers/scimitar/schemas_controller_spec.rb +75 -0
  87. data/spec/controllers/scimitar/service_provider_configurations_controller_spec.rb +22 -0
  88. data/spec/models/scimitar/complex_types/address_spec.rb +19 -0
  89. data/spec/models/scimitar/complex_types/email_spec.rb +23 -0
  90. data/spec/models/scimitar/lists/count_spec.rb +147 -0
  91. data/spec/models/scimitar/lists/query_parser_spec.rb +763 -0
  92. data/spec/models/scimitar/resource_type_spec.rb +21 -0
  93. data/spec/models/scimitar/resources/base_spec.rb +289 -0
  94. data/spec/models/scimitar/resources/base_validation_spec.rb +61 -0
  95. data/spec/models/scimitar/resources/mixin_spec.rb +2127 -0
  96. data/spec/models/scimitar/resources/user_spec.rb +55 -0
  97. data/spec/models/scimitar/schema/attribute_spec.rb +80 -0
  98. data/spec/models/scimitar/schema/base_spec.rb +64 -0
  99. data/spec/models/scimitar/schema/group_spec.rb +87 -0
  100. data/spec/models/scimitar/schema/user_spec.rb +710 -0
  101. data/spec/requests/active_record_backed_resources_controller_spec.rb +569 -0
  102. data/spec/requests/application_controller_spec.rb +49 -0
  103. data/spec/requests/controller_configuration_spec.rb +17 -0
  104. data/spec/requests/engine_spec.rb +20 -0
  105. data/spec/spec_helper.rb +66 -0
  106. metadata +315 -0
@@ -0,0 +1,569 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
4
+ before :each do
5
+ allow_any_instance_of(Scimitar::ApplicationController).to receive(:authenticated?).and_return(true)
6
+
7
+ @u1 = MockUser.create(username: '1', first_name: 'Foo', last_name: 'Ark', home_email_address: 'home_1@test.com')
8
+ @u2 = MockUser.create(username: '2', first_name: 'Foo', last_name: 'Bar', home_email_address: 'home_2@test.com')
9
+ @u3 = MockUser.create(username: '3', first_name: 'Foo', home_email_address: 'home_3@test.com')
10
+ end
11
+
12
+ # ===========================================================================
13
+
14
+ context '#index' do
15
+ context 'with no items' do
16
+ it 'returns empty list' do
17
+ MockUser.delete_all
18
+
19
+ expect_any_instance_of(MockUsersController).to receive(:index).once.and_call_original
20
+ get '/Users', params: { format: :scim }
21
+
22
+ expect(response.status).to eql(200)
23
+ result = JSON.parse(response.body)
24
+
25
+ expect(result['totalResults']).to eql(0)
26
+ expect(result['startIndex' ]).to eql(1)
27
+ expect(result['itemsPerPage']).to eql(100)
28
+ end
29
+ end # "context 'with no items' do"
30
+
31
+ context 'with items' do
32
+ it 'returns all items' do
33
+ get '/Users', params: { format: :scim }
34
+
35
+ expect(response.status).to eql(200)
36
+ result = JSON.parse(response.body)
37
+
38
+ expect(result['totalResults']).to eql(3)
39
+ expect(result['Resources'].size).to eql(3)
40
+
41
+ ids = result['Resources'].map { |resource| resource['id'] }
42
+ expect(ids).to match_array([@u1.id.to_s, @u2.id.to_s, @u3.id.to_s])
43
+
44
+ usernames = result['Resources'].map { |resource| resource['userName'] }
45
+ expect(usernames).to match_array(['1', '2', '3'])
46
+ end
47
+
48
+ it 'applies a filter, with case-insensitive value comparison' do
49
+ get '/Users', params: {
50
+ format: :scim,
51
+ filter: 'name.givenName eq "Foo" and name.familyName pr and emails ne "home_1@TEST.COM"'
52
+ }
53
+
54
+ expect(response.status).to eql(200)
55
+ result = JSON.parse(response.body)
56
+
57
+ expect(result['totalResults']).to eql(1)
58
+ expect(result['Resources'].size).to eql(1)
59
+
60
+ ids = result['Resources'].map { |resource| resource['id'] }
61
+ expect(ids).to match_array([@u2.id.to_s])
62
+
63
+ usernames = result['Resources'].map { |resource| resource['userName'] }
64
+ expect(usernames).to match_array(['2'])
65
+ end
66
+
67
+ it 'obeys a page size' do
68
+ get '/Users', params: {
69
+ format: :scim,
70
+ count: 2
71
+ }
72
+
73
+ expect(response.status).to eql(200)
74
+ result = JSON.parse(response.body)
75
+
76
+ expect(result['totalResults']).to eql(3)
77
+ expect(result['Resources'].size).to eql(2)
78
+
79
+ ids = result['Resources'].map { |resource| resource['id'] }
80
+ expect(ids).to match_array([@u1.id.to_s, @u2.id.to_s])
81
+
82
+ usernames = result['Resources'].map { |resource| resource['userName'] }
83
+ expect(usernames).to match_array(['1', '2'])
84
+ end
85
+
86
+ it 'obeys start-at-1 offsets' do
87
+ get '/Users', params: {
88
+ format: :scim,
89
+ startIndex: 2
90
+ }
91
+
92
+ expect(response.status).to eql(200)
93
+ result = JSON.parse(response.body)
94
+
95
+ expect(result['totalResults']).to eql(3)
96
+ expect(result['Resources'].size).to eql(2)
97
+
98
+ ids = result['Resources'].map { |resource| resource['id'] }
99
+ expect(ids).to match_array([@u2.id.to_s, @u3.id.to_s])
100
+
101
+ usernames = result['Resources'].map { |resource| resource['userName'] }
102
+ expect(usernames).to match_array(['2', '3'])
103
+ end
104
+ end # "context 'with items' do"
105
+
106
+ context 'with bad calls' do
107
+ it 'complains about bad filters' do
108
+ get '/Users', params: {
109
+ format: :scim,
110
+ filter: 'name.givenName'
111
+ }
112
+
113
+ expect(response.status).to eql(400)
114
+ result = JSON.parse(response.body)
115
+ expect(result['scimType']).to eql('invalidFilter')
116
+ end
117
+ end # "context 'with bad calls' do"
118
+ end # "context '#index' do"
119
+
120
+ # ===========================================================================
121
+
122
+ context '#show' do
123
+ it 'shows an item' do
124
+ expect_any_instance_of(MockUsersController).to receive(:show).once.and_call_original
125
+ get "/Users/#{@u2.id}", params: { format: :scim }
126
+
127
+ expect(response.status).to eql(200)
128
+ result = JSON.parse(response.body)
129
+
130
+ expect(result['id']).to eql(@u2.id.to_s) # Note - ID was converted String; not Integer
131
+ expect(result['userName']).to eql('2')
132
+ expect(result['name']['familyName']).to eql('Bar')
133
+ expect(result['meta']['resourceType']).to eql('User')
134
+ end
135
+
136
+ it 'renders 404' do
137
+ get '/Users/xyz', params: { format: :scim }
138
+
139
+ expect(response.status).to eql(404)
140
+ result = JSON.parse(response.body)
141
+ expect(result['status']).to eql('404')
142
+ end
143
+ end # "context '#show' do"
144
+
145
+ # ===========================================================================
146
+
147
+ context '#create' do
148
+ context 'creates an item' do
149
+ it 'with minimal parameters' do
150
+ mock_before = MockUser.all.to_a
151
+
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)
159
+
160
+ mock_after = MockUser.all.to_a
161
+ new_mock = (mock_after - mock_before).first
162
+
163
+ expect(response.status).to eql(201)
164
+ result = JSON.parse(response.body)
165
+
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
170
+
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
175
+
176
+ expect {
177
+ post "/Users", params: {
178
+ format: :scim,
179
+ userName: '4',
180
+ name: {
181
+ givenName: 'Given',
182
+ familyName: 'Family'
183
+ },
184
+ emails: [
185
+ {
186
+ type: 'work',
187
+ value: 'work_4@test.com'
188
+ },
189
+ {
190
+ type: 'home',
191
+ value: 'home_4@test.com'
192
+ }
193
+ ]
194
+ }
195
+ }.to change { MockUser.count }.by(1)
196
+
197
+ mock_after = MockUser.all.to_a
198
+ new_mock = (mock_after - mock_before).first
199
+
200
+ expect(response.status).to eql(201)
201
+ result = JSON.parse(response.body)
202
+
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
211
+ end
212
+
213
+ it 'returns 409 for duplicates (by Rails validation)' do
214
+ expect_any_instance_of(MockUsersController).to receive(:create).once.and_call_original
215
+ expect {
216
+ post "/Users", params: {
217
+ format: :scim,
218
+ userName: '1' # Already exists
219
+ }
220
+ }.to_not change { MockUser.count }
221
+
222
+ expect(response.status).to eql(409)
223
+ result = JSON.parse(response.body)
224
+ expect(result['scimType']).to eql('uniqueness')
225
+ expect(result['detail']).to include('already been taken')
226
+ end
227
+
228
+ it 'notes schema validation failures' do
229
+ expect {
230
+ post "/Users", params: {
231
+ format: :scim
232
+ # userName parameter is required by schema, but missing
233
+ }
234
+ }.to_not change { MockUser.count }
235
+
236
+ expect(response.status).to eql(400)
237
+ result = JSON.parse(response.body)
238
+ expect(result['scimType']).to eql('invalidValue')
239
+ expect(result['detail']).to include('is required')
240
+ end
241
+
242
+ it 'notes Rails validation failures' do
243
+ expect {
244
+ post "/Users", params: {
245
+ format: :scim,
246
+ userName: MockUser::INVALID_USERNAME
247
+ }
248
+ }.to_not change { MockUser.count }
249
+
250
+ expect(response.status).to eql(400)
251
+ result = JSON.parse(response.body)
252
+
253
+ expect(result['scimType']).to eql('invalidValue')
254
+ expect(result['detail']).to include('is reserved')
255
+ end
256
+ end # "context '#create' do"
257
+
258
+ # ===========================================================================
259
+
260
+ 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 }
269
+
270
+ expect(response.status).to eql(200)
271
+ result = JSON.parse(response.body)
272
+
273
+ expect(result['id']).to eql(@u2.id.to_s)
274
+ expect(result['meta']['resourceType']).to eql('User')
275
+
276
+ @u2.reload
277
+
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
283
+
284
+ it 'notes schema validation failures' do
285
+ expect {
286
+ put "/Users/#{@u2.id}", params: {
287
+ format: :scim
288
+ # userName parameter is required by schema, but missing
289
+ }
290
+ }.to_not change { MockUser.count }
291
+
292
+ expect(response.status).to eql(400)
293
+ result = JSON.parse(response.body)
294
+ expect(result['scimType']).to eql('invalidValue')
295
+ expect(result['detail']).to include('is required')
296
+
297
+ @u2.reload
298
+
299
+ expect(@u2.username).to eql('2')
300
+ expect(@u2.first_name).to eql('Foo')
301
+ expect(@u2.last_name).to eql('Bar')
302
+ expect(@u2.home_email_address).to eql('home_2@test.com')
303
+ end
304
+
305
+ it 'notes Rails validation failures' do
306
+ expect {
307
+ post "/Users", params: {
308
+ format: :scim,
309
+ userName: MockUser::INVALID_USERNAME
310
+ }
311
+ }.to_not change { MockUser.count }
312
+
313
+ expect(response.status).to eql(400)
314
+ result = JSON.parse(response.body)
315
+
316
+ expect(result['scimType']).to eql('invalidValue')
317
+ expect(result['detail']).to include('is reserved')
318
+
319
+ @u2.reload
320
+
321
+ expect(@u2.username).to eql('2')
322
+ expect(@u2.first_name).to eql('Foo')
323
+ expect(@u2.last_name).to eql('Bar')
324
+ expect(@u2.home_email_address).to eql('home_2@test.com')
325
+ end
326
+
327
+ it 'returns 404 if ID is invalid' do
328
+ expect {
329
+ put '/Users/xyz', params: {
330
+ format: :scim,
331
+ userName: '4' # Minimum required by schema
332
+ }
333
+ }.to_not change { MockUser.count }
334
+
335
+ expect(response.status).to eql(404)
336
+ result = JSON.parse(response.body)
337
+ expect(result['status']).to eql('404')
338
+ end
339
+ end # "context '#replace' do"
340
+
341
+ # ===========================================================================
342
+
343
+ 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,
349
+ Operations: [
350
+ {
351
+ op: 'add',
352
+ path: 'userName',
353
+ value: '4'
354
+ },
355
+ {
356
+ op: 'replace',
357
+ path: 'emails[type eq "work"]',
358
+ value: { type: 'work', value: 'work_4@test.com' }
359
+ }
360
+ ]
361
+ }
362
+ }.to_not change { MockUser.count }
363
+
364
+ expect(response.status).to eql(200)
365
+ result = JSON.parse(response.body)
366
+
367
+ expect(result['id']).to eql(@u2.id.to_s)
368
+ expect(result['meta']['resourceType']).to eql('User')
369
+
370
+ @u2.reload
371
+
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
378
+
379
+ context 'clears attributes' do
380
+ before :each do
381
+ @u2.update!(work_email_address: 'work_2@test.com')
382
+ end
383
+
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,
389
+ Operations: [
390
+ {
391
+ op: 'remove',
392
+ path: 'name.givenName'
393
+ }
394
+ ]
395
+ }
396
+ }.to_not change { MockUser.count }
397
+
398
+ expect(response.status).to eql(200)
399
+ result = JSON.parse(response.body)
400
+
401
+ expect(result['id']).to eql(@u2.id.to_s)
402
+ expect(result['meta']['resourceType']).to eql('User')
403
+
404
+ @u2.reload
405
+
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
412
+
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,
418
+ Operations: [
419
+ {
420
+ op: 'remove',
421
+ path: 'emails[type eq "work"]'
422
+ }
423
+ ]
424
+ }
425
+ }.to_not change { MockUser.count }
426
+
427
+ expect(response.status).to eql(200)
428
+ result = JSON.parse(response.body)
429
+
430
+ expect(result['id']).to eql(@u2.id.to_s)
431
+ expect(result['meta']['resourceType']).to eql('User')
432
+
433
+ @u2.reload
434
+
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
441
+
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,
447
+ Operations: [
448
+ {
449
+ op: 'remove',
450
+ path: 'emails'
451
+ }
452
+ ]
453
+ }
454
+ }.to_not change { MockUser.count }
455
+
456
+ expect(response.status).to eql(200)
457
+ result = JSON.parse(response.body)
458
+
459
+ expect(result['id']).to eql(@u2.id.to_s)
460
+ expect(result['meta']['resourceType']).to eql('User')
461
+
462
+ @u2.reload
463
+
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"
471
+
472
+ it 'notes Rails validation failures' do
473
+ expect {
474
+ patch "/Users/#{@u2.id}", params: {
475
+ format: :scim,
476
+ Operations: [
477
+ {
478
+ op: 'add',
479
+ path: 'userName',
480
+ value: MockUser::INVALID_USERNAME
481
+ }
482
+ ]
483
+ }
484
+ }.to_not change { MockUser.count }
485
+
486
+ expect(response.status).to eql(400)
487
+ result = JSON.parse(response.body)
488
+
489
+ expect(result['scimType']).to eql('invalidValue')
490
+ expect(result['detail']).to include('is reserved')
491
+
492
+ @u2.reload
493
+
494
+ expect(@u2.username).to eql('2')
495
+ expect(@u2.first_name).to eql('Foo')
496
+ expect(@u2.last_name).to eql('Bar')
497
+ expect(@u2.home_email_address).to eql('home_2@test.com')
498
+ end
499
+
500
+ it 'returns 404 if ID is invalid' do
501
+ expect {
502
+ patch '/Users/xyz', params: {
503
+ format: :scim,
504
+ Operations: [
505
+ {
506
+ op: 'add',
507
+ path: 'userName',
508
+ value: '4'
509
+ }
510
+ ]
511
+ }
512
+ }.to_not change { MockUser.count }
513
+
514
+ expect(response.status).to eql(404)
515
+ result = JSON.parse(response.body)
516
+ expect(result['status']).to eql('404')
517
+ end
518
+ end # "context '#update' do"
519
+
520
+ # ===========================================================================
521
+
522
+ context '#destroy' do
523
+ it 'deletes an item if given no blok' do
524
+ expect_any_instance_of(MockUsersController).to receive(:destroy).once.and_call_original
525
+ expect_any_instance_of(MockUser).to receive(:destroy!).once.and_call_original
526
+ expect {
527
+ delete "/Users/#{@u2.id}", params: { format: :scim }
528
+ }.to change { MockUser.count }.by(-1)
529
+
530
+ expect(response.status).to eql(204)
531
+ expect(response.body).to be_empty
532
+ end
533
+
534
+ it 'invokes a block if given one' do
535
+ expect_any_instance_of(CustomDestroyMockUsersController).to receive(:destroy).once.and_call_original
536
+ expect_any_instance_of(MockUser).to_not receive(:destroy!)
537
+
538
+ expect {
539
+ delete "/CustomDestroyUsers/#{@u2.id}", params: { format: :scim }
540
+ }.to_not change { MockUser.count }
541
+
542
+ expect(response.status).to eql(204)
543
+ expect(response.body).to be_empty
544
+
545
+ @u2.reload
546
+ expect(@u2.username).to eql(CustomDestroyMockUsersController::NOT_REALLY_DELETED_USERNAME_INDICATOR)
547
+ end
548
+
549
+ it 'returns 404 if ID is invalid' do
550
+ expect {
551
+ delete '/Users/xyz', params: { format: :scim }
552
+ }.to_not change { MockUser.count }
553
+
554
+ expect(response.status).to eql(404)
555
+ result = JSON.parse(response.body)
556
+ expect(result['status']).to eql('404')
557
+ end
558
+ end # "context '#destroy' do"
559
+
560
+ # ===========================================================================
561
+
562
+ context 'service methods' do
563
+ context '#storage_scope' do
564
+ it 'raises "not implemented" to warn subclass authors' do
565
+ expect { described_class.new.send(:storage_scope) }.to raise_error(NotImplementedError)
566
+ end
567
+ end # "context '#storage_class' do"
568
+ end # "context 'service methods' do"
569
+ end