scimitar 1.8.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/scimitar/active_record_backed_resources_controller.rb +20 -94
  3. data/app/controllers/scimitar/application_controller.rb +13 -41
  4. data/app/controllers/scimitar/schemas_controller.rb +0 -5
  5. data/app/models/scimitar/complex_types/address.rb +6 -0
  6. data/app/models/scimitar/engine_configuration.rb +5 -13
  7. data/app/models/scimitar/error_response.rb +0 -12
  8. data/app/models/scimitar/lists/query_parser.rb +10 -25
  9. data/app/models/scimitar/resource_invalid_error.rb +1 -1
  10. data/app/models/scimitar/resources/base.rb +4 -17
  11. data/app/models/scimitar/resources/mixin.rb +42 -539
  12. data/app/models/scimitar/schema/address.rb +0 -1
  13. data/app/models/scimitar/schema/attribute.rb +5 -14
  14. data/app/models/scimitar/schema/base.rb +1 -1
  15. data/app/models/scimitar/schema/vdtp.rb +1 -1
  16. data/app/models/scimitar/service_provider_configuration.rb +3 -14
  17. data/config/initializers/scimitar.rb +3 -28
  18. data/lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb +10 -140
  19. data/lib/scimitar/version.rb +2 -2
  20. data/lib/scimitar.rb +2 -7
  21. data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +1 -1
  22. data/spec/apps/dummy/app/models/mock_group.rb +1 -1
  23. data/spec/apps/dummy/app/models/mock_user.rb +8 -36
  24. data/spec/apps/dummy/config/application.rb +1 -0
  25. data/spec/apps/dummy/config/environments/test.rb +28 -5
  26. data/spec/apps/dummy/config/initializers/scimitar.rb +10 -61
  27. data/spec/apps/dummy/config/routes.rb +7 -28
  28. data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +1 -10
  29. data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +3 -8
  30. data/spec/apps/dummy/db/schema.rb +4 -11
  31. data/spec/controllers/scimitar/application_controller_spec.rb +3 -126
  32. data/spec/controllers/scimitar/resource_types_controller_spec.rb +2 -2
  33. data/spec/controllers/scimitar/schemas_controller_spec.rb +2 -10
  34. data/spec/models/scimitar/complex_types/address_spec.rb +4 -3
  35. data/spec/models/scimitar/complex_types/email_spec.rb +2 -0
  36. data/spec/models/scimitar/lists/query_parser_spec.rb +9 -76
  37. data/spec/models/scimitar/resources/base_spec.rb +70 -216
  38. data/spec/models/scimitar/resources/base_validation_spec.rb +2 -27
  39. data/spec/models/scimitar/resources/mixin_spec.rb +129 -1447
  40. data/spec/models/scimitar/schema/attribute_spec.rb +3 -22
  41. data/spec/models/scimitar/schema/base_spec.rb +1 -1
  42. data/spec/models/scimitar/schema/user_spec.rb +0 -10
  43. data/spec/requests/active_record_backed_resources_controller_spec.rb +68 -787
  44. data/spec/requests/application_controller_spec.rb +3 -16
  45. data/spec/spec_helper.rb +0 -8
  46. data/spec/support/hash_with_indifferent_case_insensitive_access_spec.rb +0 -108
  47. metadata +14 -25
  48. data/LICENSE.txt +0 -21
  49. data/README.md +0 -710
  50. data/lib/scimitar/support/utilities.rb +0 -51
  51. data/spec/apps/dummy/app/controllers/custom_create_mock_users_controller.rb +0 -25
  52. data/spec/apps/dummy/app/controllers/custom_replace_mock_users_controller.rb +0 -25
  53. data/spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb +0 -24
  54. data/spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb +0 -25
@@ -1,42 +1,25 @@
1
1
  require 'spec_helper'
2
- require 'time'
3
2
 
4
3
  RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
5
4
  before :each do
6
5
  allow_any_instance_of(Scimitar::ApplicationController).to receive(:authenticated?).and_return(true)
7
6
 
8
- # If a sort order is unspecified, the controller defaults to ID ascending.
9
- # With UUID based IDs, testing life is made easier by ensuring that the
10
- # creation order matches an ascending UUID sort order (which is what would
11
- # happen if we were using integer primary keys).
12
- #
13
- lmt = Time.parse("2023-01-09 14:25:00 +1300")
14
- ids = 3.times.map { SecureRandom.uuid }.sort()
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, password: 'oldpassword')
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
-
20
- @g1 = MockGroup.create!(display_name: 'Group 1')
21
- @g2 = MockGroup.create!(display_name: 'Group 2')
22
- @g3 = MockGroup.create!(display_name: 'Group 3')
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')
23
10
  end
24
11
 
25
12
  # ===========================================================================
26
13
 
27
14
  context '#index' do
28
15
  context 'with no items' do
29
- before :each do
16
+ it 'returns empty list' do
30
17
  MockUser.delete_all
31
- end
32
18
 
33
- it 'returns empty list' do
34
19
  expect_any_instance_of(MockUsersController).to receive(:index).once.and_call_original
35
20
  get '/Users', params: { format: :scim }
36
21
 
37
- expect(response.status ).to eql(200)
38
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
39
-
22
+ expect(response.status).to eql(200)
40
23
  result = JSON.parse(response.body)
41
24
 
42
25
  expect(result['totalResults']).to eql(0)
@@ -46,172 +29,55 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
46
29
  end # "context 'with no items' do"
47
30
 
48
31
  context 'with items' do
