spec/attribute_spec.rb in attributor-5.7 vs spec/attribute_spec.rb in attributor-6.0

- old
+ new

@@ -9,11 +9,11 @@ let(:context) { ['context'] } let(:value) { 'one' } context 'initialize' do its(:type) { should be type } - its(:options) { should be attribute_options } + its(:options) { should eq attribute_options } it 'calls check_options!' do expect_any_instance_of(Attributor::Attribute).to receive(:check_options!) Attributor::Attribute.new(type, attribute_options) end @@ -70,12 +70,12 @@ context 'describe' do let(:attribute_options) { {required: true, values: ['one'], description: "something", min: 0} } let(:expected) do h = {type: {name: 'String', id: type.id, family: type.family}} common = attribute_options.select{|k,v| Attributor::Attribute::TOP_LEVEL_OPTIONS.include? k } - h.merge!( common ) - h[:options] = {:min => 0 } + h.merge!(common) + h[:options] = {min: 0} h end # It has both the type-included options (min) as well as the attribute options (max) its(:describe) { should == expected } @@ -456,18 +456,57 @@ context 'validating a value' do context '#validate' do context 'applying attribute options' do context ':required' do let(:attribute_options) { { required: true } } + context 'has no effect on a bare attribute' do + let(:value) { 'val' } + it 'it does not error, as we do not know if the parent attribute key was passed in (done at the Hash level)' do + expect(attribute.validate(value, context)).to be_empty + end + end + end + context ':null false (non-nullable)' do + let(:attribute_options) { { null: false } } context 'with a nil value' do let(:value) { nil } it 'returns an error' do - expect(attribute.validate(value, context).first).to eq 'Attribute context is required' + expect(attribute.validate(value, context).first).to eq 'Attribute context is not nullable' end end end + context ':null true (nullable)' do + let(:attribute_options) { { null: true } } + context 'with a nil value' do + let(:value) { nil } + it 'does not error' do + expect(attribute.validate(value, context)).to be_empty + end + end + end + context 'defaults to non-nullable if null not defined' do + let(:attribute_options) { { } } + context 'with a nil value' do + let(:value) { nil } + it 'returns an error' do + expect(Attributor::Attribute.default_for_null).to be(false) + expect(attribute.validate(value, context).first).to eq 'Attribute context is not nullable' + end + end + end + context 'default can be overrideable with true' do + let(:attribute_options) { { } } + context 'with a nil value' do + let(:value) { nil } + it 'suceeds' do + expect(Attributor::Attribute).to receive(:default_for_null).and_return(true) + expect(attribute.validate(value, context)).to be_empty + end + end + end + context ':values' do let(:values) { %w(one two) } let(:attribute_options) { { values: values } } let(:value) { nil } @@ -505,71 +544,26 @@ it 'returns no errors' do expect(errors).to be_empty end end + context 'with a nil value' do + let(:value) { nil } + it 'returns no errors' do + expect(errors).to be_empty + end + end + context 'with a value of a value different than the native_type' do let(:value) { 1 } it 'returns errors' do expect(errors).not_to be_empty expect(errors.first).to match(/is of the wrong type/) end end end - - context '#validate_missing_value' do - let(:key) { '$.instance.ssh_key.name' } - let(:value) { /\w+/.gen } - - let(:attribute_options) { { required_if: key } } - - let(:ssh_key) { double('ssh_key', name: value) } - let(:instance) { double('instance', ssh_key: ssh_key) } - - before { Attributor::AttributeResolver.current.register('instance', instance) } - - let(:attribute_context) { ['$', 'params', 'key_material'] } - subject(:errors) { attribute.validate_missing_value(attribute_context) } - - context 'for a simple dependency without a predicate' do - context 'that is satisfied' do - it { should_not be_empty } - end - - context 'that is missing' do - let(:value) { nil } - it { should be_empty } - end - end - - context 'with a dependency that has a predicate' do - let(:value) { 'default_ssh_key_name' } - # subject(:errors) { attribute.validate_missing_value('') } - - context 'where the target attribute exists, and matches the predicate' do - let(:attribute_options) { { required_if: { key => /default/ } } } - - it { should_not be_empty } - - its(:first) { should match(/Attribute #{Regexp.quote(Attributor.humanize_context(attribute_context))} is required when #{Regexp.quote(key)} matches/) } - end - - context 'where the target attribute exists, but does not match the predicate' do - let(:attribute_options) { { required_if: { key => /other/ } } } - - it { should be_empty } - end - - context 'where the target attribute does not exist' do - let(:attribute_options) { { required_if: { key => /default/ } } } - let(:ssh_key) { double('ssh_key', name: nil) } - - it { should be_empty } - end - end - end end context 'for an attribute for a subclass of Model' do let(:type) { Chicken } let(:type_options) { Chicken.options } @@ -634,75 +628,10 @@ errors = attribute.validate(chicken) expect(errors).to match_array(age_validation_response | email_validation_response) end end end - - context '#validate_missing_value' do - let(:type) { Duck } - let(:attribute_name) { nil } - let(:attribute) { Duck.attributes[attribute_name] } - - let(:attribute_context) { ['$', 'duck', attribute_name.to_s] } - subject(:errors) { attribute.validate_missing_value(attribute_context) } - - before do - Attributor::AttributeResolver.current.register('duck', duck) - end - - context 'for a dependency with no predicate' do - let(:attribute_name) { :email } - - let(:duck) do - d = Duck.new - d.age = 1 - d.name = 'Donald' - d - end - - context 'where the target attribute exists, and matches the predicate' do - it { should_not be_empty } - its(:first) { should eq 'Attribute $.duck.email is required when name (for $.duck) is present.' } - end - context 'where the target attribute does not exist' do - before do - duck.name = nil - end - it { should be_empty } - end - end - - context 'for a dependency with a predicate' do - let(:attribute_name) { :age } - - let(:duck) do - d = Duck.new - d.name = 'Daffy' - d.email = 'daffy@darkwing.uoregon.edu' # he's a duck,get it? - d - end - - context 'where the target attribute exists, and matches the predicate' do - it { should_not be_empty } - its(:first) { should match(/Attribute #{Regexp.quote('$.duck.age')} is required when name #{Regexp.quote('(for $.duck)')} matches/) } - end - - context 'where the target attribute exists, and does not match the predicate' do - before do - duck.name = 'Donald' - end - it { should be_empty } - end - - context 'where the target attribute does not exist' do - before do - duck.name = nil - end - it { should be_empty } - end - end - end end end context 'for a Collection' do context 'of non-Model (or Struct) type' do @@ -753,8 +682,28 @@ it 'inherited the type and options from the reference' do expect(member_attribute.attributes[:angry].type).to be(Chicken.attributes[:angry].type) expect(member_attribute.attributes[:angry].options).to eq(Chicken.attributes[:angry].options.merge(required: true)) end end + end + end + + context '.nullable_attribute?' do + subject { described_class.nullable_attribute?(options) } + context 'with null: true option' do + let(:options) { { null: true } } + it { should be_truthy } + end + context 'with null: false option' do + let(:options) { { null: false } } + it { should be_falsey } + end + context 'defaults to false without any null option' do + let(:options) { { } } + it { should be_falsey } + end + context 'defaults to false if null: nil' do + let(:options) { { null: nil } } + it { should be_falsey } end end end