scimitar 1.7.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/scimitar/active_record_backed_resources_controller.rb +10 -49
  3. data/app/controllers/scimitar/application_controller.rb +11 -35
  4. data/app/controllers/scimitar/schemas_controller.rb +0 -5
  5. data/app/models/scimitar/engine_configuration.rb +5 -13
  6. data/app/models/scimitar/error_response.rb +0 -12
  7. data/app/models/scimitar/lists/query_parser.rb +10 -25
  8. data/app/models/scimitar/resources/base.rb +4 -14
  9. data/app/models/scimitar/resources/mixin.rb +13 -137
  10. data/app/models/scimitar/schema/address.rb +0 -1
  11. data/app/models/scimitar/schema/attribute.rb +5 -14
  12. data/app/models/scimitar/schema/base.rb +1 -1
  13. data/app/models/scimitar/schema/vdtp.rb +1 -1
  14. data/app/models/scimitar/service_provider_configuration.rb +3 -14
  15. data/config/initializers/scimitar.rb +3 -28
  16. data/lib/scimitar/version.rb +2 -2
  17. data/lib/scimitar.rb +2 -6
  18. data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +1 -1
  19. data/spec/apps/dummy/app/models/mock_group.rb +1 -1
  20. data/spec/apps/dummy/app/models/mock_user.rb +8 -36
  21. data/spec/apps/dummy/config/application.rb +1 -0
  22. data/spec/apps/dummy/config/environments/test.rb +28 -5
  23. data/spec/apps/dummy/config/initializers/scimitar.rb +10 -61
  24. data/spec/apps/dummy/config/routes.rb +6 -15
  25. data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +1 -10
  26. data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +3 -8
  27. data/spec/apps/dummy/db/schema.rb +4 -11
  28. data/spec/controllers/scimitar/application_controller_spec.rb +3 -72
  29. data/spec/controllers/scimitar/resource_types_controller_spec.rb +2 -2
  30. data/spec/controllers/scimitar/schemas_controller_spec.rb +2 -10
  31. data/spec/models/scimitar/complex_types/email_spec.rb +2 -0
  32. data/spec/models/scimitar/lists/query_parser_spec.rb +9 -76
  33. data/spec/models/scimitar/resources/base_spec.rb +70 -208
  34. data/spec/models/scimitar/resources/base_validation_spec.rb +2 -27
  35. data/spec/models/scimitar/resources/mixin_spec.rb +43 -768
  36. data/spec/models/scimitar/schema/attribute_spec.rb +3 -22
  37. data/spec/models/scimitar/schema/base_spec.rb +1 -1
  38. data/spec/models/scimitar/schema/user_spec.rb +0 -10
  39. data/spec/requests/active_record_backed_resources_controller_spec.rb +64 -423
  40. data/spec/requests/application_controller_spec.rb +3 -16
  41. metadata +7 -11
  42. data/LICENSE.txt +0 -21
  43. data/README.md +0 -671
  44. data/spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb +0 -24
@@ -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)
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,7 +150,7 @@ 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
156
  expect_any_instance_of(MockUsersController).to receive(:create).once.and_call_original
@@ -323,12 +161,10 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
323
161
  mock_after = MockUser.all.to_a
324
162
  new_mock = (mock_after - mock_before).first
325
163
 
326
- expect(response.status ).to eql(201)
327
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
328
-
164
+ expect(response.status).to eql(201)
329
165
  result = JSON.parse(response.body)
330
166
 
331
- expect(result['id']).to eql(new_mock.primary_key.to_s)
167
+ expect(result['id']).to eql(new_mock.id.to_s)
332
168
  expect(result['meta']['resourceType']).to eql('User')
333
169
  expect(new_mock.username).to eql('4')
334
170
  end
@@ -344,7 +180,6 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
344
180
  givenName: 'Given',
345
181
  familyName: 'Family'
346
182
  },
347
- meta: { resourceType: 'User' },
348
183
  emails: [
349
184
  {
350
185
  type: 'work',
@@ -366,9 +201,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
366
201
  mock_after = MockUser.all.to_a
367
202
  new_mock = (mock_after - mock_before).first
368
203
 
369
- expect(response.status ).to eql(201)
370
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
371
-
204
+ expect(response.status).to eql(201)
372
205
  result = JSON.parse(response.body)
373
206
 
374
207
  expect(result['id']).to eql(new_mock.id.to_s)
@@ -399,11 +232,8 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
399
232
  }
400
233
  }.to_not change { MockUser.count }
401
234
 