49
- context 'with a UUID, renamed primary key column' do
50
- it 'returns all items' do
51
- get '/Users', params: { format: :scim }
52
-
53
- expect(response.status ).to eql(200)
54
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
55
-
56
- result = JSON.parse(response.body)
57
-
58
- expect(result['totalResults']).to eql(3)
59
- expect(result['Resources'].size).to eql(3)
60
-
61
- ids = result['Resources'].map { |resource| resource['id'] }
62
- expect(ids).to match_array([@u1.primary_key.to_s, @u2.primary_key.to_s, @u3.primary_key.to_s])
63
-
64
- usernames = result['Resources'].map { |resource| resource['userName'] }
65
- expect(usernames).to match_array(['1', '2', '3'])
66
- end
67
- end # "context 'with a UUID, renamed primary key column' do"
68
-
69
- context 'with an integer, conventionally named primary key column' do
70
- it 'returns all items' do
71
- get '/Groups', params: { format: :scim }
72
-
73
- expect(response.status ).to eql(200)
74
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
75
-
76
- result = JSON.parse(response.body)
77
-
78
- expect(result['totalResults']).to eql(3)
79
- expect(result['Resources'].size).to eql(3)
80
-
81
- ids = result['Resources'].map { |resource| resource['id'] }
82
- expect(ids).to match_array([@g1.id.to_s, @g2.id.to_s, @g3.id.to_s])
83
-
84
- usernames = result['Resources'].map { |resource| resource['displayName'] }
85
- expect(usernames).to match_array(['Group 1', 'Group 2', 'Group 3'])
86
- end
87
- end # "context 'with an integer, conventionally named primary key column' do"
88
-
89
- it 'applies a filter, with case-insensitive value comparison' do
90
- get '/Users', params: {
91
- format: :scim,
92
- filter: 'name.givenName eq "FOO" and name.familyName pr and emails ne "home_1@test.com"'
93
- }
94
-
95
- expect(response.status ).to eql(200)
96
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
32
+ it 'returns all items' do
33
+ get '/Users', params: { format: :scim }
97
34
 
35
+ expect(response.status).to eql(200)
98
36
  result = JSON.parse(response.body)
99
37
 
100
- expect(result['totalResults']).to eql(1)
101
- expect(result['Resources'].size).to eql(1)
38
+ expect(result['totalResults']).to eql(3)
39
+ expect(result['Resources'].size).to eql(3)
102
40
 
103
41
  ids = result['Resources'].map { |resource| resource['id'] }
104
- expect(ids).to match_array([@u2.primary_key.to_s])
42
+ expect(ids).to match_array([@u1.id.to_s, @u2.id.to_s, @u3.id.to_s])
105
43
 
106
44
  usernames = result['Resources'].map { |resource| resource['userName'] }
107
- expect(usernames).to match_array(['2'])
45
+ expect(usernames).to match_array(['1', '2', '3'])
108
46
  end
109
47
 
110
- it 'applies a filter, with case-insensitive attribute matching (GitHub issue #37)' do
48
+ it 'applies a filter, with case-insensitive value comparison' do
111
49
  get '/Users', params: {
112
50
  format: :scim,
113
- filter: 'name.GIVENNAME eq "Foo" and name.Familyname pr and emails ne "home_1@test.com"'
51
+ filter: 'name.givenName eq "Foo" and name.familyName pr and emails ne "home_1@TEST.COM"'
114
52
  }
115
53
 
116
- expect(response.status ).to eql(200)
117
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
118
-
54
+ expect(response.status).to eql(200)
119
55
  result = JSON.parse(response.body)
120
56
 
121
57
  expect(result['totalResults']).to eql(1)
122
58
  expect(result['Resources'].size).to eql(1)
123
59
 
124
60
  ids = result['Resources'].map { |resource| resource['id'] }
125
- expect(ids).to match_array([@u2.primary_key.to_s])
61
+ expect(ids).to match_array([@u2.id.to_s])
126
62
 
127
63
  usernames = result['Resources'].map { |resource| resource['userName'] }
128
64
  expect(usernames).to match_array(['2'])
129
65
  end
130
66
 
131
- # Strange attribute capitalisation in tests here builds on test coverage
132
- # for now-fixed GitHub issue #37.
133
- #
134
- context '"meta" / IDs (GitHub issue #36)' do
135
- it 'applies a filter on primary keys, using direct comparison (rather than e.g. case-insensitive operators)' do
136
- get '/Users', params: {
137
- format: :scim,
138
- filter: "id eq \"#{@u3.primary_key}\""
139
- }
140
-
141
- expect(response.status ).to eql(200)
142
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
143
-
144
- result = JSON.parse(response.body)
145
-
146
- expect(result['totalResults']).to eql(1)
147
- expect(result['Resources'].size).to eql(1)
148
-
149
- ids = result['Resources'].map { |resource| resource['id'] }
150
- expect(ids).to match_array([@u3.primary_key.to_s])
151
-
152
- usernames = result['Resources'].map { |resource| resource['userName'] }
153
- expect(usernames).to match_array(['3'])
154
- end
155
-
156
- it 'applies a filter on external IDs, using direct comparison' do
157
- get '/Users', params: {
158
- format: :scim,
159
- filter: "externalID eq \"#{@u2.scim_uid}\""
160
- }
161
-
162
- expect(response.status ).to eql(200)
163
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
164
-
165
- result = JSON.parse(response.body)
166
-
167
- expect(result['totalResults']).to eql(1)
168
- expect(result['Resources'].size).to eql(1)
169
-
170
- ids = result['Resources'].map { |resource| resource['id'] }
171
- expect(ids).to match_array([@u2.primary_key.to_s])
172
-
173
- usernames = result['Resources'].map { |resource| resource['userName'] }
174
- expect(usernames).to match_array(['2'])
175
- end
176
-
177
- it 'applies a filter on "meta" entries, using direct comparison' do
178
- get '/Users', params: {
179
- format: :scim,
180
- filter: "Meta.LastModified eq \"#{@u3.updated_at}\""
181
- }
182
-
183
- expect(response.status ).to eql(200)
184
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
185
-
186
- result = JSON.parse(response.body)
187
-
188
- expect(result['totalResults']).to eql(1)
189
- expect(result['Resources'].size).to eql(1)
190
-
191
- ids = result['Resources'].map { |resource| resource['id'] }
192
- expect(ids).to match_array([@u3.primary_key.to_s])
193
-
194
- usernames = result['Resources'].map { |resource| resource['userName'] }
195
- expect(usernames).to match_array(['3'])
196
- end
197
- end # "context '"meta" / IDs (GitHub issue #36)' do"
198
-
199
67
  it 'obeys a page size' do
