scimitar 1.5.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/scimitar/active_record_backed_resources_controller.rb +6 -27
  3. data/app/controllers/scimitar/application_controller.rb +9 -29
  4. data/app/models/scimitar/engine_configuration.rb +3 -7
  5. data/app/models/scimitar/error_response.rb +0 -12
  6. data/app/models/scimitar/errors.rb +1 -1
  7. data/app/models/scimitar/lists/query_parser.rb +4 -14
  8. data/app/models/scimitar/resources/base.rb +1 -1
  9. data/app/models/scimitar/resources/mixin.rb +4 -113
  10. data/app/models/scimitar/schema/address.rb +0 -1
  11. data/app/models/scimitar/schema/attribute.rb +1 -1
  12. data/app/models/scimitar/schema/base.rb +3 -1
  13. data/app/models/scimitar/schema/vdtp.rb +1 -1
  14. data/config/initializers/scimitar.rb +70 -86
  15. data/lib/scimitar/version.rb +2 -2
  16. data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +1 -1
  17. data/spec/apps/dummy/app/models/mock_group.rb +1 -1
  18. data/spec/apps/dummy/app/models/mock_user.rb +8 -19
  19. data/spec/apps/dummy/config/application.rb +1 -0
  20. data/spec/apps/dummy/config/environments/test.rb +28 -5
  21. data/spec/apps/dummy/config/initializers/scimitar.rb +9 -44
  22. data/spec/apps/dummy/config/routes.rb +0 -4
  23. data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +1 -9
  24. data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +3 -8
  25. data/spec/apps/dummy/db/schema.rb +4 -10
  26. data/spec/controllers/scimitar/application_controller_spec.rb +1 -70
  27. data/spec/controllers/scimitar/schemas_controller_spec.rb +2 -2
  28. data/spec/models/scimitar/complex_types/email_spec.rb +2 -0
  29. data/spec/models/scimitar/lists/query_parser_spec.rb +9 -9
  30. data/spec/models/scimitar/resources/base_spec.rb +66 -161
  31. data/spec/models/scimitar/resources/base_validation_spec.rb +2 -27
  32. data/spec/models/scimitar/resources/mixin_spec.rb +43 -757
  33. data/spec/models/scimitar/resources/user_spec.rb +4 -4
  34. data/spec/models/scimitar/schema/attribute_spec.rb +3 -0
  35. data/spec/models/scimitar/schema/base_spec.rb +1 -1
  36. data/spec/models/scimitar/schema/user_spec.rb +0 -10
  37. data/spec/requests/active_record_backed_resources_controller_spec.rb +40 -309
  38. data/spec/requests/application_controller_spec.rb +3 -17
  39. metadata +7 -7
@@ -42,25 +42,25 @@ RSpec.describe Scimitar::Resources::User do
42
42
  let(:user) { described_class.new }
43
43
 
44
44
  it 'adds the error when the value is a string' do
45
- user.add_errors_from_hash({key: 'some error'})
45
+ user.add_errors_from_hash(errors_hash: {key: 'some error'})
46
46
  expect(user.errors.messages.to_h).to eql({key: ['some error']})
47
47
  expect(user.errors.full_messages).to eql(['Key some error'])
48
48
  end
49
49
 
50
50
  it 'adds the error when the value is an array' do
51
- user.add_errors_from_hash({key: ['error1', 'error2']})
51
+ user.add_errors_from_hash(errors_hash: {key: ['error1', 'error2']})
52
52
  expect(user.errors.messages.to_h).to eql({key: ['error1', 'error2']})
53
53
  expect(user.errors.full_messages).to eql(['Key error1', 'Key error2'])
54
54
  end
55
55
 
56
56
  it 'adds the error with prefix when the value is a string' do
57
- user.add_errors_from_hash({key: 'some error'}, prefix: :pre)
57
+ user.add_errors_from_hash(errors_hash: {key: 'some error'}, prefix: :pre)
58
58
  expect(user.errors.messages.to_h).to eql({:'pre.key' => ['some error']})
59
59
  expect(user.errors.full_messages).to eql(['Pre key some error'])
