scimitar 1.8.2 → 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.
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"