200
68
  get '/Users', params: {
201
69
  format: :scim,
202
70
  count: 2
203
71
  }
204
72
 
205
- expect(response.status ).to eql(200)
206
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
207
-
73
+ expect(response.status).to eql(200)
208
74
  result = JSON.parse(response.body)
209
75
 
210
76
  expect(result['totalResults']).to eql(3)
211
77
  expect(result['Resources'].size).to eql(2)
212
78
 
213
79
  ids = result['Resources'].map { |resource| resource['id'] }
214
- expect(ids).to match_array([@u1.primary_key.to_s, @u2.primary_key.to_s])
80
+ expect(ids).to match_array([@u1.id.to_s, @u2.id.to_s])
215
81
 
216
82
  usernames = result['Resources'].map { |resource| resource['userName'] }
217
83
  expect(usernames).to match_array(['1', '2'])
@@ -223,16 +89,14 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
223
89
  startIndex: 2
224
90
  }
225
91
 
226
- expect(response.status ).to eql(200)
227
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
228
-
92
+ expect(response.status).to eql(200)
229
93
  result = JSON.parse(response.body)
230
94
 
231
95
  expect(result['totalResults']).to eql(3)
232
96
  expect(result['Resources'].size).to eql(2)
233
97
 
234
98
  ids = result['Resources'].map { |resource| resource['id'] }
235
- expect(ids).to match_array([@u2.primary_key.to_s, @u3.primary_key.to_s])
99
+ expect(ids).to match_array([@u2.id.to_s, @u3.id.to_s])
236
100
 
237
101
  usernames = result['Resources'].map { |resource| resource['userName'] }
238
102
  expect(usernames).to match_array(['2', '3'])
@@ -246,11 +110,8 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
246
110
  filter: 'name.givenName'
247
111
  }
248
112
 
249
- expect(response.status ).to eql(400)
250
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
251
-
113
+ expect(response.status).to eql(400)
252
114
  result = JSON.parse(response.body)
253
-
254
115
  expect(result['scimType']).to eql('invalidFilter')
255
116
  end
256
117
  end # "context 'with bad calls' do"
@@ -259,47 +120,24 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
259
120
  # ===========================================================================
260
121
 
261
122
  context '#show' do
262
- context 'with a UUID, renamed primary key column' do
263
- it 'shows an item' do
264
- expect_any_instance_of(MockUsersController).to receive(:show).once.and_call_original
265
- get "/Users/#{@u2.primary_key}", params: { format: :scim }
266
-
267
- expect(response.status ).to eql(200)
268
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
269
-
270
- result = JSON.parse(response.body)
271
-
272
- expect(result['id']).to eql(@u2.primary_key.to_s)
273
- expect(result['userName']).to eql('2')
274
- expect(result['name']['familyName']).to eql('Bar')
275
- expect(result['meta']['resourceType']).to eql('User')
276
- end
277
- end # "context 'with a UUID, renamed primary key column' do"
278
-
279
- context 'with an integer, conventionally named primary key column' do
280
- it 'shows an item' do
281
- expect_any_instance_of(MockGroupsController).to receive(:show).once.and_call_original
282
- get "/Groups/#{@g2.id}", params: { format: :scim }
283
-
284
- expect(response.status ).to eql(200)
285
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
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 }
286
126
 
287
- result = JSON.parse(response.body)
127
+ expect(response.status).to eql(200)
128
+ result = JSON.parse(response.body)
288
129
 
289
- expect(result['id']).to eql(@g2.id.to_s) # Note - ID was converted String; not Integer
290
- expect(result['displayName']).to eql('Group 2')
291
- expect(result['meta']['resourceType']).to eql('Group')
292
- end
293
- end # "context 'with an integer, conventionally named primary key column' do"
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
294
135
 
295
136
  it 'renders 404' do
296
137
  get '/Users/xyz', params: { format: :scim }
297
138
 
298
- expect(response.status ).to eql(404)
299
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
300
-
139
+ expect(response.status).to eql(404)
301
140
  result = JSON.parse(response.body)
302
-
303
141
  expect(result['status']).to eql('404')
304
142
  end
305
143
  end # "context '#show' do"
@@ -312,15 +150,10 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
312
150
  it 'with minimal parameters' do
313
151
  mock_before = MockUser.all.to_a
314
152
 
315
- attributes = { userName: '4' } # Minimum required by schema
153
+ attributes = { userName: '4' } # Minimum required by schema
316
154
  attributes = spec_helper_hupcase(attributes) if force_upper_case
317
155
 
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
- #
321
156
  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
-
324
157
  expect {
325
158
  post "/Users", params: attributes.merge(format: :scim)
326
159
  }.to change { MockUser.count }.by(1)
@@ -328,12 +161,10 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
328
161
  mock_after = MockUser.all.to_a
329
162
  new_mock = (mock_after - mock_before).first
330
163
 
331
- expect(response.status ).to eql(201)
332
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
333
-
164
+ expect(response.status).to eql(201)
334
165
  result = JSON.parse(response.body)
335
166
 
336
- expect(result['id']).to eql(new_mock.primary_key.to_s)
167
+ expect(result['id']).to eql(new_mock.id.to_s)
337
168
  expect(result['meta']['resourceType']).to eql('User')
338
169
  expect(new_mock.username).to eql('4')
339
170
  end
@@ -345,12 +176,10 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
345
176
 
