Commit be537d2

mokha <mokha@cisco.com>
2019-01-11 19:56:47
update type conversions and add tests
1 parent 0fb71c8
lib/scim/kit/v2/attribute.rb
@@ -21,8 +21,12 @@ module Scim
           define_attributes_for(type.attributes)
         end
 
+        def _assign(new_value, coerce: true)
+          @_value = coerce ? type.coerce(new_value) : new_value
+        end
+
         def _value=(new_value)
-          @_value = type.coerce(new_value)
+          _assign(new_value, coerce: true)
         end
 
         private
lib/scim/kit/v2/attribute_type.rb
@@ -63,13 +63,17 @@ module Scim
         end
 
         def coerce(value)
-          if type_is?(:boolean) && !BOOLEAN_VALUES.include?(value)
-            raise ArgumentError, value
-          end
-          return value if multi_valued
+          return value if complex?
+
+          if multi_valued
+            return value unless value.respond_to?(:to_a)
 
-          coercion = COERCION[type]
-          coercion ? coercion.call(value) : value
+            value.to_a.map do |x|
+              COERCION.fetch(type, ->(y) { y }).call(x)
+            end
+          else
+            COERCION.fetch(type, ->(x) { x }).call(value)
+          end
         end
 
         def valid?(value)
lib/scim/kit/v2.rb
@@ -36,11 +36,17 @@ module Scim
         complex: 'complex'
       }.freeze
       COERCION = {
-        string: ->(x) { x.to_s },
+        binary: ->(x) { Base64.strict_encode64(x) },
+        boolean: lambda { |x|
+          return true if x == 'true'
+          return false if x == 'false'
+
+          x
+        },
+        datetime: ->(x) { x.is_a?(::String) ? DateTime.parse(x) : x },
         decimal: ->(x) { x.to_f },
         integer: ->(x) { x.to_i },
-        datetime: ->(x) { x.is_a?(::String) ? DateTime.parse(x) : x },
-        binary: ->(x) { Base64.strict_encode64(x) }
+        string: ->(x) { x.to_s }
       }.freeze
       VALIDATIONS = {
         binary: ->(x) { x.is_a?(String) && x.match?(BASE64) },
spec/scim/kit/v2/attribute_spec.rb
@@ -39,7 +39,7 @@ RSpec.describe Scim::Kit::V2::Attribute do
     context 'when the wrong type is used' do
       before do
         type.multi_valued = true
-        subject._value = [1.0, 2.0]
+        subject._assign([1.0, 2.0], coerce: false)
         subject.valid?
       end
 
@@ -101,8 +101,11 @@ RSpec.describe Scim::Kit::V2::Attribute do
       specify { expect(subject.as_json[:hungry]).to be(false) }
     end
 
-    context 'when string' do
-      specify { expect { subject._value = 'hello' }.to raise_error(ArgumentError) }
+    context 'when invalid string' do
+      before { subject._value = 'hello' }
+
+      specify { expect(subject._value).to eql('hello') }
+      specify { expect(subject).not_to be_valid }
     end
   end
 
spec/scim/kit/v2/attribute_type_spec.rb
@@ -1,6 +1,9 @@
 # frozen_string_literal: true
 
 RSpec.describe Scim::Kit::V2::AttributeType do
+  let(:image) { Base64.strict_encode64(raw_image) }
+  let(:raw_image) { IO.read('./spec/fixtures/avatar.png', mode: 'rb') }
+
   specify { expect { described_class.new(name: 'displayName', type: :string) }.not_to raise_error }
   specify { expect { described_class.new(name: 'primary', type: :boolean) }.not_to raise_error }
   specify { expect { described_class.new(name: 'salary', type: :decimal) }.not_to raise_error }
@@ -65,8 +68,6 @@ RSpec.describe Scim::Kit::V2::AttributeType do
   end
 
   describe '#valid?' do
-    let(:image) { Base64.strict_encode64(IO.read('./spec/fixtures/avatar.png', mode: 'rb')) }
-
     specify { expect(described_class.new(name: :x, type: :binary)).to be_valid(image) }
     specify { expect(described_class.new(name: :x, type: :binary)).not_to be_valid('hello') }
     specify { expect(described_class.new(name: :x, type: :binary)).not_to be_valid(1) }
@@ -150,4 +151,76 @@ RSpec.describe Scim::Kit::V2::AttributeType do
       specify { expect(subject).not_to be_valid([value: email, primary: 'true']) }
     end
   end
+
+  describe '#coerce' do
+    let(:now) { DateTime.now }
+    let(:uri) { FFaker::Internet.uri('https') }
+
+    specify { expect(described_class.new(name: :x, type: :binary).coerce(raw_image)).to eql(image) }
+
+    specify { expect(described_class.new(name: :x, type: :boolean).coerce(true)).to be(true) }
+    specify { expect(described_class.new(name: :x, type: :boolean).coerce('true')).to be(true) }
+    specify { expect(described_class.new(name: :x, type: :boolean).coerce(false)).to be(false) }
+    specify { expect(described_class.new(name: :x, type: :boolean).coerce('false')).to be(false) }
+    specify { expect(described_class.new(name: :x, type: :boolean).coerce('invalid')).to eql('invalid') }
+
+    specify { expect(described_class.new(name: :x, type: :datetime).coerce(now)).to eql(now) }
+    specify { expect(described_class.new(name: :x, type: :datetime).coerce(now.iso8601)).to be_within(1).of(now) }
+
+    specify { expect(described_class.new(name: :x, type: :decimal).coerce(1.0)).to be(1.0) }
+    specify { expect(described_class.new(name: :x, type: :decimal).coerce(1)).to be(1.0) }
+    specify { expect(described_class.new(name: :x, type: :decimal).coerce('1.0')).to be(1.0) }
+    specify { expect(described_class.new(name: :x, type: :decimal).coerce('1')).to be(1.0) }
+
+    specify { expect(described_class.new(name: :x, type: :integer).coerce(1)).to be(1) }
+    specify { expect(described_class.new(name: :x, type: :integer).coerce(1.0)).to be(1) }
+    specify { expect(described_class.new(name: :x, type: :integer).coerce('1.0')).to be(1) }
+    specify { expect(described_class.new(name: :x, type: :integer).coerce('1')).to be(1) }
+
+    specify { expect(described_class.new(name: :x, type: :reference).coerce(uri)).to eql(uri) }
+    specify { expect(described_class.new(name: :x, type: :reference).coerce('hello')).to eql('hello') }
+
+    specify { expect(described_class.new(name: :x, type: :string).coerce('name')).to eql('name') }
+    specify { expect(described_class.new(name: :x, type: :string).coerce(1)).to eql('1') }
+
+    context 'when multi valued string type' do
+      subject do
+        x = described_class.new(name: :x, type: :string)
+        x.multi_valued = true
+        x
+      end
+
+      specify { expect(subject.coerce(['1'])).to match_array(['1']) }
+      specify { expect(subject.coerce([1])).to match_array(['1']) }
+    end
+
+    context 'when single valued complex type' do
+      subject { described_class.new(name: :location, type: :complex) }
+
+      before do
+        subject.multi_valued = false
+        subject.add_attribute(name: :name, type: :string)
+        subject.add_attribute(name: :latitude, type: :integer)
+        subject.add_attribute(name: :longitude, type: :integer)
+      end
+
+      specify { expect(subject.coerce(name: 'work', latitude: 100, longitude: 100)).to eql(name: 'work', latitude: 100, longitude: 100) }
+    end
+
+    context 'when multi valued complex type' do
+      subject { described_class.new(name: :emails, type: :complex) }
+
+      let(:email) { FFaker::Internet.email }
+      let(:other_email) { FFaker::Internet.email }
+
+      before do
+        subject.multi_valued = true
+        subject.add_attribute(name: 'value', type: :string)
+        subject.add_attribute(name: 'primary', type: :boolean)
+      end
+
+      specify { expect(subject.coerce([value: email, primary: true])).to match_array([value: email, primary: true]) }
+      specify { expect(subject.coerce([{ value: email, primary: true }, { value: other_email, primary: false }])).to match_array([{ value: email, primary: true }, { value: other_email, primary: false }]) }
+    end
+  end
 end