# frozen_string_literal: true
require_relative "spec_helper"

model_class = proc do |klass, &block|
  c = Class.new(klass)
  c.plugin :validation_class_methods
  c.class_eval(&block) if block
  c
end

describe Sequel::Model do
  before do
    @c = model_class.call Sequel::Model do
      def self.validates_coolness_of(attr)
        validates_each(attr) {|o, a, v| o.errors.add(a, 'is not cool') if v != :cool}
      end
    end
  end
  
  it "should freeze validation metadata when freezing model class" do
    @c.validates_acceptance_of(:a)
    @c.freeze
    @c.validations.frozen?.must_equal true
    @c.validations.values.all?(&:frozen?).must_equal true
    @c.validation_reflections.frozen?.must_equal true
    @c.validation_reflections.values.all? do |vs|
      vs.frozen? && vs.all? do |v|
        v.frozen? && v.last.frozen?
      end
    end.must_equal true
  end

  it "should respond to validations, has_validations?, and validation_reflections" do
    @c.must_respond_to(:validations)
    @c.must_respond_to(:has_validations?)
    @c.must_respond_to(:validation_reflections)
  end
  
  it "should be able to reflect on validations" do
    @c.validation_reflections.must_equal({})
    @c.validates_acceptance_of(:a)
    @c.validation_reflections.must_equal(:a=>[[:acceptance, {:tag=>:acceptance, :message=>"is not accepted", :allow_nil=>true, :accept=>"1"}]])
    @c.validates_presence_of(:a)
    @c.validation_reflections[:a].length.must_equal 2
    @c.validation_reflections[:a].last.must_equal [:presence, {:tag=>:presence, :message=>"is not present"}]
  end

  it "should handle validation reflections correctly when subclassing" do
    @c.validates_acceptance_of(:a)
    c = Class.new(@c)
    c.validation_reflections.map{|k,v| k}.must_equal [:a]
    c.validates_presence_of(:a)
    @c.validation_reflections.must_equal(:a=>[[:acceptance, {:tag=>:acceptance, :message=>"is not accepted", :allow_nil=>true, :accept=>"1"}]])
    c.validation_reflections[:a].last.must_equal [:presence, {:tag=>:presence, :message=>"is not present"}]
  end

  it "should acccept validation definitions using validates_each" do
    @c.validates_each(:xx, :yy) {|o, a, v| o.errors.add(a, 'too low') if v < 50}
    o = @c.new
    def o.xx; 40; end
    def o.yy; 60; end
    o.valid?.must_equal false
    o.errors.full_messages.must_equal ['xx too low']
  end

  it "should return true/false for has_validations?" do
    @c.has_validations?.must_equal false
    @c.validates_each(:xx) {1}
    @c.has_validations?.must_equal true
  end
  
  it "should validate multiple attributes at once" do
    o = @c.new
    def o.xx
      1
    end
    def o.yy
      2
    end
    vals = nil
    atts = nil
    @c.validates_each([:xx, :yy]){|obj,a,v| atts=a; vals=v}
    o.valid?
    vals.must_equal [1,2]
    atts.must_equal [:xx, :yy]
  end
  
  it "should respect allow_missing option when using multiple attributes" do
    o = @c.new
    def o.xx
      self[:xx]
    end
    def o.yy
      self[:yy]
    end
    vals = nil
    atts = nil
    @c.validates_each([:xx, :yy], :allow_missing=>true){|obj,a,v| atts=a; vals=v}

    o.values[:xx] = 1
    o.valid?
    vals.must_equal [1,nil]
    atts.must_equal [:xx, :yy]

    vals = nil
    atts = nil
    o.values.clear
    o.values[:yy] = 2
    o.valid?
    vals.must_equal [nil, 2]
    atts.must_equal [:xx, :yy]

    vals = nil
    atts = nil
    o.values.clear
    o.valid?.must_equal true
    vals.must_be_nil
    atts.must_be_nil
  end
  
  it "should overwrite existing validation with the same tag and attribute" do
    @c.validates_each(:xx, :xx, :tag=>:low) {|o, a, v| o.xxx; o.errors.add(a, 'too low') if v < 50}
    @c.validates_each(:yy, :yy) {|o, a, v| o.yyy; o.errors.add(a, 'too low') if v < 50}
    @c.validates_presence_of(:zz, :zz)
    @c.validates_length_of(:aa, :aa, :tag=>:blah)
    o = @c.new
    def o.zz
      @a ||= 0
      @a += 1
    end
    def o.aa
      @b ||= 0
      @b += 1
    end
    def o.xx; 40; end
    def o.yy; 60; end
    def o.xxx; end
    def o.yyy; end
    o.valid?.must_equal false
    o.zz.must_equal 2
    o.aa.must_equal 2
    o.errors.full_messages.must_equal ['xx too low']
  end

  it "should provide a validates method that takes block with validation definitions" do
    @c.validates do
      coolness_of :blah
    end
    @c.validations[:blah].wont_be :empty?
    o = @c.new
    def o.blah; end
    o.valid?.must_equal false
    o.errors.full_messages.must_equal ['blah is not cool']
  end

  it "should have the validates block have appropriate respond_to?" do
    c = nil
    @c.validates{c = respond_to?(:foo)}
    c.must_equal false
    @c.validates{c = respond_to?(:length_of)}
    c.must_equal true
  end