346
177
  attributes = {
347
178
  userName: '4',
348
- password: 'correcthorsebatterystaple',
349
179
  name: {
350
180
  givenName: 'Given',
351
181
  familyName: 'Family'
352
182
  },
353
- meta: { resourceType: 'User' },
354
183
  emails: [
355
184
  {
356
185
  type: 'work',
@@ -372,15 +201,12 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
372
201
  mock_after = MockUser.all.to_a
373
202
  new_mock = (mock_after - mock_before).first
374
203
 
375
- expect(response.status ).to eql(201)
376
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
377
-
204
+ expect(response.status).to eql(201)
378
205
  result = JSON.parse(response.body)
379
206
 
380
207
  expect(result['id']).to eql(new_mock.id.to_s)
381
208
  expect(result['meta']['resourceType']).to eql('User')
382
209
  expect(new_mock.username).to eql('4')
383
- expect(new_mock.password).to eql('correcthorsebatterystaple')
384
210
  expect(new_mock.first_name).to eql('Given')
385
211
  expect(new_mock.last_name).to eql('Family')
386
212
  expect(new_mock.home_email_address).to eql('home_4@test.com')
@@ -406,11 +232,8 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
406
232
  }
407
233
  }.to_not change { MockUser.count }
408
234
 
409
- expect(response.status ).to eql(409)
410
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
411
-
235
+ expect(response.status).to eql(409)
412
236
  result = JSON.parse(response.body)
413
-
414
237
  expect(result['scimType']).to eql('uniqueness')
415
238
  expect(result['detail']).to include('already been taken')
416
239
  end
@@ -423,11 +246,8 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
423
246
  }
424
247
  }.to_not change { MockUser.count }
425
248
 
426
- expect(response.status ).to eql(400)
427
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
428
-
249
+ expect(response.status).to eql(400)
429
250
  result = JSON.parse(response.body)
430
-
431
251
  expect(result['scimType']).to eql('invalidValue')
432
252
  expect(result['detail']).to include('is required')
433
253
  end
@@ -440,84 +260,12 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
440
260
  }
441
261
  }.to_not change { MockUser.count }
442
262
 
443
- expect(response.status ).to eql(400)
444
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
445
-
263
+ expect(response.status).to eql(400)
446
264
  result = JSON.parse(response.body)
447
265
 
448
266
  expect(result['scimType']).to eql('invalidValue')
449
267
  expect(result['detail']).to include('is reserved')
450
268
  end
451
-
452
- context 'with a block' do
453
- it 'invokes the block' do
454
- mock_before = MockUser.all.to_a
455
-
456
- expect_any_instance_of(CustomCreateMockUsersController).to receive(:create).once.and_call_original
457
- expect {
458
- post "/CustomCreateUsers", params: {
459
- format: :scim,
460
- userName: '4' # Minimum required by schema
461
- }
462
- }.to change { MockUser.count }.by(1)
463
-
464
- mock_after = MockUser.all.to_a
465
- new_mock = (mock_after - mock_before).first
466
-
467
- expect(response.status ).to eql(201)
468
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
469
-
470
- result = JSON.parse(response.body)
471
-
472
- expect(result['id']).to eql(new_mock.id.to_s)
473
- expect(result['meta']['resourceType']).to eql('User')
474
- expect(new_mock.first_name).to eql(CustomCreateMockUsersController::OVERRIDDEN_NAME)
475
- end
476
-
477
- it 'returns 409 for duplicates (by Rails validation)' do
478
- existing_user = MockUser.create!(
479
- username: '4',
480
- first_name: 'Will Be Overridden',
481
- last_name: 'Baz',
482
- home_email_address: 'random@test.com',
483
- scim_uid: '999'
484
- )
485
-
486
- expect_any_instance_of(CustomCreateMockUsersController).to receive(:create).once.and_call_original
487
- expect {
488
- post "/CustomCreateUsers", params: {
489
- format: :scim,
490
- userName: '4' # Already exists
491
- }
492
- }.to_not change { MockUser.count }
493
-
494
- expect(response.status ).to eql(409)
495
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
496
-
497
- result = JSON.parse(response.body)
498
-
499
- expect(result['scimType']).to eql('uniqueness')
500
- expect(result['detail']).to include('already been taken')
501
- end
502
-
503
- it 'notes Rails validation failures' do
504
- expect_any_instance_of(CustomCreateMockUsersController).to receive(:create).once.and_call_original
505
- expect {
506
- post "/CustomCreateUsers", params: {
507
- format: :scim,
508
- userName: MockUser::INVALID_USERNAME
509
- }
510
- }.to_not change { MockUser.count }
511
-
512
- expect(response.status ).to eql(400)
513
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
514
-
515
- result = JSON.parse(response.body)
516
-
517
- expect(result['scimType']).to eql('invalidValue')
518
- expect(result['detail']).to include('is reserved')
519
- end
520
- end # "context 'with a block' do"
521
269
  end # "context '#create' do"
522
270
 
523
271
  # ===========================================================================
@@ -525,64 +273,26 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
525
273
  context '#replace' do
526
274
  shared_examples 'a replacer' do | force_upper_case: |
527
275
  it 'which replaces all attributes in an instance' do
528
- attributes = { userName: '4' } # Minimum required by schema
276
+ attributes = { userName: '4' } # Minimum required by schema
529
277
  attributes = spec_helper_hupcase(attributes) if force_upper_case
530
278
 
531
- # Prove that certain known pathways are called; can then unit test
532
- # those if need be and be sure that this covers #replace actions.
533
- #
534
279
  expect_any_instance_of(MockUsersController).to receive(:replace).once.and_call_original
535
- expect_any_instance_of(MockUsersController).to receive(:save! ).once.and_call_original
536
280
  expect {
537
- put "/Users/#{@u2.primary_key}", params: attributes.merge(format: :scim)
281
+ put "/Users/#{@u2.id}", params: attributes.merge(format: :scim)
538
282
  }.to_not change { MockUser.count }
539
283
 
