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.
- checksums.yaml +4 -4
- data/app/controllers/scimitar/active_record_backed_resources_controller.rb +20 -94
- data/app/controllers/scimitar/application_controller.rb +13 -41
- data/app/controllers/scimitar/schemas_controller.rb +0 -5
- data/app/models/scimitar/complex_types/address.rb +6 -0
- data/app/models/scimitar/engine_configuration.rb +5 -13
- data/app/models/scimitar/error_response.rb +0 -12
- data/app/models/scimitar/lists/query_parser.rb +10 -25
- data/app/models/scimitar/resource_invalid_error.rb +1 -1
- data/app/models/scimitar/resources/base.rb +4 -17
- data/app/models/scimitar/resources/mixin.rb +42 -539
- data/app/models/scimitar/schema/address.rb +0 -1
- data/app/models/scimitar/schema/attribute.rb +5 -14
- data/app/models/scimitar/schema/base.rb +1 -1
- data/app/models/scimitar/schema/vdtp.rb +1 -1
- data/app/models/scimitar/service_provider_configuration.rb +3 -14
- data/config/initializers/scimitar.rb +3 -28
- data/lib/scimitar/support/hash_with_indifferent_case_insensitive_access.rb +10 -140
- data/lib/scimitar/version.rb +2 -2
- data/lib/scimitar.rb +2 -7
- data/spec/apps/dummy/app/controllers/mock_groups_controller.rb +1 -1
- data/spec/apps/dummy/app/models/mock_group.rb +1 -1
- data/spec/apps/dummy/app/models/mock_user.rb +8 -36
- data/spec/apps/dummy/config/application.rb +1 -0
- data/spec/apps/dummy/config/environments/test.rb +28 -5
- data/spec/apps/dummy/config/initializers/scimitar.rb +10 -61
- data/spec/apps/dummy/config/routes.rb +7 -28
- data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +1 -10
- data/spec/apps/dummy/db/migrate/20210308044214_create_join_table_mock_groups_mock_users.rb +3 -8
- data/spec/apps/dummy/db/schema.rb +4 -11
- data/spec/controllers/scimitar/application_controller_spec.rb +3 -126
- data/spec/controllers/scimitar/resource_types_controller_spec.rb +2 -2
- data/spec/controllers/scimitar/schemas_controller_spec.rb +2 -10
- data/spec/models/scimitar/complex_types/address_spec.rb +4 -3
- data/spec/models/scimitar/complex_types/email_spec.rb +2 -0
- data/spec/models/scimitar/lists/query_parser_spec.rb +9 -76
- data/spec/models/scimitar/resources/base_spec.rb +70 -216
- data/spec/models/scimitar/resources/base_validation_spec.rb +2 -27
- data/spec/models/scimitar/resources/mixin_spec.rb +129 -1447
- data/spec/models/scimitar/schema/attribute_spec.rb +3 -22
- data/spec/models/scimitar/schema/base_spec.rb +1 -1
- data/spec/models/scimitar/schema/user_spec.rb +0 -10
- data/spec/requests/active_record_backed_resources_controller_spec.rb +68 -787
- data/spec/requests/application_controller_spec.rb +3 -16
- data/spec/spec_helper.rb +0 -8
- data/spec/support/hash_with_indifferent_case_insensitive_access_spec.rb +0 -108
- metadata +14 -25
- data/LICENSE.txt +0 -21
- data/README.md +0 -710
- data/lib/scimitar/support/utilities.rb +0 -51
- data/spec/apps/dummy/app/controllers/custom_create_mock_users_controller.rb +0 -25
- data/spec/apps/dummy/app/controllers/custom_replace_mock_users_controller.rb +0 -25
- data/spec/apps/dummy/app/controllers/custom_save_mock_users_controller.rb +0 -24
- 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
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
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
|
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
|
-
|
50
|
-
|
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(
|
101
|
-
expect(result['Resources'].size).to eql(
|
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.
|
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
|
48
|
+
it 'applies a filter, with case-insensitive value comparison' do
|
111
49
|
get '/Users', params: {
|
112
50
|
format: :scim,
|
113
|
-
filter: 'name.
|
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
|
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.
|
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
|
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.
|
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
|
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.
|
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
|
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
|
-
|
263
|
-
|
264
|
-
|
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
|
-
|
127
|
+
expect(response.status).to eql(200)
|
128
|
+
result = JSON.parse(response.body)
|
288
129
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
end
|
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
|
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' }
|
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
|
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.
|
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
|
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
|
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
|
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
|
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' }
|
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.
|
281
|
+
put "/Users/#{@u2.id}", params: attributes.merge(format: :scim)
|
538
282
|
}.to_not change { MockUser.count }
|
539
283
|
|
540
|
-
expect(response.status
|
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.
|
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.
|
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
|
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
|
-
|
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
|
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
|
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
|
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.
|
388
|
+
patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
|
744
389
|
}.to_not change { MockUser.count }
|
745
390
|
|
746
|
-
expect(response.status
|
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.
|
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.
|
425
|
+
patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
|
825
426
|
}.to_not change { MockUser.count }
|
826
427
|
|
827
|
-
expect(response.status
|
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.
|
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.
|
457
|
+
patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
|
859
458
|
}.to_not change { MockUser.count }
|
860
459
|
|
861
|
-
expect(response.status
|
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.
|
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.
|
489
|
+
patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
|
893
490
|
}.to_not change { MockUser.count }
|
894
491
|
|
895
|
-
expect(response.status
|
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.
|
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.
|
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
|
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
|
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
|
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.
|
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.
|
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
|
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"
|