Comparing changes

v0.2.12 v0.2.13
9 commits 5 files changed
lib/scim/kit/v2/attributable.rb
@@ -12,10 +12,22 @@ module Scim
           types.each { |x| attribute(x, resource) }
         end
 
+        def assign_attributes(attributes = {})
+          attributes.each do |key, value|
+            next if key.to_sym == :schemas
+
+            if key.to_s.start_with?(Schemas::EXTENSION)
+              assign_attributes(value)
+            else
+              write_attribute(key, value)
+            end
+          end
+        end
+
         private
 
         def attribute_for(name)
-          dynamic_attributes[name]
+          dynamic_attributes[name.to_s.underscore]
         end
 
         def read_attribute(name)
@@ -26,8 +38,11 @@ module Scim
         end
 
         def write_attribute(name, value)
-          attribute = attribute_for(name)
-          attribute._value = value
+          if value.is_a?(Hash)
+            attribute_for(name)&.assign_attributes(value)
+          else
+            attribute_for(name)&._value = value
+          end
         end
 
         def create_module_for(type)
lib/scim/kit/v2/resource.rb
@@ -13,16 +13,16 @@ module Scim
         attr_reader :meta
         attr_reader :schemas
 
-        validates_presence_of :id
         validate :schema_validations
 
-        def initialize(schemas:, location: nil)
+        def initialize(schemas:, location: nil, attributes: {})
           @meta = Meta.new(schemas[0].name, location)
           @meta.disable_timestamps
           @schemas = schemas
           schemas.each do |schema|
             define_attributes_for(self, schema.attributes)
           end
+          assign_attributes(attributes)
           yield self if block_given?
         end
 
lib/scim/kit/v2/schemas.rb
@@ -4,7 +4,11 @@ module Scim
   module Kit
     module V2
       module Schemas
-        CORE = 'urn:ietf:params:scim:schemas:core:2.0'
+        ROOT = 'urn:ietf:params:scim:schemas'
+
+        CORE = "#{ROOT}:core:2.0"
+        EXTENSION = "#{ROOT}:extension"
+        ENTERPRISE_USER = "#{EXTENSION}:enterprise:2.0:User"
         GROUP = "#{CORE}:Group"
         RESOURCE_TYPE = "#{CORE}:ResourceType"
         SERVICE_PROVIDER_CONFIGURATION = "#{CORE}:ServiceProviderConfig"
lib/scim/kit/version.rb
@@ -2,6 +2,6 @@
 
 module Scim
   module Kit
-    VERSION = '0.2.12'
+    VERSION = '0.2.13'
   end
 end
spec/scim/kit/v2/resource_spec.rb
@@ -147,13 +147,6 @@ RSpec.describe Scim::Kit::V2::Resource do
   end
 
   describe '#valid?' do
-    context 'when invalid' do
-      before { subject.valid? }
-
-      specify { expect(subject).not_to be_valid }
-      specify { expect(subject.errors[:id]).to be_present }
-    end
-
     context 'when valid' do
       before { subject.id = SecureRandom.uuid }
 
@@ -344,4 +337,137 @@ RSpec.describe Scim::Kit::V2::Resource do
       specify { expect(subject).to be_mode(:client) }
     end
   end