540
- expect(response.status ).to eql(200)
541
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
542
-
543
- result = JSON.parse(response.body)
544
-
545
- expect(result['id']).to eql(@u2.primary_key.to_s)
546
- expect(result['meta']['resourceType']).to eql('User')
547
-
548
- expect(result).to have_key('name')
549
- expect(result).to_not have_key('password')
550
-
551
- @u2.reload
552
-
553
- expect(@u2.username).to eql('4')
554
- expect(@u2.first_name).to be_nil
555
- expect(@u2.last_name).to be_nil
556
- expect(@u2.home_email_address).to be_nil
557
- expect(@u2.password).to be_nil
558
- end
559
-
560
- it 'can replace passwords' do
561
- attributes = { userName: '4', password: 'correcthorsebatterystaple' }
562
- attributes = spec_helper_hupcase(attributes) if force_upper_case
563
-
564
- expect {
565
- put "/Users/#{@u2.primary_key}", params: attributes.merge(format: :scim)
566
- }.to_not change { MockUser.count }
567
-
568
- expect(response.status ).to eql(200)
569
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
570
-
284
+ expect(response.status).to eql(200)
571
285
  result = JSON.parse(response.body)
572
286
 
573
- expect(result['id']).to eql(@u2.primary_key.to_s)
287
+ expect(result['id']).to eql(@u2.id.to_s)
574
288
  expect(result['meta']['resourceType']).to eql('User')
575
289
 
576
- expect(result).to have_key('name')
577
- expect(result).to_not have_key('password')
578
-
579
290
  @u2.reload
580
291
 
581
292
  expect(@u2.username).to eql('4')
582
293
  expect(@u2.first_name).to be_nil
583
294
  expect(@u2.last_name).to be_nil
584
295
  expect(@u2.home_email_address).to be_nil
585
- expect(@u2.password).to eql('correcthorsebatterystaple')
586
296
  end
587
297
  end # "shared_examples 'a replacer' do | force_upper_case: |"
588
298
 
@@ -596,17 +306,14 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
596
306
 
597
307
  it 'notes schema validation failures' do
598
308
  expect {
599
- put "/Users/#{@u2.primary_key}", params: {
309
+ put "/Users/#{@u2.id}", params: {
600
310
  format: :scim
601
311
  # userName parameter is required by schema, but missing
602
312
  }
603
313
  }.to_not change { MockUser.count }
604
314
 
605
- expect(response.status ).to eql(400)
606
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
607
-
315
+ expect(response.status).to eql(400)
608
316
  result = JSON.parse(response.body)
609
-
610
317
  expect(result['scimType']).to eql('invalidValue')
611
318
  expect(result['detail']).to include('is required')
612
319
 
@@ -620,15 +327,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
620
327
 
621
328
  it 'notes Rails validation failures' do
622
329
  expect {
623
- put "/Users/#{@u2.primary_key}", params: {
330
+ post "/Users", params: {
624
331
  format: :scim,
625
332
  userName: MockUser::INVALID_USERNAME
626
333
  }
627
334
  }.to_not change { MockUser.count }
628
335
 
629
- expect(response.status ).to eql(400)
630
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
631
-
336
+ expect(response.status).to eql(400)
632
337
  result = JSON.parse(response.body)
633
338
 
634
339
  expect(result['scimType']).to eql('invalidValue')
@@ -650,72 +355,17 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
650
355
  }
651
356
  }.to_not change { MockUser.count }
652
357
 
653
- expect(response.status ).to eql(404)
654
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
655
-
358
+ expect(response.status).to eql(404)
656
359
  result = JSON.parse(response.body)
657
-
658
360
  expect(result['status']).to eql('404')
659
361
  end
660
-
661
- context 'with a block' do
662
- it 'invokes the block' do
663
- attributes = { userName: '4' } # Minimum required by schema
664
-
665
- expect_any_instance_of(CustomReplaceMockUsersController).to receive(:replace).once.and_call_original
666
- expect {
667
- put "/CustomReplaceUsers/#{@u2.primary_key}", params: {
668
- format: :scim,
669
- userName: '4'
670
- }
671
- }.to_not change { MockUser.count }
672
-
673
- expect(response.status ).to eql(200)
674
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
675
-
676
- result = JSON.parse(response.body)
677
-
678
- expect(result['id']).to eql(@u2.primary_key.to_s)
679
- expect(result['meta']['resourceType']).to eql('User')
680
-
681
- @u2.reload
682
-
683
- expect(@u2.username ).to eql('4')
684
- expect(@u2.first_name).to eql(CustomReplaceMockUsersController::OVERRIDDEN_NAME)
685
- end
686
-
687
- it 'notes Rails validation failures' do
688
- expect_any_instance_of(CustomReplaceMockUsersController).to receive(:replace).once.and_call_original
689
- expect {
690
- put "/CustomReplaceUsers/#{@u2.primary_key}", params: {
691
- format: :scim,
692
- userName: MockUser::INVALID_USERNAME
693
- }
694
- }.to_not change { MockUser.count }
695
-
696
- expect(response.status ).to eql(400)
697
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
698
-
699
- result = JSON.parse(response.body)
700
-
701
- expect(result['scimType']).to eql('invalidValue')
702
- expect(result['detail']).to include('is reserved')
703
-
704
- @u2.reload
705
-
706
- expect(@u2.username).to eql('2')
707
- expect(@u2.first_name).to eql('Foo')
708
- expect(@u2.last_name).to eql('Bar')
709
- expect(@u2.home_email_address).to eql('home_2@test.com')
710
- end
711
- end # "context 'with a block' do"
712
362
  end # "context '#replace' do"
713
363
 
714
364
  # ===========================================================================
715
365
 
716
366
  context '#update' do
717
367
  shared_examples 'an updater' do | force_upper_case: |