end

describe Sequel::Model do
  before do
    @c = model_class.call Sequel::Model do
      columns :score
      validates_each :score do |o, a, v|
        o.errors.add(a, 'too low') if v < 87
      end
    end
    
    @o = @c.new
  end
  
  it "should supply a #valid? method that returns true if validations pass" do
    @o.score = 50
    @o.wont_be :valid?
    @o.score = 100
    @o.must_be :valid?
  end
  
  it "should provide an errors object" do
    @o.score = 100
    @o.must_be :valid?
    @o.errors.must_be :empty?
    
    @o.score = 86
    @o.wont_be :valid?
    @o.errors[:score].must_equal ['too low']
    @o.errors.on(:blah).must_be_nil
  end
end

describe "Sequel::Plugins::ValidationClassMethods::ClassMethods::Generator" do
  before do
    @testit = testit = []
    
    @c = model_class.call Sequel::Model do
      singleton_class.send(:define_method, :validates_blah) do
        testit << 1324
      end
    end
  end
  
  it "should instance_eval the block, sending everything to its receiver" do
    @c.validates do
      blah
    end
    @testit.must_equal [1324]
  end
end

describe Sequel::Model do
  before do
    @c = model_class.call Sequel::Model do
      columns :value
      
      def self.where(*args)
        o = Object.new
        def o.count; 2; end
        o
      end

      def skip; false; end
      def dont_skip; true; end
    end
    @m = @c.new
  end

  it "should validate acceptance_of" do
    @c.validates_acceptance_of :value
    @m.must_be :valid?
    @m.value = '1'
    @m.must_be :valid?
  end
  
  it "should validate acceptance_of with accept" do
    @c.validates_acceptance_of :value, :accept => 'true'
    @m.value = '1'
    @m.wont_be :valid?
    @m.value = 'true'
    @m.must_be :valid?
  end
  
  it "should validate acceptance_of with allow_nil => false" do
    @c.validates_acceptance_of :value, :allow_nil => false
    @m.wont_be :valid?
  end

  it "should validate acceptance_of with allow_missing => true" do
    @c.validates_acceptance_of :value, :allow_missing => true
    @m.must_be :valid?
  end

  it "should validate acceptance_of with allow_missing => true and allow_nil => false" do
    @c.validates_acceptance_of :value, :allow_missing => true, :allow_nil => false
    @m.must_be :valid?
    @m.value = nil
    @m.wont_be :valid?
  end

  it "should validate acceptance_of with allow_blank => true" do
    @c.validates_acceptance_of :value, :allow_blank => true
    @m.must_be :valid?
    @m.value = ''
    @m.must_be :valid?
    @m.value = '0'
    @m.wont_be :valid?
  end

  it "should validate acceptance_of with if => true" do
    @c.validates_acceptance_of :value, :if => :dont_skip
    @m.value = '0'
    @m.wont_be :valid?
  end

  it "should validate acceptance_of with if => false" do
    @c.validates_acceptance_of :value, :if => :skip
    @m.value = '0'
    @m.must_be :valid?
  end

  it "should validate acceptance_of with if proc that evaluates to true" do
    @c.validates_acceptance_of :value, :if => proc{true}
    @m.value = '0'
    @m.wont_be :valid?
  end

  it "should validate acceptance_of with if proc that evaluates to false" do
    @c.validates_acceptance_of :value, :if => proc{false}
    @m.value = '0'
    @m.must_be :valid?
  end

  it "should raise an error if :if option is not a Symbol, Proc, or nil" do
    @c.validates_acceptance_of :value, :if => 1
    @m.value = '0'
    proc{@m.valid?}.must_raise(Sequel::Error)
  end

  it "should validate confirmation_of" do
    @c.send(:attr_accessor, :value_confirmation)
    @c.validates_confirmation_of :value
    
    @m.value = 'blah'
    @m.wont_be :valid?
    
    @m.value_confirmation = 'blah'
    @m.must_be :valid?
  end
  
  it "should validate confirmation_of with if => true" do
    @c.send(:attr_accessor, :value_confirmation)
    @c.validates_confirmation_of :value, :if => :dont_skip

    @m.value = 'blah'
    @m.wont_be :valid?
  end

  it "should validate confirmation_of with if => false" do
    @c.send(:attr_accessor, :value_confirmation)
    @c.validates_confirmation_of :value, :if => :skip

    @m.value = 'blah'
    @m.must_be :valid?
  end

  it "should validate confirmation_of with allow_missing => true" do
    @c.send(:attr_accessor, :value_confirmation)
    @c.validates_confirmation_of :value, :allow_missing => true
    @m.must_be :valid?
    @m.value_confirmation = 'blah'
    @m.must_be :valid?
    @m.value = nil
    @m.wont_be :valid?
  end

  it "should validate format_of" do
    @c.validates_format_of :value, :with => /.+_.+/
    @m.value = 'abc_'
    @m.wont_be :valid?
    @m.value = 'abc_def'
    @m.must_be :valid?
  end
  
  it "should raise for validate_format_of without regexp" do
    proc {@c.validates_format_of :value}.must_raise(ArgumentError)
    proc {@c.validates_format_of :value, :with => :blah}.must_raise(ArgumentError)
  end
  
  it "should validate format_of with if => true" do
    @c.validates_format_of :value, :with => /_/, :if => :dont_skip

    @m.value = 'a'
    @m.wont_be :valid?
  end

  it "should validate format_of with if => false" do
    @c.validates_format_of :value, :with => /_/, :if => :skip

    @m.value = 'a'
    @m.must_be :valid?
  end
  
  it "should validate format_of with allow_missing => true" do
    @c.validates_format_of :value, :allow_missing => true, :with=>/./
    @m.must_be :valid?
    @m.value = nil
    @m.wont_be :valid?
  end

  it "should validate length_of with maximum" do
    @c.validates_length_of :value, :maximum => 5
    @m.wont_be :valid?
    @m.value = '12345'
    @m.must_be :valid?
    @m.value = '123456'
    @m.wont_be :valid?
    @m.errors[:value].must_equal ['is too long']
    @m.value = nil
    @m.wont_be :valid?
    @m.errors[:value].must_equal ['is not present']
  end

  it "should validate length_of with maximum using customized error messages" do
    @c.validates_length_of :value, :maximum => 5, :too_long=>'tl', :nil_message=>'np'
    @m.value = '123456'
    @m.wont_be :valid?
    @m.errors[:value].must_equal ['tl']
    @m.value = nil
    @m.wont_be :valid?
    @m.errors[:value].must_equal ['np']
  end

  it "should validate length_of with minimum" do
    @c.validates_length_of :value, :minimum => 5
    @m.wont_be :valid?
    @m.value = '12345'
    @m.must_be :valid?
    @m.value = '1234'
    @m.wont_be :valid?
  end

  it "should validate length_of with within" do
    @c.validates_length_of :value, :within => 2..5
    @m.wont_be :valid?
    @m.value = '12345'
    @m.must_be :valid?
    @m.value = '1'
    @m.wont_be :valid?
    @m.value = '123456'
    @m.wont_be :valid?
  end

  it "should validate length_of with within" do
    @c.validates_length_of :value, :within => [2,3,4,5]
    @m.wont_be :valid?
    @m.value = '12345'
    @m.must_be :valid?
    @m.value = '1'
    @m.wont_be :valid?
    @m.value = '123456'
    @m.wont_be :valid?
  end

  it "should validate length_of with is" do
    @c.validates_length_of :value, :is => 3
    @m.wont_be :valid?
    @m.value = '123'
    @m.must_be :valid?
    @m.value = '12'
    @m.wont_be :valid?
    @m.value = '1234'
    @m.wont_be :valid?
  end
  
  it "should validate length_of with allow_nil" do
    @c.validates_length_of :value, :is => 3, :allow_nil => true
    @m.must_be :valid?
  end

  it "should validate length_of with if => true" do
    @c.validates_length_of :value, :is => 3, :if => :dont_skip

    @m.value = 'a'
    @m.wont_be :valid?
  end

  it "should validate length_of with if => false" do
    @c.validates_length_of :value, :is => 3, :if => :skip

    @m.value = 'a'
    @m.must_be :valid?
  end

  it "should validate length_of with allow_missing => true" do
    @c.validates_length_of :value, :allow_missing => true, :minimum => 5
    @m.must_be :valid?
    @m.value = nil
    @m.wont_be :valid?
  end

  it "should allow multiple calls to validates_length_of with different options without overwriting" do
    @c.validates_length_of :value, :maximum => 5
    @c.validates_length_of :value, :minimum => 5
    @m.wont_be :valid?
    @m.value = '12345'
    @m.must_be :valid?
    @m.value = '123456'
    @m.wont_be :valid?
    @m.value = '12345'
    @m.must_be :valid?
    @m.value = '1234'
    @m.wont_be :valid?
  end

  it "should validate numericality_of" do
    @c.validates_numericality_of :value
    @m.value = 'blah'
    @m.wont_be :valid?
    @m.value = '123'
    @m.must_be :valid?
    @m.value = '123.1231'
    @m.must_be :valid?
    @m.value = '+1'
    @m.must_be :valid?
    @m.value = '-1'
    @m.must_be :valid?
    @m.value = '+1.123'
    @m.must_be :valid?
    @m.value = '-0.123'
    @m.must_be :valid?
    @m.value = '-0.123E10'
    @m.must_be :valid?
    @m.value = '32.123e10'
    @m.must_be :valid?
    @m.value = '+32.123E10'
    @m.must_be :valid?
    @m.must_be :valid?
    @m.value = '.0123'
  end

  it "should validate numericality_of with only_integer" do
    @c.validates_numericality_of :value, :only_integer => true
    @m.value = 'blah'
    @m.wont_be :valid?
    @m.value = '123'
    @m.must_be :valid?
    @m.value = '123.1231'
    @m.wont_be :valid?
  end
  
  it "should validate numericality_of with if => true" do
    @c.validates_numericality_of :value, :if => :dont_skip

    @m.value = 'a'
    @m.wont_be :valid?
  end

  it "should validate numericality_of with if => false" do
    @c.validates_numericality_of :value, :if => :skip

    @m.value = 'a'
    @m.must_be :valid?
  end

  it "should validate numericality_of with allow_missing => true" do
    @c.validates_numericality_of :value, :allow_missing => true
    @m.must_be :valid?
    @m.value = nil
    @m.wont_be :valid?
  end

  it "should validate presence_of" do
    @c.validates_presence_of :value
    @m.wont_be :valid?
    @m.value = ''
    @m.wont_be :valid?
    @m.value = 1234
    @m.must_be :valid?
    @m.value = nil
    @m.wont_be :valid?
    @m.value = true
    @m.must_be :valid?
    @m.value = false
    @m.must_be :valid?
  end
  
  it "should validate inclusion_of with an array" do
    @c.validates_inclusion_of :value, :in => [1,2]
    @m.wont_be :valid?
    @m.value = 1
    @m.must_be :valid?
    @m.value = 1.5
    @m.wont_be :valid?
    @m.value = 2
    @m.must_be :valid?    
    @m.value = 3
    @m.wont_be :valid? 
  end
  
  it "should validate inclusion_of with a range" do
    @c.validates_inclusion_of :value, :in => 1..4
    @m.wont_be :valid?
    @m.value = 1
    @m.must_be :valid?
    @m.value = 1.5
    @m.must_be :valid?
    @m.value = 0
    @m.wont_be :valid?
    @m.value = 5
    @m.wont_be :valid?    
  end
  
  it "should raise an error if inclusion_of doesn't receive a valid :in option" do
    lambda{@c.validates_inclusion_of :value}.must_raise(ArgumentError)
    lambda{@c.validates_inclusion_of :value, :in => 1}.must_raise(ArgumentError)
  end
  
  it "should raise an error if inclusion_of handles :allow_nil too" do
    @c.validates_inclusion_of :value, :in => 1..4, :allow_nil => true
    @m.value = nil
    @m.must_be :valid?
    @m.value = 0
    @m.wont_be :valid?
  end

  it "should validate presence_of with if => true" do
    @c.validates_presence_of :value, :if => :dont_skip
    @m.wont_be :valid?
  end

  it "should validate presence_of with if => false" do
    @c.validates_presence_of :value, :if => :skip
    @m.must_be :valid?
  end

  it "should validate presence_of with allow_missing => true" do
    @c.validates_presence_of :value, :allow_missing => true
    @m.must_be :valid?
    @m.value = nil
    @m.wont_be :valid?
  end

  it "should validate uniqueness_of with if => true" do
    @c.validates_uniqueness_of :value, :if => :dont_skip

    @m.value = 'a'
    @m.wont_be :valid?
  end

  it "should validate uniqueness_of with if => false" do
    @c.validates_uniqueness_of :value, :if => :skip
    @m.value = 'a'
    @m.must_be :valid?
  end
  
  it "should validate uniqueness_of with allow_missing => true" do
    @c.validates_uniqueness_of :value, :allow_missing => true
    @m.must_be :valid?
    @m.value = 1
    @m.wont_be :valid?
  end