402
- expect(response.status ).to eql(409)
403
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
404
-
235
+ expect(response.status).to eql(409)
405
236
  result = JSON.parse(response.body)
406
-
407
237
  expect(result['scimType']).to eql('uniqueness')
408
238
  expect(result['detail']).to include('already been taken')
409
239
  end
@@ -416,11 +246,8 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
416
246
  }
417
247
  }.to_not change { MockUser.count }
418
248
 
419
- expect(response.status ).to eql(400)
420
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
421
-
249
+ expect(response.status).to eql(400)
422
250
  result = JSON.parse(response.body)
423
-
424
251
  expect(result['scimType']).to eql('invalidValue')
425
252
  expect(result['detail']).to include('is required')
426
253
  end
@@ -433,32 +260,12 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
433
260
  }
434
261
  }.to_not change { MockUser.count }
435
262
 
436
- expect(response.status ).to eql(400)
437
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
438
-
263
+ expect(response.status).to eql(400)
439
264
  result = JSON.parse(response.body)
440
265
 
441
266
  expect(result['scimType']).to eql('invalidValue')
442
267
  expect(result['detail']).to include('is reserved')
443
268
  end
444
-
445
- it 'invokes a block if given one' do
446
- mock_before = MockUser.all.to_a
447
- attributes = { userName: '5' } # Minimum required by schema
448
-
449
- expect_any_instance_of(CustomSaveMockUsersController).to receive(:create).once.and_call_original
450
- expect {
451
- post "/CustomSaveUsers", params: attributes.merge(format: :scim)
452
- }.to change { MockUser.count }.by(1)
453
-
454
- mock_after = MockUser.all.to_a
455
- new_mock = (mock_after - mock_before).first
456
-
457
- expect(response.status ).to eql(201)
458
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
459
-
460
- expect(new_mock.username).to eql(CustomSaveMockUsersController::CUSTOM_SAVE_BLOCK_USERNAME_INDICATOR)
461
- end
462
269
  end # "context '#create' do"
463
270
 
464
271
  # ===========================================================================
@@ -471,15 +278,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
471
278
 
472
279
  expect_any_instance_of(MockUsersController).to receive(:replace).once.and_call_original
473
280
  expect {
474
- put "/Users/#{@u2.primary_key}", params: attributes.merge(format: :scim)
281
+ put "/Users/#{@u2.id}", params: attributes.merge(format: :scim)
475
282
  }.to_not change { MockUser.count }
476
283
 
477
- expect(response.status ).to eql(200)
478
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
479
-
284
+ expect(response.status).to eql(200)
480
285
  result = JSON.parse(response.body)
481
286
 
482
- expect(result['id']).to eql(@u2.primary_key.to_s)
287
+ expect(result['id']).to eql(@u2.id.to_s)
483
288
  expect(result['meta']['resourceType']).to eql('User')
484
289
 
485
290
  @u2.reload
@@ -501,17 +306,14 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
501
306
 
502
307
  it 'notes schema validation failures' do
503
308
  expect {
504
- put "/Users/#{@u2.primary_key}", params: {
309
+ put "/Users/#{@u2.id}", params: {
505
310
  format: :scim
506
311
  # userName parameter is required by schema, but missing
507
312
  }
508
313
  }.to_not change { MockUser.count }
509
314
 
510
- expect(response.status ).to eql(400)
511
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
512
-
315
+ expect(response.status).to eql(400)
513
316
  result = JSON.parse(response.body)
514
-
515
317
  expect(result['scimType']).to eql('invalidValue')
516
318
  expect(result['detail']).to include('is required')
517
319
 
@@ -531,9 +333,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
531
333
  }
532
334
  }.to_not change { MockUser.count }
533
335
 
534
- expect(response.status ).to eql(400)
535
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
536
-
336
+ expect(response.status).to eql(400)
537
337
  result = JSON.parse(response.body)
538
338
 
539
339
  expect(result['scimType']).to eql('invalidValue')
@@ -555,11 +355,8 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
555
355
  }
556
356
  }.to_not change { MockUser.count }
557
357
 
558
- expect(response.status ).to eql(404)
559
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
560
-
358
+ expect(response.status).to eql(404)
561
359
  result = JSON.parse(response.body)
562
-
563
360
  expect(result['status']).to eql('404')
564
361
  end
565
362
  end # "context '#replace' do"
@@ -588,15 +385,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
588
385
 
589
386
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
590
387
  expect {
591
- patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
388
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
592
389
  }.to_not change { MockUser.count }
593
390
 
