diff options
Diffstat (limited to 'activerecord/test/cases/validations')
4 files changed, 1256 insertions, 0 deletions
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb new file mode 100644 index 0000000000..b1203c12ed --- /dev/null +++ b/activerecord/test/cases/validations/association_validation_test.rb @@ -0,0 +1,101 @@ +# encoding: utf-8 +require "cases/helper" +require 'models/topic' +require 'models/reply' +require 'models/owner' + +class AssociationValidationTest < ActiveRecord::TestCase + fixtures :topics, :owners + + repair_validations(Topic) + + def test_validates_size_of_association + repair_validations(Owner) do + assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 } + o = Owner.new('name' => 'nopets') + assert !o.save + assert o.errors[:pets].any? + pet = o.pets.build('name' => 'apet') + assert o.valid? + end + end + + def test_validates_size_of_association_using_within + repair_validations(Owner) do + assert_nothing_raised { Owner.validates_size_of :pets, :within => 1..2 } + o = Owner.new('name' => 'nopets') + assert !o.save + assert o.errors[:pets].any? + + pet = o.pets.build('name' => 'apet') + assert o.valid? + + 2.times { o.pets.build('name' => 'apet') } + assert !o.save + assert o.errors[:pets].any? + end + end + + def test_validates_associated_many + Topic.validates_associated( :replies ) + t = Topic.create("title" => "uhohuhoh", "content" => "whatever") + t.replies << [r = Reply.new("title" => "A reply"), r2 = Reply.new("title" => "Another reply", "content" => "non-empty"), r3 = Reply.new("title" => "Yet another reply"), r4 = Reply.new("title" => "The last reply", "content" => "non-empty")] + assert !t.valid? + assert t.errors[:replies].any? + assert_equal 1, r.errors.count # make sure all associated objects have been validated + assert_equal 0, r2.errors.count + assert_equal 1, r3.errors.count + assert_equal 0, r4.errors.count + r.content = r3.content = "non-empty" + assert t.valid? + end + + def test_validates_associated_one + repair_validations(Reply) do + Reply.validates_associated( :topic ) + Topic.validates_presence_of( :content ) + r = Reply.new("title" => "A reply", "content" => "with content!") + r.topic = Topic.create("title" => "uhohuhoh") + assert !r.valid? + assert r.errors[:topic].any? + r.topic.content = "non-empty" + assert r.valid? + end + end + + def test_validates_associated_with_custom_message_using_quotes + repair_validations(Reply) do + Reply.validates_associated :topic, :message=> "This string contains 'single' and \"double\" quotes" + Topic.validates_presence_of :content + r = Reply.create("title" => "A reply", "content" => "with content!") + r.topic = Topic.create("title" => "uhohuhoh") + assert !r.valid? + assert_equal ["This string contains 'single' and \"double\" quotes"], r.errors[:topic] + end + end + + def test_validates_associated_missing + repair_validations(Reply) do + Reply.validates_presence_of(:topic) + r = Reply.create("title" => "A reply", "content" => "with content!") + assert !r.valid? + assert r.errors[:topic].any? + + r.topic = Topic.find :first + assert r.valid? + end + end + + def test_validates_size_of_association_utf8 + repair_validations(Owner) do + with_kcode('UTF8') do + assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 } + o = Owner.new('name' => 'あいうえおかきくけこ') + assert !o.save + assert o.errors[:pets].any? + o.pets.build('name' => 'あいうえおかきくけこ') + assert o.valid? + end + end + end +end diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb new file mode 100644 index 0000000000..29c10de4fe --- /dev/null +++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb @@ -0,0 +1,166 @@ +require "cases/helper" +require 'models/topic' +require 'models/reply' + +class I18nGenerateMessageValidationTest < Test::Unit::TestCase + def setup + reset_callbacks Topic + @topic = Topic.new + I18n.backend.store_translations :'en', { + :activerecord => { + :errors => { + :messages => { + :taken => "has already been taken", + } + } + } + } + end + + def reset_callbacks(*models) + models.each do |model| + model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new) + model.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new) + model.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new) + end + end + + # validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value) + def test_generate_message_inclusion_with_default_message + assert_equal 'is not included in the list', @topic.errors.generate_message(:title, :inclusion, :default => nil, :value => 'title') + end + + def test_generate_message_inclusion_with_custom_message + assert_equal 'custom message title', @topic.errors.generate_message(:title, :inclusion, :default => 'custom message {{value}}', :value => 'title') + end + + # validates_exclusion_of: generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value) + def test_generate_message_exclusion_with_default_message + assert_equal 'is reserved', @topic.errors.generate_message(:title, :exclusion, :default => nil, :value => 'title') + end + + def test_generate_message_exclusion_with_custom_message + assert_equal 'custom message title', @topic.errors.generate_message(:title, :exclusion, :default => 'custom message {{value}}', :value => 'title') + end + + # validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) + # validates_format_of: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) + def test_generate_message_invalid_with_default_message + assert_equal 'is invalid', @topic.errors.generate_message(:title, :invalid, :default => nil, :value => 'title') + end + + def test_generate_message_invalid_with_custom_message + assert_equal 'custom message title', @topic.errors.generate_message(:title, :invalid, :default => 'custom message {{value}}', :value => 'title') + end + + # validates_confirmation_of: generate_message(attr_name, :confirmation, :default => configuration[:message]) + def test_generate_message_confirmation_with_default_message + assert_equal "doesn't match confirmation", @topic.errors.generate_message(:title, :confirmation, :default => nil) + end + + def test_generate_message_confirmation_with_custom_message + assert_equal 'custom message', @topic.errors.generate_message(:title, :confirmation, :default => 'custom message') + end + + # validates_acceptance_of: generate_message(attr_name, :accepted, :default => configuration[:message]) + def test_generate_message_accepted_with_default_message + assert_equal "must be accepted", @topic.errors.generate_message(:title, :accepted, :default => nil) + end + + def test_generate_message_accepted_with_custom_message + assert_equal 'custom message', @topic.errors.generate_message(:title, :accepted, :default => 'custom message') + end + + # add_on_empty: generate_message(attr, :empty, :default => custom_message) + def test_generate_message_empty_with_default_message + assert_equal "can't be empty", @topic.errors.generate_message(:title, :empty, :default => nil) + end + + def test_generate_message_empty_with_custom_message + assert_equal 'custom message', @topic.errors.generate_message(:title, :empty, :default => 'custom message') + end + + # add_on_blank: generate_message(attr, :blank, :default => custom_message) + def test_generate_message_blank_with_default_message + assert_equal "can't be blank", @topic.errors.generate_message(:title, :blank, :default => nil) + end + + def test_generate_message_blank_with_custom_message + assert_equal 'custom message', @topic.errors.generate_message(:title, :blank, :default => 'custom message') + end + + # validates_length_of: generate_message(attr, :too_long, :default => options[:too_long], :count => option_value.end) + def test_generate_message_too_long_with_default_message + assert_equal "is too long (maximum is 10 characters)", @topic.errors.generate_message(:title, :too_long, :default => nil, :count => 10) + end + + def test_generate_message_too_long_with_custom_message + assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_long, :default => 'custom message {{count}}', :count => 10) + end + + # validates_length_of: generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin) + def test_generate_message_too_short_with_default_message + assert_equal "is too short (minimum is 10 characters)", @topic.errors.generate_message(:title, :too_short, :default => nil, :count => 10) + end + + def test_generate_message_too_short_with_custom_message + assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_short, :default => 'custom message {{count}}', :count => 10) + end + + # validates_length_of: generate_message(attr, key, :default => custom_message, :count => option_value) + def test_generate_message_wrong_length_with_default_message + assert_equal "is the wrong length (should be 10 characters)", @topic.errors.generate_message(:title, :wrong_length, :default => nil, :count => 10) + end + + def test_generate_message_wrong_length_with_custom_message + assert_equal 'custom message 10', @topic.errors.generate_message(:title, :wrong_length, :default => 'custom message {{count}}', :count => 10) + end + + # validates_numericality_of: generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message]) + def test_generate_message_not_a_number_with_default_message + assert_equal "is not a number", @topic.errors.generate_message(:title, :not_a_number, :default => nil, :value => 'title') + end + + def test_generate_message_not_a_number_with_custom_message + assert_equal 'custom message title', @topic.errors.generate_message(:title, :not_a_number, :default => 'custom message {{value}}', :value => 'title') + end + + # validates_numericality_of: generate_message(attr_name, option, :value => raw_value, :default => configuration[:message]) + def test_generate_message_greater_than_with_default_message + assert_equal "must be greater than 10", @topic.errors.generate_message(:title, :greater_than, :default => nil, :value => 'title', :count => 10) + end + + def test_generate_message_greater_than_or_equal_to_with_default_message + assert_equal "must be greater than or equal to 10", @topic.errors.generate_message(:title, :greater_than_or_equal_to, :default => nil, :value => 'title', :count => 10) + end + + def test_generate_message_equal_to_with_default_message + assert_equal "must be equal to 10", @topic.errors.generate_message(:title, :equal_to, :default => nil, :value => 'title', :count => 10) + end + + def test_generate_message_less_than_with_default_message + assert_equal "must be less than 10", @topic.errors.generate_message(:title, :less_than, :default => nil, :value => 'title', :count => 10) + end + + def test_generate_message_less_than_or_equal_to_with_default_message + assert_equal "must be less than or equal to 10", @topic.errors.generate_message(:title, :less_than_or_equal_to, :default => nil, :value => 'title', :count => 10) + end + + def test_generate_message_odd_with_default_message + assert_equal "must be odd", @topic.errors.generate_message(:title, :odd, :default => nil, :value => 'title', :count => 10) + end + + def test_generate_message_even_with_default_message + assert_equal "must be even", @topic.errors.generate_message(:title, :even, :default => nil, :value => 'title', :count => 10) + end + + # validates_uniqueness_of: generate_message(attr_name, :taken, :default => configuration[:message]) + def test_generate_message_taken_with_default_message + assert_equal "has already been taken", @topic.errors.generate_message(:title, :taken, :default => nil, :value => 'title') + end + + def test_generate_message_taken_with_custom_message + assert_equal 'custom message title', @topic.errors.generate_message(:title, :taken, :default => 'custom message {{value}}', :value => 'title') + end + +end diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb new file mode 100644 index 0000000000..0303147e50 --- /dev/null +++ b/activerecord/test/cases/validations/i18n_validation_test.rb @@ -0,0 +1,711 @@ +require "cases/helper" +require 'models/topic' +require 'models/reply' + +class I18nValidationTest < ActiveRecord::TestCase + def setup + reset_callbacks Topic + @topic = Topic.new + @old_load_path, @old_backend = I18n.load_path, I18n.backend + I18n.load_path.clear + I18n.backend = I18n::Backend::Simple.new + I18n.backend.store_translations('en', :activerecord => {:errors => {:messages => {:custom => nil}}}) + end + + def teardown + reset_callbacks Topic + I18n.load_path.replace @old_load_path + I18n.backend = @old_backend + end + + def unique_topic + @unique ||= Topic.create :title => 'unique!' + end + + def replied_topic + @replied_topic ||= begin + topic = Topic.create(:title => "topic") + topic.replies << Reply.new + topic + end + end + + def reset_callbacks(*models) + models.each do |model| + model.instance_variable_set("@validate_callbacks", ActiveSupport::Callbacks::CallbackChain.new) + model.instance_variable_set("@validate_on_create_callbacks", ActiveSupport::Callbacks::CallbackChain.new) + model.instance_variable_set("@validate_on_update_callbacks", ActiveSupport::Callbacks::CallbackChain.new) + end + end + + def test_percent_s_interpolation_syntax_in_error_messages_was_deprecated + assert_not_deprecated do + default = "%s interpolation syntax was deprecated" + assert_equal default, I18n.t(:does_not_exist, :default => default, :value => 'this') + end + end + + def test_percent_d_interpolation_syntax_in_error_messages_was_deprecated + assert_not_deprecated do + default = "%d interpolation syntaxes are deprecated" + assert_equal default, I18n.t(:does_not_exist, :default => default, :count => 2) + end + end + + # ActiveRecord::Errors + def test_errors_generate_message_translates_custom_model_attribute_key + I18n.expects(:translate).with( + :topic, + { :count => 1, + :default => ['Topic'], + :scope => [:activerecord, :models] + } + ).returns('Topic') + + I18n.expects(:translate).with( + :"topic.title", + { :count => 1, + :default => ['Title'], + :scope => [:activerecord, :attributes] + } + ).returns('Title') + + I18n.expects(:translate).with( + :"models.topic.attributes.title.invalid", + :value => nil, + :scope => [:activerecord, :errors], + :default => [ + :"models.topic.invalid", + 'default from class def error 1', + :"messages.invalid"], + :attribute => "Title", + :model => "Topic" + ).returns('default from class def error 1') + + @topic.errors.generate_message :title, :invalid, :default => 'default from class def error 1' + end + + def test_errors_generate_message_translates_custom_model_attribute_keys_with_sti + + I18n.expects(:translate).with( + :reply, + { :count => 1, + :default => [:topic, 'Reply'], + :scope => [:activerecord, :models] + } + ).returns('Reply') + + I18n.expects(:translate).with( + :"reply.title", + { :count => 1, + :default => [:'topic.title', 'Title'], + :scope => [:activerecord, :attributes] + } + ).returns('Title') + + I18n.expects(:translate).with( + :"models.reply.attributes.title.invalid", + :value => nil, + :scope => [:activerecord, :errors], + :default => [ + :"models.reply.invalid", + :"models.topic.attributes.title.invalid", + :"models.topic.invalid", + 'default from class def', + :"messages.invalid"], + :model => 'Reply', + :attribute => 'Title' + ).returns("default from class def") + + Reply.new.errors.generate_message :title, :invalid, :default => 'default from class def' + + end + + # validates_uniqueness_of w/ mocha + + def test_validates_uniqueness_of_generates_message + Topic.validates_uniqueness_of :title + @topic.title = unique_topic.title + @topic.errors.expects(:generate_message).with(:title, :taken, {:default => nil, :value => 'unique!'}) + @topic.valid? + end + + def test_validates_uniqueness_of_generates_message_with_custom_default_message + Topic.validates_uniqueness_of :title, :message => 'custom' + @topic.title = unique_topic.title + @topic.errors.expects(:generate_message).with(:title, :taken, {:default => 'custom', :value => 'unique!'}) + @topic.valid? + end + + # validates_associated w/ mocha + + def test_validates_associated_generates_message + Topic.validates_associated :replies + replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil}) + replied_topic.valid? + end + + def test_validates_associated_generates_message_with_custom_default_message + Topic.validates_associated :replies + replied_topic.errors.expects(:generate_message).with(:replies, :invalid, {:value => replied_topic.replies, :default => nil}) + replied_topic.valid? + end + + # validates_associated w/o mocha + + def test_validates_associated_finds_custom_model_key_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:replies => {:invalid => 'custom message'}}}}}} + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} + + Topic.validates_associated :replies + replied_topic.valid? + assert_equal ['custom message'], replied_topic.errors[:replies] + end + + def test_validates_associated_finds_global_default_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} + + Topic.validates_associated :replies + replied_topic.valid? + assert_equal ['global message'], replied_topic.errors[:replies] + end + + def test_errors_add_on_empty_generates_message + @topic.errors.expects(:generate_message).with(:title, :empty, {:default => nil}) + @topic.errors.add_on_empty :title + end + + def test_errors_add_on_empty_generates_message_with_custom_default_message + @topic.errors.expects(:generate_message).with(:title, :empty, {:default => 'custom'}) + @topic.errors.add_on_empty :title, 'custom' + end + + def test_errors_add_on_blank_generates_message + @topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil}) + @topic.errors.add_on_blank :title + end + + def test_errors_add_on_blank_generates_message_with_custom_default_message + @topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'}) + @topic.errors.add_on_blank :title, 'custom' + end + + def test_errors_full_messages_translates_human_attribute_name_for_model_attributes + @topic.errors.add('title', 'empty') + I18n.expects(:translate).with(:"topic.title", :default => ['Title'], :scope => [:activerecord, :attributes], :count => 1).returns('Title') + @topic.errors.full_messages :locale => 'en' + end + + # ActiveRecord::Validations + # validates_confirmation_of w/ mocha + def test_validates_confirmation_of_generates_message + Topic.validates_confirmation_of :title + @topic.title_confirmation = 'foo' + @topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => nil}) + @topic.valid? + end + + def test_validates_confirmation_of_generates_message_with_custom_default_message + Topic.validates_confirmation_of :title, :message => 'custom' + @topic.title_confirmation = 'foo' + @topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => 'custom'}) + @topic.valid? + end + + # validates_acceptance_of w/ mocha + + def test_validates_acceptance_of_generates_message + Topic.validates_acceptance_of :title, :allow_nil => false + @topic.errors.expects(:generate_message).with(:title, :accepted, {:default => nil}) + @topic.valid? + end + + def test_validates_acceptance_of_generates_message_with_custom_default_message + Topic.validates_acceptance_of :title, :message => 'custom', :allow_nil => false + @topic.errors.expects(:generate_message).with(:title, :accepted, {:default => 'custom'}) + @topic.valid? + end + + # validates_presence_of w/ mocha + + def test_validates_presence_of_generates_message + Topic.validates_presence_of :title + @topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil}) + @topic.valid? + end + + def test_validates_presence_of_generates_message_with_custom_default_message + Topic.validates_presence_of :title, :message => 'custom' + @topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'}) + @topic.valid? + end + + def test_validates_length_of_within_generates_message_with_title_too_short + Topic.validates_length_of :title, :within => 3..5 + @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil}) + @topic.valid? + end + + def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message + Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom' + @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'}) + @topic.valid? + end + + def test_validates_length_of_within_generates_message_with_title_too_long + Topic.validates_length_of :title, :within => 3..5 + @topic.title = 'this title is too long' + @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil}) + @topic.valid? + end + + def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message + Topic.validates_length_of :title, :within => 3..5, :too_long => 'custom' + @topic.title = 'this title is too long' + @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'}) + @topic.valid? + end + + # validates_length_of :within w/ mocha + + def test_validates_length_of_within_generates_message_with_title_too_short + Topic.validates_length_of :title, :within => 3..5 + @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil}) + @topic.valid? + end + + def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message + Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom' + @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'}) + @topic.valid? + end + + def test_validates_length_of_within_generates_message_with_title_too_long + Topic.validates_length_of :title, :within => 3..5 + @topic.title = 'this title is too long' + @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil}) + @topic.valid? + end + + def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message + Topic.validates_length_of :title, :within => 3..5, :too_long => 'custom' + @topic.title = 'this title is too long' + @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'}) + @topic.valid? + end + + # validates_length_of :is w/ mocha + + def test_validates_length_of_is_generates_message + Topic.validates_length_of :title, :is => 5 + @topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => nil}) + @topic.valid? + end + + def test_validates_length_of_is_generates_message_with_custom_default_message + Topic.validates_length_of :title, :is => 5, :message => 'custom' + @topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => 'custom'}) + @topic.valid? + end + + # validates_format_of w/ mocha + + def test_validates_format_of_generates_message + Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/ + @topic.title = '72x' + @topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => nil}) + @topic.valid? + end + + def test_validates_format_of_generates_message_with_custom_default_message + Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/, :message => 'custom' + @topic.title = '72x' + @topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => 'custom'}) + @topic.valid? + end + + # validates_inclusion_of w/ mocha + + def test_validates_inclusion_of_generates_message + Topic.validates_inclusion_of :title, :in => %w(a b c) + @topic.title = 'z' + @topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => nil}) + @topic.valid? + end + + def test_validates_inclusion_of_generates_message_with_custom_default_message + Topic.validates_inclusion_of :title, :in => %w(a b c), :message => 'custom' + @topic.title = 'z' + @topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => 'custom'}) + @topic.valid? + end + + # validates_exclusion_of w/ mocha + + def test_validates_exclusion_of_generates_message + Topic.validates_exclusion_of :title, :in => %w(a b c) + @topic.title = 'a' + @topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => nil}) + @topic.valid? + end + + def test_validates_exclusion_of_generates_message_with_custom_default_message + Topic.validates_exclusion_of :title, :in => %w(a b c), :message => 'custom' + @topic.title = 'a' + @topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => 'custom'}) + @topic.valid? + end + + # validates_numericality_of without :only_integer w/ mocha + + def test_validates_numericality_of_generates_message + Topic.validates_numericality_of :title + @topic.title = 'a' + @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil}) + @topic.valid? + end + + def test_validates_numericality_of_generates_message_with_custom_default_message + Topic.validates_numericality_of :title, :message => 'custom' + @topic.title = 'a' + @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'}) + @topic.valid? + end + + # validates_numericality_of with :only_integer w/ mocha + + def test_validates_numericality_of_only_integer_generates_message + Topic.validates_numericality_of :title, :only_integer => true + @topic.title = 'a' + @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil}) + @topic.valid? + end + + def test_validates_numericality_of_only_integer_generates_message_with_custom_default_message + Topic.validates_numericality_of :title, :only_integer => true, :message => 'custom' + @topic.title = 'a' + @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'}) + @topic.valid? + end + + # validates_numericality_of :odd w/ mocha + + def test_validates_numericality_of_odd_generates_message + Topic.validates_numericality_of :title, :only_integer => true, :odd => true + @topic.title = 0 + @topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => nil}) + @topic.valid? + end + + def test_validates_numericality_of_odd_generates_message_with_custom_default_message + Topic.validates_numericality_of :title, :only_integer => true, :odd => true, :message => 'custom' + @topic.title = 0 + @topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => 'custom'}) + @topic.valid? + end + + # validates_numericality_of :less_than w/ mocha + + def test_validates_numericality_of_less_than_generates_message + Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0 + @topic.title = 1 + @topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => nil}) + @topic.valid? + end + + def test_validates_numericality_of_odd_generates_message_with_custom_default_message + Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0, :message => 'custom' + @topic.title = 1 + @topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => 'custom'}) + @topic.valid? + end + + # validates_confirmation_of w/o mocha + + def test_validates_confirmation_of_finds_custom_model_key_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:confirmation => 'custom message'}}}}}} + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:confirmation => 'global message'}}} + + Topic.validates_confirmation_of :title + @topic.title_confirmation = 'foo' + @topic.valid? + assert_equal ['custom message'], @topic.errors[:title] + end + + def test_validates_confirmation_of_finds_global_default_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:confirmation => 'global message'}}} + + Topic.validates_confirmation_of :title + @topic.title_confirmation = 'foo' + @topic.valid? + assert_equal ['global message'], @topic.errors[:title] + end + + # validates_acceptance_of w/o mocha + + def test_validates_acceptance_of_finds_custom_model_key_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:accepted => 'custom message'}}}}}} + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:accepted => 'global message'}}} + + Topic.validates_acceptance_of :title, :allow_nil => false + @topic.valid? + assert_equal ['custom message'], @topic.errors[:title] + end + + def test_validates_acceptance_of_finds_global_default_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:accepted => 'global message'}}} + + Topic.validates_acceptance_of :title, :allow_nil => false + @topic.valid? + assert_equal ['global message'], @topic.errors[:title] + end + + # validates_presence_of w/o mocha + + def test_validates_presence_of_finds_custom_model_key_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:blank => 'custom message'}}}}}} + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:blank => 'global message'}}} + + Topic.validates_presence_of :title + @topic.valid? + assert_equal ['custom message'], @topic.errors[:title] + end + + def test_validates_presence_of_finds_global_default_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:blank => 'global message'}}} + + Topic.validates_presence_of :title + @topic.valid? + assert_equal ['global message'], @topic.errors[:title] + end + + # validates_length_of :within w/o mocha + + def test_validates_length_of_within_finds_custom_model_key_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:too_short => 'custom message'}}}}}} + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:too_short => 'global message'}}} + + Topic.validates_length_of :title, :within => 3..5 + @topic.valid? + assert_equal ['custom message'], @topic.errors[:title] + end + + def test_validates_length_of_within_finds_global_default_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:too_short => 'global message'}}} + + Topic.validates_length_of :title, :within => 3..5 + @topic.valid? + assert_equal ['global message'], @topic.errors[:title] + end + + # validates_length_of :is w/o mocha + + def test_validates_length_of_is_finds_custom_model_key_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}} + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} + + Topic.validates_length_of :title, :is => 5 + @topic.valid? + assert_equal ['custom message'], @topic.errors[:title] + end + + def test_validates_length_of_is_finds_global_default_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} + + Topic.validates_length_of :title, :is => 5 + @topic.valid? + assert_equal ['global message'], @topic.errors[:title] + end + + def test_validates_length_of_is_finds_custom_model_key_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}} + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} + + Topic.validates_length_of :title, :is => 5 + @topic.valid? + assert_equal ['custom message'], @topic.errors[:title] + end + + def test_validates_length_of_is_finds_global_default_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} + + Topic.validates_length_of :title, :is => 5 + @topic.valid? + assert_equal ['global message'], @topic.errors[:title] + end + + + # validates_format_of w/o mocha + + def test_validates_format_of_finds_custom_model_key_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:invalid => 'custom message'}}}}}} + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} + + Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/ + @topic.valid? + assert_equal ['custom message'], @topic.errors[:title] + end + + def test_validates_format_of_finds_global_default_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} + + Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/ + @topic.valid? + assert_equal ['global message'], @topic.errors[:title] + end + + # validates_inclusion_of w/o mocha + + def test_validates_inclusion_of_finds_custom_model_key_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:inclusion => 'custom message'}}}}}} + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:inclusion => 'global message'}}} + + Topic.validates_inclusion_of :title, :in => %w(a b c) + @topic.valid? + assert_equal ['custom message'], @topic.errors[:title] + end + + def test_validates_inclusion_of_finds_global_default_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:inclusion => 'global message'}}} + + Topic.validates_inclusion_of :title, :in => %w(a b c) + @topic.valid? + assert_equal ['global message'], @topic.errors[:title] + end + + # validates_exclusion_of w/o mocha + + def test_validates_exclusion_of_finds_custom_model_key_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:exclusion => 'custom message'}}}}}} + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:exclusion => 'global message'}}} + + Topic.validates_exclusion_of :title, :in => %w(a b c) + @topic.title = 'a' + @topic.valid? + assert_equal ['custom message'], @topic.errors[:title] + end + + def test_validates_exclusion_of_finds_global_default_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:exclusion => 'global message'}}} + + Topic.validates_exclusion_of :title, :in => %w(a b c) + @topic.title = 'a' + @topic.valid? + assert_equal ['global message'], @topic.errors[:title] + end + + # validates_numericality_of without :only_integer w/o mocha + + def test_validates_numericality_of_finds_custom_model_key_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}} + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} + + Topic.validates_numericality_of :title + @topic.title = 'a' + @topic.valid? + assert_equal ['custom message'], @topic.errors[:title] + end + + def test_validates_numericality_of_finds_global_default_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} + + Topic.validates_numericality_of :title, :only_integer => true + @topic.title = 'a' + @topic.valid? + assert_equal ['global message'], @topic.errors[:title] + end + + # validates_numericality_of with :only_integer w/o mocha + + def test_validates_numericality_of_only_integer_finds_custom_model_key_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}} + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} + + Topic.validates_numericality_of :title, :only_integer => true + @topic.title = 'a' + @topic.valid? + assert_equal ['custom message'], @topic.errors[:title] + end + + def test_validates_numericality_of_only_integer_finds_global_default_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} + + Topic.validates_numericality_of :title, :only_integer => true + @topic.title = 'a' + @topic.valid? + assert_equal ['global message'], @topic.errors[:title] + end + + # validates_numericality_of :odd w/o mocha + + def test_validates_numericality_of_odd_finds_custom_model_key_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:odd => 'custom message'}}}}}} + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:odd => 'global message'}}} + + Topic.validates_numericality_of :title, :only_integer => true, :odd => true + @topic.title = 0 + @topic.valid? + assert_equal ['custom message'], @topic.errors[:title] + end + + def test_validates_numericality_of_odd_finds_global_default_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:odd => 'global message'}}} + + Topic.validates_numericality_of :title, :only_integer => true, :odd => true + @topic.title = 0 + @topic.valid? + assert_equal ['global message'], @topic.errors[:title] + end + + # validates_numericality_of :less_than w/o mocha + + def test_validates_numericality_of_less_than_finds_custom_model_key_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:less_than => 'custom message'}}}}}} + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:less_than => 'global message'}}} + + Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0 + @topic.title = 1 + @topic.valid? + assert_equal ['custom message'], @topic.errors[:title] + end + + def test_validates_numericality_of_less_than_finds_global_default_translation + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:less_than => 'global message'}}} + + Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0 + @topic.title = 1 + @topic.valid? + assert_equal ['global message'], @topic.errors[:title] + end + + def test_validations_with_message_symbol_must_translate + I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:custom_error => "I am a custom error"}}} + Topic.validates_presence_of :title, :message => :custom_error + @topic.title = nil + @topic.valid? + assert_equal ["I am a custom error"], @topic.errors[:title] + end + + def test_validates_with_message_symbol_must_translate_per_attribute + I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}}} + Topic.validates_presence_of :title, :message => :custom_error + @topic.title = nil + @topic.valid? + assert_equal ["I am a custom error"], @topic.errors[:title] + end + + def test_validates_with_message_symbol_must_translate_per_model + I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:custom_error => "I am a custom error"}}}} + Topic.validates_presence_of :title, :message => :custom_error + @topic.title = nil + @topic.valid? + assert_equal ["I am a custom error"], @topic.errors[:title] + end + + def test_validates_with_message_string + Topic.validates_presence_of :title, :message => "I am a custom error" + @topic.title = nil + @topic.valid? + assert_equal ["I am a custom error"], @topic.errors[:title] + end +end diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb new file mode 100644 index 0000000000..961db51d1d --- /dev/null +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -0,0 +1,278 @@ +# encoding: utf-8 +require "cases/helper" +require 'models/topic' +require 'models/reply' +require 'models/warehouse_thing' +require 'models/guid' +require 'models/event' + +# The following methods in Topic are used in test_conditional_validation_* +class Topic + has_many :unique_replies, :dependent => :destroy, :foreign_key => "parent_id" + has_many :silly_unique_replies, :dependent => :destroy, :foreign_key => "parent_id" +end + +class UniqueReply < Reply + validates_uniqueness_of :content, :scope => 'parent_id' +end + +class SillyUniqueReply < UniqueReply +end + +class Wizard < ActiveRecord::Base + self.abstract_class = true + + validates_uniqueness_of :name +end + +class IneptWizard < Wizard + validates_uniqueness_of :city +end + +class Conjurer < IneptWizard +end + +class Thaumaturgist < IneptWizard +end + +class UniquenessValidationTest < ActiveRecord::TestCase + fixtures :topics, 'warehouse-things' + + repair_validations(Topic) + + def test_validate_uniqueness + Topic.validates_uniqueness_of(:title) + + t = Topic.new("title" => "I'm unique!") + assert t.save, "Should save t as unique" + + t.content = "Remaining unique" + assert t.save, "Should still save t as unique" + + t2 = Topic.new("title" => "I'm unique!") + assert !t2.valid?, "Shouldn't be valid" + assert !t2.save, "Shouldn't save t2 as unique" + assert_equal ["has already been taken"], t2.errors[:title] + + t2.title = "Now Im really also unique" + assert t2.save, "Should now save t2 as unique" + end + + def test_validates_uniquness_with_newline_chars + Topic.validates_uniqueness_of(:title, :case_sensitive => false) + + t = Topic.new("title" => "new\nline") + assert t.save, "Should save t as unique" + end + + def test_validate_uniqueness_with_scope + repair_validations(Reply) do + Reply.validates_uniqueness_of(:content, :scope => "parent_id") + + t = Topic.create("title" => "I'm unique!") + + r1 = t.replies.create "title" => "r1", "content" => "hello world" + assert r1.valid?, "Saving r1" + + r2 = t.replies.create "title" => "r2", "content" => "hello world" + assert !r2.valid?, "Saving r2 first time" + + r2.content = "something else" + assert r2.save, "Saving r2 second time" + + t2 = Topic.create("title" => "I'm unique too!") + r3 = t2.replies.create "title" => "r3", "content" => "hello world" + assert r3.valid?, "Saving r3" + end + end + + def test_validate_uniqueness_scoped_to_defining_class + t = Topic.create("title" => "What, me worry?") + + r1 = t.unique_replies.create "title" => "r1", "content" => "a barrel of fun" + assert r1.valid?, "Saving r1" + + r2 = t.silly_unique_replies.create "title" => "r2", "content" => "a barrel of fun" + assert !r2.valid?, "Saving r2" + + # Should succeed as validates_uniqueness_of only applies to + # UniqueReply and its subclasses + r3 = t.replies.create "title" => "r2", "content" => "a barrel of fun" + assert r3.valid?, "Saving r3" + end + + def test_validate_uniqueness_with_scope_array + repair_validations(Reply) do + Reply.validates_uniqueness_of(:author_name, :scope => [:author_email_address, :parent_id]) + + t = Topic.create("title" => "The earth is actually flat!") + + r1 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply" + assert r1.valid?, "Saving r1" + + r2 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy@rubyonrails.com", "title" => "You're crazy!", "content" => "Crazy reply again..." + assert !r2.valid?, "Saving r2. Double reply by same author." + + r2.author_email_address = "jeremy_alt_email@rubyonrails.com" + assert r2.save, "Saving r2 the second time." + + r3 = t.replies.create "author_name" => "jeremy", "author_email_address" => "jeremy_alt_email@rubyonrails.com", "title" => "You're wrong", "content" => "It's cubic" + assert !r3.valid?, "Saving r3" + + r3.author_name = "jj" + assert r3.save, "Saving r3 the second time." + + r3.author_name = "jeremy" + assert !r3.save, "Saving r3 the third time." + end + end + + def test_validate_case_insensitive_uniqueness + Topic.validates_uniqueness_of(:title, :parent_id, :case_sensitive => false, :allow_nil => true) + + t = Topic.new("title" => "I'm unique!", :parent_id => 2) + assert t.save, "Should save t as unique" + + t.content = "Remaining unique" + assert t.save, "Should still save t as unique" + + t2 = Topic.new("title" => "I'm UNIQUE!", :parent_id => 1) + assert !t2.valid?, "Shouldn't be valid" + assert !t2.save, "Shouldn't save t2 as unique" + assert t2.errors[:title].any? + assert t2.errors[:parent_id].any? + assert_equal ["has already been taken"], t2.errors[:title] + + t2.title = "I'm truly UNIQUE!" + assert !t2.valid?, "Shouldn't be valid" + assert !t2.save, "Shouldn't save t2 as unique" + assert t2.errors[:title].empty? + assert t2.errors[:parent_id].any? + + t2.parent_id = 4 + assert t2.save, "Should now save t2 as unique" + + t2.parent_id = nil + t2.title = nil + assert t2.valid?, "should validate with nil" + assert t2.save, "should save with nil" + + with_kcode('UTF8') do + t_utf8 = Topic.new("title" => "Я тоже уникальный!") + assert t_utf8.save, "Should save t_utf8 as unique" + + # If database hasn't UTF-8 character set, this test fails + if Topic.find(t_utf8, :select => 'LOWER(title) AS title').title == "я тоже уникальный!" + t2_utf8 = Topic.new("title" => "я тоже УНИКАЛЬНЫЙ!") + assert !t2_utf8.valid?, "Shouldn't be valid" + assert !t2_utf8.save, "Shouldn't save t2_utf8 as unique" + end + end + end + + def test_validate_case_sensitive_uniqueness + Topic.validates_uniqueness_of(:title, :case_sensitive => true, :allow_nil => true) + + t = Topic.new("title" => "I'm unique!") + assert t.save, "Should save t as unique" + + t.content = "Remaining unique" + assert t.save, "Should still save t as unique" + + t2 = Topic.new("title" => "I'M UNIQUE!") + assert t2.valid?, "Should be valid" + assert t2.save, "Should save t2 as unique" + assert t2.errors[:title].empty? + assert t2.errors[:parent_id].empty? + assert_not_equal ["has already been taken"], t2.errors[:title] + + t3 = Topic.new("title" => "I'M uNiQUe!") + assert t3.valid?, "Should be valid" + assert t3.save, "Should save t2 as unique" + assert t3.errors[:title].empty? + assert t3.errors[:parent_id].empty? + assert_not_equal ["has already been taken"], t3.errors[:title] + end + + def test_validate_case_sensitive_uniqueness_with_attribute_passed_as_integer + Topic.validates_uniqueness_of(:title, :case_sensitve => true) + t = Topic.create!('title' => 101) + + t2 = Topic.new('title' => 101) + assert !t2.valid? + assert t2.errors[:title] + end + + def test_validate_uniqueness_with_non_standard_table_names + i1 = WarehouseThing.create(:value => 1000) + assert !i1.valid?, "i1 should not be valid" + assert i1.errors[:value].any?, "Should not be empty" + end + + def test_validates_uniqueness_inside_with_scope + Topic.validates_uniqueness_of(:title) + + Topic.with_scope(:find => { :conditions => { :author_name => "David" } }) do + t1 = Topic.new("title" => "I'm unique!", "author_name" => "Mary") + assert t1.save + t2 = Topic.new("title" => "I'm unique!", "author_name" => "David") + assert !t2.valid? + end + end + + def test_validate_uniqueness_with_columns_which_are_sql_keywords + repair_validations(Guid) do + Guid.validates_uniqueness_of :key + g = Guid.new + g.key = "foo" + assert_nothing_raised { !g.valid? } + end + end + + def test_validate_uniqueness_with_limit + # Event.title is limited to 5 characters + e1 = Event.create(:title => "abcde") + assert e1.valid?, "Could not create an event with a unique, 5 character title" + e2 = Event.create(:title => "abcdefgh") + assert !e2.valid?, "Created an event whose title, with limit taken into account, is not unique" + end + + def test_validate_straight_inheritance_uniqueness + w1 = IneptWizard.create(:name => "Rincewind", :city => "Ankh-Morpork") + assert w1.valid?, "Saving w1" + + # Should use validation from base class (which is abstract) + w2 = IneptWizard.new(:name => "Rincewind", :city => "Quirm") + assert !w2.valid?, "w2 shouldn't be valid" + assert w2.errors[:name].any?, "Should have errors for name" + assert_equal ["has already been taken"], w2.errors[:name], "Should have uniqueness message for name" + + w3 = Conjurer.new(:name => "Rincewind", :city => "Quirm") + assert !w3.valid?, "w3 shouldn't be valid" + assert w3.errors[:name].any?, "Should have errors for name" + assert_equal ["has already been taken"], w3.errors[:name], "Should have uniqueness message for name" + + w4 = Conjurer.create(:name => "The Amazing Bonko", :city => "Quirm") + assert w4.valid?, "Saving w4" + + w5 = Thaumaturgist.new(:name => "The Amazing Bonko", :city => "Lancre") + assert !w5.valid?, "w5 shouldn't be valid" + assert w5.errors[:name].any?, "Should have errors for name" + assert_equal ["has already been taken"], w5.errors[:name], "Should have uniqueness message for name" + + w6 = Thaumaturgist.new(:name => "Mustrum Ridcully", :city => "Quirm") + assert !w6.valid?, "w6 shouldn't be valid" + assert w6.errors[:city].any?, "Should have errors for city" + assert_equal ["has already been taken"], w6.errors[:city], "Should have uniqueness message for city" + end + + def test_validates_uniqueness_of_with_custom_message_using_quotes + repair_validations(Developer) do + Developer.validates_uniqueness_of :name, :message=> "This string contains 'single' and \"double\" quotes" + d = Developer.new + d.name = "David" + assert !d.valid? + assert_equal ["This string contains 'single' and \"double\" quotes"], d.errors[:name] + end + end +end |