end

describe "Superclass validations" do
  before do
    @c1 = model_class.call Sequel::Model do
      columns :value
      validates_length_of :value, :minimum => 5
    end
    
    @c2 = Class.new(@c1)
    @c2.class_eval do
      columns :value
      validates_format_of :value, :with => /^[a-z]+$/
    end
  end
  
  it "should be checked when validating" do
    o = @c2.new
    o.value = 'ab'
    o.valid?.must_equal false
    o.errors.full_messages.must_equal ['value is too short']

    o.value = '12'
    o.valid?.must_equal false
    o.errors.full_messages.must_equal ['value is too short', 'value is invalid']

    o.value = 'abcde'
    o.valid?.must_equal true
  end
  
  it "should have skip_superclass_validations? return whether superclass validations were skipped" do
    @c2.skip_superclass_validations?.must_be_nil
    @c2.skip_superclass_validations
    @c2.skip_superclass_validations?.must_equal true
  end

  it "should be skipped if skip_superclass_validations is called" do
    @c2.skip_superclass_validations

    o = @c2.new
    o.value = 'ab'
    o.valid?.must_equal true

    o.value = '12'
    o.valid?.must_equal false
    o.errors.full_messages.must_equal ['value is invalid']

    o.value = 'abcde'
    o.valid?.must_equal true
  end

  it "should handle case where superclass validation has already been deleted" do
    @c1.class_eval do
      validates_length_of :foo, :minimum => 5
    end
    
    @c2 = Class.new(@c1)
    @c2.class_eval do
      columns :value
      validates_format_of :value, :with => /^[a-z]+$/
    end
    @c2.validations.delete(:foo)
    @c2.skip_superclass_validations

    o = @c2.new
    o.value = 'ab'
    o.valid?.must_equal true

    o.value = '12'
    o.valid?.must_equal false
    o.errors.full_messages.must_equal ['value is invalid']

    o.value = 'abcde'
    o.valid?.must_equal true
  end