60
60
  end
61
61
 
62
62
  it 'adds the error wity prefix when the value is an array' do
63
- user.add_errors_from_hash({key: ['error1', 'error2']}, prefix: :pre)
63
+ user.add_errors_from_hash(errors_hash: {key: ['error1', 'error2']}, prefix: :pre)
64
64
  expect(user.errors.messages.to_h).to eql({:'pre.key' => ['error1', 'error2']})
65
65
  expect(user.errors.full_messages).to eql(['Pre key error1', 'Pre key error2'])
66
66
  end
@@ -21,8 +21,10 @@ RSpec.describe Scimitar::Schema::Attribute do
21
21
  expect(name.type).to eql('complex')
22
22
  expect(name.subAttributes).to eql(Scimitar::Schema::Name.scim_attributes)
23
23
  end
24
+
24
25
  end
25
26
 
27
+
26
28
  context '#valid?' do
27
29
  it 'is invalid if attribute is required but value is blank' do
28
30
  attribute = described_class.new(name: 'userName', type: 'string', required: true)
@@ -74,4 +76,5 @@ RSpec.describe Scimitar::Schema::Attribute do
74
76
  expect(described_class.new(name: 'startDate', type: 'dateTime').valid?('gaga')).to be(false)
75
77
  end
76
78
  end
79
+
77
80
  end
@@ -11,7 +11,7 @@ RSpec.describe Scimitar::Schema::Base do
11
11
  end
12
12
 
13
13
  context '#initialize' do
14
- it 'creates "meta"' do
14
+ it 'creates a meta' do
15
15
  schema = described_class.new
16
16
  expect(schema.meta.resourceType).to eql('Schema')
17
17
  end
@@ -419,16 +419,6 @@ RSpec.describe Scimitar::Schema::User do
419
419
  "name": "type",
420
420
  "type": "string"
421
421
  },
422
- {
423
- "multiValued": false,
424
- "required": false,
425
- "caseExact": false,
426
- "mutability": "readWrite",
427
- "uniqueness": "none",
428
- "returned": "default",
429
- "name": "primary",
430
- "type": "boolean"
431
- },
432
422
  {
433
423
  "multiValued": false,
434
424
  "required": false,
@@ -1,27 +1,12 @@
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
- lmt = Time.parse("2023-01-09 14:25:00 +1300")
9
-
10
- # If a sort order is unspecified, the controller defaults to ID ascending.
11
- # With UUID based IDs, testing life is made easier by ensuring that the
12
- # creation order matches an ascending UUID sort order (which is what would
13
- # happen if we were using integer primary keys).
14
- #
15
- lmt = Time.parse("2023-01-09 14:25:00 +1300")
16
- ids = 3.times.map { SecureRandom.uuid }.sort()
17
-
18
- @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)
19
- @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)
20
- @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)
21
-
22
- @g1 = MockGroup.create!(display_name: 'Group 1')
23
- @g2 = MockGroup.create!(display_name: 'Group 2')
24
- @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')
25
10
  end
26
11
 
27
12
  # ===========================================================================
@@ -44,65 +29,26 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
44
29
  end # "context 'with no items' do"
45
30
 
46
31
  context 'with items' do