594
- expect(response.status ).to eql(200)
595
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
596
-
391
+ expect(response.status).to eql(200)
597
392
  result = JSON.parse(response.body)
598
393
 
599
- expect(result['id']).to eql(@u2.primary_key.to_s)
394
+ expect(result['id']).to eql(@u2.id.to_s)
600
395
  expect(result['meta']['resourceType']).to eql('User')
601
396
 
602
397
  @u2.reload
@@ -627,15 +422,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
627
422
 
628
423
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
629
424
  expect {
630
- patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
425
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
631
426
  }.to_not change { MockUser.count }
632
427
 
633
- expect(response.status ).to eql(200)
634
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
635
-
428
+ expect(response.status).to eql(200)
636
429
  result = JSON.parse(response.body)
637
430
 
638
- expect(result['id']).to eql(@u2.primary_key.to_s)
431
+ expect(result['id']).to eql(@u2.id.to_s)
639
432
  expect(result['meta']['resourceType']).to eql('User')
640
433
 
641
434
  @u2.reload
@@ -661,15 +454,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
661
454
 
662
455
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
663
456
  expect {
664
- patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
457
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
665
458
  }.to_not change { MockUser.count }
666
459
 
667
- expect(response.status ).to eql(200)
668
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
669
-
460
+ expect(response.status).to eql(200)
670
461
  result = JSON.parse(response.body)
671
462
 
672
- expect(result['id']).to eql(@u2.primary_key.to_s)
463
+ expect(result['id']).to eql(@u2.id.to_s)
673
464
  expect(result['meta']['resourceType']).to eql('User')
674
465
 
675
466
  @u2.reload
@@ -695,15 +486,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
695
486
 
696
487
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
697
488
  expect {
698
- patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
489
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
699
490
  }.to_not change { MockUser.count }
700
491
 
701
- expect(response.status ).to eql(200)
702
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
703
-
492
+ expect(response.status).to eql(200)
704
493
  result = JSON.parse(response.body)
705
494
 
706
- expect(result['id']).to eql(@u2.primary_key.to_s)
495
+ expect(result['id']).to eql(@u2.id.to_s)
707
496
  expect(result['meta']['resourceType']).to eql('User')
708
497
 
709
498
  @u2.reload
@@ -727,7 +516,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
727
516
 
728
517
  it 'notes Rails validation failures' do