718
- it 'which patches regular attributes' do
368
+ it 'which patches specific attributes' do
719
369
  payload = {
720
370
  Operations: [
721
371
  {
@@ -733,27 +383,17 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
733
383
 
734
384
  payload = spec_helper_hupcase(payload) if force_upper_case
735
385
 
736
- # Prove that certain known pathways are called; can then unit test
737
- # those if need be and be sure that this covers #update actions.
738
- #
739
386
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
740
- expect_any_instance_of(MockUsersController).to receive(:save! ).once.and_call_original
741
-
742
387
  expect {
743
- patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
388
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
744
389
  }.to_not change { MockUser.count }
745
390
 
746
- expect(response.status ).to eql(200)
747
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
748
-
391
+ expect(response.status).to eql(200)
749
392
  result = JSON.parse(response.body)
750
393
 
751
- expect(result['id']).to eql(@u2.primary_key.to_s)
394
+ expect(result['id']).to eql(@u2.id.to_s)
752
395
  expect(result['meta']['resourceType']).to eql('User')
753
396
 
754
- expect(result).to have_key('name')
755
- expect(result).to_not have_key('password')
756
-
757
397
  @u2.reload
758
398
 
759
399
  expect(@u2.username).to eql('4')
@@ -761,45 +401,6 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
761
401
  expect(@u2.last_name).to eql('Bar')
762
402
  expect(@u2.home_email_address).to eql('home_2@test.com')
763
403
  expect(@u2.work_email_address).to eql('work_4@test.com')
764
- expect(@u2.password).to eql('oldpassword')
765
- end
766
-
767
- it 'which patches "returned: \'never\'" fields' do
768
- payload = {
769
- Operations: [
770
- {
771
- op: 'replace',
772
- path: 'password',
773
- value: 'correcthorsebatterystaple'
774
- }
775
- ]
776
- }
777
-
778
- payload = spec_helper_hupcase(payload) if force_upper_case
779
-
780
- expect {
781
- patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
782
- }.to_not change { MockUser.count }
783
-
784
- expect(response.status ).to eql(200)
785
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
786
-
787
- result = JSON.parse(response.body)
788
-
789
- expect(result['id']).to eql(@u2.primary_key.to_s)
790
- expect(result['meta']['resourceType']).to eql('User')
791
-
792
- expect(result).to have_key('name')
793
- expect(result).to_not have_key('password')
794
-
795
- @u2.reload
796
-
797
- expect(@u2.username).to eql('2')
798
- expect(@u2.first_name).to eql('Foo')
799
- expect(@u2.last_name).to eql('Bar')
800
- expect(@u2.home_email_address).to eql('home_2@test.com')
801
- expect(@u2.work_email_address).to be_nil
802
- expect(@u2.password).to eql('correcthorsebatterystaple')
803
404
  end
804
405
 
805
406
  context 'which clears attributes' do
@@ -821,15 +422,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
821
422
 
822
423
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
823
424
  expect {
824
- patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
425
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
825
426
  }.to_not change { MockUser.count }
826
427
 
827
- expect(response.status ).to eql(200)
828
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
829
-
428
+ expect(response.status).to eql(200)
830
429
  result = JSON.parse(response.body)
831
430
 
832
- expect(result['id']).to eql(@u2.primary_key.to_s)
431
+ expect(result['id']).to eql(@u2.id.to_s)
833
432
  expect(result['meta']['resourceType']).to eql('User')
834
433
 
835
434
  @u2.reload
@@ -855,15 +454,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
855
454
 
856
455
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
857
456
  expect {
858
- patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
457
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
859
458
  }.to_not change { MockUser.count }
860
459
 
861
- expect(response.status ).to eql(200)
862
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
863
-
460
+ expect(response.status).to eql(200)
864
461
  result = JSON.parse(response.body)
865
462
 
866
- expect(result['id']).to eql(@u2.primary_key.to_s)
463
+ expect(result['id']).to eql(@u2.id.to_s)
867
464
  expect(result['meta']['resourceType']).to eql('User')
868
465
 
869
466
  @u2.reload
@@ -889,15 +486,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
889
486
 
890
487
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
891
488
  expect {
892
- patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
489
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
893
490
  }.to_not change { MockUser.count }
894
491
 
895
- expect(response.status ).to eql(200)
896
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
897
-
492
+ expect(response.status).to eql(200)
898
493
  result = JSON.parse(response.body)
899
494
 
900
- expect(result['id']).to eql(@u2.primary_key.to_s)
495
+ expect(result['id']).to eql(@u2.id.to_s)
901
496
  expect(result['meta']['resourceType']).to eql('User')
902
497
 
903
498
  @u2.reload
@@ -921,7 +516,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
921
516
 
922
517
  it 'notes Rails validation failures' do
923
518
  expect {
924
- patch "/Users/#{@u2.primary_key}", params: {
519
+ patch "/Users/#{@u2.id}", params: {
925
520
  format: :scim,
926
521
  Operations: [
927
522
  {
@@ -933,9 +528,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
933
528
  }
934
529
  }.to_not change { MockUser.count }
935
530
 
936
- expect(response.status ).to eql(400)
937
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
938
-
531
+ expect(response.status).to eql(400)
939
532
  result = JSON.parse(response.body)
940
533
 
941
534
  expect(result['scimType']).to eql('invalidValue')
@@ -963,329 +556,20 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
963
556
  }
964
557
  }.to_not change { MockUser.count }
965
558
 
966
- expect(response.status ).to eql(404)
967
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
968
-
559
+ expect(response.status).to eql(404)
969
560
  result = JSON.parse(response.body)
970
-
971
561
  expect(result['status']).to eql('404')
972
562
  end
973
-
974
- context 'when removing users from groups' do
975
- before :each do
976
- @g1.mock_users << @u1
977
- @g1.mock_users << @u2
978
- @g1.mock_users << @u3
979
-
980
- # (Self-check) Verify group representation
981
- #
982
- get "/Groups/#{@g1.id}", params: { format: :scim }
983
-
984
- expect(response.status).to eql(200)
985
- result = JSON.parse(response.body)
986
-
987
- expect(result['members'].map { |m| m['value'] }.sort()).to eql(MockUser.pluck(:primary_key).sort())
988
- end
989
-
990
- it 'can remove all users' do
991
- expect {
992
- expect {
993
- patch "/Groups/#{@g1.id}", params: {
994
- format: :scim,
995
- Operations: [
996
- {
997
- op: 'remove',
998
- path: 'members'
999
- }
1000
- ]
1001
- }
1002
- }.to_not change { MockUser.count }
1003
- }.to_not change { MockGroup.count }
1004
-
1005
- get "/Groups/#{@g1.id}", params: { format: :scim }
1006
-
1007
- expect(response.status ).to eql(200)
1008
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
1009
-
1010
- result = JSON.parse(response.body)
1011
-
1012
- expect(result['members']).to be_empty
1013
- expect(@g1.reload().mock_users).to be_empty
1014
- end
1015
-
1016
- # Define via 'let':
1017
- #
1018
- # * Hash 'payload', to send via 'patch'
1019
- # * MockUser 'removed_user', which is the user that should be removed
1020
- #
1021
- shared_examples 'a user remover' do
1022
- it 'which removes the identified user' do
1023
- expect {
1024
- expect {
1025
- patch "/Groups/#{@g1.id}", params: payload()
1026
- }.to_not change { MockUser.count }
1027
- }.to_not change { MockGroup.count }
1028
-
1029
- expected_remaining_user_ids = MockUser
1030
- .where.not(primary_key: removed_user().id)
1031
- .pluck(:primary_key)
1032
- .sort()
1033
-
1034
- get "/Groups/#{@g1.id}", params: { format: :scim }
1035
-
1036
- expect(response.status ).to eql(200)
1037
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
1038
-
1039
- result = JSON.parse(response.body)
1040
-
1041
- expect(result['members'].map { |m| m['value'] }.sort()).to eql(expected_remaining_user_ids)
1042
- expect(@g1.reload().mock_users.map(&:primary_key).sort()).to eql(expected_remaining_user_ids)
1043
- end
1044
- end
1045
-
1046
- # https://tools.ietf.org/html/rfc7644#section-3.5.2.2
1047
- #
1048
- context 'and using an RFC-compliant payload' do
1049
- let(:removed_user) { @u2 }
1050
- let(:payload) do
1051
- {
1052
- format: :scim,
1053
- Operations: [
1054
- {
1055
- op: 'remove',
1056
- path: "members[value eq \"#{removed_user().primary_key}\"]",
1057
- }
1058
- ]
1059
- }
1060
- end
1061
-
1062
- it_behaves_like 'a user remover'
1063
- end # context 'and using an RFC-compliant payload' do
1064
-
1065
- # https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups#update-group-remove-members
1066
- #
1067
- context 'and using a Microsoft variant payload' do
1068
- let(:removed_user) { @u2 }
1069
- let(:payload) do
1070
- {
1071
- format: :scim,
1072
- Operations: [
1073
- {
1074
- op: 'remove',
1075
- path: 'members',
1076
- value: [{
1077
- '$ref' => nil,
1078
- 'value' => removed_user().primary_key
1079
- }]
1080
- }
1081
- ]
1082
- }
1083
- end
1084
-
1085
- it_behaves_like 'a user remover'
1086
- end # context 'and using a Microsoft variant payload' do
1087
-
1088
- # https://help.salesforce.com/s/articleView?id=sf.identity_scim_manage_groups.htm&type=5
1089
- #
1090
- context 'and using a Salesforce variant payload' do
1091
- let(:removed_user) { @u2 }
1092
- let(:payload) do
1093
- {
1094
- format: :scim,
1095
- Operations: [
1096
- {
1097
- op: 'remove',
1098
- path: 'members',
1099
- value: {
1100
- 'members' => [{
1101
- '$ref' => nil,
1102
- 'value' => removed_user().primary_key
1103
- }]
1104
- }
1105
- }
1106
- ]
1107
- }
1108
- end
1109
-
1110
- it_behaves_like 'a user remover'
1111
- end # context 'and using a Salesforce variant payload' do
1112
- end # "context 'when removing users from groups' do"
1113
-
1114
- context 'with a block' do
1115
- it 'invokes the block' do
1116
- payload = {
1117
- format: :scim,
1118
- Operations: [
1119
- {
1120
- op: 'add',
1121
- path: 'userName',
1122
- value: '4'
1123
- },
1124
- {
1125
- op: 'replace',
1126
- path: 'emails[type eq "work"]',
1127
- value: { type: 'work', value: 'work_4@test.com' }
1128
- }
1129
- ]
1130
- }
1131
-
1132
- expect_any_instance_of(CustomUpdateMockUsersController).to receive(:update).once.and_call_original
1133
- expect {
1134
- patch "/CustomUpdateUsers/#{@u2.primary_key}", params: payload
1135
- }.to_not change { MockUser.count }
1136
-
1137
- expect(response.status ).to eql(200)
1138
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
1139
-
1140
- result = JSON.parse(response.body)
1141
-
1142
- expect(result['id']).to eql(@u2.primary_key.to_s)
1143
- expect(result['meta']['resourceType']).to eql('User')
1144
-
1145
- @u2.reload
1146
-
1147
- expect(@u2.username ).to eql('4')
1148
- expect(@u2.first_name ).to eql(CustomUpdateMockUsersController::OVERRIDDEN_NAME)
1149
- expect(@u2.work_email_address).to eql('work_4@test.com')
1150
- end
1151
-
1152
- it 'notes Rails validation failures' do
1153
- expect_any_instance_of(CustomUpdateMockUsersController).to receive(:update).once.and_call_original
1154
- expect {
1155
- patch "/CustomUpdateUsers/#{@u2.primary_key}", params: {
1156
- format: :scim,
1157
- Operations: [
1158
- {
1159
- op: 'add',
1160
- path: 'userName',
1161
- value: MockUser::INVALID_USERNAME
1162
- }
1163
- ]
1164
- }
1165
- }.to_not change { MockUser.count }
1166
-
1167
- expect(response.status ).to eql(400)
1168
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
1169
-
1170
- result = JSON.parse(response.body)
1171
-
1172
- expect(result['scimType']).to eql('invalidValue')
1173
- expect(result['detail']).to include('is reserved')
1174
-
1175
- @u2.reload
1176
-
1177
- expect(@u2.username).to eql('2')
1178
- expect(@u2.first_name).to eql('Foo')
1179
- expect(@u2.last_name).to eql('Bar')
1180
- expect(@u2.home_email_address).to eql('home_2@test.com')
1181
- end
1182
- end # "context 'with a block' do"
1183
563
  end # "context '#update' do"
1184
564
 
1185
- # ===========================================================================
1186
- # In-passing parts of tests above show that #create, #replace and #update all
1187
- # route through #save!, so now add some unit tests for that and for exception
1188
- # handling overrides invoked via #save!.
1189
- # ===========================================================================
1190
-
1191
- context 'overriding #save!' do
1192
- it 'invokes a block if given one' do
1193
- mock_before = MockUser.all.to_a
1194
- attributes = { userName: '5' } # Minimum required by schema
1195
-
1196
- expect_any_instance_of(CustomSaveMockUsersController).to receive(:create).once.and_call_original
1197
- expect {
1198
- post "/CustomSaveUsers", params: attributes.merge(format: :scim)
1199
- }.to change { MockUser.count }.by(1)
1200
-
1201
- mock_after = MockUser.all.to_a
1202
- new_mock = (mock_after - mock_before).first
1203
-
1204
- expect(response.status ).to eql(201)
1205
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
1206
-
1207
- expect(new_mock.username).to eql(CustomSaveMockUsersController::CUSTOM_SAVE_BLOCK_USERNAME_INDICATOR)
1208
- end
1209
- end # "context 'overriding #save!' do
1210
-
1211
- context 'custom on-save exceptions' do
1212
- MockUsersController.new.send(:scimitar_rescuable_exceptions).each do | exception_class |
1213
- it "handles out-of-box exception #{exception_class}" do
1214
- expect_any_instance_of(MockUsersController).to receive(:create).once.and_call_original
1215
- expect_any_instance_of(MockUsersController).to receive(:save! ).once.and_call_original
1216
-
1217
- expect_any_instance_of(MockUser).to receive(:save!).once { raise exception_class }
1218
-
1219
- expect {
1220
- post "/Users", params: { format: :scim, userName: SecureRandom.uuid }
1221
- }.to_not change { MockUser.count }
1222
-
1223
- expected_status, expected_prefix = if exception_class == ActiveRecord::RecordNotUnique
1224
- [409, 'Operation failed due to a uniqueness constraint: ']
1225
- else
1226
- [400, 'Operation failed since record has become invalid: ']
1227
- end
1228
-
1229
- expect(response.status ).to eql(expected_status)
1230
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
1231
-
1232
- result = JSON.parse(response.body)
1233
-
1234
- # Check basic SCIM error rendering - good enough given other tests
1235
- # elsewhere. Exact message varies by exception.
1236
- #
1237
- expect(result['detail']).to start_with(expected_prefix)
1238
- end
1239
- end
1240
-
1241
- it 'handles custom exceptions' do
1242
- exception_class = RuntimeError # (for testing only; usually, this would provoke a 500 response)
1243
-
1244
- expect_any_instance_of(MockUsersController).to receive(:create).once.and_call_original
1245
- expect_any_instance_of(MockUsersController).to receive(:save! ).once.and_call_original
1246
-
1247
- expect_any_instance_of(MockUsersController).to receive(:scimitar_rescuable_exceptions).once { [ exception_class ] }
1248
- expect_any_instance_of(MockUser ).to receive(:save! ).once { raise exception_class }
1249
-
1250
- expect {
1251
- post "/Users", params: { format: :scim, userName: SecureRandom.uuid }
1252
- }.to_not change { MockUser.count }
1253
-
1254
- expect(response.status ).to eql(400)
1255
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
1256
-
1257
- result = JSON.parse(response.body)
1258
-
1259
- expect(result['detail']).to start_with('Operation failed since record has become invalid: ')
1260
- end
1261
-
1262
- it 'reports other exceptions as 500s' do
1263
- expect_any_instance_of(MockUsersController).to receive(:create).once.and_call_original
1264
- expect_any_instance_of(MockUsersController).to receive(:save! ).once.and_call_original
1265
-
1266
- expect_any_instance_of(MockUser).to receive(:save!).once { raise RuntimeError }
1267
-
1268
- expect {
1269
- post "/Users", params: { format: :scim, userName: SecureRandom.uuid }
1270
- }.to_not change { MockUser.count }
1271
-
1272
- expect(response.status ).to eql(500)
1273
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
1274
-
1275
- result = JSON.parse(response.body)
1276
-
1277
- expect(result['detail']).to eql('RuntimeError')
1278
- end
1279
- end
1280
-
1281
565
  # ===========================================================================
1282
566
 
1283
567
  context '#destroy' do
1284
- it 'deletes an item if given no block' do
568
+ it 'deletes an item if given no blok' do
1285
569
  expect_any_instance_of(MockUsersController).to receive(:destroy).once.and_call_original
1286
570
  expect_any_instance_of(MockUser).to receive(:destroy!).once.and_call_original
1287
571
  expect {
1288
- delete "/Users/#{@u2.primary_key}", params: { format: :scim }
572
+ delete "/Users/#{@u2.id}", params: { format: :scim }
1289
573
  }.to change { MockUser.count }.by(-1)
1290
574
 
1291
575
  expect(response.status).to eql(204)
@@ -1297,7 +581,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
1297
581
  expect_any_instance_of(MockUser).to_not receive(:destroy!)
1298
582
 
1299
583
  expect {
1300
- delete "/CustomDestroyUsers/#{@u2.primary_key}", params: { format: :scim }
584
+ delete "/CustomDestroyUsers/#{@u2.id}", params: { format: :scim }
1301
585
  }.to_not change { MockUser.count }
1302
586
 
1303
587
  expect(response.status).to eql(204)
@@ -1312,11 +596,8 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
1312
596
  delete '/Users/xyz', params: { format: :scim }
1313
597
  }.to_not change { MockUser.count }
1314
598
 
1315
- expect(response.status ).to eql(404)
1316
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
1317
-
599
+ expect(response.status).to eql(404)
1318
600
  result = JSON.parse(response.body)
1319
-
1320
601
  expect(result['status']).to eql('404')
1321
602
  end
1322
603
  end # "context '#destroy' do"