47
- context 'with a UUID, renamed primary key column' do
48
- it 'returns all items' do
49
- get '/Users', params: { format: :scim }
50
-
51
- expect(response.status).to eql(200)
52
- result = JSON.parse(response.body)
53
-
54
- expect(result['totalResults']).to eql(3)
55
- expect(result['Resources'].size).to eql(3)
56
-
57
- ids = result['Resources'].map { |resource| resource['id'] }
58
- expect(ids).to match_array([@u1.primary_key.to_s, @u2.primary_key.to_s, @u3.primary_key.to_s])
59
-
60
- usernames = result['Resources'].map { |resource| resource['userName'] }
61
- expect(usernames).to match_array(['1', '2', '3'])
62
- end
63
- end # "context 'with a UUID, renamed primary key column' do"
64
-
65
- context 'with an integer, conventionally named primary key column' do
66
- it 'returns all items' do
67
- get '/Groups', params: { format: :scim }
68
-
69
- expect(response.status).to eql(200)
70
- result = JSON.parse(response.body)
71
-
72
- expect(result['totalResults']).to eql(3)
73
- expect(result['Resources'].size).to eql(3)
74
-
75
- ids = result['Resources'].map { |resource| resource['id'] }
76
- expect(ids).to match_array([@g1.id.to_s, @g2.id.to_s, @g3.id.to_s])
77
-
78
- usernames = result['Resources'].map { |resource| resource['displayName'] }
79
- expect(usernames).to match_array(['Group 1', 'Group 2', 'Group 3'])
80
- end
81
- end # "context 'with an integer, conventionally named primary key column' do"
82
-
83
- it 'applies a filter, with case-insensitive value comparison' do
84
- get '/Users', params: {
85
- format: :scim,
86
- filter: 'name.givenName eq "FOO" and name.familyName pr and emails ne "home_1@test.com"'
87
- }
32
+ it 'returns all items' do
33
+ get '/Users', params: { format: :scim }
88
34
 
89
35
  expect(response.status).to eql(200)
90
36
  result = JSON.parse(response.body)
91
37
 
92
- expect(result['totalResults']).to eql(1)
93
- expect(result['Resources'].size).to eql(1)
38
+ expect(result['totalResults']).to eql(3)
39
+ expect(result['Resources'].size).to eql(3)
94
40
 
95
41
  ids = result['Resources'].map { |resource| resource['id'] }
96
- 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])
97
43
 
98
44
  usernames = result['Resources'].map { |resource| resource['userName'] }
99
- expect(usernames).to match_array(['2'])
45
+ expect(usernames).to match_array(['1', '2', '3'])
100
46
  end
101
47
 
102
- it 'applies a filter, with case-insensitive attribute matching (GitHub issue #37)' do
48
+ it 'applies a filter, with case-insensitive value comparison' do
103
49
  get '/Users', params: {
104
50
  format: :scim,
105
- 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"'
106
52
  }
107
53
 
108
54
  expect(response.status).to eql(200)
@@ -112,74 +58,12 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
112
58
  expect(result['Resources'].size).to eql(1)
113
59
 
114
60
  ids = result['Resources'].map { |resource| resource['id'] }
115
- expect(ids).to match_array([@u2.primary_key.to_s])
61
+ expect(ids).to match_array([@u2.id.to_s])
116
62
 
117
63
  usernames = result['Resources'].map { |resource| resource['userName'] }
118
64
  expect(usernames).to match_array(['2'])
119
65
  end
120
66
 
121
- # Strange attribute capitalisation in tests here builds on test coverage
122
- # for now-fixed GitHub issue #37.
123
- #
124
- context '"meta" / IDs (GitHub issue #36)' do
125
- it 'applies a filter on primary keys, using direct comparison (rather than e.g. case-insensitive operators)' do
126
- get '/Users', params: {
127
- format: :scim,
128
- filter: "id eq \"#{@u3.primary_key}\""
129
- }
130
-
131
- expect(response.status).to eql(200)
132
- result = JSON.parse(response.body)
133
-
134
- expect(result['totalResults']).to eql(1)
135
- expect(result['Resources'].size).to eql(1)
136
-
137
- ids = result['Resources'].map { |resource| resource['id'] }
138
- expect(ids).to match_array([@u3.primary_key.to_s])
139
-
140
- usernames = result['Resources'].map { |resource| resource['userName'] }
141
- expect(usernames).to match_array(['3'])
142
- end
143
-
144
- it 'applies a filter on external IDs, using direct comparison' do
145
- get '/Users', params: {
146
- format: :scim,
147
- filter: "externalID eq \"#{@u2.scim_uid}\""
148
- }
149
-
150
- expect(response.status).to eql(200)
151
- result = JSON.parse(response.body)
152
-
153
- expect(result['totalResults']).to eql(1)
154
- expect(result['Resources'].size).to eql(1)
155
-
156
- ids = result['Resources'].map { |resource| resource['id'] }
157
- expect(ids).to match_array([@u2.primary_key.to_s])
158
-
159
- usernames = result['Resources'].map { |resource| resource['userName'] }
160
- expect(usernames).to match_array(['2'])
161
- end
162
-
163
- it 'applies a filter on "meta" entries, using direct comparison' do
164
- get '/Users', params: {
165
- format: :scim,
166
- filter: "Meta.LastModified eq \"#{@u3.updated_at}\""
167
- }
168
-
169
- expect(response.status).to eql(200)
170
- result = JSON.parse(response.body)
171
-
172
- expect(result['totalResults']).to eql(1)
173
- expect(result['Resources'].size).to eql(1)
174
-
175
- ids = result['Resources'].map { |resource| resource['id'] }
176
- expect(ids).to match_array([@u3.primary_key.to_s])
177
-
178
- usernames = result['Resources'].map { |resource| resource['userName'] }
179
- expect(usernames).to match_array(['3'])
180
- end
181
- end # "context '"meta" / IDs (GitHub issue #36)' do"
182
-
183
67
  it 'obeys a page size' do