729
518
  expect {
730
- patch "/Users/#{@u2.primary_key}", params: {
519
+ patch "/Users/#{@u2.id}", params: {
731
520
  format: :scim,
732
521
  Operations: [
733
522
  {
@@ -739,9 +528,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
739
528
  }
740
529
  }.to_not change { MockUser.count }
741
530
 
742
- expect(response.status ).to eql(400)
743
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
744
-
531
+ expect(response.status).to eql(400)
745
532
  result = JSON.parse(response.body)
746
533
 
747
534
  expect(result['scimType']).to eql('invalidValue')
@@ -769,153 +556,10 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
769
556
  }
770
557
  }.to_not change { MockUser.count }
771
558
 
772
- expect(response.status ).to eql(404)
773
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
774
-
559
+ expect(response.status).to eql(404)
775
560
  result = JSON.parse(response.body)
776
-
777
561
  expect(result['status']).to eql('404')
778
562
  end
779
-
780
- context 'when removing users from groups' do
781
- before :each do
782
- @g1.mock_users << @u1
783
- @g1.mock_users << @u2
784
- @g1.mock_users << @u3
785
-
786
- # (Self-check) Verify group representation
787
- #
788
- get "/Groups/#{@g1.id}", params: { format: :scim }
789
-
790
- expect(response.status).to eql(200)
791
- result = JSON.parse(response.body)
792
-
793
- expect(result['members'].map { |m| m['value'] }.sort()).to eql(MockUser.pluck(:primary_key).sort())
794
- end
795
-
796
- it 'can remove all users' do
797
- expect {
798
- expect {
799
- patch "/Groups/#{@g1.id}", params: {
800
- format: :scim,
801
- Operations: [
802
- {
803
- op: 'remove',
804
- path: 'members'
805
- }
806
- ]
807
- }
808
- }.to_not change { MockUser.count }
809
- }.to_not change { MockGroup.count }
810
-
811
- get "/Groups/#{@g1.id}", params: { format: :scim }
812
-
813
- expect(response.status ).to eql(200)
814
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
815
-
816
- result = JSON.parse(response.body)
817
-
818
- expect(result['members']).to be_empty
819
- expect(@g1.reload().mock_users).to be_empty
820
- end
821
-
822
- # Define via 'let':
823
- #
824
- # * Hash 'payload', to send via 'patch'
825
- # * MockUser 'removed_user', which is the user that should be removed
826
- #
827
- shared_examples 'a user remover' do
828
- it 'which removes the identified user' do
829
- expect {
830
- expect {
831
- patch "/Groups/#{@g1.id}", params: payload()
832
- }.to_not change { MockUser.count }
833
- }.to_not change { MockGroup.count }
834
-
835
- expected_remaining_user_ids = MockUser
836
- .where.not(primary_key: removed_user().id)
837
- .pluck(:primary_key)
838
- .sort()
839
-
840
- get "/Groups/#{@g1.id}", params: { format: :scim }
841
-
842
- expect(response.status ).to eql(200)
843
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
844
-
845
- result = JSON.parse(response.body)
846
-
847
- expect(result['members'].map { |m| m['value'] }.sort()).to eql(expected_remaining_user_ids)
848
- expect(@g1.reload().mock_users.map(&:primary_key).sort()).to eql(expected_remaining_user_ids)
849
- end
850
- end
851
-
852
- # https://tools.ietf.org/html/rfc7644#section-3.5.2.2
853
- #
854
- context 'and using an RFC-compliant payload' do
855
- let(:removed_user) { @u2 }
856
- let(:payload) do
857
- {
858
- format: :scim,
859
- Operations: [
860
- {
861
- op: 'remove',
862
- path: "members[value eq \"#{removed_user().primary_key}\"]",
863
- }
864
- ]
865
- }
866
- end
867
-
868
- it_behaves_like 'a user remover'
869
- end # context 'and using an RFC-compliant payload' do
870
-
871
- # https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups#update-group-remove-members
872
- #
873
- context 'and using a Microsoft variant payload' do
874
- let(:removed_user) { @u2 }
875
- let(:payload) do
876
- {
877
- format: :scim,
878
- Operations: [
879
- {
880
- op: 'remove',
881
- path: 'members',
882
- value: [{
883
- '$ref' => nil,
884
- 'value' => removed_user().primary_key
885
- }]
886
- }
887
- ]
888
- }
889
- end
890
-
891
- it_behaves_like 'a user remover'
892
- end # context 'and using a Microsoft variant payload' do
893
-
894
- # https://help.salesforce.com/s/articleView?id=sf.identity_scim_manage_groups.htm&type=5
895
- #
896
- context 'and using a Salesforce variant payload' do
897
- let(:removed_user) { @u2 }
898
- let(:payload) do
899
- {
900
- format: :scim,
901
- Operations: [
902
- {
903
- op: 'remove',
904
- path: 'members',
905
- value: {
906
- 'members' => [{
907
- '$ref' => nil,
908
- 'value' => removed_user().primary_key
909
- }]
910
- }
911
- }
912
- ]
913
- }
914
- end
915
-
916
- it_behaves_like 'a user remover'
917
- end # context 'and using a Salesforce variant payload' do
918
- end # "context 'when removing users from groups' do"
919
563
  end # "context '#update' do"
920
564
 
921
565
  # ===========================================================================
@@ -925,7 +569,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
925
569
  expect_any_instance_of(MockUsersController).to receive(:destroy).once.and_call_original
926
570
  expect_any_instance_of(MockUser).to receive(:destroy!).once.and_call_original
927
571
  expect {
928
- delete "/Users/#{@u2.primary_key}", params: { format: :scim }
572
+ delete "/Users/#{@u2.id}", params: { format: :scim }
929
573
  }.to change { MockUser.count }.by(-1)
930
574
 
931
575
  expect(response.status).to eql(204)
@@ -937,7 +581,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
937
581
  expect_any_instance_of(MockUser).to_not receive(:destroy!)
938
582
 
939
583
  expect {
940
- delete "/CustomDestroyUsers/#{@u2.primary_key}", params: { format: :scim }
584
+ delete "/CustomDestroyUsers/#{@u2.id}", params: { format: :scim }
941
585
  }.to_not change { MockUser.count }
942
586
 
943
587
  expect(response.status).to eql(204)
@@ -952,11 +596,8 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
952
596
  delete '/Users/xyz', params: { format: :scim }
953
597
  }.to_not change { MockUser.count }
954
598
 
955
- expect(response.status ).to eql(404)
956
- expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
957
-
599
+ expect(response.status).to eql(404)
958
600
  result = JSON.parse(response.body)
959
-
960
601
  expect(result['status']).to eql('404')
961
602
  end
962
603
  end # "context '#destroy' do"