scimitar 2.6.0 → 2.7.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/LICENSE.txt +21 -0
- data/README.md +710 -0
- data/app/controllers/scimitar/active_record_backed_resources_controller.rb +53 -17
- data/app/controllers/scimitar/application_controller.rb +13 -4
- data/app/models/scimitar/complex_types/address.rb +0 -6
- data/app/models/scimitar/resource_invalid_error.rb +1 -1
- data/app/models/scimitar/resources/mixin.rb +4 -1
- data/app/models/scimitar/schema/attribute.rb +3 -3
- data/config/initializers/scimitar.rb +3 -3
- data/lib/scimitar/support/utilities.rb +51 -0
- data/lib/scimitar/version.rb +2 -2
- data/lib/scimitar.rb +1 -0
- data/spec/apps/dummy/app/controllers/custom_create_mock_users_controller.rb +25 -0
- data/spec/apps/dummy/app/controllers/custom_replace_mock_users_controller.rb +25 -0
- data/spec/apps/dummy/app/controllers/custom_update_mock_users_controller.rb +25 -0
- data/spec/apps/dummy/config/routes.rb +15 -3
- data/spec/controllers/scimitar/application_controller_spec.rb +56 -2
- data/spec/controllers/scimitar/schemas_controller_spec.rb +1 -1
- data/spec/models/scimitar/complex_types/address_spec.rb +3 -4
- data/spec/models/scimitar/resources/mixin_spec.rb +22 -0
- data/spec/requests/active_record_backed_resources_controller_spec.rb +410 -50
- data/spec/spec_helper.rb +9 -1
- metadata +34 -11
@@ -60,12 +60,20 @@ module Scimitar
|
|
60
60
|
|
61
61
|
# POST (create)
|
62
62
|
#
|
63
|
-
|
63
|
+
# Calls #save! on the new record if no block is given, else invokes the
|
64
|
+
# block, passing it the new ActiveRecord model instance to be saved. It
|
65
|
+
# is up to the block to make any further changes and persist the record.
|
66
|
+
#
|
67
|
+
# Blocks are invoked from within a wrapping database transaction.
|
68
|
+
# ActiveRecord::RecordInvalid exceptions are handled for you, rendering
|
69
|
+
# an appropriate SCIM error.
|
70
|
+
#
|
71
|
+
def create(&block)
|
64
72
|
super do |scim_resource|
|
65
73
|
self.storage_class().transaction do
|
66
74
|
record = self.storage_class().new
|
67
75
|
record.from_scim!(scim_hash: scim_resource.as_json())
|
68
|
-
self.save!(record)
|
76
|
+
self.save!(record, &block)
|
69
77
|
record_to_scim(record)
|
70
78
|
end
|
71
79
|
end
|
@@ -73,12 +81,16 @@ module Scimitar
|
|
73
81
|
|
74
82
|
# PUT (replace)
|
75
83
|
#
|
76
|
-
|
84
|
+
# Calls #save! on the updated record if no block is given, else invokes the
|
85
|
+
# block, passing the updated record which the block must persist, with the
|
86
|
+
# same rules as for #create.
|
87
|
+
#
|
88
|
+
def replace(&block)
|
77
89
|
super do |record_id, scim_resource|
|
78
90
|
self.storage_class().transaction do
|
79
91
|
record = self.find_record(record_id)
|
80
92
|
record.from_scim!(scim_hash: scim_resource.as_json())
|
81
|
-
self.save!(record)
|
93
|
+
self.save!(record, &block)
|
82
94
|
record_to_scim(record)
|
83
95
|
end
|
84
96
|
end
|
@@ -86,12 +98,16 @@ module Scimitar
|
|
86
98
|
|
87
99
|
# PATCH (update)
|
88
100
|
#
|
89
|
-
|
101
|
+
# Calls #save! on the updated record if no block is given, else invokes the
|
102
|
+
# block, passing the updated record which the block must persist, with the
|
103
|
+
# same rules as for #create.
|
104
|
+
#
|
105
|
+
def update(&block)
|
90
106
|
super do |record_id, patch_hash|
|
91
107
|
self.storage_class().transaction do
|
92
108
|
record = self.find_record(record_id)
|
93
109
|
record.from_scim_patch!(patch_hash: patch_hash)
|
94
|
-
self.save!(record)
|
110
|
+
self.save!(record, &block)
|
95
111
|
record_to_scim(record)
|
96
112
|
end
|
97
113
|
end
|
@@ -134,6 +150,17 @@ module Scimitar
|
|
134
150
|
raise NotImplementedError
|
135
151
|
end
|
136
152
|
|
153
|
+
# Return an Array of exceptions that #save! can rescue and handle with a
|
154
|
+
# SCIM error automatically.
|
155
|
+
#
|
156
|
+
def scimitar_rescuable_exceptions
|
157
|
+
[
|
158
|
+
ActiveRecord::RecordInvalid,
|
159
|
+
ActiveRecord::RecordNotSaved,
|
160
|
+
ActiveRecord::RecordNotUnique,
|
161
|
+
]
|
162
|
+
end
|
163
|
+
|
137
164
|
# Find a record by ID. Subclasses can override this if they need special
|
138
165
|
# lookup behaviour.
|
139
166
|
#
|
@@ -158,8 +185,8 @@ module Scimitar
|
|
158
185
|
# If you just let this superclass handle things, it'll call the standard
|
159
186
|
# +#save!+ method on the record. If you pass a block, then this block is
|
160
187
|
# invoked and passed the ActiveRecord model instance to be saved. You can
|
161
|
-
# then do things like calling a different method, using a service object
|
162
|
-
# some kind, perform audit-related operations and so-on.
|
188
|
+
# then do things like calling a different method, using a service object
|
189
|
+
# of some kind, perform audit-related operations and so-on.
|
163
190
|
#
|
164
191
|
# The return value is not used internally, making life easier for
|
165
192
|
# overriding subclasses to "do the right thing" / avoid mistakes (instead
|
@@ -173,16 +200,25 @@ module Scimitar
|
|
173
200
|
else
|
174
201
|
record.save!
|
175
202
|
end
|
176
|
-
rescue
|
177
|
-
|
203
|
+
rescue *self.scimitar_rescuable_exceptions() => exception
|
204
|
+
handle_on_save_exception(record, exception)
|
178
205
|
end
|
179
206
|
|
180
|
-
# Deal with
|
207
|
+
# Deal with exceptions related to errors upon saving, by responding with
|
208
|
+
# an appropriate SCIM error. This is most effective if the record has
|
209
|
+
# validation errors defined, but falls back to the provided exception's
|
210
|
+
# message otherwise.
|
181
211
|
#
|
182
|
-
# +record+::
|
212
|
+
# +record+:: The record that provoked the exception. Mandatory.
|
213
|
+
# +exception+:: The exception that was raised. If omitted, a default of
|
214
|
+
# 'Unknown', in English with no I18n, is used.
|
183
215
|
#
|
184
|
-
def
|
185
|
-
|
216
|
+
def handle_on_save_exception(record, exception = RuntimeError.new('Unknown'))
|
217
|
+
details = if record.errors.present?
|
218
|
+
record.errors.full_messages.join('; ')
|
219
|
+
else
|
220
|
+
exception.message
|
221
|
+
end
|
186
222
|
|
187
223
|
# https://tools.ietf.org/html/rfc7644#page-12
|
188
224
|
#
|
@@ -192,14 +228,14 @@ module Scimitar
|
|
192
228
|
# status code 409 (Conflict) with a "scimType" error code of
|
193
229
|
# "uniqueness"
|
194
230
|
#
|
195
|
-
if record.errors.any? { | e | e.type == :taken }
|
231
|
+
if exception.is_a?(ActiveRecord::RecordNotUnique) || record.errors.any? { | e | e.type == :taken }
|
196
232
|
raise Scimitar::ErrorResponse.new(
|
197
233
|
status: 409,
|
198
234
|
scimType: 'uniqueness',
|
199
|
-
detail:
|
235
|
+
detail: "Operation failed due to a uniqueness constraint: #{details}"
|
200
236
|
)
|
201
237
|
else
|
202
|
-
raise Scimitar::ResourceInvalidError.new(
|
238
|
+
raise Scimitar::ResourceInvalidError.new(details)
|
203
239
|
end
|
204
240
|
end
|
205
241
|
|
@@ -124,8 +124,13 @@ module Scimitar
|
|
124
124
|
#
|
125
125
|
# https://stackoverflow.com/questions/10239970/what-is-the-delimiter-for-www-authenticate-for-multiple-schemes
|
126
126
|
#
|
127
|
-
response.set_header('
|
128
|
-
response.set_header('
|
127
|
+
response.set_header('WWW-Authenticate', 'Basic' ) if Scimitar.engine_configuration.basic_authenticator.present?
|
128
|
+
response.set_header('WWW-Authenticate', 'Bearer') if Scimitar.engine_configuration.token_authenticator.present?
|
129
|
+
|
130
|
+
# No matter what a caller might request via headers, the only content
|
131
|
+
# type we can ever respond with is JSON-for-SCIM.
|
132
|
+
#
|
133
|
+
response.set_header('Content-Type', "#{Mime::Type.lookup_by_extension(:scim)}; charset=utf-8")
|
129
134
|
end
|
130
135
|
|
131
136
|
def authenticate
|
@@ -134,11 +139,15 @@ module Scimitar
|
|
134
139
|
|
135
140
|
def authenticated?
|
136
141
|
result = if Scimitar.engine_configuration.basic_authenticator.present?
|
137
|
-
authenticate_with_http_basic
|
142
|
+
authenticate_with_http_basic do |username, password|
|
143
|
+
instance_exec(username, password, &Scimitar.engine_configuration.basic_authenticator)
|
144
|
+
end
|
138
145
|
end
|
139
146
|
|
140
147
|
result ||= if Scimitar.engine_configuration.token_authenticator.present?
|
141
|
-
authenticate_with_http_token
|
148
|
+
authenticate_with_http_token do |token, options|
|
149
|
+
instance_exec(token, options, &Scimitar.engine_configuration.token_authenticator)
|
150
|
+
end
|
142
151
|
end
|
143
152
|
|
144
153
|
return result
|
@@ -2,7 +2,7 @@ module Scimitar
|
|
2
2
|
class ResourceInvalidError < ErrorResponse
|
3
3
|
|
4
4
|
def initialize(error_message)
|
5
|
-
super(status: 400, scimType: 'invalidValue', detail:"Operation failed since record has become invalid: #{error_message}")
|
5
|
+
super(status: 400, scimType: 'invalidValue', detail: "Operation failed since record has become invalid: #{error_message}")
|
6
6
|
end
|
7
7
|
|
8
8
|
end
|
@@ -952,7 +952,10 @@ module Scimitar
|
|
952
952
|
|
953
953
|
when 'replace'
|
954
954
|
if path_component == 'root'
|
955
|
-
|
955
|
+
dot_pathed_value = value.inject({}) do |hash, (k, v)|
|
956
|
+
hash.deep_merge!(::Scimitar::Support::Utilities.dot_path(k.split('.'), v))
|
957
|
+
end
|
958
|
+
altering_hash[path_component].deep_merge!(dot_pathed_value)
|
956
959
|
else
|
957
960
|
altering_hash[path_component] = value
|
958
961
|
end
|
@@ -105,9 +105,9 @@ module Scimitar
|
|
105
105
|
|
106
106
|
def simple_type?(value)
|
107
107
|
(type == 'string' && value.is_a?(String)) ||
|
108
|
-
|
109
|
-
|
110
|
-
|
108
|
+
(type == 'boolean' && (value.is_a?(TrueClass) || value.is_a?(FalseClass))) ||
|
109
|
+
(type == 'integer' && (value.is_a?(Integer))) ||
|
110
|
+
(type == 'dateTime' && valid_date_time?(value))
|
111
111
|
end
|
112
112
|
|
113
113
|
def valid_date_time?(value)
|
@@ -37,11 +37,11 @@ Rails.application.config.to_prepare do # (required for >= Rails 7 / Zeitwerk)
|
|
37
37
|
#
|
38
38
|
Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
|
39
39
|
|
40
|
-
# If you have filters you want to run for any Scimitar action/route, you
|
41
|
-
# define them here. You can also override any shared controller methods
|
40
|
+
# If you have filters you want to run for any Scimitar action/route, you
|
41
|
+
# can define them here. You can also override any shared controller methods
|
42
42
|
# here. For example, you might use a before-action to set up some
|
43
43
|
# multi-tenancy related state, skip Rails CSRF token verification, or
|
44
|
-
#
|
44
|
+
# customise how Scimitar generates URLs:
|
45
45
|
#
|
46
46
|
# application_controller_mixin: Module.new do
|
47
47
|
# def self.included(base)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Scimitar
|
2
|
+
|
3
|
+
# Namespace containing various chunks of Scimitar support code that don't
|
4
|
+
# logically fit into other areas.
|
5
|
+
#
|
6
|
+
module Support
|
7
|
+
|
8
|
+
# A namespace that contains various stand-alone utility methods which act
|
9
|
+
# as helpers for other parts of the code base, without risking namespace
|
10
|
+
# pollution by e.g. being part of a module loaded into a client class.
|
11
|
+
#
|
12
|
+
module Utilities
|
13
|
+
|
14
|
+
# Takes an array of components that usually come from a dotted path such
|
15
|
+
# as <tt>foo.bar.baz</tt>, along with a value that is found at the end of
|
16
|
+
# that path, then converts it into a nested Hash with each level of the
|
17
|
+
# Hash corresponding to a step along the path.
|
18
|
+
#
|
19
|
+
# This was written to help with edge case SCIM uses where (most often, at
|
20
|
+
# least) inbound calls use a dotted notation where nested values are more
|
21
|
+
# commonly accepted; converting to nesting makes it easier for subsequent
|
22
|
+
# processing code, which needs only handle nested Hash data.
|
23
|
+
#
|
24
|
+
# As an example, passing:
|
25
|
+
#
|
26
|
+
# ['foo', 'bar', 'baz'], 'value'
|
27
|
+
#
|
28
|
+
# ...yields:
|
29
|
+
#
|
30
|
+
# {'foo' => {'bar' => {'baz' => 'value'}}}
|
31
|
+
#
|
32
|
+
# Parameters:
|
33
|
+
#
|
34
|
+
# +array+:: Array containing path components, usually acquired from a
|
35
|
+
# string with dot separators and a call to String#split.
|
36
|
+
#
|
37
|
+
# +value+:: The value found at the path indicated by +array+.
|
38
|
+
#
|
39
|
+
# If +array+ is empty, +value+ is returned directly, with no nesting
|
40
|
+
# Hash wrapping it.
|
41
|
+
#
|
42
|
+
def self.dot_path(array, value)
|
43
|
+
return value if array.empty?
|
44
|
+
|
45
|
+
{}.tap do | hash |
|
46
|
+
hash[array.shift()] = self.dot_path(array, value)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/scimitar/version.rb
CHANGED
@@ -3,11 +3,11 @@ module Scimitar
|
|
3
3
|
# Gem version. If this changes, be sure to re-run "bundle install" or
|
4
4
|
# "bundle update".
|
5
5
|
#
|
6
|
-
VERSION = '2.
|
6
|
+
VERSION = '2.7.0'
|
7
7
|
|
8
8
|
# Date for VERSION. If this changes, be sure to re-run "bundle install"
|
9
9
|
# or "bundle update".
|
10
10
|
#
|
11
|
-
DATE = '
|
11
|
+
DATE = '2024-01-15'
|
12
12
|
|
13
13
|
end
|
data/lib/scimitar.rb
CHANGED
@@ -0,0 +1,25 @@
|
|
1
|
+
# For tests only - uses custom 'create' implementation which passes a block to
|
2
|
+
# Scimitar::ActiveRecordBackedResourcesController#create.
|
3
|
+
#
|
4
|
+
class CustomCreateMockUsersController < Scimitar::ActiveRecordBackedResourcesController
|
5
|
+
|
6
|
+
OVERRIDDEN_NAME = SecureRandom.uuid
|
7
|
+
|
8
|
+
def create
|
9
|
+
super do | resource |
|
10
|
+
resource.first_name = OVERRIDDEN_NAME
|
11
|
+
resource.save!
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def storage_class
|
18
|
+
MockUser
|
19
|
+
end
|
20
|
+
|
21
|
+
def storage_scope
|
22
|
+
MockUser.all
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# For tests only - uses custom 'replace' implementation which passes a block to
|
2
|
+
# Scimitar::ActiveRecordBackedResourcesController#create.
|
3
|
+
#
|
4
|
+
class CustomReplaceMockUsersController < Scimitar::ActiveRecordBackedResourcesController
|
5
|
+
|
6
|
+
OVERRIDDEN_NAME = SecureRandom.uuid
|
7
|
+
|
8
|
+
def replace
|
9
|
+
super do | resource |
|
10
|
+
resource.first_name = OVERRIDDEN_NAME
|
11
|
+
resource.save!
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def storage_class
|
18
|
+
MockUser
|
19
|
+
end
|
20
|
+
|
21
|
+
def storage_scope
|
22
|
+
MockUser.all
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# For tests only - uses custom 'update' implementation which passes a block to
|
2
|
+
# Scimitar::ActiveRecordBackedResourcesController#create.
|
3
|
+
#
|
4
|
+
class CustomUpdateMockUsersController < Scimitar::ActiveRecordBackedResourcesController
|
5
|
+
|
6
|
+
OVERRIDDEN_NAME = SecureRandom.uuid
|
7
|
+
|
8
|
+
def update
|
9
|
+
super do | resource |
|
10
|
+
resource.first_name = OVERRIDDEN_NAME
|
11
|
+
resource.save!
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def storage_class
|
18
|
+
MockUser
|
19
|
+
end
|
20
|
+
|
21
|
+
def storage_scope
|
22
|
+
MockUser.all
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -17,14 +17,26 @@ Rails.application.routes.draw do
|
|
17
17
|
get 'Groups/:id', to: 'mock_groups#show'
|
18
18
|
patch 'Groups/:id', to: 'mock_groups#update'
|
19
19
|
|
20
|
-
# For testing blocks passed to ActiveRecordBackedResourcesController#
|
20
|
+
# For testing blocks passed to ActiveRecordBackedResourcesController#create,
|
21
|
+
# #update, #replace and #destroy.
|
21
22
|
#
|
23
|
+
post 'CustomCreateUsers', to: 'custom_create_mock_users#create'
|
24
|
+
patch 'CustomUpdateUsers/:id', to: 'custom_update_mock_users#update'
|
25
|
+
put 'CustomReplaceUsers/:id', to: 'custom_replace_mock_users#replace'
|
22
26
|
delete 'CustomDestroyUsers/:id', to: 'custom_destroy_mock_users#destroy'
|
23
27
|
|
28
|
+
# Needed because the auto-render of most of the above includes a 'url_for'
|
29
|
+
# call for a 'show' action, so we must include routes (implemented in the
|
30
|
+
# base class) for the "show" endpoint.
|
31
|
+
#
|
32
|
+
get 'CustomCreateUsers/:id', to: 'custom_create_mock_users#show'
|
33
|
+
get 'CustomUpdateUsers/:id', to: 'custom_update_mock_users#show'
|
34
|
+
get 'CustomReplaceUsers/:id', to: 'custom_replace_mock_users#show'
|
35
|
+
|
24
36
|
# For testing blocks passed to ActiveRecordBackedResourcesController#save!
|
25
37
|
#
|
26
|
-
post 'CustomSaveUsers',
|
27
|
-
get
|
38
|
+
post 'CustomSaveUsers', to: 'custom_save_mock_users#create'
|
39
|
+
get 'CustomSaveUsers/:id', to: 'custom_save_mock_users#show'
|
28
40
|
|
29
41
|
# For testing environment inside Scimitar::ApplicationController subclasses.
|
30
42
|
#
|
@@ -24,7 +24,7 @@ RSpec.describe Scimitar::ApplicationController do
|
|
24
24
|
get :index, params: { format: :scim }
|
25
25
|
expect(response).to be_ok
|
26
26
|
expect(JSON.parse(response.body)).to eql({ 'message' => 'cool, cool!' })
|
27
|
-
expect(response.headers['
|
27
|
+
expect(response.headers['WWW-Authenticate']).to eql('Basic')
|
28
28
|
end
|
29
29
|
|
30
30
|
it 'renders failure with bad password' do
|
@@ -84,7 +84,61 @@ RSpec.describe Scimitar::ApplicationController do
|
|
84
84
|
get :index, params: { format: :scim }
|
85
85
|
expect(response).to be_ok
|
86
86
|
expect(JSON.parse(response.body)).to eql({ 'message' => 'cool, cool!' })
|
87
|
-
expect(response.headers['
|
87
|
+
expect(response.headers['WWW-Authenticate']).to eql('Bearer')
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'renders failure with bad token' do
|
91
|
+
request.env['HTTP_AUTHORIZATION'] = 'Bearer Invalid'
|
92
|
+
|
93
|
+
get :index, params: { format: :scim }
|
94
|
+
expect(response).not_to be_ok
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'renders failure with blank token' do
|
98
|
+
request.env['HTTP_AUTHORIZATION'] = 'Bearer'
|
99
|
+
|
100
|
+
get :index, params: { format: :scim }
|
101
|
+
expect(response).not_to be_ok
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'renders failure with missing header' do
|
105
|
+
get :index, params: { format: :scim }
|
106
|
+
expect(response).not_to be_ok
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'authenticator evaluated within controller context' do
|
111
|
+
|
112
|
+
# Define a controller with a custom instance method 'valid_token'.
|
113
|
+
#
|
114
|
+
controller do
|
115
|
+
def index
|
116
|
+
render json: { 'message' => 'cool, cool!' }, format: :scim
|
117
|
+
end
|
118
|
+
|
119
|
+
def valid_token
|
120
|
+
'B'
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Call the above controller method from the token authenticator Proc,
|
125
|
+
# proving that it was executed in the controller's context.
|
126
|
+
#
|
127
|
+
before do
|
128
|
+
Scimitar.engine_configuration = Scimitar::EngineConfiguration.new(
|
129
|
+
token_authenticator: Proc.new do | token, options |
|
130
|
+
token == self.valid_token()
|
131
|
+
end
|
132
|
+
)
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'renders success when valid creds are given' do
|
136
|
+
request.env['HTTP_AUTHORIZATION'] = 'Bearer B'
|
137
|
+
|
138
|
+
get :index, params: { format: :scim }
|
139
|
+
expect(response).to be_ok
|
140
|
+
expect(JSON.parse(response.body)).to eql({ 'message' => 'cool, cool!' })
|
141
|
+
expect(response.headers['WWW-Authenticate']).to eql('Bearer')
|
88
142
|
end
|
89
143
|
|
90
144
|
it 'renders failure with bad token' do
|
@@ -27,7 +27,7 @@ RSpec.describe Scimitar::SchemasController do
|
|
27
27
|
expect(parsed_body['name']).to eql('User')
|
28
28
|
end
|
29
29
|
|
30
|
-
it 'includes the controller
|
30
|
+
it 'includes the controller customised schema location' do
|
31
31
|
get :index, params: { name: Scimitar::Schema::User.id, format: :scim }
|
32
32
|
expect(response).to be_ok
|
33
33
|
parsed_body = JSON.parse(response.body)
|
@@ -2,8 +2,8 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
RSpec.describe Scimitar::ComplexTypes::Address do
|
4
4
|
context '#as_json' do
|
5
|
-
it 'assumes
|
6
|
-
expect(described_class.new.as_json).to eq(
|
5
|
+
it 'assumes no defaults' do
|
6
|
+
expect(described_class.new.as_json).to eq({})
|
7
7
|
end
|
8
8
|
|
9
9
|
it 'allows a custom address type' do
|
@@ -11,9 +11,8 @@ RSpec.describe Scimitar::ComplexTypes::Address do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
it 'shows the set address' do
|
14
|
-
expect(described_class.new(country: 'NZ').as_json).to eq('
|
14
|
+
expect(described_class.new(country: 'NZ').as_json).to eq('country' => 'NZ')
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
18
|
end
|
19
|
-
|
@@ -2717,6 +2717,28 @@ RSpec.describe Scimitar::Resources::Mixin do
|
|
2717
2717
|
expect(@instance.username).to eql('1234')
|
2718
2718
|
end
|
2719
2719
|
|
2720
|
+
it 'which updates nested values using root syntax' do
|
2721
|
+
@instance.update!(first_name: 'Foo', last_name: 'Bar')
|
2722
|
+
|
2723
|
+
path = 'name.givenName'
|
2724
|
+
path = path.upcase if force_upper_case
|
2725
|
+
|
2726
|
+
patch = {
|
2727
|
+
'schemas' => ['urn:ietf:params:scim:api:messages:2.0:PatchOp'],
|
2728
|
+
'Operations' => [
|
2729
|
+
{
|
2730
|
+
'op' => 'replace',
|
2731
|
+
'value' => {
|
2732
|
+
path => 'Baz'
|
2733
|
+
}
|
2734
|
+
}
|
2735
|
+
]
|
2736
|
+
}
|
2737
|
+
|
2738
|
+
@instance.from_scim_patch!(patch_hash: patch)
|
2739
|
+
expect(@instance.first_name).to eql('Baz')
|
2740
|
+
end
|
2741
|
+
|
2720
2742
|
it 'which updates nested values' do
|
2721
2743
|
@instance.update!(first_name: 'Foo', last_name: 'Bar')
|
2722
2744
|
|