scimitar 2.3.0 → 2.4.1
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/models/scimitar/resources/mixin.rb +108 -3
- data/lib/scimitar/version.rb +2 -2
- data/spec/apps/dummy/app/models/mock_user.rb +9 -1
- data/spec/apps/dummy/config/initializers/scimitar.rb +37 -0
- data/spec/apps/dummy/config/routes.rb +1 -0
- data/spec/apps/dummy/db/migrate/20210304014602_create_mock_users.rb +8 -0
- data/spec/apps/dummy/db/schema.rb +2 -0
- data/spec/controllers/scimitar/schemas_controller_spec.rb +2 -2
- data/spec/models/scimitar/resources/base_spec.rb +161 -66
- data/spec/models/scimitar/resources/mixin_spec.rb +673 -5
- data/spec/requests/active_record_backed_resources_controller_spec.rb +136 -0
- metadata +10 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7db2b533d9bc2fe1e359a12ad51fea89bdd5498589451b16b99987ca0cd99f09
|
4
|
+
data.tar.gz: 7b30102df8ee36d8bac2698ef150a3537c7b40f6193a64e3baed7e8d6c8386d4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 238c6bdebc57c0d8c186ad77d8222a79c7f9fad98b86a1d564c2c27d62a58dd809c1a4b2a7a9c57af535728f6a23d36821c12e74be81e8191d96960cdd1d1db9
|
7
|
+
data.tar.gz: 684c778f5e3d03719b3df872ca8efcb29a18cd802360964282e38ed914b095676aeec4556a625c99f2a3de86f898cae1dd989be67614711194fd176a1c5895c1
|
@@ -443,9 +443,30 @@ module Scimitar
|
|
443
443
|
ci_scim_hash = { 'root' => ci_scim_hash }.with_indifferent_case_insensitive_access()
|
444
444
|
end
|
445
445
|
|
446
|
+
# Handle extension schema. Contributed by @bettysteger and
|
447
|
+
# @MorrisFreeman via:
|
448
|
+
#
|
449
|
+
# https://github.com/RIPAGlobal/scimitar/issues/48
|
450
|
+
# https://github.com/RIPAGlobal/scimitar/pull/49
|
451
|
+
#
|
452
|
+
# Note the ":" separating the schema ID (URN) from the attribute.
|
453
|
+
# The nature of JSON rendering / other payloads might lead you to
|
454
|
+
# expect a "." as with any complex types, but that's not the case;
|
455
|
+
# see https://tools.ietf.org/html/rfc7644#section-3.10, or
|
456
|
+
# https://tools.ietf.org/html/rfc7644#section-3.5.2 of which in
|
457
|
+
# particular, https://tools.ietf.org/html/rfc7644#page-35.
|
458
|
+
#
|
459
|
+
paths = []
|
460
|
+
self.class.scim_resource_type.extended_schemas.each do |schema|
|
461
|
+
path_str.downcase.split(schema.id.downcase + ':').drop(1).each do |path|
|
462
|
+
paths += [schema.id] + path.split('.')
|
463
|
+
end
|
464
|
+
end
|
465
|
+
paths = path_str.split('.') if paths.empty?
|
466
|
+
|
446
467
|
self.from_patch_backend!(
|
447
468
|
nature: nature,
|
448
|
-
path:
|
469
|
+
path: paths,
|
449
470
|
value: value,
|
450
471
|
altering_hash: ci_scim_hash
|
451
472
|
)
|
@@ -616,7 +637,19 @@ module Scimitar
|
|
616
637
|
attrs_map_or_leaf_value.each do | scim_attribute, sub_attrs_map_or_leaf_value |
|
617
638
|
next if scim_attribute&.to_s&.downcase == 'id' && path.empty?
|
618
639
|
|
619
|
-
|
640
|
+
# Handle extension schema. Contributed by @bettysteger and
|
641
|
+
# @MorrisFreeman via:
|
642
|
+
#
|
643
|
+
# https://github.com/RIPAGlobal/scimitar/issues/48
|
644
|
+
# https://github.com/RIPAGlobal/scimitar/pull/49
|
645
|
+
#
|
646
|
+
attribute_tree = []
|
647
|
+
resource_class.extended_schemas.each do |schema|
|
648
|
+
attribute_tree << schema.id and break if schema.scim_attributes.any? { |attribute| attribute.name == scim_attribute.to_s }
|
649
|
+
end
|
650
|
+
attribute_tree << scim_attribute.to_s
|
651
|
+
|
652
|
+
sub_scim_hash_or_leaf_value = scim_hash_or_leaf_value&.dig(*attribute_tree)
|
620
653
|
|
621
654
|
self.from_scim_backend!(
|
622
655
|
attrs_map_or_leaf_value: sub_attrs_map_or_leaf_value,
|
@@ -901,14 +934,86 @@ module Scimitar
|
|
901
934
|
else
|
902
935
|
altering_hash[path_component] = value
|
903
936
|
end
|
937
|
+
|
904
938
|
when 'replace'
|
905
939
|
if path_component == 'root'
|
906
940
|
altering_hash[path_component].merge!(value)
|
907
941
|
else
|
908
942
|
altering_hash[path_component] = value
|
909
943
|
end
|
944
|
+
|
945
|
+
# The array check handles payloads seen from e.g. Microsoft for
|
946
|
+
# remove-user-from-group, where contrary to examples in the RFC
|
947
|
+
# which would imply "payload removes all users", there is the
|
948
|
+
# clear intent to remove just one.
|
949
|
+
#
|
950
|
+
# https://tools.ietf.org/html/rfc7644#section-3.5.2.2
|
951
|
+
# https://learn.microsoft.com/en-us/azure/active-directory/app-provisioning/use-scim-to-provision-users-and-groups#update-group-remove-members
|
952
|
+
#
|
953
|
+
# Since remove-all in the face of remove-one is destructive, we
|
954
|
+
# do a special check here to see if there's an array value for
|
955
|
+
# the array path that the payload yielded. If so, we can match
|
956
|
+
# each value against array items and remove just those items.
|
957
|
+
#
|
958
|
+
# There is an additional special case to handle a bad example
|
959
|
+
# from Salesforce:
|
960
|
+
#
|
961
|
+
# https://help.salesforce.com/s/articleView?id=sf.identity_scim_manage_groups.htm&type=5
|
962
|
+
#
|
910
963
|
when 'remove'
|
911
|
-
altering_hash.
|
964
|
+
if altering_hash[path_component].is_a?(Array) && value.present?
|
965
|
+
|
966
|
+
# Handle bad Salesforce example. That might be simply a
|
967
|
+
# documentation error, but just in case...
|
968
|
+
#
|
969
|
+
value = value.values.first if (
|
970
|
+
path_component&.downcase == 'members' &&
|
971
|
+
value.is_a?(Hash) &&
|
972
|
+
value.keys.size == 1 &&
|
973
|
+
value.keys.first&.downcase == 'members'
|
974
|
+
)
|
975
|
+
|
976
|
+
# The Microsoft example provides an array of values, but we
|
977
|
+
# may as well cope with a value specified 'flat'. Promote
|
978
|
+
# such a thing to an Array to simplify the following code.
|
979
|
+
#
|
980
|
+
value = [value] unless value.is_a?(Array)
|
981
|
+
|
982
|
+
# For each value item, delete matching array entries. The
|
983
|
+
# concept of "matching" is:
|
984
|
+
#
|
985
|
+
# * For simple non-Hash values (if possible) just delete on
|
986
|
+
# an exact match
|
987
|
+
#
|
988
|
+
# * For Hash-based values, only delete if all 'patch' keys
|
989
|
+
# are present in the resource and all values thus match.
|
990
|
+
#
|
991
|
+
# Special case to ignore '$ref' from the Microsoft payload.
|
992
|
+
#
|
993
|
+
# Note coercion to strings to account for SCIM vs the usual
|
994
|
+
# tricky case of underlying implementations with (say)
|
995
|
+
# integer primary keys, which all end up as strings anyway.
|
996
|
+
#
|
997
|
+
value.each do | value_item |
|
998
|
+
altering_hash[path_component].delete_if do | item |
|
999
|
+
if item.is_a?(Hash) && value_item.is_a?(Hash)
|
1000
|
+
matched_all = true
|
1001
|
+
value_item.each do | value_key, value_value |
|
1002
|
+
next if value_key == '$ref'
|
1003
|
+
if ! item.key?(value_key) || item[value_key]&.to_s != value_value&.to_s
|
1004
|
+
matched_all = false
|
1005
|
+
end
|
1006
|
+
end
|
1007
|
+
matched_all
|
1008
|
+
else
|
1009
|
+
item&.to_s == value_item&.to_s
|
1010
|
+
end
|
1011
|
+
end
|
1012
|
+
end
|
1013
|
+
else
|
1014
|
+
altering_hash.delete(path_component)
|
1015
|
+
end
|
1016
|
+
|
912
1017
|
end
|
913
1018
|
end
|
914
1019
|
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.4.1'
|
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 = '2023-
|
11
|
+
DATE = '2023-03-02'
|
12
12
|
|
13
13
|
end
|
@@ -15,6 +15,8 @@ class MockUser < ActiveRecord::Base
|
|
15
15
|
work_email_address
|
16
16
|
home_email_address
|
17
17
|
work_phone_number
|
18
|
+
organization
|
19
|
+
department
|
18
20
|
}
|
19
21
|
|
20
22
|
has_and_belongs_to_many :mock_groups
|
@@ -84,7 +86,13 @@ class MockUser < ActiveRecord::Base
|
|
84
86
|
}
|
85
87
|
}
|
86
88
|
],
|
87
|
-
active: :is_active
|
89
|
+
active: :is_active,
|
90
|
+
|
91
|
+
# Custom extension schema - see configuration in
|
92
|
+
# "spec/apps/dummy/config/initializers/scimitar.rb".
|
93
|
+
#
|
94
|
+
organization: :organization,
|
95
|
+
department: :department
|
88
96
|
}
|
89
97
|
end
|
90
98
|
|
@@ -1,5 +1,14 @@
|
|
1
1
|
# Test app configuration.
|
2
2
|
#
|
3
|
+
# Note that as a result of https://github.com/RIPAGlobal/scimitar/issues/48,
|
4
|
+
# tests include a custom extension of the core User schema. A shortcoming of
|
5
|
+
# some of the code from which Scimitar was originally built is that those
|
6
|
+
# extensions are done with class-level ivars, so it is largely impossible (or
|
7
|
+
# at least, impractical in tests) to avoid polluting the core class itself
|
8
|
+
# with the extension.
|
9
|
+
#
|
10
|
+
# All related schema tests are written with this in mind.
|
11
|
+
#
|
3
12
|
Rails.application.config.to_prepare do
|
4
13
|
Scimitar.engine_configuration = Scimitar::EngineConfiguration.new({
|
5
14
|
|
@@ -13,4 +22,32 @@ Rails.application.config.to_prepare do
|
|
13
22
|
end
|
14
23
|
|
15
24
|
})
|
25
|
+
|
26
|
+
module ScimSchemaExtensions
|
27
|
+
module User
|
28
|
+
class Enterprise < Scimitar::Schema::Base
|
29
|
+
def initialize(options = {})
|
30
|
+
super(
|
31
|
+
name: 'ExtendedUser',
|
32
|
+
description: 'Enterprise extension for a User',
|
33
|
+
id: self.class.id,
|
34
|
+
scim_attributes: self.class.scim_attributes
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.id
|
39
|
+
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.scim_attributes
|
43
|
+
[
|
44
|
+
Scimitar::Schema::Attribute.new(name: 'organization', type: 'string'),
|
45
|
+
Scimitar::Schema::Attribute.new(name: 'department', type: 'string')
|
46
|
+
]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
Scimitar::Resources::User.extend_schema ScimSchemaExtensions::User::Enterprise
|
16
53
|
end
|
@@ -3,6 +3,8 @@ class CreateMockUsers < ActiveRecord::Migration[6.1]
|
|
3
3
|
create_table :mock_users, id: :uuid, primary_key: :primary_key do |t|
|
4
4
|
t.timestamps
|
5
5
|
|
6
|
+
# Support part of the core schema
|
7
|
+
#
|
6
8
|
t.text :scim_uid
|
7
9
|
t.text :username
|
8
10
|
t.text :first_name
|
@@ -10,6 +12,12 @@ class CreateMockUsers < ActiveRecord::Migration[6.1]
|
|
10
12
|
t.text :work_email_address
|
11
13
|
t.text :home_email_address
|
12
14
|
t.text :work_phone_number
|
15
|
+
|
16
|
+
# Support the custom extension schema - see configuration in
|
17
|
+
# "spec/apps/dummy/config/initializers/scimitar.rb".
|
18
|
+
#
|
19
|
+
t.text :organization
|
20
|
+
t.text :department
|
13
21
|
end
|
14
22
|
end
|
15
23
|
end
|
@@ -38,6 +38,8 @@ ActiveRecord::Schema[7.0].define(version: 2021_03_08_044214) do
|
|
38
38
|
t.text "work_email_address"
|
39
39
|
t.text "home_email_address"
|
40
40
|
t.text "work_phone_number"
|
41
|
+
t.text "organization"
|
42
|
+
t.text "department"
|
41
43
|
end
|
42
44
|
|
43
45
|
add_foreign_key "mock_groups_users", "mock_groups"
|
@@ -14,9 +14,9 @@ RSpec.describe Scimitar::SchemasController do
|
|
14
14
|
get :index, params: { format: :scim }
|
15
15
|
expect(response).to be_ok
|
16
16
|
parsed_body = JSON.parse(response.body)
|
17
|
-
expect(parsed_body.length).to eql(
|
17
|
+
expect(parsed_body.length).to eql(3)
|
18
18
|
schema_names = parsed_body.map {|schema| schema['name']}
|
19
|
-
expect(schema_names).to match_array(['User', 'Group'])
|
19
|
+
expect(schema_names).to match_array(['User', 'ExtendedUser', 'Group'])
|
20
20
|
end
|
21
21
|
|
22
22
|
it 'returns only the User schema when its id is provided' do
|
@@ -250,90 +250,185 @@ RSpec.describe Scimitar::Resources::Base do
|
|
250
250
|
end # "context 'dynamic setters based on schema' do"
|
251
251
|
|
252
252
|
context 'schema extension' do
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
253
|
+
context 'of custom schema' do
|
254
|
+
ThirdCustomSchema = Class.new(Scimitar::Schema::Base) do
|
255
|
+
def self.id
|
256
|
+
'custom-id'
|
257
|
+
end
|
257
258
|
|
258
|
-
|
259
|
-
|
259
|
+
def self.scim_attributes
|
260
|
+
[ Scimitar::Schema::Attribute.new(name: 'name', type: 'string') ]
|
261
|
+
end
|
260
262
|
end
|
261
|
-
end
|
262
263
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
264
|
+
ExtensionSchema = Class.new(Scimitar::Schema::Base) do
|
265
|
+
def self.id
|
266
|
+
'extension-id'
|
267
|
+
end
|
267
268
|
|
268
|
-
|
269
|
-
|
269
|
+
def self.scim_attributes
|
270
|
+
[ Scimitar::Schema::Attribute.new(name: 'relationship', type: 'string', required: true) ]
|
271
|
+
end
|
270
272
|
end
|
271
|
-
end
|
272
273
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
274
|
+
let(:resource_class) {
|
275
|
+
Class.new(Scimitar::Resources::Base) do
|
276
|
+
set_schema ThirdCustomSchema
|
277
|
+
extend_schema ExtensionSchema
|
277
278
|
|
278
|
-
|
279
|
-
|
279
|
+
def self.endpoint
|
280
|
+
'/gaga'
|
281
|
+
end
|
282
|
+
|
283
|
+
def self.resource_type_id
|
284
|
+
'CustomResource'
|
285
|
+
end
|
280
286
|
end
|
287
|
+
}
|
281
288
|
|
282
|
-
|
283
|
-
|
289
|
+
context '#initialize' do
|
290
|
+
it 'allows setting extension attributes' do
|
291
|
+
resource = resource_class.new('extension-id' => {relationship: 'GAGA'})
|
292
|
+
expect(resource.relationship).to eql('GAGA')
|
284
293
|
end
|
285
|
-
end
|
286
|
-
|
294
|
+
end # "context '#initialize' do"
|
295
|
+
|
296
|
+
context '#as_json' do
|
297
|
+
it 'namespaces the extension attributes' do
|
298
|
+
resource = resource_class.new(relationship: 'GAGA')
|
299
|
+
hash = resource.as_json
|
300
|
+
expect(hash["schemas"]).to eql(['custom-id', 'extension-id'])
|
301
|
+
expect(hash["extension-id"]).to eql("relationship" => 'GAGA')
|
302
|
+
end
|
303
|
+
end # "context '#as_json' do"
|
287
304
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
305
|
+
context '.resource_type' do
|
306
|
+
it 'appends the extension schemas' do
|
307
|
+
resource_type = resource_class.resource_type('http://gaga')
|
308
|
+
expect(resource_type.meta.location).to eql('http://gaga')
|
309
|
+
expect(resource_type.schemaExtensions.count).to eql(1)
|
310
|
+
end
|
294
311
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
expect(hash["schemas"]).to eql(['custom-id', 'extension-id'])
|
300
|
-
expect(hash["extension-id"]).to eql("relationship" => 'GAGA')
|
301
|
-
end
|
302
|
-
end # "context '#as_json' do"
|
312
|
+
context 'validation' do
|
313
|
+
it 'validates into custom schema' do
|
314
|
+
resource = resource_class.new('extension-id' => {})
|
315
|
+
expect(resource.valid?).to eql(false)
|
303
316
|
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
end
|
317
|
+
resource = resource_class.new('extension-id' => {relationship: 'GAGA'})
|
318
|
+
expect(resource.relationship).to eql('GAGA')
|
319
|
+
expect(resource.valid?).to eql(true)
|
320
|
+
end
|
321
|
+
end # context 'validation'
|
322
|
+
end # "context '.resource_type' do"
|
310
323
|
|
311
|
-
context '
|
312
|
-
it '
|
313
|
-
|
314
|
-
expect(
|
324
|
+
context '.find_attribute' do
|
325
|
+
it 'finds in first schema' do
|
326
|
+
found = resource_class().find_attribute('name') # Defined in ThirdCustomSchema
|
327
|
+
expect(found).to be_present
|
328
|
+
expect(found.name).to eql('name')
|
329
|
+
expect(found.type).to eql('string')
|
330
|
+
end
|
315
331
|
|
316
|
-
|
317
|
-
|
318
|
-
expect(
|
332
|
+
it 'finds across schemas' do
|
333
|
+
found = resource_class().find_attribute('relationship') # Defined in ExtensionSchema
|
334
|
+
expect(found).to be_present
|
335
|
+
expect(found.name).to eql('relationship')
|
336
|
+
expect(found.type).to eql('string')
|
319
337
|
end
|
320
|
-
end # context '
|
321
|
-
end # "context '
|
338
|
+
end # "context '.find_attribute' do"
|
339
|
+
end # "context 'of custom schema' do"
|
322
340
|
|
323
|
-
context '
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
expect(found.type).to eql('string')
|
329
|
-
end
|
341
|
+
context 'of core schema' do
|
342
|
+
EnterpriseExtensionSchema = Class.new(Scimitar::Schema::Base) do
|
343
|
+
def self.id
|
344
|
+
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'
|
345
|
+
end
|
330
346
|
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
347
|
+
def self.scim_attributes
|
348
|
+
[
|
349
|
+
Scimitar::Schema::Attribute.new(name: 'organization', type: 'string'),
|
350
|
+
Scimitar::Schema::Attribute.new(name: 'department', type: 'string')
|
351
|
+
]
|
352
|
+
end
|
336
353
|
end
|
337
|
-
|
354
|
+
|
355
|
+
let(:resource_class) {
|
356
|
+
Class.new(Scimitar::Resources::Base) do
|
357
|
+
set_schema Scimitar::Schema::User
|
358
|
+
extend_schema EnterpriseExtensionSchema
|
359
|
+
|
360
|
+
def self.endpoint
|
361
|
+
'/Users'
|
362
|
+
end
|
363
|
+
|
364
|
+
def self.resource_type_id
|
365
|
+
'User'
|
366
|
+
end
|
367
|
+
end
|
368
|
+
}
|
369
|
+
|
370
|
+
context '#initialize' do
|
371
|
+
it 'allows setting extension attributes' do
|
372
|
+
resource = resource_class.new('urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {organization: 'SOMEORG', department: 'SOMEDPT'})
|
373
|
+
|
374
|
+
expect(resource.organization).to eql('SOMEORG')
|
375
|
+
expect(resource.department ).to eql('SOMEDPT')
|
376
|
+
end
|
377
|
+
end # "context '#initialize' do"
|
378
|
+
|
379
|
+
context '#as_json' do
|
380
|
+
it 'namespaces the extension attributes' do
|
381
|
+
resource = resource_class.new(organization: 'SOMEORG', department: 'SOMEDPT')
|
382
|
+
hash = resource.as_json
|
383
|
+
|
384
|
+
expect(hash['schemas']).to eql(['urn:ietf:params:scim:schemas:core:2.0:User', 'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User'])
|
385
|
+
expect(hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']).to eql('organization' => 'SOMEORG', 'department' => 'SOMEDPT')
|
386
|
+
end
|
387
|
+
end # "context '#as_json' do"
|
388
|
+
|
389
|
+
context '.resource_type' do
|
390
|
+
it 'appends the extension schemas' do
|
391
|
+
resource_type = resource_class.resource_type('http://example.com')
|
392
|
+
expect(resource_type.meta.location).to eql('http://example.com')
|
393
|
+
expect(resource_type.schemaExtensions.count).to eql(1)
|
394
|
+
end
|
395
|
+
|
396
|
+
context 'validation' do
|
397
|
+
it 'validates into custom schema' do
|
398
|
+
resource = resource_class.new('urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {})
|
399
|
+
expect(resource.valid?).to eql(false)
|
400
|
+
|
401
|
+
resource = resource_class.new(
|
402
|
+
'urn:ietf:params:scim:schemas:extension:enterprise:2.0:User' => {
|
403
|
+
userName: 'SOMEUSR',
|
404
|
+
organization: 'SOMEORG',
|
405
|
+
department: 'SOMEDPT'
|
406
|
+
}
|
407
|
+
)
|
408
|
+
|
409
|
+
expect(resource.organization).to eql('SOMEORG')
|
410
|
+
expect(resource.department ).to eql('SOMEDPT')
|
411
|
+
expect(resource.valid? ).to eql(true)
|
412
|
+
end
|
413
|
+
end # context 'validation'
|
414
|
+
end # "context '.resource_type' do"
|
415
|
+
|
416
|
+
context '.find_attribute' do
|
417
|
+
it 'finds in first schema' do
|
418
|
+
found = resource_class().find_attribute('userName') # Defined in Scimitar::Schema::User
|
419
|
+
|
420
|
+
expect(found).to be_present
|
421
|
+
expect(found.name).to eql('userName')
|
422
|
+
expect(found.type).to eql('string')
|
423
|
+
end
|
424
|
+
|
425
|
+
it 'finds across schemas' do
|
426
|
+
found = resource_class().find_attribute('organization') # Defined in EnterpriseExtensionSchema
|
427
|
+
expect(found).to be_present
|
428
|
+
expect(found.name).to eql('organization')
|
429
|
+
expect(found.type).to eql('string')
|
430
|
+
end
|
431
|
+
end # "context '.find_attribute' do"
|
432
|
+
end # "context 'of core schema' do"
|
338
433
|
end # "context 'schema extension' do"
|
339
434
|
end
|