184
68
  get '/Users', params: {
185
69
  format: :scim,
@@ -193,7 +77,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
193
77
  expect(result['Resources'].size).to eql(2)
194
78
 
195
79
  ids = result['Resources'].map { |resource| resource['id'] }
196
- 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])
197
81
 
198
82
  usernames = result['Resources'].map { |resource| resource['userName'] }
199
83
  expect(usernames).to match_array(['1', '2'])
@@ -212,7 +96,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
212
96
  expect(result['Resources'].size).to eql(2)
213
97
 
214
98
  ids = result['Resources'].map { |resource| resource['id'] }
215
- 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])
216
100
 
217
101
  usernames = result['Resources'].map { |resource| resource['userName'] }
218
102
  expect(usernames).to match_array(['2', '3'])
@@ -236,34 +120,18 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
236
120
  # ===========================================================================
237
121
 
238
122
  context '#show' do
239
- context 'with a UUID, renamed primary key column' do
240
- it 'shows an item' do
241
- expect_any_instance_of(MockUsersController).to receive(:show).once.and_call_original
242
- get "/Users/#{@u2.primary_key}", params: { format: :scim }
243
-
244
- expect(response.status).to eql(200)
245
- result = JSON.parse(response.body)
246
-
247
- expect(result['id']).to eql(@u2.primary_key.to_s)
248
- expect(result['userName']).to eql('2')
249
- expect(result['name']['familyName']).to eql('Bar')
250
- expect(result['meta']['resourceType']).to eql('User')
251
- end
252
- end # "context 'with a UUID, renamed primary key column' do"
123
+ it 'shows an item' do
124
+ expect_any_instance_of(MockUsersController).to receive(:show).once.and_call_original
125
+ get "/Users/#{@u2.id}", params: { format: :scim }
253
126
 
254
- context 'with an integer, conventionally named primary key column' do
255
- it 'shows an item' do
256
- expect_any_instance_of(MockGroupsController).to receive(:show).once.and_call_original
257
- get "/Groups/#{@g2.id}", params: { format: :scim }
258
-
259
- expect(response.status).to eql(200)
260
- result = JSON.parse(response.body)
127
+ expect(response.status).to eql(200)
128
+ result = JSON.parse(response.body)
261
129
 
262
- expect(result['id']).to eql(@g2.id.to_s) # Note - ID was converted String; not Integer
263
- expect(result['displayName']).to eql('Group 2')
264
- expect(result['meta']['resourceType']).to eql('Group')
265
- end
266
- 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
267
135
 
268
136
  it 'renders 404' do
269
137
  get '/Users/xyz', params: { format: :scim }
@@ -296,7 +164,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
296
164
  expect(response.status).to eql(201)
297
165
  result = JSON.parse(response.body)
298
166
 
299
- expect(result['id']).to eql(new_mock.primary_key.to_s)
167
+ expect(result['id']).to eql(new_mock.id.to_s)
300
168
  expect(result['meta']['resourceType']).to eql('User')
301
169
  expect(new_mock.username).to eql('4')
302
170
  end
