# encoding: UTF-8
require 'spec_helper'

describe ActiveData::Model::Callbacks do
  before do
    stub_model(:user) do
      include ActiveData::Model::Callbacks
      attribute :actions, Array, default: []

      def append(action)
        self.actions = actions + [action]
      end

      define_create { append :create }
      define_update { append :update }
      define_destroy { append :destroy }
    end
  end

  describe '.after_initialize' do
    before do
      User.after_initialize { append :after_initialize }
    end

    specify { expect(User.new.actions).to eq([:after_initialize]) }
    specify { expect(User.create.actions).to eq([:after_initialize, :create]) }
  end

  describe '.before_save, .after_save' do
    before do
      User.before_save { append :before_save }
      User.after_save { append :after_save }
    end

    specify { expect(User.create.actions).to eq([:before_save, :create, :after_save]) }
    specify { expect(User.new.tap(&:save).actions).to eq([:before_save, :create, :after_save]) }
    specify { expect(User.new.tap { |u| u.update({}) }.actions).to eq([:before_save, :create, :after_save]) }
    specify { expect(User.create.tap(&:save).actions).to eq([:before_save, :create, :after_save, :before_save, :update, :after_save]) }
    specify { expect(User.create.tap { |u| u.update({}) }.actions).to eq([:before_save, :create, :after_save, :before_save, :update, :after_save]) }

    specify { expect(User.new.tap { |u| u.save { false } }.actions).to eq([:before_save]) }
    specify { expect(User.create.tap { |u| u.save { false } }.actions).to eq([:before_save, :create, :after_save, :before_save]) }
  end

  describe '.around_save' do
    before do
      User.around_save do |_, block|
        append :before_around_save
        block.call
        append :after_around_save
      end
    end

    specify { expect(User.create.actions).to eq([:before_around_save, :create, :after_around_save]) }
    specify { expect(User.new.tap(&:save).actions).to eq([:before_around_save, :create, :after_around_save]) }
    specify { expect(User.new.tap { |u| u.update({}) }.actions).to eq([:before_around_save, :create, :after_around_save]) }
    specify { expect(User.create.tap(&:save).actions).to eq([:before_around_save, :create, :after_around_save, :before_around_save, :update, :after_around_save]) }
    specify { expect(User.create.tap { |u| u.update({}) }.actions).to eq([:before_around_save, :create, :after_around_save, :before_around_save, :update, :after_around_save]) }

    specify { expect(User.new.tap { |u| u.save { false } }.actions).to eq([:before_around_save, :after_around_save]) }
    specify { expect(User.create.tap { |u| u.save { false } }.actions).to eq([:before_around_save, :create, :after_around_save, :before_around_save, :after_around_save]) }
  end

  describe '.before_create, .after_create' do
    before do
      User.before_create { append :before_create }
      User.after_create { append :after_create }
    end

    specify { expect(User.create.actions).to eq([:before_create, :create, :after_create]) }
    specify { expect(User.new.tap(&:save).actions).to eq([:before_create, :create, :after_create]) }
    specify { expect(User.new.tap { |u| u.update({}) }.actions).to eq([:before_create, :create, :after_create]) }
    specify { expect(User.create.tap(&:save).actions).to eq([:before_create, :create, :after_create, :update]) }
    specify { expect(User.create.tap { |u| u.update({}) }.actions).to eq([:before_create, :create, :after_create, :update]) }

    specify { expect(User.new.tap { |u| u.save { false } }.actions).to eq([:before_create]) }
  end

  describe '.around_create' do
    before do
      User.around_create do |_, block|
        append :before_around_create
        block.call
        append :after_around_create
      end
    end

    specify { expect(User.create.actions).to eq([:before_around_create, :create, :after_around_create]) }
    specify { expect(User.new.tap(&:save).actions).to eq([:before_around_create, :create, :after_around_create]) }
    specify { expect(User.new.tap { |u| u.update({}) }.actions).to eq([:before_around_create, :create, :after_around_create]) }
    specify { expect(User.create.tap(&:save).actions).to eq([:before_around_create, :create, :after_around_create, :update]) }
    specify { expect(User.create.tap { |u| u.update({}) }.actions).to eq([:before_around_create, :create, :after_around_create, :update]) }

    specify { expect(User.new.tap { |u| u.save { false } }.actions).to eq([:before_around_create, :after_around_create]) }
  end

  describe '.before_update, .after_update' do
    before do
      User.before_update { append :before_update }
      User.after_update { append :after_update }
    end

    specify { expect(User.create.actions).to eq([:create]) }
    specify { expect(User.new.tap(&:save).actions).to eq([:create]) }
    specify { expect(User.new.tap { |u| u.update({}) }.actions).to eq([:create]) }
    specify { expect(User.create.tap(&:save).actions).to eq([:create, :before_update, :update, :after_update]) }
    specify { expect(User.create.tap { |u| u.update({}) }.actions).to eq([:create, :before_update, :update, :after_update]) }

    specify { expect(User.create.tap { |u| u.save { false } }.actions).to eq([:create, :before_update]) }
  end

  describe '.around_update' do
    before do
      User.around_update do |_, block|
        append :before_around_update
        block.call
        append :after_around_update
      end
    end

    specify { expect(User.create.actions).to eq([:create]) }
    specify { expect(User.new.tap(&:save).actions).to eq([:create]) }
    specify { expect(User.new.tap { |u| u.update({}) }.actions).to eq([:create]) }
    specify { expect(User.create.tap(&:save).actions).to eq([:create, :before_around_update, :update, :after_around_update]) }
    specify { expect(User.create.tap { |u| u.update({}) }.actions).to eq([:create, :before_around_update, :update, :after_around_update]) }

    specify { expect(User.create.tap { |u| u.save { false } }.actions).to eq([:create, :before_around_update, :after_around_update]) }
  end

  describe '.before_validation, .after_validation,
            .before_save, .after_save, .around_save,
            .before_create, .after_create, .around_create,
            .before_update, .after_update, .around_update
            .before_destroy, .after_destroy, .around_destroy' do
    before do
      User.before_validation { append :before_validation }
      User.after_validation { append :after_validation }

      User.before_save { append :before_save }
      User.after_save { append :after_save }
      User.around_save do |_, block|
        append :before_around_save
        block.call
        append :after_around_save
      end

      User.before_create { append :before_create }
      User.after_create { append :after_create }
      User.around_create do |_, block|
        append :before_around_create
        block.call
        append :after_around_create
      end

      User.before_update { append :before_update }
      User.after_update { append :after_update }
      User.around_update do |_, block|
        append :before_around_update
        block.call
        append :after_around_update
      end

      User.before_destroy { append :before_destroy }
      User.after_destroy { append :after_destroy }
      User.around_destroy do |_, block|
        append :before_around_destroy
        block.call
        append :after_around_destroy
      end
    end

    specify do
      expect(User.create.tap(&:save).destroy.actions).to eq([
        :before_validation, :after_validation,
        :before_save, :before_around_save,
        :before_create, :before_around_create,
        :create,
        :after_around_create, :after_create,
        :after_around_save, :after_save,

        :before_validation, :after_validation,
        :before_save, :before_around_save,
        :before_update, :before_around_update,
        :update,
        :after_around_update, :after_update,
        :after_around_save, :after_save,

        :before_destroy, :before_around_destroy,
        :destroy,
        :after_around_destroy, :after_destroy
      ])
    end
  end

  describe '.before_destroy, .after_destroy' do
    before do
      User.before_destroy { append :before_destroy }
      User.after_destroy { append :after_destroy }
    end

    specify { expect(User.new.destroy.actions).to eq([:before_destroy, :destroy, :after_destroy]) }
    specify { expect(User.create.destroy.actions).to eq([:create, :before_destroy, :destroy, :after_destroy]) }

    specify { expect(User.new.destroy { false }.actions).to eq([:before_destroy]) }
    specify { expect(User.create.destroy { false }.actions).to eq([:create, :before_destroy]) }
  end

  describe '.around_destroy' do
    before do
      User.around_destroy do |_, block|
        append :before_around_destroy
        block.call
        append :after_around_destroy
      end
    end

    specify { expect(User.new.destroy.actions).to eq([:before_around_destroy, :destroy, :after_around_destroy]) }
    specify { expect(User.create.destroy.actions).to eq([:create, :before_around_destroy, :destroy, :after_around_destroy]) }

    specify { expect(User.new.destroy { false }.actions).to eq([:before_around_destroy, :after_around_destroy]) }
    specify { expect(User.create.destroy { false }.actions).to eq([:create, :before_around_destroy, :after_around_destroy]) }
  end

  context 'unsavable, undestroyable' do
    before do
      stub_model(:user) do
        include ActiveData::Model::Callbacks

        attribute :actions, Array, default: []
        attribute :validated, Boolean, default: false

        validates :validated, presence: true

        def append(action)
          self.actions = actions + [action]
        end
      end
    end

    before do
      User.before_validation { append :before_validation }
      User.after_validation { append :after_validation }

      User.before_save { append :before_save }
      User.after_save { append :after_save }
      User.around_save do |&block|
        append :before_around_save
        block.call
        append :after_around_save
      end

      User.before_create { append :before_create }
      User.after_create { append :after_create }
      User.around_create do |&block|
        append :before_around_create
        block.call
        append :after_around_create
      end

      User.before_update { append :before_update }
      User.after_update { append :after_update }
      User.around_update do |&block|
        append :before_around_update
        block.call
        append :after_around_update
      end

      User.before_destroy { append :before_destroy }
      User.after_destroy { append :after_destroy }
      User.around_destroy do |&block|
        append :before_around_destroy
        block.call
        append :after_around_destroy
      end
    end

    let(:user) { User.new }

    specify do
      begin
        user.save
      rescue ActiveData::UnsavableObject
        expect(user.actions).to eq([])
      end
    end

    specify do
      begin
        user.save!
      rescue ActiveData::UnsavableObject
        expect(user.actions).to eq([])
      end
    end

    specify do
      user.save { true }
      expect(user.actions).to eq([:before_validation, :after_validation])
    end

    specify do
      begin
        user.save! { true }
      rescue ActiveData::ValidationError
        expect(user.actions).to eq([:before_validation, :after_validation])
      end
    end

    specify do
      begin
        user.update({})
      rescue ActiveData::UnsavableObject
        expect(user.actions).to eq([])
      end
    end

    specify do
      begin
        user.update!({})
      rescue ActiveData::UnsavableObject
        expect(user.actions).to eq([])
      end
    end

    specify do
      begin
        user.destroy
      rescue ActiveData::UndestroyableObject
        expect(user.actions).to eq([])
      end
    end

    specify do
      begin
        user.destroy!
      rescue ActiveData::UndestroyableObject
        expect(user.actions).to eq([])
      end
    end
  end
end
