require 'spec_helper'

describe "Validations" do
  context "Saving a new document that is invalid" do
    before do
      @document = Doc do
        key :name, String, :required => true
      end
    end

    it "should not insert document" do
      doc = @document.new
      doc.save
      @document.count.should == 0
    end

    it "should populate document's errors" do
      doc = @document.new
      doc.errors.size.should == 0
      doc.save
      doc.errors.full_messages.should == ["Name can't be blank"]
    end
  end

  context "Saving a document that is invalid (destructive)" do
    before do
      @document = Doc do
        key :name, String, :required => true
      end
    end

    it "should raise error" do
      doc = @document.new
      lambda { doc.save! }.should raise_error(MongoMapper::DocumentNotValid)
    end

    it "should set document on exception" do
      doc = @document.new
      begin
        doc.save!
      rescue MongoMapper::DocumentNotValid => e
        e.document.should == doc
      end
    end
  end

  context "Creating a document that is invalid (destructive)" do
    before do
      @document = Doc do
        key :name, String, :required => true
      end
    end

    it "should raise error" do
      lambda { @document.create! }.should raise_error(MongoMapper::DocumentNotValid)
    end

    it "should create a new document" do
      instance = @document.create!(:name => "James")
      instance.new_record?.should be_falsey
    end
  end

  context "Saving an existing document that is invalid" do
    before do
      @document = Doc do
        key :name, String, :required => true
      end

      @doc = @document.create(:name => 'John Nunemaker')
    end

    it "should not update document" do
      @doc.name = nil
      @doc.save
      @doc.reload.name.should == 'John Nunemaker'
    end

    it "should populate document's errors" do
      @doc.name = nil
      @doc.save
      @doc.errors.full_messages.should == ["Name can't be blank"]
    end
  end

  context "Adding validation errors" do
    before do
      @document = Doc do
        key :action, String
        def action_present
          errors.add(:action, 'is invalid') if action.blank?
        end
      end
    end

    it "should work with validate :on => :create callback" do
      @document.validate :action_present, :on => :create

      doc = @document.create(:action => nil)
      doc.should have_error_on(:action)

      doc.action = 'kick'
      doc.save
      doc.should_not have_error_on(:action)
    end

    it "should work with validate :on => :update callback" do
      @document.validate :action_present, :on => :update

      doc = @document.new
      doc.action = nil
      doc.should_not have_error_on(:action)
      doc.save

      doc.action = nil
      doc.should have_error_on(:action)

      doc.action = 'kick'
      doc.should_not have_error_on(:action)
    end
  end

  context "validating uniqueness of" do
    before do
      @document = Doc do
        key :name, String
        validates_uniqueness_of :name
      end
    end

    it "should not fail if object is new" do
      doc = @document.new
      doc.should_not have_error_on(:name)
    end

    it "should not fail when new object is out of scope" do
      document = Doc do
        key :name
        key :adult
        validates_uniqueness_of :name, :scope => :adult
      end
      doc = document.new("name" => "joe", :adult => true)
      doc.save.should be_truthy

      doc2 = document.new("name" => "joe", :adult => false)
      doc2.should be_valid
    end

    it "should work with i18n taken message" do
      @document.create(:name => 'joe')
      doc = @document.create(:name => 'joe')
      doc.should have_error_on(:name)
      doc.errors[:name].should == ['has already been taken']
    end

    it "should allow to update an object" do
      doc = @document.new("name" => "joe")
      doc.save.should be_truthy

      @document.stub(:first).
        with(:name => 'joe').
        and_return(doc)

      doc.name = "joe"
      doc.valid?.should be_truthy
      doc.should_not have_error_on(:name)
    end

    it "should fail if object name is not unique" do
      doc = @document.new("name" => "joe")
      doc.save.should be_truthy

      @document.stub(:first).
        with(:name => 'joe').
        and_return(doc)

      doc2 = @document.new("name" => "joe")
      doc2.should have_error_on(:name)
    end

    it "should allow multiple blank entries if :allow_blank => true" do
      document = Doc do
        key :name
        validates_uniqueness_of :name, :allow_blank => :true
      end

      doc = document.new("name" => "")
      doc.save.should be_truthy

      @document.stub(:first).
        with(:name => '').
        and_return(doc)

      doc2 = document.new("name" => "")
      doc2.should_not have_error_on(:name)
    end

    it "should allow multiple nil entries if :allow_nil => true" do
      document = Doc do
        key :name
        validates_uniqueness_of :name, :allow_nil => :true
      end

      doc = document.new('name' => nil)
      doc.save.should be_truthy

      doc2 = document.new('name' => nil)
      doc2.should_not have_error_on(:name)
    end

    it "should allow entries that differ only in case by default" do
      document = Doc do
        key :name
        validates_uniqueness_of :name
      end

      doc = document.new("name" => "BLAMMO")
      doc.save.should be_truthy

      doc2 = document.new("name" => "blammo")
      doc2.should_not have_error_on(:name)
    end

    context "with :case_sensitive => false" do
      before do
        @document = Doc do
          key :name
          validates_uniqueness_of :name, :case_sensitive => false
        end
      end

      it "should fail on entries that differ only in case" do
        doc = @document.new("name" => "BLAMMO")
        doc.save.should be_truthy

        doc2 = @document.new("name" => "blammo")
        doc2.should have_error_on(:name)
      end

      it "should not raise an error if value is nil" do
        doc = @document.new("name" => nil)
        lambda { doc.valid? }.should_not raise_error
      end

      it "should not raise an error if special Regexp characters used" do
        doc = @document.new("name" => '?')
        lambda { doc.valid? }.should_not raise_error
      end

      it "should check for uniqueness using entire string" do
        doc = @document.new("name" => "John Doe")
        doc.save.should be_truthy

        doc2 = @document.new("name" => "John")
        doc2.valid?.should be_truthy
      end
    end

    context "scoped by a single attribute" do
      before do
        @document = Doc do
          key :name, String
          key :scope, String
          validates_uniqueness_of :name, :scope => :scope
        end
      end

      it "should fail if the same name exists in the scope" do
        doc = @document.new("name" => "joe", "scope" => "one")
        doc.save.should be_truthy

        @document.stub(:first).
          with(:name => 'joe', :scope => "one").
          and_return(doc)

        doc2 = @document.new("name" => "joe", "scope" => "one")
        doc2.should have_error_on(:name)
      end

      it "should pass if the same name exists in a different scope" do
        doc = @document.new("name" => "joe", "scope" => "one")
        doc.save.should be_truthy

        @document.stub(:first).
          with(:name => 'joe', :scope => 'two').
          and_return(nil)

        doc2 = @document.new("name" => "joe", "scope" => "two")
        doc2.should_not have_error_on(:name)
      end
    end

    context "scoped by a multiple attributes" do
      before do
        @document = Doc do
          key :name, String
          key :first_scope, String
          key :second_scope, String
          validates_uniqueness_of :name, :scope => [:first_scope, :second_scope]
        end
      end

      it "should fail if the same name exists in the scope" do
        doc = @document.new("name" => "joe", "first_scope" => "one", "second_scope" => "two")
        doc.save.should be_truthy

        @document.stub(:first).
          with(:name => 'joe', :first_scope => 'one', :second_scope => 'two').
          and_return(doc)

        doc2 = @document.new("name" => "joe", "first_scope" => "one", "second_scope" => "two")
        doc2.should have_error_on(:name)
      end

      it "should pass if the same name exists in a different scope" do
        doc = @document.new("name" => "joe", "first_scope" => "one", "second_scope" => "two")
        doc.save.should be_truthy

        @document.stub(:first).
          with(:name => 'joe', :first_scope => 'one', :second_scope => 'one').
          and_return(nil)

        doc2 = @document.new("name" => "joe", "first_scope" => "one", "second_scope" => "one")
        doc2.should_not have_error_on(:name)
      end
    end
  end

  context "validating associated docs" do
    before do
      @child_class = EDoc do
        key :name, :required => true
      end

      @root_class = Doc { }
      @root_class.many :children, :class => @child_class
      @root_class.validates_associated :children, :message => 'are invalid'
    end

    it "should pass if there are no associated docs" do
      doc = @root_class.new
      doc.save.should be_truthy
    end

    it "should pass if the associated doc is valid" do
      doc = @root_class.new
      doc.children.build(:name => 'Joe')
      doc.save.should be_truthy
    end

    it "should fail if the associated doc is invalid" do
      doc = @root_class.new
      doc.children.build
      doc.should have_error_on(:children)
      doc.errors[:children].should == ['are invalid']
    end
  end

  context "validating associated docs with custom context" do
    before do
      @child_class = EDoc do
        key :name

        validates_length_of :name, :minimum => 5, :on => :custom_context
      end

      @root_class = Doc { }
      @root_class.many :children, :class => @child_class
      @root_class.validates_associated :children, :context => :custom_context
    end

    it "should pass if there are no associated docs" do
      doc = @root_class.new
      doc.valid?(:custom_context).should be_truthy
    end

    it "should pass if the associated doc is valid" do
      doc = @root_class.new
      doc.children.build(:name => 'George')
      doc.valid?(:custom_context).should be_truthy
    end

    it "should fail if the associated doc is invalid" do
      doc = @root_class.new
      doc.children.build(:name => 'Bob')
      doc.valid?(:custom_context).should_not be_truthy
    end
  end

  # context "validates uniqueness of with :unique shortcut" do
  #   it "should work" do
  #     @document = Doc do
  #       key :name, String, :unique => true
  #     end
  #
  #     doc = @document.create(:name => 'John')
  #     doc.should_not have_error_on(:name)
  #
  #     @document.stub(:first).
  #       with(:name => 'John').
  #       and_return(doc)
  #
  #     second_john = @document.create(:name => 'John')
  #     second_john.should have_error_on(:name)
  #     second_john.errors[:name].should == ['has already been taken']
  #   end
  # end
end
