# frozen_string_literal: true
require_relative "spec_helper"

describe Sequel::Model::Errors do
  before do
    @errors = Sequel::Model::Errors.new
  end
  
  it "should be clearable using #clear" do
    @errors.add(:a, 'b')
    @errors.must_equal(:a=>['b'])
    @errors.clear
    @errors.must_equal({})
  end
  
  it "should be empty if there are no errors" do
    @errors.must_be :empty?
  end
  
  it "should not be empty if there are errors" do
    @errors.add(:blah, "blah")
    @errors.wont_be :empty?
  end
  
  it "should return an array of errors for a specific attribute using #on if there are errors" do
    @errors.add(:blah, 'blah')
    @errors.on(:blah).must_equal ['blah']
  end
  
  it "should return nil using #on if there are no errors for that attribute" do
    @errors.on(:blah).must_be_nil
  end
  
  it "should accept errors using #add" do
    @errors.add :blah, 'zzzz'
    @errors[:blah].must_equal ['zzzz']
  end
  
  it "should return full messages using #full_messages" do
    @errors.full_messages.must_equal []
    
    @errors.add(:blow, 'blieuh')
    @errors.add(:blow, 'blich')
    @errors.add(:blay, 'bliu')
    msgs = @errors.full_messages
    msgs.sort.must_equal ['blay bliu', 'blow blich', 'blow blieuh']
  end

  it "should not add column names for LiteralStrings" do
    @errors.full_messages.must_equal []
    
    @errors.add(:blow, 'blieuh')
    @errors.add(:blow, Sequel.lit('blich'))
    @errors.add(:blay, 'bliu')
    msgs = @errors.full_messages
    msgs.sort.must_equal ['blay bliu', 'blich', 'blow blieuh']
  end

  it "should allow for customizing #full_messages via #full_message if not a LiteralString" do
    @errors.full_messages.must_equal []
    
    def @errors.full_message(attribute, msg)
      "#{attribute.class}-#{msg}-#{attribute.inspect}"
    end
    @errors.add(:blow, 'blieuh')
    @errors.add(:blow, Sequel.lit('blich'))
    @errors.add(:blay, 'bliu')
    msgs = @errors.full_messages
    msgs.sort.must_equal ['Symbol-blieuh-:blow', 'Symbol-bliu-:blay', 'blich']
  end

  it "should return the number of error messages using #count" do
    @errors.count.must_equal 0
    @errors.add(:a, 'b')
    @errors.count.must_equal 1
    @errors.add(:a, 'c')
    @errors.count.must_equal 2
    @errors.add(:b, 'c')
    @errors.count.must_equal 3
  end

  it "should return the array of error messages for a given attribute using #on" do
    @errors.add(:a, 'b')
    @errors.on(:a).must_equal ['b']
    @errors.add(:a, 'c')
    @errors.on(:a).must_equal ['b', 'c']
    @errors.add(:b, 'c')
    @errors.on(:a).must_equal ['b', 'c']
  end

  it "should return nil if there are no error messages for a given attribute using #on" do
    @errors.on(:a).must_be_nil
    @errors.add(:b, 'b')
    @errors.on(:a).must_be_nil
  end
end

describe Sequel::Model do
  before do
    @c = Class.new(Sequel::Model) do
      columns :score
      def validate
        errors.add(:score, 'too low') if score < 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
  
  it "should allow raising of ValidationFailed with a Model instance with errors" do
    @o.errors.add(:score, 'is too low')
    begin
      raise Sequel::ValidationFailed, @o
    rescue Sequel::ValidationFailed => e
    end
    e.model.must_be_same_as(@o)
    e.errors.must_be_same_as(@o.errors)
    e.message.must_equal 'score is too low'
  end
  
  it "should allow raising of ValidationFailed with an Errors instance" do
    @o.errors.add(:score, 'is too low')
    begin
      raise Sequel::ValidationFailed, @o.errors
    rescue Sequel::ValidationFailed => e
    end
    e.model.must_be_nil
    e.errors.must_be_same_as(@o.errors)
    e.message.must_equal 'score is too low'
  end

  it "should allow raising of ValidationFailed with a string" do
    proc{raise Sequel::ValidationFailed, "no reason"}.must_raise(Sequel::ValidationFailed, "no reason")
  end
end

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

      def validate
        errors.add(:id, 'blah') unless x == 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 skip validations if the skip_validation_on_save! method is used" do
    @m.raise_on_save_failure = false
    @m.wont_be :valid?
    @m.skip_validation_on_next_save!
    @m.save
    DB.sqls.must_equal ['UPDATE people SET x = 6 WHERE (id = 4)']
  end

  it "should not skip future validations if the skip_validation_on_save! method is used" do
    @m.wont_be :valid?
    @m.skip_validation_on_next_save!
    @m.save
    DB.sqls.must_equal ['UPDATE people SET x = 6 WHERE (id = 4)']
    proc{@m.save}.must_raise Sequel::ValidationFailed

    @m.skip_validation_on_next_save!
    @m.save
    DB.sqls.must_equal ['UPDATE people SET x = 6 WHERE (id = 4)']
  end

  it "should skip validations if the skip_validation_on_save! method is used and :validate=>true option is used" do
    @m.wont_be :valid?
    @m.skip_validation_on_next_save!
    @m.save(:validate=>true)
    DB.sqls.must_equal ['UPDATE people SET x = 6 WHERE (id = 4)']
  end

  it "should raise error if validations fail and raise_on_save_failure is true" do
    begin
      @m.save
    rescue Sequel::ValidationFailed => e
      e.model.must_be_same_as(@m)
      e.errors.must_be_same_as(@m.errors)
    else
      raise
    end
  end

  it "should raise error if validations fail and :raise_on_failure option is true" do
    @m.raise_on_save_failure = false
    proc{@m.save(:raise_on_failure => true)}.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