+
+  describe '#assign_attributes' do
+    context 'with a simple string attribute' do
+      let(:user_name) { FFaker::Internet.user_name }
+
+      before do
+        schema.add_attribute(name: 'userName')
+        subject.assign_attributes('schemas' => schemas.map(&:id), userName: user_name)
+      end
+
+      specify { expect(subject.user_name).to eql(user_name) }
+    end
+
+    context 'with a simple integer attribute' do
+      before do
+        schema.add_attribute(name: 'age', type: :integer)
+        subject.assign_attributes(schemas: schemas.map(&:id), age: 34)
+      end
+
+      specify { expect(subject.age).to be(34) }
+    end
+
+    context 'with a multi-valued simple string attribute' do
+      before do
+        schema.add_attribute(name: 'colours', type: :string) do |x|
+          x.multi_valued = true
+        end
+        subject.assign_attributes(schemas: schemas.map(&:id), colours: ['red', 'green', :blue])
+      end
+
+      specify { expect(subject.colours).to match_array(%w[red green blue]) }
+    end
+
+    context 'with a single complex attribute' do
+      before do
+        schema.add_attribute(name: :name) do |x|
+          x.add_attribute(name: :given_name)
+          x.add_attribute(name: :family_name)
+        end
+        subject.assign_attributes(schemas: schemas.map(&:id), name: { givenName: 'Tsuyoshi', familyName: 'Garrett' })
+      end
+
+      specify { expect(subject.name.given_name).to eql('Tsuyoshi') }
+      specify { expect(subject.name.family_name).to eql('Garrett') }
+    end
+
+    context 'with a multi-valued complex attribute' do
+      let(:email) { FFaker::Internet.email }
+      let(:other_email) { FFaker::Internet.email }
+
+      before do
+        schema.add_attribute(name: :emails) do |x|
+          x.multi_valued = true
+          x.add_attribute(name: :value)
+          x.add_attribute(name: :primary, type: :boolean)
+        end
+        subject.assign_attributes(schemas: schemas.map(&:id), emails: [
+                                    { value: email, primary: true },
+                                    { value: other_email, primary: false }
+                                  ])
+      end
+
+      specify do
+        expect(subject.emails).to match_array([
+                                                { value: email, primary: true },
+                                                { value: other_email, primary: false }
+                                              ])
+      end
+
+      specify { expect(subject.emails[0][:value]).to eql(email) }
+      specify { expect(subject.emails[0][:primary]).to be(true) }
+      specify { expect(subject.emails[1][:value]).to eql(other_email) }
+      specify { expect(subject.emails[1][:primary]).to be(false) }
+    end
+
+    context 'with an extension schema' do
+      let(:schemas) { [schema, extension] }
+      let(:extension) { Scim::Kit::V2::Schema.new(id: extension_id, name: 'Extension', location: FFaker::Internet.uri('https')) }
+      let(:extension_id) { Scim::Kit::V2::Schemas::ENTERPRISE_USER }
+
+      before do
+        extension.add_attribute(name: :preferred_name)
+        subject.assign_attributes(
+          schemas: schemas.map(&:id),
+          extension_id => { preferredName: 'hunk' }
+        )
+      end
+
+      specify { expect(subject.preferred_name).to eql('hunk') }
+    end
+
+    context 'when initializing the resource with attributes' do
+      subject { described_class.new(schemas: schemas, attributes: attributes) }
+
+      let(:user_name) { FFaker::Internet.user_name }
+      let(:email) { FFaker::Internet.email }
+      let(:attributes) do
+        {
+          schemas: schemas.map(&:id),
+          userName: user_name,
+          age: 34,
+          colours: %w[red green blue],
+          name: { given_name: 'Tsuyoshi', family_name: 'Garrett' },
+          emails: [{ value: email, primary: true }]
+        }
+      end
+
+      before do
+        schema.add_attribute(name: :user_name)
+        schema.add_attribute(name: :age, type: :integer)
+        schema.add_attribute(name: :colours, type: :string) do |x|
+          x.multi_valued = true
+        end
+        schema.add_attribute(name: :name) do |x|
+          x.add_attribute(name: :given_name)
+          x.add_attribute(name: :family_name)
+        end
+        schema.add_attribute(name: :emails) do |x|
+          x.multi_valued = true
+          x.add_attribute(name: :value)
+          x.add_attribute(name: :primary, type: :boolean)
+        end
+      end
+
+      specify { expect(subject.user_name).to eql(user_name) }
+      specify { expect(subject.age).to be(34) }
+      specify { expect(subject.colours).to match_array(%w[red green blue]) }
+      specify { expect(subject.name.given_name).to eql('Tsuyoshi') }
+      specify { expect(subject.name.family_name).to eql('Garrett') }
+      specify { expect(subject.emails[0][:value]).to eql(email) }
+      specify { expect(subject.emails[0][:primary]).to be(true) }
+    end
+  end
 end