spec/models/scimitar/resources/mixin_spec.rb in scimitar-1.8.1 vs spec/models/scimitar/resources/mixin_spec.rb in scimitar-1.8.2

- old
+ new

@@ -79,10 +79,106 @@ end include Scimitar::Resources::Mixin end + # A simple schema containing two attributes that looks very like complex + # type "name", except shorter and with "familyName" never returned. + # + NestedReturnedNeverTestNameSchema = Class.new(Scimitar::Schema::Base) do + def self.id + 'nested-returned-never-name-id' + end + + def self.scim_attributes + @scim_attributes ||= [ + Scimitar::Schema::Attribute.new(name: 'familyName', type: 'string', required: true, returned: 'never'), + Scimitar::Schema::Attribute.new(name: 'givenName', type: 'string', required: true) + ] + end + end + + # A complex type that uses the above schema, giving us the ability to define + # an attribute using this complex type, with therefore the *nested* attribute + # "familyName" being never returned. + # + NestedReturnedNeverTestNameType = Class.new(Scimitar::ComplexTypes::Base) do + set_schema NestedReturnedNeverTestNameSchema + end + + # A test schema that uses the above type, the standard name type (but that + # *entire* top-level attribute is never returned) and a simple String item. + # + NestedReturnedNeverTestSchema = Class.new(Scimitar::Schema::Base) do + def self.id + 'nested-returned-never-id' + end + + def self.scim_attributes + [ + Scimitar::Schema::Attribute.new( + name: 'name', complexType: NestedReturnedNeverTestNameType + ), + Scimitar::Schema::Attribute.new( + name: 'privateName', complexType: Scimitar::ComplexTypes::Name, returned: 'never' + ), + Scimitar::Schema::Attribute.new( + name: 'simpleName', type: 'string' + ) + ] + end + end + + # Define a resource that is based upon the above schema. + # + NestedReturnedNeverTestResourse = Class.new(Scimitar::Resources::Base) do + set_schema NestedReturnedNeverTestSchema + end + + # Create a testable model that is our internal representation of the above + # resource. + # + class NestedReturnedNeverTest + include ActiveModel::Model + + def self.scim_resource_type + return NestedReturnedNeverTestResourse + end + + attr_accessor :given_name, + :last_name, + :private_given_name, + :private_last_name, + :simple_name + + def self.scim_attributes_map + return { + name: { + givenName: :given_name, + familyName: :last_name + }, + + privateName: { + givenName: :private_given_name, + familyName: :private_last_name + }, + + simpleName: :simple_name + } + end + + def self.scim_mutable_attributes + return nil + end + + def self.scim_queryable_attributes + return nil + end + + include Scimitar::Resources::Mixin + end + # =========================================================================== # Errant class definitions # =========================================================================== context 'with bad class definitions' do @@ -360,10 +456,34 @@ }) end end # "context 'using dynamic lists' do" end # "context 'with arrays' do" + context 'with "returned: \'never\' fields' do + it 'hides appropriate top-level and nested attributes' do + instance = NestedReturnedNeverTest.new( + given_name: 'One', + last_name: 'Two', + private_given_name: 'Three', + private_last_name: 'Four', + simple_name: 'Five' + ) + + scim = instance.to_scim(location: 'https://test.com/never_retutrned_test') + json = scim.to_json() + hash = JSON.parse(json) + + expect(hash).to eql({ + 'name' => { 'givenName' => 'One' }, + 'simpleName' => 'Five', + + 'meta' => {'location'=>'https://test.com/never_retutrned_test', 'resourceType'=>'NestedReturnedNeverTestResourse'}, + 'schemas' => ['nested-returned-never-id'] + }) + end + end # "context 'with "returned: \'never\' fields' do" + context 'with bad definitions' do it 'complains about non-Hash entries in mapping Arrays' do expect(StaticMapTest).to receive(:scim_attributes_map).and_return({ emails: [ 'this is not Hash' @@ -700,11 +820,12 @@ @instance.send( :from_patch_backend!, nature: 'add', path: path, value: 'foo', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { userName: :user_name } ) expect(scim_hash['userName']).to eql('foo') end @@ -715,11 +836,12 @@ @instance.send( :from_patch_backend!, nature: 'add', path: path, value: 'Baz', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { name: { givenName: :first_name, familyName: :last_name } } ) expect(scim_hash['name']['givenName' ]).to eql('Baz') expect(scim_hash['name']['familyName']).to eql('Bar') end @@ -731,11 +853,12 @@ @instance.send( :from_patch_backend!, nature: 'add', path: path, value: 'OTHERORG', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { organization: :org_name } ) expect(scim_hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']['organization' ]).to eql('OTHERORG') end @@ -761,11 +884,17 @@ @instance.send( :from_patch_backend!, nature: 'add', path: path, value: 'added_over_original@test.com', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'][0]['value']).to eql('home@test.com') expect(scim_hash['emails'][1]['value']).to eql('added_over_original@test.com') end @@ -787,11 +916,17 @@ @instance.send( :from_patch_backend!, nature: 'add', path: path, value: 'added_over_original@test.com', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'][0]['value']).to eql('home@test.com') expect(scim_hash['emails'][1]['value']).to eql('added_over_original@test.com') end @@ -814,11 +949,17 @@ @instance.send( :from_patch_backend!, nature: 'add', path: path, value: 'added_over_original@test.com', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'][0]['value']).to eql('added_over_original@test.com') expect(scim_hash['emails'][1]['value']).to eql('added_over_original@test.com') end @@ -838,11 +979,17 @@ @instance.send( :from_patch_backend!, nature: 'add', path: path, value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be) - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'].size).to eql(2) expect(scim_hash['emails'][1]['type' ]).to eql('work') expect(scim_hash['emails'][1]['value']).to eql('work@test.com') @@ -886,11 +1033,16 @@ @instance.send( :from_patch_backend!, nature: 'add', path: ['root'], value: {'members' => [{'value' => '3'}]}, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + members: [ + { list: :members, using: { value: :id } } + ] + } ) expect(scim_hash['root']['members']).to match_array([{'value' => '1'}, {'value' => '2'}, {'value' => '3'}]) end end # "context 'with complex value addition' do" @@ -904,11 +1056,12 @@ @instance.send( :from_patch_backend!, nature: 'add', path: path, value: 'foo', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { userName: :user_name } ) expect(scim_hash['userName']).to eql('foo') end @@ -919,11 +1072,12 @@ @instance.send( :from_patch_backend!, nature: 'add', path: path, value: 'Baz', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { name: { givenName: :first_name, familyName: :last_name } } ) expect(scim_hash['name']['givenName']).to eql('Baz') end @@ -934,11 +1088,12 @@ @instance.send( :from_patch_backend!, nature: 'add', path: path, value: 'SOMEORG', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { organization: :org_name } ) expect(scim_hash['urn:ietf:params:scim:schemas:extension:enterprise:2.0:User']['organization' ]).to eql('SOMEORG') end @@ -960,11 +1115,17 @@ @instance.send( :from_patch_backend!, nature: 'add', path: path, value: 'added@test.com', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'][0]['value']).to eql('home@test.com') expect(scim_hash['emails'][1]['value']).to eql('added@test.com') end @@ -985,11 +1146,17 @@ @instance.send( :from_patch_backend!, nature: 'add', path: path, value: 'added@test.com', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'][0]['value']).to eql('home@test.com') expect(scim_hash['emails'][1]['value']).to eql('added@test.com') end @@ -1001,11 +1168,17 @@ @instance.send( :from_patch_backend!, nature: 'add', path: path, value: 'added@test.com', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'][0]['value']).to eql('added@test.com') end @@ -1025,11 +1198,17 @@ @instance.send( :from_patch_backend!, nature: 'add', path: path, value: 'added@test.com', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'][0]['value']).to eql('added@test.com') expect(scim_hash['emails'][1]['value']).to eql('added@test.com') end @@ -1042,11 +1221,17 @@ @instance.send( :from_patch_backend!, nature: 'add', path: path, value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be) - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'].size).to eql(1) expect(scim_hash['emails'][0]['type' ]).to eql('work') expect(scim_hash['emails'][0]['value']).to eql('work@test.com') @@ -1058,43 +1243,44 @@ # Internal: #from_patch_backend - remove # ------------------------------------------------------------------- # context 'remove' do context 'when prior value already exists' do - it 'simple value: removes' do + it 'simple value: clears to "nil" in order to remove' do path = [ 'userName' ] scim_hash = { 'userName' => 'bar' }.with_indifferent_case_insensitive_access() @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: nil, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { userName: :user_name } ) - expect(scim_hash).to be_empty + expect(scim_hash).to eql({ 'userName' => nil }) end - it 'nested simple value: removes' do + it 'nested simple value: clears to "nil" in order to remove' do path = [ 'name', 'givenName' ] scim_hash = { 'name' => { 'givenName' => 'Foo', 'familyName' => 'Bar' } }.with_indifferent_case_insensitive_access() @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: nil, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { name: { givenName: :first_name, familyName: :last_name } } ) - expect(scim_hash['name']).to_not have_key('givenName') - expect(scim_hash['name']['familyName']).to eql('Bar') + expect(scim_hash).to eql({ 'name' => { 'givenName' => nil, 'familyName' => 'Bar' } }) end context 'with filter mid-path' do - it 'by string match: removes' do + it 'by string match: clears to "nil" in order to remove' do path = [ 'emails[type eq "work"]', 'value' ] scim_hash = { 'emails' => [ { 'type' => 'home', @@ -1110,18 +1296,34 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: nil, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) - expect(scim_hash['emails'][0]['value']).to eql('home@test.com') - expect(scim_hash['emails'][1]).to_not have_key('value') + expect(scim_hash).to eql({ + 'emails' => [ + { + 'type' => 'home', + 'value' => 'home@test.com' + }, + { + 'type' => 'work', + 'value' => nil + } + ] + }) end - it 'by boolean match: removes' do + it 'by boolean match: clears to "nil" in order to remove' do path = [ 'emails[primary eq true]', 'value' ] scim_hash = { 'emails' => [ { 'value' => 'home@test.com' @@ -1136,47 +1338,86 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: nil, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) - expect(scim_hash['emails'][0]['value']).to eql('home@test.com') - expect(scim_hash['emails'][1]).to_not have_key('value') + expect(scim_hash).to eql({ + 'emails' => [ + { + 'value' => 'home@test.com' + }, + { + 'value' => nil, + 'primary' => true + } + ] + }) end - it 'multiple matches: removes all' do + it 'multiple matches: clears all to "nil" in order to remove' do path = [ 'emails[type eq "work"]', 'value' ] scim_hash = { 'emails' => [ { 'type' => 'work', 'value' => 'work_1@test.com' }, { 'type' => 'work', 'value' => 'work_2@test.com' - } + }, + { + 'type' => 'home', + 'value' => 'home@test.com' + }, ] }.with_indifferent_case_insensitive_access() @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: nil, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) - expect(scim_hash['emails'][0]).to_not have_key('value') - expect(scim_hash['emails'][1]).to_not have_key('value') + expect(scim_hash).to eql({ + 'emails' => [ + { + 'type' => 'work', + 'value' => nil + }, + { + 'type' => 'work', + 'value' => nil + }, + { + 'type' => 'home', + 'value' => 'home@test.com' + }, + ] + }) end end # "context 'with filter mid-path' do" context 'with filter at end of path' do - it 'by string match: removes entire matching array entry' do + it 'by string match: clears to "nil" in order to remove' do path = [ 'emails[type eq "work"]' ] scim_hash = { 'emails' => [ { 'type' => 'home', @@ -1192,44 +1433,75 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: nil, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) - expect(scim_hash['emails'].size).to eql(1) - expect(scim_hash['emails'][0]['value']).to eql('home@test.com') + expect(scim_hash).to eql({ + 'emails' => [ + { + 'type' => 'home', + 'value' => 'home@test.com' + }, + { + 'type' => 'work', + 'value' => nil + } + ] + }) end - it 'by boolean match: removes entire matching array entry' do + it 'by boolean match: clears to "nil" in order to remove' do path = [ 'emails[primary eq true]' ] scim_hash = { 'emails' => [ { - 'value' => 'home@test.com' + 'value' => 'home@test.com', + 'primary' => true }, { - 'value' => 'work@test.com', - 'primary' => true + 'value' => 'work@test.com' } ] }.with_indifferent_case_insensitive_access() @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: nil, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) - expect(scim_hash['emails'].size).to eql(1) - expect(scim_hash['emails'][0]['value']).to eql('home@test.com') + expect(scim_hash).to eql({ + 'emails' => [ + { + 'value' => nil, + 'primary' => true + }, + { + 'value' => 'work@test.com' + } + ] + }) end - it 'multiple matches: removes all matching array entries' do + it 'multiple matches: clears all to "nil" in order to remove' do path = [ 'emails[type eq "work"]' ] scim_hash = { 'emails' => [ { 'type' => 'work', @@ -1249,38 +1521,79 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: nil, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) - expect(scim_hash['emails'].size).to eql(1) - expect(scim_hash['emails'][0]['value']).to eql('home@test.com') + expect(scim_hash).to eql({ + 'emails' => [ + { + 'type' => 'work', + 'value' => nil + }, + { + 'type' => 'work', + 'value' => nil + }, + { + 'type' => 'home', + 'value' => 'home@test.com' + }, + ] + }) end end # "context 'with filter at end of path' do" - it 'whole array: removes' do + it 'whole array: clears mapped values to "nil" to remove them' do path = [ 'emails' ] scim_hash = { 'emails' => [ { 'type' => 'home', 'value' => 'home@test.com' + }, + { + 'type' => 'work', + 'value' => 'work@test.com' } ] }.with_indifferent_case_insensitive_access() @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: nil, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) - expect(scim_hash).to_not have_key('emails') + expect(scim_hash).to eql({ + 'emails' => [ + { + 'type' => 'home', + 'value' => nil + }, + { + 'type' => 'work', + 'value' => nil + } + ] + }) end # What we expect: # # https://tools.ietf.org/html/rfc7644#section-3.5.2.2 @@ -1322,11 +1635,16 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: value, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + members: [ + { list: :members, using: { value: :id, display: :full_name, type: 'User' } } + ] + } ) expect(scim_hash).to eql({ 'displayname' => 'Mock group', 'members' => [ @@ -1374,11 +1692,16 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: value, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + members: [ + { list: :members, using: { value: :id, display: :full_name, type: 'User' } } + ] + } ) expect(scim_hash).to eql({ 'displayname' => 'Mock group', 'members' => [ @@ -1408,11 +1731,16 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: value, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + members: [ + { list: :members, using: { value: :id, display: :full_name, type: 'User' } } + ] + } ) expect(scim_hash).to eql({ 'displayname' => 'Mock group', 'members' => [] @@ -1436,11 +1764,16 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: value, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + members: [ + { list: :members, using: { value: :id, display: :full_name, type: 'User' } } + ] + } ) expect(scim_hash).to eql({ 'displayname' => 'Mock group', 'members' => [] @@ -1464,11 +1797,16 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: value, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + members: [ + { list: :members, using: { value: :id, display: :full_name, type: 'User' } } + ] + } ) # The 'value' mismatched, so the user was not removed. # expect(scim_hash).to eql({ @@ -1500,11 +1838,16 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: value, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + members: [ + { list: :members, using: { value: :id, display: :full_name, type: 'User' } } + ] + } ) # Type 'Group' mismatches 'User', so the user was not # removed. # @@ -1518,11 +1861,11 @@ } ] }) end - it 'matches keys case-insensitive' do + it 'matches keys case-insensitive (but preserves case in response)' do path = [ 'members' ] value = [ { '$ref' => nil, 'VALUe' => 'f648f8d5ea4e4cd38e9c' } ] scim_hash = { 'displayname' => 'Mock group', 'memBERS' => [ @@ -1537,24 +1880,29 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: value, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + members: [ + { list: :members, using: { value: :id, display: :full_name, type: 'User' } } + ] + } ) expect(scim_hash).to eql({ 'displayname' => 'Mock group', - 'members' => [] + 'memBERS' => [] }) end it 'matches values case-sensitive' do path = [ 'members' ] value = [ { '$ref' => nil, 'value' => 'f648f8d5ea4e4cd38e9c', 'type' => 'USER' } ] scim_hash = { - 'displayname' => 'Mock group', + 'displayName' => 'Mock group', 'members' => [ { 'value' => 'f648f8d5ea4e4cd38e9c', 'display' => 'Fred Smith', 'type' => 'User' @@ -1565,17 +1913,22 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: value, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + members: [ + { list: :members, using: { value: :id, display: :full_name, type: 'User' } } + ] + } ) - # USER mismatchs User, so the user was not removed. + # USER mismatches User, so the user was not removed. # expect(scim_hash).to eql({ - 'displayname' => 'Mock group', + 'displayName' => 'Mock group', 'members' => [ { 'value' => 'f648f8d5ea4e4cd38e9c', 'display' => 'Fred Smith', 'type' => 'User' @@ -1584,11 +1937,11 @@ }) end end # "context 'removing a user from a group' do" context 'generic use' do - it 'removes matched items' do + it 'clears static map matched items to "nil" in order to remove' do path = [ 'emails' ] value = [ { 'type' => 'work' } ] scim_hash = { 'emails' => [ { @@ -1605,18 +1958,28 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: value, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash).to eql({ 'emails' => [ { 'type' => 'home', 'value' => 'home@test.com' + }, + { + 'type' => 'work', + 'value' => nil } ] }) end @@ -1639,11 +2002,17 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: value, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash).to eql({ 'emails' => [ { @@ -1671,23 +2040,45 @@ 'value' => 12 }, { 'active' => false, 'value' => '42' + }, + { + 'active' => 'hello', + 'value' => 'world' } ] }.with_indifferent_case_insensitive_access() @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: value, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + test: [ + { + list: :test, + using: { + active: :active, + value: :value + } + } + ] + } ) - expect(scim_hash).to eql({'test' => []}) + expect(scim_hash).to eql({ + 'test' => [ + { + 'active' => 'hello', + 'value' => 'world' + } + ] + }) end it 'handles a singular to-remove value rather than an array' do path = [ 'emails' ] value = { 'type' => 'work' } @@ -1707,18 +2098,28 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: value, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash).to eql({ 'emails' => [ { 'type' => 'home', 'value' => 'home@test.com' + }, + { + 'type' => 'work', + 'value' => nil } ] }) end @@ -1736,11 +2137,14 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: value, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + test: [] + } ) expect(scim_hash).to eql({ 'test' => [ '21', @@ -1776,11 +2180,22 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: value, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + displayName: :name, + members: [ + list: :members, + using: { + value: :id, + display: :full_name, + type: 'User' + } + ] + } ) expect(scim_hash).to eql({ 'displayname' => 'Mock group', 'members' => [ @@ -1815,11 +2230,22 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: value, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + displayName: :name, + members: [ + list: :members, + using: { + value: :id, + display: :full_name, + type: 'User' + } + ] + } ) expect(scim_hash).to eql({ 'displayname' => 'Mock group', 'members' => [ @@ -1849,11 +2275,22 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: value, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + displayName: :name, + members: [ + list: :members, + using: { + value: :id, + display: :full_name, + type: 'User' + } + ] + } ) # The 'value' mismatched, so the user was not removed. # expect(scim_hash).to eql({ @@ -1879,11 +2316,12 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: nil, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { userName: :user_name } ) expect(scim_hash).to be_empty end @@ -1894,11 +2332,12 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: nil, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { name: { givenName: :first_name, familyName: :last_name } } ) expect(scim_hash['name']).to_not have_key('givenName') expect(scim_hash['name']['familyName']).to eql('Bar') end @@ -1918,11 +2357,17 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: nil, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'].size).to eql(1) expect(scim_hash['emails'][0]['value']).to eql('home@test.com') end @@ -1940,11 +2385,17 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: nil, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'].size).to eql(1) expect(scim_hash['emails'][0]['value']).to eql('home@test.com') end @@ -1963,11 +2414,17 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: nil, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'].size).to eql(1) expect(scim_hash['emails'][0]['value']).to eql('home@test.com') end @@ -1981,11 +2438,17 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: nil, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash).to be_empty end @@ -2003,11 +2466,17 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: nil, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'].size).to eql(1) expect(scim_hash['emails'][0]['value']).to eql('home@test.com') end @@ -2020,11 +2489,17 @@ @instance.send( :from_patch_backend!, nature: 'remove', path: path, value: nil, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash).to_not have_key('emails') end end # context 'when value is not present' do @@ -2046,11 +2521,12 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: 'foo', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { userName: :user_name } ) expect(scim_hash['userName']).to eql('foo') end @@ -2061,11 +2537,12 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: 'Baz', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { name: { givenName: :first_name, familyName: :last_name } } ) expect(scim_hash['name']['givenName' ]).to eql('Baz') expect(scim_hash['name']['familyName']).to eql('Bar') end @@ -2089,11 +2566,17 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: 'added_over_original@test.com', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'][0]['value']).to eql('home@test.com') expect(scim_hash['emails'][1]['value']).to eql('added_over_original@test.com') end @@ -2115,11 +2598,17 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: 'added_over_original@test.com', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'][0]['value']).to eql('home@test.com') expect(scim_hash['emails'][1]['value']).to eql('added_over_original@test.com') end @@ -2142,11 +2631,17 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: 'added_over_original@test.com', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'][0]['value']).to eql('added_over_original@test.com') expect(scim_hash['emails'][1]['value']).to eql('added_over_original@test.com') end @@ -2171,11 +2666,17 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: {'type' => 'home', 'primary' => true, 'value' => 'home@test.com'}, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'].size).to eql(2) expect(scim_hash['emails'][0]['type' ]).to eql('holiday') # unchanged expect(scim_hash['emails'][1]['type' ]).to eql('home') # "work" became "home" @@ -2205,11 +2706,17 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: {'type' => 'workinate', 'value' => 'replaced@test.com'}, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'].size).to eql(3) expect(scim_hash['emails'][0]['type' ]).to eql('workinate') expect(scim_hash['emails'][0]['value']).to eql('replaced@test.com') @@ -2234,11 +2741,17 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be) - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'].size).to eql(1) expect(scim_hash['emails'][0]['type' ]).to eql('work') expect(scim_hash['emails'][0]['value']).to eql('work@test.com') @@ -2253,11 +2766,12 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: 'foo', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { userName: :user_name } ) expect(scim_hash['userName']).to eql('foo') end @@ -2268,11 +2782,12 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: 'Baz', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { name: { givenName: :first_name, familyName: :last_name } } ) expect(scim_hash['name']['givenName']).to eql('Baz') end @@ -2294,11 +2809,17 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: 'added@test.com', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'][0]['value']).to eql('home@test.com') expect(scim_hash['emails'][1]['value']).to eql('added@test.com') end @@ -2319,11 +2840,17 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: 'added@test.com', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'][0]['value']).to eql('home@test.com') expect(scim_hash['emails'][1]['value']).to eql('added@test.com') end @@ -2344,11 +2871,17 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: 'added@test.com', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'][0]['value']).to eql('added@test.com') expect(scim_hash['emails'][1]['value']).to eql('added@test.com') end @@ -2362,11 +2895,17 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: {'type' => 'work', 'value' => 'work@test.com'}, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'].size).to eql(1) expect(scim_hash['emails'][0]['type' ]).to eql('work') expect(scim_hash['emails'][0]['value']).to eql('work@test.com') @@ -2386,11 +2925,17 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: {'type' => 'work', 'value' => 'work@test.com'}, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'].size).to eql(2) expect(scim_hash['emails'][0]['value']).to eql('home@test.com') expect(scim_hash['emails'][1]['type' ]).to eql('work') @@ -2405,11 +2950,17 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: [ { 'type' => 'work', 'value' => 'work@test.com' } ], # NOTE - to-add value is an Array (and must be) - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) expect(scim_hash['emails'].size).to eql(1) expect(scim_hash['emails'][0]['type' ]).to eql('work') expect(scim_hash['emails'][0]['value']).to eql('work@test.com') @@ -2423,11 +2974,15 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: { 'active' => false }.with_indifferent_case_insensitive_access(), - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + userName: :user_name, + active: :active + } ) expect(scim_hash['root']['userName']).to eql('bar') expect(scim_hash['root']['active']).to eql(false) end @@ -2544,11 +3099,12 @@ contrived_instance.send( :from_patch_backend!, nature: 'add', path: ['complex[type eq "type1"]', 'data', 'nested[nature eq "nature2"]', 'info'], value: [{ 'deeper' => 'addition' }], - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: @contrived_class.scim_attributes_map() ) expect(scim_hash.dig('complex', 0, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged expect(scim_hash.dig('complex', 0, 'data', 'nested', 1, 'info').count).to eql(2) # One new item expect(scim_hash.dig('complex', 0, 'data', 'nested', 2, 'info').count).to eql(2) # One new item @@ -2567,11 +3123,12 @@ contrived_instance.send( :from_patch_backend!, nature: 'replace', path: ['complex[type eq "type1"]', 'data', 'nested[nature eq "nature2"]', 'info'], value: [{ 'deeper' => 'addition' }], - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: @contrived_class.scim_attributes_map() ) expect(scim_hash.dig('complex', 0, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged? expect(scim_hash.dig('complex', 0, 'data', 'nested', 1, 'info').count).to eql(1) expect(scim_hash.dig('complex', 0, 'data', 'nested', 2, 'info').count).to eql(1) @@ -2584,28 +3141,29 @@ expect(scim_hash.dig('complex', 2, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged expect(scim_hash.dig('complex', 2, 'data', 'nested', 0, 'info', 0, 'deep')).to eql('nature2deep3') # Unchanged end - it 'removes across multiple deep matching points' do + it 'removes via clearing to "nil" or empty Array across multiple deep matching points' do scim_hash = @original_hash.deep_dup().with_indifferent_case_insensitive_access() contrived_instance = @contrived_class.new contrived_instance.send( :from_patch_backend!, nature: 'remove', path: ['complex[type eq "type1"]', 'data', 'nested[nature eq "nature2"]', 'info'], value: nil, - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: @contrived_class.scim_attributes_map() ) expect(scim_hash.dig('complex', 0, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged - expect(scim_hash.dig('complex', 0, 'data', 'nested', 1, 'info')).to be_nil + expect(scim_hash.dig('complex', 0, 'data', 'nested', 1, 'info')).to eql([]) expect(scim_hash.dig('complex', 0, 'data', 'nested', 2, 'nature')).to be_present - expect(scim_hash.dig('complex', 0, 'data', 'nested', 1, 'info')).to be_nil + expect(scim_hash.dig('complex', 0, 'data', 'nested', 1, 'info')).to eql([]) expect(scim_hash.dig('complex', 0, 'data', 'nested', 2, 'nature')).to be_present - expect(scim_hash.dig('complex', 1, 'data', 'nested', 0, 'info')).to be_nil + expect(scim_hash.dig('complex', 1, 'data', 'nested', 0, 'info')).to eql([]) expect(scim_hash.dig('complex', 1, 'data', 'nested', 0, 'nature')).to be_present expect(scim_hash.dig('complex', 2, 'data', 'nested', 0, 'info').count).to eql(1) # Unchanged expect(scim_hash.dig('complex', 2, 'data', 'nested', 0, 'info', 0, 'deep')).to eql('nature2deep3') # Unchanged end @@ -2635,11 +3193,17 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: 'ignored', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) end.to raise_error(Scimitar::ErrorResponse) { |e| expect(e.as_json['scimType']).to eql('invalidSyntax') } end it 'when filters are specified for non-array types' do @@ -2652,11 +3216,12 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: 'ignored', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { userName: :user_name } ) end.to raise_error(Scimitar::ErrorResponse) { |e| expect(e.as_json['scimType']).to eql('invalidSyntax') } end it 'when a filter tries to match an array which does not contain Hashes' do @@ -2672,10 +3237,16 @@ @instance.send( :from_patch_backend!, nature: 'replace', path: path, value: 'ignored', - altering_hash: scim_hash + altering_hash: scim_hash, + with_attr_map: { + emails: [ + { match: 'type', with: 'home', using: { value: :home_email, primary: true } }, + { match: 'type', with: 'work', using: { value: :work_email } }, + ] + } ) end.to raise_error(Scimitar::ErrorResponse) { |e| expect(e.as_json['scimType']).to eql('invalidSyntax') } end end # context 'with bad patches, raises errors' do end # "context '#from_patch_backend!' do"