end

describe ".validates with block" do
  it "should support calling .each" do
    @c = model_class.call Sequel::Model do
      columns :vvv
      validates do
        each :vvv do |o, a, v|
          o.errors.add(a, "is less than zero") if v.to_i < 0
        end
      end
    end
    
    o = @c.new
    o.vvv = 1
    o.must_be :valid?
    o.vvv = -1
    o.wont_be :valid?
  end
end

describe Sequel::Model, "Validations" do
  before do
    class ::Person < Sequel::Model
      plugin :validation_class_methods
      columns :id,:name,:first_name,:last_name,:middle_name,:initials,:age, :terms
    end

    class ::Smurf < Person
    end

    class ::Can < Sequel::Model
      plugin :validation_class_methods
      columns :id, :name
    end
    
    class ::Cow < Sequel::Model
      plugin :validation_class_methods
      columns :id, :name, :got_milk
    end

    class ::User < Sequel::Model
      plugin :validation_class_methods
      columns :id, :username, :password
    end
    
    class ::Address < Sequel::Model
      plugin :validation_class_methods
      columns :id, :zip_code
    end
  end
  after do
    [:Person, :Smurf, :Cow, :User, :Address].each{|c| Object.send(:remove_const, c)}
  end
  
  it "should validate the acceptance of a column" do
    class ::Cow < Sequel::Model
      validations.clear
      validates_acceptance_of :got_milk, :accept => 'blah', :allow_nil => false
    end
    
    @cow = Cow.new
    @cow.wont_be :valid?
    @cow.errors.full_messages.must_equal ["got_milk is not accepted"]
    
    @cow.got_milk = "blah"
    @cow.must_be :valid?
  end
  
  it "should validate the confirmation of a column" do
    class ::User < Sequel::Model
      def password_confirmation
        "test"
      end
      
      validations.clear
      validates_confirmation_of :password
    end
    
    @user = User.new
    @user.wont_be :valid?
    @user.errors.full_messages.must_equal ["password is not confirmed"]
    
    @user.password = "test"
    @user.must_be :valid?
  end
  
  it "should validate format of column" do
    class ::Person < Sequel::Model
      validates_format_of :first_name, :with => /^[a-zA-Z]+$/
    end

    @person = Person.new :first_name => "Lancelot99"
    @person.valid?.must_equal false
    @person = Person.new :first_name => "Anita"
    @person.valid?.must_equal true
  end
  
  it "should validate length of column" do
    class ::Person < Sequel::Model
      validations.clear
      validates_length_of :first_name, :maximum => 30
      validates_length_of :last_name, :minimum => 30
      validates_length_of :middle_name, :within => 1..5
      validates_length_of :initials, :is => 2
    end
    
    @person = Person.new(
      :first_name => "Anamethatiswaytofreakinglongandwayoverthirtycharacters",
      :last_name => "Alastnameunderthirtychars",
      :initials => "LGC",
      :middle_name => "danger"
    )
    
    @person.wont_be :valid?
    @person.errors.full_messages.size.must_equal 4
    @person.errors.full_messages.sort.must_equal [
      'first_name is too long',
      'initials is the wrong length',
      'last_name is too short',
      'middle_name is the wrong length'
    ]
    
    @person.first_name  = "Lancelot"
    @person.last_name   = "1234567890123456789012345678901"
    @person.initials    = "LC"
    @person.middle_name = "Will"
    @person.must_be :valid?
  end
  
  it "should validate that a column has the correct type for the schema column" do
    p = model_class.call Sequel::Model do
      columns :age, :d, :b
      self.raise_on_typecast_failure = false
      validates_schema_type :age
      validates_schema_type :d, :message=>'is a bad choice'
      validates_schema_type :b
      @db_schema = {:age=>{:type=>:integer}, :d=>{:type=>:date}, :b=>{:type=>:datetime}}
    end
    
    @person = p.new
    @person.must_be :valid?

    @person.age = 'a'
    @person.wont_be :valid?
    @person.errors.full_messages.must_equal ['age is not a valid integer']
    @person.age = 1
    @person.must_be :valid?

    @person.d = 'a'
    @person.wont_be :valid?
    @person.errors.full_messages.must_equal ['d is a bad choice']
    @person.d = Date.today
    @person.must_be :valid?

    @person.b = 'XX'
    @person.wont_be :valid?
    @person.errors.full_messages.must_equal ['b is not a valid time or datetime']
    @person.b = DateTime.now
    @person.must_be :valid?
  end

  it "should validate numericality of column" do
    class ::Person < Sequel::Model
      validations.clear
      validates_numericality_of :age
    end
    
    @person = Person.new :age => "Twenty"
    @person.wont_be :valid?
    @person.errors.full_messages.must_equal ['age is not a number']
    
    @person.age = 20
    @person.must_be :valid?
  end
  
  it "should validate the presence of a column" do
    class ::Cow < Sequel::Model
      validations.clear
      validates_presence_of :name
    end
    
    @cow = Cow.new
    @cow.wont_be :valid?
    @cow.errors.full_messages.must_equal ['name is not present']
    
    @cow.name = "Betsy"
    @cow.must_be :valid?
  end
 
  it "should validate the uniqueness of a column" do
    class ::User < Sequel::Model
      validations.clear
      validates do
        uniqueness_of :username
      end
    end
    User.dataset = User.dataset.with_fetch(proc do |sql|
      case sql
      when /count.*username = '0records'/
        {:v => 0}
      when /count.*username = '2records'/
        {:v => 2}
      when /count.*username = '1record'/
        {:v => 1}
      when /username = '1record'/
        {:id => 3, :username => "1record", :password => "test"}
      end
    end)
    
    @user = User.new(:username => "2records", :password => "anothertest")
    @user.wont_be :valid?
    @user.errors.full_messages.must_equal ['username is already taken']

    @user = User.new(:username => "1record", :password => "anothertest")
    @user.wont_be :valid?
    @user.errors.full_messages.must_equal ['username is already taken']

    @user = User.load(:id=>4, :username => "1record", :password => "anothertest")
    @user.wont_be :valid?
    @user.errors.full_messages.must_equal ['username is already taken']

    @user = User.load(:id=>3, :username => "1record", :password => "anothertest")
    @user.must_be :valid?
    @user.errors.full_messages.must_equal []

    @user = User.new(:username => "0records", :password => "anothertest")
    @user.must_be :valid?
    @user.errors.full_messages.must_equal []

    User.db.sqls
    @user = User.new(:password => "anothertest")
    @user.must_be :valid?
    @user.errors.full_messages.must_equal []
    User.db.sqls.must_equal []
  end
  
  it "should validate the uniqueness of multiple columns" do
    class ::User < Sequel::Model
      validations.clear
      validates do
        uniqueness_of [:username, :password]
      end
    end
    User.dataset = User.dataset.with_fetch(proc do |sql|
      case sql
      when /count.*username = '0records'/
        {:v => 0}
      when /count.*username = '2records'/
        {:v => 2}
      when /count.*username = '1record'/
        {:v => 1}
      when /username = '1record'/
        if sql =~ /password = 'anothertest'/
          {:id => 3, :username => "1record", :password => "anothertest"}
        else
          {:id => 4, :username => "1record", :password => "test"}
        end
      end
    end)
    
    @user = User.new(:username => "2records", :password => "anothertest")
    @user.wont_be :valid?
    @user.errors.full_messages.must_equal ['username and password is already taken']

    @user = User.new(:username => "1record", :password => "anothertest")
    @user.wont_be :valid?
    @user.errors.full_messages.must_equal ['username and password is already taken']

    @user = User.load(:id=>4, :username => "1record", :password => "anothertest")
    @user.wont_be :valid?
    @user.errors.full_messages.must_equal ['username and password is already taken']

    @user = User.load(:id=>3, :username => "1record", :password => "test")
    @user.wont_be :valid?
    @user.errors.full_messages.must_equal ['username and password is already taken']

    @user = User.load(:id=>3, :username => "1record", :password => "anothertest")
    @user.must_be :valid?
    @user.errors.full_messages.must_equal []

    @user = User.new(:username => "0records", :password => "anothertest")
    @user.must_be :valid?
    @user.errors.full_messages.must_equal []

    User.db.sqls
    @user = User.new(:password => "anothertest")
    @user.must_be :valid?
    @user.errors.full_messages.must_equal []
    @user = User.new(:username => "0records")
    @user.must_be :valid?
    @user.errors.full_messages.must_equal []
    @user = User.new
    @user.must_be :valid?
    @user.errors.full_messages.must_equal []
    User.db.sqls.must_equal []
  end
  
  it "should have a validates block that contains multiple validations" do
    class ::Person < Sequel::Model
      validations.clear
      validates do
        format_of :first_name, :with => /^[a-zA-Z]+$/
        length_of :first_name, :maximum => 30
      end
    end

    Person.validations[:first_name].size.must_equal 2
    
    @person = Person.new :first_name => "Lancelot99"
    @person.valid?.must_equal false
    
    @person2 = Person.new :first_name => "Wayne"
    @person2.valid?.must_equal true
  end

  it "should allow 'longhand' validations direcly within the model." do
    class ::Person < Sequel::Model
      validations.clear
      validates_length_of :first_name, :maximum => 30
    end
    Person.validations.length.must_equal(1)
  end

  it "should define a has_validations? method which returns true if the model has validations, false otherwise" do
    class ::Person < Sequel::Model
      validations.clear
      validates do
        format_of :first_name, :with => /\w+/
        length_of :first_name, :maximum => 30
      end
    end

    class ::Smurf < Person
      validations.clear
    end

    Person.validations.wont_be :empty?
    Smurf.validations.must_be :empty?
  end

  it "should validate correctly instances initialized with string keys" do
    class ::Can < Sequel::Model
      validates_length_of :name, :minimum => 4
    end
    
    Can.new('name' => 'ab').wont_be :valid?
    Can.new('name' => 'abcd').must_be :valid?
  end
  
end

describe "Model#save" do
  before do
    @c = model_class.call Sequel::Model(:people) do
      columns :id, :x

      validates_each :x do |o, a, v|
        o.errors.add(a, 'blah') unless v == 7
      end
    end
    @m = @c.load(:id => 4, :x=>6)
    DB.reset
  end

  it "should save only if validations pass" do
    @m.raise_on_save_failure = false
    @m.wont_be :valid?
    @m.save
    DB.sqls.must_be :empty?
    
    @m.x = 7
    @m.must_be :valid?
    @m.save.wont_equal false
    DB.sqls.must_equal ['UPDATE people SET x = 7 WHERE (id = 4)']
  end
  
  it "should skip validations if the :validate=>false option is used" do
    @m.raise_on_save_failure = false
    @m.wont_be :valid?
    @m.save(:validate=>false)
    DB.sqls.must_equal ['UPDATE people SET x = 6 WHERE (id = 4)']
  end
    
  it "should raise error if validations fail and raise_on_save_faiure is true" do
    proc{@m.save}.must_raise(Sequel::ValidationFailed)
  end
  
  it "should return nil if validations fail and raise_on_save_faiure is false" do
    @m.raise_on_save_failure = false
    @m.save.must_be_nil
  end
end