@@ -312,7 +180,6 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
312
180
  givenName: 'Given',
313
181
  familyName: 'Family'
314
182
  },
315
- meta: { resourceType: 'User' },
316
183
  emails: [
317
184
  {
318
185
  type: 'work',
@@ -337,7 +204,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
337
204
  expect(response.status).to eql(201)
338
205
  result = JSON.parse(response.body)
339
206
 
340
- expect(result['id']).to eql(new_mock.primary_key.to_s)
207
+ expect(result['id']).to eql(new_mock.id.to_s)
341
208
  expect(result['meta']['resourceType']).to eql('User')
342
209
  expect(new_mock.username).to eql('4')
343
210
  expect(new_mock.first_name).to eql('Given')
@@ -411,13 +278,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
411
278
 
412
279
  expect_any_instance_of(MockUsersController).to receive(:replace).once.and_call_original
413
280
  expect {
414
- put "/Users/#{@u2.primary_key}", params: attributes.merge(format: :scim)
281
+ put "/Users/#{@u2.id}", params: attributes.merge(format: :scim)
415
282
  }.to_not change { MockUser.count }
416
283
 
417
284
  expect(response.status).to eql(200)
418
285
  result = JSON.parse(response.body)
419
286
 
420
- expect(result['id']).to eql(@u2.primary_key.to_s)
287
+ expect(result['id']).to eql(@u2.id.to_s)
421
288
  expect(result['meta']['resourceType']).to eql('User')
422
289
 
423
290
  @u2.reload
@@ -439,7 +306,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
439
306
 
440
307
  it 'notes schema validation failures' do
441
308
  expect {
442
- put "/Users/#{@u2.primary_key}", params: {
309
+ put "/Users/#{@u2.id}", params: {
443
310
  format: :scim
444
311
  # userName parameter is required by schema, but missing
445
312
  }
@@ -518,13 +385,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
518
385
 
519
386
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
520
387
  expect {
521
- patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
388
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
522
389
  }.to_not change { MockUser.count }
523
390
 
524
391
  expect(response.status).to eql(200)
525
392
  result = JSON.parse(response.body)
526
393
 
527
- expect(result['id']).to eql(@u2.primary_key.to_s)
394
+ expect(result['id']).to eql(@u2.id.to_s)
528
395
  expect(result['meta']['resourceType']).to eql('User')
529
396
 
530
397
  @u2.reload
@@ -555,13 +422,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
555
422
 
556
423
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
557
424
  expect {
558
- patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
425
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
559
426
  }.to_not change { MockUser.count }
560
427
 
561
428
  expect(response.status).to eql(200)
562
429
  result = JSON.parse(response.body)
563
430
 
564
- expect(result['id']).to eql(@u2.primary_key.to_s)
431
+ expect(result['id']).to eql(@u2.id.to_s)
565
432
  expect(result['meta']['resourceType']).to eql('User')
566
433
 
567
434
  @u2.reload
@@ -587,13 +454,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
587
454
 
588
455
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
589
456
  expect {
590
- patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
457
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
591
458
  }.to_not change { MockUser.count }
592
459
 
593
460
  expect(response.status).to eql(200)
594
461
  result = JSON.parse(response.body)
595
462
 
596
- expect(result['id']).to eql(@u2.primary_key.to_s)
463
+ expect(result['id']).to eql(@u2.id.to_s)
597
464
  expect(result['meta']['resourceType']).to eql('User')
598
465
 
599
466
  @u2.reload
@@ -619,13 +486,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
619
486
 
620
487
  expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
621
488
  expect {
622
- patch "/Users/#{@u2.primary_key}", params: payload.merge(format: :scim)
489
+ patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
623
490
  }.to_not change { MockUser.count }
624
491
 
625
492
  expect(response.status).to eql(200)
626
493
  result = JSON.parse(response.body)
627
494
 
628
- expect(result['id']).to eql(@u2.primary_key.to_s)
495
+ expect(result['id']).to eql(@u2.id.to_s)
629
496
  expect(result['meta']['resourceType']).to eql('User')
630
497
 
631
498
  @u2.reload
@@ -649,7 +516,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
649
516
 
650
517
  it 'notes Rails validation failures' do
651
518
  expect {
652
- patch "/Users/#{@u2.primary_key}", params: {
519
+ patch "/Users/#{@u2.id}", params: {
653
520
  format: :scim,
654
521
  Operations: [
655
522
  {
@@ -693,142 +560,6 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
693
560
  result = JSON.parse(response.body)
694
561
  expect(result['status']).to eql('404')
695
562
  end
696
-
697
- context 'when removing users from groups' do
698
- before :each do
699
- @g1.mock_users << @u1
700
- @g1.mock_users << @u2
701
- @g1.mock_users << @u3
702
-
703
- # (Self-check) Verify group representation
704
- #
705
- get "/Groups/#{@g1.id}", params: { format: :scim }
706
-
707
- expect(response.status).to eql(200)
708
- result = JSON.parse(response.body)
709
-
710
- expect(result['members'].map { |m| m['value'] }.sort()).to eql(MockUser.pluck(:primary_key).sort())
711
- end
712
-
713
- it 'can remove all users' do
714
- expect {
715
- expect {
716
- patch "/Groups/#{@g1.id}", params: {
717
- format: :scim,
718
- Operations: [
719
- {
720
- op: 'remove',
721
- path: 'members'
722
- }
723
- ]
724
- }
725
- }.to_not change { MockUser.count }
726
- }.to_not change { MockGroup.count }
727
-
728
- get "/Groups/#{@g1.id}", params: { format: :scim }
729
-
730
- expect(response.status).to eql(200)
731
- result = JSON.parse(response.body)
732
-
733
- expect(result['members']).to be_empty
734
- expect(@g1.reload().mock_users).to be_empty
735
- end
736
-
737
- # Define via 'let':
738
- #
739
- # * Hash 'payload', to send via 'patch'
740
- # * MockUser 'removed_user', which is the user that should be removed
741
- #
742
- shared_examples 'a user remover' do
743
- it 'which removes the identified user' do
744
- expect {
745
- expect {
746
- patch "/Groups/#{@g1.id}", params: payload()
747
- }.to_not change { MockUser.count }
748
- }.to_not change { MockGroup.count }
749
-
750
- expected_remaining_user_ids = MockUser
751
- .where.not(primary_key: removed_user().id)
752
- .pluck(:primary_key)
753
- .sort()
754
-
755
- get "/Groups/#{@g1.id}", params: { format: :scim }
756
-
757
- expect(response.status).to eql(200)
758
- result = JSON.parse(response.body)
759
-
760
- expect(result['members'].map { |m| m['value'] }.sort()).to eql(expected_remaining_user_ids)
761
- expect(@g1.reload().mock_users.map(&:primary_key).sort()).to eql(expected_remaining_user_ids)
762
- end
763
- end
764
-
765
- # https://tools.ietf.org/html/rfc7644#section-3.5.2.2
766
- #
767
- context 'and using an RFC-compliant payload' do
768
- let(:removed_user) { @u2 }
769
- let(:payload) do
770
- {
771
- format: :scim,
772
- Operations: [
773
- {
774
- op: 'remove',
775
- path: "members[value eq \"#{removed_user().primary_key}\"]",
776
- }
777
- ]
778
- }
779
- end
780
-
781
- it_behaves_like 'a user remover'
782
- end # context 'and using an RFC-compliant payload' do
783
-
784
- # https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups#update-group-remove-members
785
- #
786
- context 'and using a Microsoft variant payload' do
787
- let(:removed_user) { @u2 }
788
- let(:payload) do
789
- {
790
- format: :scim,
791
- Operations: [
792
- {
793
- op: 'remove',
794
- path: 'members',
795
- value: [{
796
- '$ref' => nil,
797
- 'value' => removed_user().primary_key
798
- }]
799
- }
800
- ]
801
- }
802
- end
803
-
804
- it_behaves_like 'a user remover'
805
- end # context 'and using a Microsoft variant payload' do
806
-
807
- # https://help.salesforce.com/s/articleView?id=sf.identity_scim_manage_groups.htm&type=5
808
- #
809
- context 'and using a Salesforce variant payload' do
810
- let(:removed_user) { @u2 }
811
- let(:payload) do
812
- {
813
- format: :scim,
814
- Operations: [
815
- {
816
- op: 'remove',
817
- path: 'members',
818
- value: {
819
- 'members' => [{
820
- '$ref' => nil,
821
- 'value' => removed_user().primary_key
822
- }]
823
- }
824
- }
825
- ]
826
- }
827
- end
828
-
829
- it_behaves_like 'a user remover'
830
- end # context 'and using a Salesforce variant payload' do
831
- end # "context 'when removing users from groups' do"
832
563
  end # "context '#update' do"
833
564
 
834
565
  # ===========================================================================
@@ -838,7 +569,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
838
569
  expect_any_instance_of(MockUsersController).to receive(:destroy).once.and_call_original
839
570
  expect_any_instance_of(MockUser).to receive(:destroy!).once.and_call_original
840
571
  expect {
841
- delete "/Users/#{@u2.primary_key}", params: { format: :scim }
572
+ delete "/Users/#{@u2.id}", params: { format: :scim }
842
573
  }.to change { MockUser.count }.by(-1)
843
574
 
844
575
  expect(response.status).to eql(204)
@@ -850,7 +581,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
850
581
  expect_any_instance_of(MockUser).to_not receive(:destroy!)
851
582
 
852
583
  expect {
853
- delete "/CustomDestroyUsers/#{@u2.primary_key}", params: { format: :scim }
584
+ delete "/CustomDestroyUsers/#{@u2.id}", params: { format: :scim }
854
585
  }.to_not change { MockUser.count }
855
586
 
856
587
  expect(response.status).to eql(204)
@@ -11,14 +11,11 @@ RSpec.describe Scimitar::ApplicationController do
11
11
  end
12
12
 
13
13
  context 'format handling' do
14
- it 'renders "OK" if the request does not provide any Content-Type value' do
14
+ it 'renders "not acceptable" if the request does not use SCIM type' do
15
15
  get '/CustomRequestVerifiers', params: { format: :html }
16
16
 
17
- expect(response).to have_http_status(:ok)
18
- parsed_body = JSON.parse(response.body)
19
- expect(parsed_body['request']['is_scim' ]).to eql(true)
20
- expect(parsed_body['request']['format' ]).to eql('application/scim+json')
21
- expect(parsed_body['request']['content_type']).to eql('application/scim+json')
17
+ expect(response).to have_http_status(:not_acceptable)
18
+ expect(JSON.parse(response.body)['detail']).to eql('Only application/scim+json type is accepted.')
22
19
  end
23
20
 
24
21
  it 'renders 400 if given bad JSON' do
@@ -26,7 +23,6 @@ RSpec.describe Scimitar::ApplicationController do
26
23
 
27
24
  expect(response).to have_http_status(:bad_request)
28
25
  expect(JSON.parse(response.body)['detail']).to start_with('Invalid JSON - ')
29
- expect(JSON.parse(response.body)['detail']).to include("'not-json-12345'")
30
26
  end
31
27
 
32
28
  it 'translates Content-Type to Rails request format' do
@@ -39,16 +35,6 @@ RSpec.describe Scimitar::ApplicationController do
39
35
  expect(parsed_body['request']['content_type']).to eql('application/scim+json')
40
36
  end
41
37
 
42
- it 'translates Content-Type with charset to Rails request format' do
43
- get '/CustomRequestVerifiers', headers: { 'CONTENT_TYPE' => 'application/scim+json; charset=utf-8' }
44
-
45
- expect(response).to have_http_status(:ok)
46
- parsed_body = JSON.parse(response.body)
47
- expect(parsed_body['request']['is_scim' ]).to eql(true)
48
- expect(parsed_body['request']['format' ]).to eql('application/scim+json')
49
- expect(parsed_body['request']['content_type']).to eql('application/scim+json; charset=utf-8')
50
- end
51
-
52
38
  it 'translates Rails request format to header' do
53
39
  get '/CustomRequestVerifiers', params: { format: :scim }
54
40