scimitar 1.8.1 → 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 -14
- data/app/models/scimitar/resources/mixin.rb +13 -140
- 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/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 -208
- data/spec/models/scimitar/resources/base_validation_spec.rb +2 -27
- data/spec/models/scimitar/resources/mixin_spec.rb +43 -790
- 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 +66 -709
- data/spec/requests/application_controller_spec.rb +3 -16
- data/spec/spec_helper.rb +0 -8
- 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)
|
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 }
|
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 }
|
283
126
|
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
result = JSON.parse(response.body)
|
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
|
@@ -349,7 +180,6 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
349
180
|
givenName: 'Given',
|
350
181
|
familyName: 'Family'
|
351
182
|
},
|
352
|
-
meta: { resourceType: 'User' },
|
353
183
|
emails: [
|
354
184
|
{
|
355
185
|
type: 'work',
|
@@ -371,9 +201,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
371
201
|
mock_after = MockUser.all.to_a
|
372
202
|
new_mock = (mock_after - mock_before).first
|
373
203
|
|
374
|
-
expect(response.status
|
375
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
376
|
-
|
204
|
+
expect(response.status).to eql(201)
|
377
205
|
result = JSON.parse(response.body)
|
378
206
|
|
379
207
|
expect(result['id']).to eql(new_mock.id.to_s)
|
@@ -404,11 +232,8 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
404
232
|
}
|
405
233
|
}.to_not change { MockUser.count }
|
406
234
|
|
407
|
-
expect(response.status
|
408
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
409
|
-
|
235
|
+
expect(response.status).to eql(409)
|
410
236
|
result = JSON.parse(response.body)
|
411
|
-
|
412
237
|
expect(result['scimType']).to eql('uniqueness')
|
413
238
|
expect(result['detail']).to include('already been taken')
|
414
239
|
end
|
@@ -421,11 +246,8 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
421
246
|
}
|
422
247
|
}.to_not change { MockUser.count }
|
423
248
|
|
424
|
-
expect(response.status
|
425
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
426
|
-
|
249
|
+
expect(response.status).to eql(400)
|
427
250
|
result = JSON.parse(response.body)
|
428
|
-
|
429
251
|
expect(result['scimType']).to eql('invalidValue')
|
430
252
|
expect(result['detail']).to include('is required')
|
431
253
|
end
|
@@ -438,83 +260,12 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
438
260
|
}
|
439
261
|
}.to_not change { MockUser.count }
|
440
262
|
|
441
|
-
expect(response.status
|
442
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
443
|
-
|
263
|
+
expect(response.status).to eql(400)
|
444
264
|
result = JSON.parse(response.body)
|
445
265
|
|
446
266
|
expect(result['scimType']).to eql('invalidValue')
|
447
267
|
expect(result['detail']).to include('is reserved')
|
448
268
|
end
|
449
|
-
|
450
|
-
context 'with a block' do
|
451
|
-
it 'invokes the block' do
|
452
|
-
mock_before = MockUser.all.to_a
|
453
|
-
|
454
|
-
expect_any_instance_of(CustomCreateMockUsersController).to receive(:create).once.and_call_original
|
455
|
-
expect {
|
456
|
-
post "/CustomCreateUsers", params: {
|
457
|
-
format: :scim,
|
458
|
-
userName: '4' # Minimum required by schema
|
459
|
-
}
|
460
|
-
}.to change { MockUser.count }.by(1)
|
461
|
-
|
462
|
-
mock_after = MockUser.all.to_a
|
463
|
-
new_mock = (mock_after - mock_before).first
|
464
|
-
|
465
|
-
expect(response.status ).to eql(201)
|
466
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
467
|
-
|
468
|
-
result = JSON.parse(response.body)
|
469
|
-
|
470
|
-
expect(result['id']).to eql(new_mock.id.to_s)
|
471
|
-
expect(result['meta']['resourceType']).to eql('User')
|
472
|
-
expect(new_mock.first_name).to eql(CustomCreateMockUsersController::OVERRIDDEN_NAME)
|
473
|
-
end
|
474
|
-
|
475
|
-
it 'returns 409 for duplicates (by Rails validation)' do
|
476
|
-
existing_user = MockUser.create!(
|
477
|
-
username: '4',
|
478
|
-
first_name: 'Will Be Overridden',
|
479
|
-
last_name: 'Baz',
|
480
|
-
home_email_address: 'random@test.com',
|
481
|
-
scim_uid: '999'
|
482
|
-
)
|
483
|
-
|
484
|
-
expect_any_instance_of(CustomCreateMockUsersController).to receive(:create).once.and_call_original
|
485
|
-
expect {
|
486
|
-
post "/CustomCreateUsers", params: {
|
487
|
-
format: :scim,
|
488
|
-
userName: '4' # Already exists
|
489
|
-
}
|
490
|
-
}.to_not change { MockUser.count }
|
491
|
-
|
492
|
-
expect(response.status ).to eql(409)
|
493
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
494
|
-
|
495
|
-
result = JSON.parse(response.body)
|
496
|
-
|
497
|
-
expect(result['scimType']).to eql('uniqueness')
|
498
|
-
expect(result['detail']).to include('already been taken')
|
499
|
-
end
|
500
|
-
|
501
|
-
it 'notes Rails validation failures' do
|
502
|
-
expect_any_instance_of(CustomCreateMockUsersController).to receive(:create).once.and_call_original
|
503
|
-
expect {
|
504
|
-
post "/CustomCreateUsers", params: {
|
505
|
-
format: :scim,
|
506
|
-
userName: MockUser::INVALID_USERNAME
|
507
|
-
}
|
508
|
-
}.to_not change { MockUser.count }
|
509
|
-
|
510
|
-
expect(response.status ).to eql(400)
|
511
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
512
|
-
|
513
|
-
result = JSON.parse(response.body)
|
514
|
-
expect(result['scimType']).to eql('invalidValue')
|
515
|
-
expect(result['detail']).to include('is reserved')
|
516
|
-
end
|
517
|
-
end # "context 'with a block' do"
|
518
269
|
end # "context '#create' do"
|
519
270
|
|
520
271
|
# ===========================================================================
|
@@ -525,22 +276,15 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
525
276
|
attributes = { userName: '4' } # Minimum required by schema
|
526
277
|
attributes = spec_helper_hupcase(attributes) if force_upper_case
|
527
278
|
|
528
|
-
# Prove that certain known pathways are called; can then unit test
|
529
|
-
# those if need be and be sure that this covers #replace actions.
|
530
|
-
#
|
531
279
|
expect_any_instance_of(MockUsersController).to receive(:replace).once.and_call_original
|
532
|
-
expect_any_instance_of(MockUsersController).to receive(:save! ).once.and_call_original
|
533
|
-
|
534
280
|
expect {
|
535
|
-
put "/Users/#{@u2.
|
281
|
+
put "/Users/#{@u2.id}", params: attributes.merge(format: :scim)
|
536
282
|
}.to_not change { MockUser.count }
|
537
283
|
|
538
|
-
expect(response.status
|
539
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
540
|
-
|
284
|
+
expect(response.status).to eql(200)
|
541
285
|
result = JSON.parse(response.body)
|
542
286
|
|
543
|
-
expect(result['id']).to eql(@u2.
|
287
|
+
expect(result['id']).to eql(@u2.id.to_s)
|
544
288
|
expect(result['meta']['resourceType']).to eql('User')
|
545
289
|
|
546
290
|
@u2.reload
|
@@ -562,17 +306,14 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
562
306
|
|
563
307
|
it 'notes schema validation failures' do
|
564
308
|
expect {
|
565
|
-
put "/Users/#{@u2.
|
309
|
+
put "/Users/#{@u2.id}", params: {
|
566
310
|
format: :scim
|
567
311
|
# userName parameter is required by schema, but missing
|
568
312
|
}
|
569
313
|
}.to_not change { MockUser.count }
|
570
314
|
|
571
|
-
expect(response.status
|
572
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
573
|
-
|
315
|
+
expect(response.status).to eql(400)
|
574
316
|
result = JSON.parse(response.body)
|
575
|
-
|
576
317
|
expect(result['scimType']).to eql('invalidValue')
|
577
318
|
expect(result['detail']).to include('is required')
|
578
319
|
|
@@ -586,15 +327,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
586
327
|
|
587
328
|
it 'notes Rails validation failures' do
|
588
329
|
expect {
|
589
|
-
|
330
|
+
post "/Users", params: {
|
590
331
|
format: :scim,
|
591
332
|
userName: MockUser::INVALID_USERNAME
|
592
333
|
}
|
593
334
|
}.to_not change { MockUser.count }
|
594
335
|
|
595
|
-
expect(response.status
|
596
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
597
|
-
|
336
|
+
expect(response.status).to eql(400)
|
598
337
|
result = JSON.parse(response.body)
|
599
338
|
|
600
339
|
expect(result['scimType']).to eql('invalidValue')
|
@@ -616,65 +355,10 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
616
355
|
}
|
617
356
|
}.to_not change { MockUser.count }
|
618
357
|
|
619
|
-
expect(response.status
|
620
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
621
|
-
|
358
|
+
expect(response.status).to eql(404)
|
622
359
|
result = JSON.parse(response.body)
|
623
|
-
|
624
360
|
expect(result['status']).to eql('404')
|
625
361
|
end
|
626
|
-
|
627
|
-
context 'with a block' do
|
628
|
-
it 'invokes the block' do
|
629
|
-
attributes = { userName: '4' } # Minimum required by schema
|
630
|
-
|
631
|
-
expect_any_instance_of(CustomReplaceMockUsersController).to receive(:replace).once.and_call_original
|
632
|
-
expect {
|
633
|
-
put "/CustomReplaceUsers/#{@u2.primary_key}", params: {
|
634
|
-
format: :scim,
|
635
|
-
userName: '4'
|
636
|
-
}
|
637
|
-
}.to_not change { MockUser.count }
|
638
|
-
|
639
|
-
expect(response.status ).to eql(200)
|
640
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
641
|
-
|
642
|
-
result = JSON.parse(response.body)
|
643
|
-
|
644
|
-
expect(result['id']).to eql(@u2.primary_key.to_s)
|
645
|
-
expect(result['meta']['resourceType']).to eql('User')
|
646
|
-
|
647
|
-
@u2.reload
|
648
|
-
|
649
|
-
expect(@u2.username ).to eql('4')
|
650
|
-
expect(@u2.first_name).to eql(CustomReplaceMockUsersController::OVERRIDDEN_NAME)
|
651
|
-
end
|
652
|
-
|
653
|
-
it 'notes Rails validation failures' do
|
654
|
-
expect_any_instance_of(CustomReplaceMockUsersController).to receive(:replace).once.and_call_original
|
655
|
-
expect {
|
656
|
-
put "/CustomReplaceUsers/#{@u2.primary_key}", params: {
|
657
|
-
format: :scim,
|
658
|
-
userName: MockUser::INVALID_USERNAME
|
659
|
-
}
|
660
|
-
}.to_not change { MockUser.count }
|
661
|
-
|
662
|
-
expect(response.status ).to eql(400)
|
663
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
664
|
-
|
665
|
-
result = JSON.parse(response.body)
|
666
|
-
|
667
|
-
expect(result['scimType']).to eql('invalidValue')
|
668
|
-
expect(result['detail']).to include('is reserved')
|
669
|
-
|
670
|
-
@u2.reload
|
671
|
-
|
672
|
-
expect(@u2.username).to eql('2')
|
673
|
-
expect(@u2.first_name).to eql('Foo')
|
674
|
-
expect(@u2.last_name).to eql('Bar')
|
675
|
-
expect(@u2.home_email_address).to eql('home_2@test.com')
|
676
|
-
end
|
677
|
-
end # "context 'with a block' do"
|
678
362
|
end # "context '#replace' do"
|
679
363
|
|
680
364
|
# ===========================================================================
|
@@ -699,22 +383,15 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
699
383
|
|
700
384
|
payload = spec_helper_hupcase(payload) if force_upper_case
|
701
385
|
|
702
|
-
# Prove that certain known pathways are called; can then unit test
|
703
|
-
# those if need be and be sure that this covers #update actions.
|
704
|
-
#
|
705
386
|
expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
|
706
|
-
expect_any_instance_of(MockUsersController).to receive(:save! ).once.and_call_original
|
707
|
-
|
708
387
|
expect {
|
709
|
-
patch "/Users/#{@u2.
|
388
|
+
patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
|
710
389
|
}.to_not change { MockUser.count }
|
711
390
|
|
712
|
-
expect(response.status
|
713
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
714
|
-
|
391
|
+
expect(response.status).to eql(200)
|
715
392
|
result = JSON.parse(response.body)
|
716
393
|
|
717
|
-
expect(result['id']).to eql(@u2.
|
394
|
+
expect(result['id']).to eql(@u2.id.to_s)
|
718
395
|
expect(result['meta']['resourceType']).to eql('User')
|
719
396
|
|
720
397
|
@u2.reload
|
@@ -745,15 +422,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
745
422
|
|
746
423
|
expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
|
747
424
|
expect {
|
748
|
-
patch "/Users/#{@u2.
|
425
|
+
patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
|
749
426
|
}.to_not change { MockUser.count }
|
750
427
|
|
751
|
-
expect(response.status
|
752
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
753
|
-
|
428
|
+
expect(response.status).to eql(200)
|
754
429
|
result = JSON.parse(response.body)
|
755
430
|
|
756
|
-
expect(result['id']).to eql(@u2.
|
431
|
+
expect(result['id']).to eql(@u2.id.to_s)
|
757
432
|
expect(result['meta']['resourceType']).to eql('User')
|
758
433
|
|
759
434
|
@u2.reload
|
@@ -779,15 +454,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
779
454
|
|
780
455
|
expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
|
781
456
|
expect {
|
782
|
-
patch "/Users/#{@u2.
|
457
|
+
patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
|
783
458
|
}.to_not change { MockUser.count }
|
784
459
|
|
785
|
-
expect(response.status
|
786
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
787
|
-
|
460
|
+
expect(response.status).to eql(200)
|
788
461
|
result = JSON.parse(response.body)
|
789
462
|
|
790
|
-
expect(result['id']).to eql(@u2.
|
463
|
+
expect(result['id']).to eql(@u2.id.to_s)
|
791
464
|
expect(result['meta']['resourceType']).to eql('User')
|
792
465
|
|
793
466
|
@u2.reload
|
@@ -813,15 +486,13 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
813
486
|
|
814
487
|
expect_any_instance_of(MockUsersController).to receive(:update).once.and_call_original
|
815
488
|
expect {
|
816
|
-
patch "/Users/#{@u2.
|
489
|
+
patch "/Users/#{@u2.id}", params: payload.merge(format: :scim)
|
817
490
|
}.to_not change { MockUser.count }
|
818
491
|
|
819
|
-
expect(response.status
|
820
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
821
|
-
|
492
|
+
expect(response.status).to eql(200)
|
822
493
|
result = JSON.parse(response.body)
|
823
494
|
|
824
|
-
expect(result['id']).to eql(@u2.
|
495
|
+
expect(result['id']).to eql(@u2.id.to_s)
|
825
496
|
expect(result['meta']['resourceType']).to eql('User')
|
826
497
|
|
827
498
|
@u2.reload
|
@@ -845,7 +516,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
845
516
|
|
846
517
|
it 'notes Rails validation failures' do
|
847
518
|
expect {
|
848
|
-
patch "/Users/#{@u2.
|
519
|
+
patch "/Users/#{@u2.id}", params: {
|
849
520
|
format: :scim,
|
850
521
|
Operations: [
|
851
522
|
{
|
@@ -857,9 +528,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
857
528
|
}
|
858
529
|
}.to_not change { MockUser.count }
|
859
530
|
|
860
|
-
expect(response.status
|
861
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
862
|
-
|
531
|
+
expect(response.status).to eql(400)
|
863
532
|
result = JSON.parse(response.body)
|
864
533
|
|
865
534
|
expect(result['scimType']).to eql('invalidValue')
|
@@ -887,329 +556,20 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
887
556
|
}
|
888
557
|
}.to_not change { MockUser.count }
|
889
558
|
|
890
|
-
expect(response.status
|
891
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
892
|
-
|
559
|
+
expect(response.status).to eql(404)
|
893
560
|
result = JSON.parse(response.body)
|
894
|
-
|
895
561
|
expect(result['status']).to eql('404')
|
896
562
|
end
|
897
|
-
|
898
|
-
context 'when removing users from groups' do
|
899
|
-
before :each do
|
900
|
-
@g1.mock_users << @u1
|
901
|
-
@g1.mock_users << @u2
|
902
|
-
@g1.mock_users << @u3
|
903
|
-
|
904
|
-
# (Self-check) Verify group representation
|
905
|
-
#
|
906
|
-
get "/Groups/#{@g1.id}", params: { format: :scim }
|
907
|
-
|
908
|
-
expect(response.status).to eql(200)
|
909
|
-
result = JSON.parse(response.body)
|
910
|
-
|
911
|
-
expect(result['members'].map { |m| m['value'] }.sort()).to eql(MockUser.pluck(:primary_key).sort())
|
912
|
-
end
|
913
|
-
|
914
|
-
it 'can remove all users' do
|
915
|
-
expect {
|
916
|
-
expect {
|
917
|
-
patch "/Groups/#{@g1.id}", params: {
|
918
|
-
format: :scim,
|
919
|
-
Operations: [
|
920
|
-
{
|
921
|
-
op: 'remove',
|
922
|
-
path: 'members'
|
923
|
-
}
|
924
|
-
]
|
925
|
-
}
|
926
|
-
}.to_not change { MockUser.count }
|
927
|
-
}.to_not change { MockGroup.count }
|
928
|
-
|
929
|
-
get "/Groups/#{@g1.id}", params: { format: :scim }
|
930
|
-
|
931
|
-
expect(response.status ).to eql(200)
|
932
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
933
|
-
|
934
|
-
result = JSON.parse(response.body)
|
935
|
-
|
936
|
-
expect(result['members']).to be_empty
|
937
|
-
expect(@g1.reload().mock_users).to be_empty
|
938
|
-
end
|
939
|
-
|
940
|
-
# Define via 'let':
|
941
|
-
#
|
942
|
-
# * Hash 'payload', to send via 'patch'
|
943
|
-
# * MockUser 'removed_user', which is the user that should be removed
|
944
|
-
#
|
945
|
-
shared_examples 'a user remover' do
|
946
|
-
it 'which removes the identified user' do
|
947
|
-
expect {
|
948
|
-
expect {
|
949
|
-
patch "/Groups/#{@g1.id}", params: payload()
|
950
|
-
}.to_not change { MockUser.count }
|
951
|
-
}.to_not change { MockGroup.count }
|
952
|
-
|
953
|
-
expected_remaining_user_ids = MockUser
|
954
|
-
.where.not(primary_key: removed_user().id)
|
955
|
-
.pluck(:primary_key)
|
956
|
-
.sort()
|
957
|
-
|
958
|
-
get "/Groups/#{@g1.id}", params: { format: :scim }
|
959
|
-
|
960
|
-
expect(response.status ).to eql(200)
|
961
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
962
|
-
|
963
|
-
result = JSON.parse(response.body)
|
964
|
-
|
965
|
-
expect(result['members'].map { |m| m['value'] }.sort()).to eql(expected_remaining_user_ids)
|
966
|
-
expect(@g1.reload().mock_users.map(&:primary_key).sort()).to eql(expected_remaining_user_ids)
|
967
|
-
end
|
968
|
-
end
|
969
|
-
|
970
|
-
# https://tools.ietf.org/html/rfc7644#section-3.5.2.2
|
971
|
-
#
|
972
|
-
context 'and using an RFC-compliant payload' do
|
973
|
-
let(:removed_user) { @u2 }
|
974
|
-
let(:payload) do
|
975
|
-
{
|
976
|
-
format: :scim,
|
977
|
-
Operations: [
|
978
|
-
{
|
979
|
-
op: 'remove',
|
980
|
-
path: "members[value eq \"#{removed_user().primary_key}\"]",
|
981
|
-
}
|
982
|
-
]
|
983
|
-
}
|
984
|
-
end
|
985
|
-
|
986
|
-
it_behaves_like 'a user remover'
|
987
|
-
end # context 'and using an RFC-compliant payload' do
|
988
|
-
|
989
|
-
# https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups#update-group-remove-members
|
990
|
-
#
|
991
|
-
context 'and using a Microsoft variant payload' do
|
992
|
-
let(:removed_user) { @u2 }
|
993
|
-
let(:payload) do
|
994
|
-
{
|
995
|
-
format: :scim,
|
996
|
-
Operations: [
|
997
|
-
{
|
998
|
-
op: 'remove',
|
999
|
-
path: 'members',
|
1000
|
-
value: [{
|
1001
|
-
'$ref' => nil,
|
1002
|
-
'value' => removed_user().primary_key
|
1003
|
-
}]
|
1004
|
-
}
|
1005
|
-
]
|
1006
|
-
}
|
1007
|
-
end
|
1008
|
-
|
1009
|
-
it_behaves_like 'a user remover'
|
1010
|
-
end # context 'and using a Microsoft variant payload' do
|
1011
|
-
|
1012
|
-
# https://help.salesforce.com/s/articleView?id=sf.identity_scim_manage_groups.htm&type=5
|
1013
|
-
#
|
1014
|
-
context 'and using a Salesforce variant payload' do
|
1015
|
-
let(:removed_user) { @u2 }
|
1016
|
-
let(:payload) do
|
1017
|
-
{
|
1018
|
-
format: :scim,
|
1019
|
-
Operations: [
|
1020
|
-
{
|
1021
|
-
op: 'remove',
|
1022
|
-
path: 'members',
|
1023
|
-
value: {
|
1024
|
-
'members' => [{
|
1025
|
-
'$ref' => nil,
|
1026
|
-
'value' => removed_user().primary_key
|
1027
|
-
}]
|
1028
|
-
}
|
1029
|
-
}
|
1030
|
-
]
|
1031
|
-
}
|
1032
|
-
end
|
1033
|
-
|
1034
|
-
it_behaves_like 'a user remover'
|
1035
|
-
end # context 'and using a Salesforce variant payload' do
|
1036
|
-
end # "context 'when removing users from groups' do"
|
1037
|
-
|
1038
|
-
context 'with a block' do
|
1039
|
-
it 'invokes the block' do
|
1040
|
-
payload = {
|
1041
|
-
format: :scim,
|
1042
|
-
Operations: [
|
1043
|
-
{
|
1044
|
-
op: 'add',
|
1045
|
-
path: 'userName',
|
1046
|
-
value: '4'
|
1047
|
-
},
|
1048
|
-
{
|
1049
|
-
op: 'replace',
|
1050
|
-
path: 'emails[type eq "work"]',
|
1051
|
-
value: { type: 'work', value: 'work_4@test.com' }
|
1052
|
-
}
|
1053
|
-
]
|
1054
|
-
}
|
1055
|
-
|
1056
|
-
expect_any_instance_of(CustomUpdateMockUsersController).to receive(:update).once.and_call_original
|
1057
|
-
expect {
|
1058
|
-
patch "/CustomUpdateUsers/#{@u2.primary_key}", params: payload
|
1059
|
-
}.to_not change { MockUser.count }
|
1060
|
-
|
1061
|
-
expect(response.status ).to eql(200)
|
1062
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
1063
|
-
|
1064
|
-
result = JSON.parse(response.body)
|
1065
|
-
|
1066
|
-
expect(result['id']).to eql(@u2.primary_key.to_s)
|
1067
|
-
expect(result['meta']['resourceType']).to eql('User')
|
1068
|
-
|
1069
|
-
@u2.reload
|
1070
|
-
|
1071
|
-
expect(@u2.username ).to eql('4')
|
1072
|
-
expect(@u2.first_name ).to eql(CustomUpdateMockUsersController::OVERRIDDEN_NAME)
|
1073
|
-
expect(@u2.work_email_address).to eql('work_4@test.com')
|
1074
|
-
end
|
1075
|
-
|
1076
|
-
it 'notes Rails validation failures' do
|
1077
|
-
expect_any_instance_of(CustomUpdateMockUsersController).to receive(:update).once.and_call_original
|
1078
|
-
expect {
|
1079
|
-
patch "/CustomUpdateUsers/#{@u2.primary_key}", params: {
|
1080
|
-
format: :scim,
|
1081
|
-
Operations: [
|
1082
|
-
{
|
1083
|
-
op: 'add',
|
1084
|
-
path: 'userName',
|
1085
|
-
value: MockUser::INVALID_USERNAME
|
1086
|
-
}
|
1087
|
-
]
|
1088
|
-
}
|
1089
|
-
}.to_not change { MockUser.count }
|
1090
|
-
|
1091
|
-
expect(response.status ).to eql(400)
|
1092
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
1093
|
-
|
1094
|
-
result = JSON.parse(response.body)
|
1095
|
-
|
1096
|
-
expect(result['scimType']).to eql('invalidValue')
|
1097
|
-
expect(result['detail']).to include('is reserved')
|
1098
|
-
|
1099
|
-
@u2.reload
|
1100
|
-
|
1101
|
-
expect(@u2.username).to eql('2')
|
1102
|
-
expect(@u2.first_name).to eql('Foo')
|
1103
|
-
expect(@u2.last_name).to eql('Bar')
|
1104
|
-
expect(@u2.home_email_address).to eql('home_2@test.com')
|
1105
|
-
end
|
1106
|
-
end # "context 'with a block' do"
|
1107
563
|
end # "context '#update' do"
|
1108
564
|
|
1109
|
-
# ===========================================================================
|
1110
|
-
# In-passing parts of tests above show that #create, #replace and #update all
|
1111
|
-
# route through #save!, so now add some unit tests for that and for exception
|
1112
|
-
# handling overrides invoked via #save!.
|
1113
|
-
# ===========================================================================
|
1114
|
-
|
1115
|
-
context 'overriding #save!' do
|
1116
|
-
it 'invokes a block if given one' do
|
1117
|
-
mock_before = MockUser.all.to_a
|
1118
|
-
attributes = { userName: '5' } # Minimum required by schema
|
1119
|
-
|
1120
|
-
expect_any_instance_of(CustomSaveMockUsersController).to receive(:create).once.and_call_original
|
1121
|
-
expect {
|
1122
|
-
post "/CustomSaveUsers", params: attributes.merge(format: :scim)
|
1123
|
-
}.to change { MockUser.count }.by(1)
|
1124
|
-
|
1125
|
-
mock_after = MockUser.all.to_a
|
1126
|
-
new_mock = (mock_after - mock_before).first
|
1127
|
-
|
1128
|
-
expect(response.status ).to eql(201)
|
1129
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
1130
|
-
|
1131
|
-
expect(new_mock.username).to eql(CustomSaveMockUsersController::CUSTOM_SAVE_BLOCK_USERNAME_INDICATOR)
|
1132
|
-
end
|
1133
|
-
end # "context 'overriding #save!' do
|
1134
|
-
|
1135
|
-
context 'custom on-save exceptions' do
|
1136
|
-
MockUsersController.new.send(:scimitar_rescuable_exceptions).each do | exception_class |
|
1137
|
-
it "handles out-of-box exception #{exception_class}" do
|
1138
|
-
expect_any_instance_of(MockUsersController).to receive(:create).once.and_call_original
|
1139
|
-
expect_any_instance_of(MockUsersController).to receive(:save! ).once.and_call_original
|
1140
|
-
|
1141
|
-
expect_any_instance_of(MockUser).to receive(:save!).once { raise exception_class }
|
1142
|
-
|
1143
|
-
expect {
|
1144
|
-
post "/Users", params: { format: :scim, userName: SecureRandom.uuid }
|
1145
|
-
}.to_not change { MockUser.count }
|
1146
|
-
|
1147
|
-
expected_status, expected_prefix = if exception_class == ActiveRecord::RecordNotUnique
|
1148
|
-
[409, 'Operation failed due to a uniqueness constraint: ']
|
1149
|
-
else
|
1150
|
-
[400, 'Operation failed since record has become invalid: ']
|
1151
|
-
end
|
1152
|
-
|
1153
|
-
expect(response.status ).to eql(expected_status)
|
1154
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
1155
|
-
|
1156
|
-
result = JSON.parse(response.body)
|
1157
|
-
|
1158
|
-
# Check basic SCIM error rendering - good enough given other tests
|
1159
|
-
# elsewhere. Exact message varies by exception.
|
1160
|
-
#
|
1161
|
-
expect(result['detail']).to start_with(expected_prefix)
|
1162
|
-
end
|
1163
|
-
end
|
1164
|
-
|
1165
|
-
it 'handles custom exceptions' do
|
1166
|
-
exception_class = RuntimeError # (for testing only; usually, this would provoke a 500 response)
|
1167
|
-
|
1168
|
-
expect_any_instance_of(MockUsersController).to receive(:create).once.and_call_original
|
1169
|
-
expect_any_instance_of(MockUsersController).to receive(:save! ).once.and_call_original
|
1170
|
-
|
1171
|
-
expect_any_instance_of(MockUsersController).to receive(:scimitar_rescuable_exceptions).once { [ exception_class ] }
|
1172
|
-
expect_any_instance_of(MockUser ).to receive(:save! ).once { raise exception_class }
|
1173
|
-
|
1174
|
-
expect {
|
1175
|
-
post "/Users", params: { format: :scim, userName: SecureRandom.uuid }
|
1176
|
-
}.to_not change { MockUser.count }
|
1177
|
-
|
1178
|
-
expect(response.status ).to eql(400)
|
1179
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
1180
|
-
|
1181
|
-
result = JSON.parse(response.body)
|
1182
|
-
|
1183
|
-
expect(result['detail']).to start_with('Operation failed since record has become invalid: ')
|
1184
|
-
end
|
1185
|
-
|
1186
|
-
it 'reports other exceptions as 500s' do
|
1187
|
-
expect_any_instance_of(MockUsersController).to receive(:create).once.and_call_original
|
1188
|
-
expect_any_instance_of(MockUsersController).to receive(:save! ).once.and_call_original
|
1189
|
-
|
1190
|
-
expect_any_instance_of(MockUser).to receive(:save!).once { raise RuntimeError }
|
1191
|
-
|
1192
|
-
expect {
|
1193
|
-
post "/Users", params: { format: :scim, userName: SecureRandom.uuid }
|
1194
|
-
}.to_not change { MockUser.count }
|
1195
|
-
|
1196
|
-
expect(response.status ).to eql(500)
|
1197
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
1198
|
-
|
1199
|
-
result = JSON.parse(response.body)
|
1200
|
-
|
1201
|
-
expect(result['detail']).to eql('RuntimeError')
|
1202
|
-
end
|
1203
|
-
end
|
1204
|
-
|
1205
565
|
# ===========================================================================
|
1206
566
|
|
1207
567
|
context '#destroy' do
|
1208
|
-
it 'deletes an item if given no
|
568
|
+
it 'deletes an item if given no blok' do
|
1209
569
|
expect_any_instance_of(MockUsersController).to receive(:destroy).once.and_call_original
|
1210
570
|
expect_any_instance_of(MockUser).to receive(:destroy!).once.and_call_original
|
1211
571
|
expect {
|
1212
|
-
delete "/Users/#{@u2.
|
572
|
+
delete "/Users/#{@u2.id}", params: { format: :scim }
|
1213
573
|
}.to change { MockUser.count }.by(-1)
|
1214
574
|
|
1215
575
|
expect(response.status).to eql(204)
|
@@ -1221,7 +581,7 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
1221
581
|
expect_any_instance_of(MockUser).to_not receive(:destroy!)
|
1222
582
|
|
1223
583
|
expect {
|
1224
|
-
delete "/CustomDestroyUsers/#{@u2.
|
584
|
+
delete "/CustomDestroyUsers/#{@u2.id}", params: { format: :scim }
|
1225
585
|
}.to_not change { MockUser.count }
|
1226
586
|
|
1227
587
|
expect(response.status).to eql(204)
|
@@ -1236,11 +596,8 @@ RSpec.describe Scimitar::ActiveRecordBackedResourcesController do
|
|
1236
596
|
delete '/Users/xyz', params: { format: :scim }
|
1237
597
|
}.to_not change { MockUser.count }
|
1238
598
|
|
1239
|
-
expect(response.status
|
1240
|
-
expect(response.headers['Content-Type']).to eql('application/scim+json; charset=utf-8')
|
1241
|
-
|
599
|
+
expect(response.status).to eql(404)
|
1242
600
|
result = JSON.parse(response.body)
|
1243
|
-
|
1244
601
|
expect(result['status']).to eql('404')
|
1245
602
|
end
|
1246
603
|
end # "context '#destroy' do"
|