require File.dirname(__FILE__) + '/test_helper'

class PreloaderTest < Test::Unit::TestCase
  def setup
    FactoryDataPreloader.reset!
  end

  context 'A new preloader' do
    setup do
      proc = lambda { |data|
        data.add(:thom) { User.create(:first_name => 'Thom', :last_name => 'York') }
        data.add(:john) { User.create(:first_name => 'John', :last_name => 'Doe') }
      }
      @preloader = FactoryDataPreloader::Preloader.new(:users, User, proc, [])
    end

    should 'be automatically added to the PreloaderCollection' do
      assert_equal [@preloader], FactoryDataPreloader::AllPreloaders.instance
    end

    context 'when preloaded' do
      setup do
        @user_count = User.count
        @out, @err = OutputCapturer.capture do
          @preloader.preload!
        end
      end

      should 'change User.count by 2' do
        assert_equal(@user_count + 2, User.count)
      end

      should 'return the preloaded data when #get_record is called' do
        assert_equal 'York', @preloader.get_record(:thom).last_name
        assert_equal 'Doe',  @preloader.get_record(:john).last_name
      end

      should 'print out a preloader message, a dot for each record and a benchmark' do
        assert_equal '', @err
        assert_match /Preloading users:\.\.\([\d\.]+ secs\)/, @out
      end

      context 'when preloaded again' do
        setup do
          @user_count = User.count
          @out, @err = OutputCapturer.capture do
            @preloader.preload!
          end
        end

        should 'print nothing' do
          assert_equal '', @err
          assert_equal '', @out
        end

        should 'not change User.count' do
          assert_equal(@user_count, User.count)
        end
      end

      should 'issue a delete statement if #delete_table_data! is called' do
        User.expects(:delete_all).once
        @preloader.delete_table_data!
      end

      context 'when #delete_table_data! is called' do
        setup do
          @preloader.delete_table_data!
        end

        should 'not issue another delete statement if #delete_table_data! is later called on the same preloader' do
          User.expects(:delete_all).never
          @preloader.delete_table_data!
        end
      end
    end
  end

  context 'A new preloader for email_addresses' do
    setup do
      @preloader = FactoryDataPreloader::Preloader.new(:email_addresses, nil, lambda { }, [])
    end

    should 'infer the model class' do
      assert_equal EmailAddress, @preloader.model_class
    end
  end

  context 'A preloader with errors' do
    setup do
      proc = lambda { |data|
        data.add(:thom) { raise StandardError('Error for thom') }
        data.add(:john) { @john = User.create(:first_name => 'John', :last_name => 'Doe') }
      }
      @preloader = FactoryDataPreloader::Preloader.new(:users, User, proc, [])
      @out, @err = OutputCapturer.capture do
        @preloader.preload!
      end
    end

    should 'raise an exception when the record with the error is accessed' do
      assert_raise FactoryDataPreloader::ErrorWhilePreloadingRecord do
        @preloader.get_record(:thom)
      end
    end

    should 'allow the error-free records to be accessed, even when they were created after the error record' do
      assert_equal @john, @preloader.get_record(:john)
    end
  end

  context 'A preloader with dependencies' do
    setup do
      @comments = FactoryDataPreloader::Preloader.new(:comments, Comment, nil, [:users, :posts])
    end

    should 'raise PreloaderNotDefinedError for #dependencies if the preloader it depends on are not defined' do
      assert_raise FactoryDataPreloader::PreloaderNotDefinedError do
        @comments.dependencies
      end
    end

    context 'when the dependency preloaders have also been defined' do
      setup do
        @posts = FactoryDataPreloader::Preloader.new(:posts, Post, nil, [:users])
        @users = FactoryDataPreloader::Preloader.new(:users, User, nil, [])
      end

      should 'return the preloader objects for #dependencies' do
        assert_equal [@users, @posts], @comments.dependencies
      end
    end
  end

  context 'A series of preloaders, with dependencies,' do
    setup do
      @post_image_ratings  = FactoryDataPreloader::Preloader.new(:post_image_ratings,  PostImageRating, nil, [:post_images])
      @post_images         = FactoryDataPreloader::Preloader.new(:post_images,         PostImage,       nil, [:posts])
      @ip_addresses        = FactoryDataPreloader::Preloader.new(:ip_addresses,        IpAddress,       nil, [])
      @posts               = FactoryDataPreloader::Preloader.new(:posts,               Post,            nil, [:users])
      @users               = FactoryDataPreloader::Preloader.new(:users,               User,            nil, [])
    end

    should 'sort correctly for PreloaderCollection.instance.dependency_order' do
      expected = [@ip_addresses, @users, @posts, @post_images, @post_image_ratings]
      assert_equal expected.map(&:model_type), FactoryDataPreloader::AllPreloaders.instance.dependency_order.map(&:model_type)
    end

    should 'return the correct preloader objects for #all_dependencies' do
      assert_same_elements [@post_images, @posts, @users], @post_image_ratings.all_dependencies
      assert_same_elements [@posts, @users], @post_images.all_dependencies
      assert_same_elements [], @ip_addresses.all_dependencies
      assert_same_elements [@users], @posts.all_dependencies
      assert_same_elements [], @users.all_dependencies
    end

    context 'when FactoryDataPreloader.preload_all = true' do
      setup do
        FactoryDataPreloader.preload_all = true
      end

      should 'return all preloaders for FactoryDataPreloader.requested_preloaders' do
        expected = [@ip_addresses, @users, @posts, @post_images, @post_image_ratings]
        assert_equal expected.map(&:model_type), FactoryDataPreloader.requested_preloaders.dependency_order.map(&:model_type)
      end
    end

    context 'when FactoryDataPreloader.preload_all = false' do
      setup do
        FactoryDataPreloader.preload_all = false
      end

      should 'return no preloaders when for FactoryDataPreloader.requested_preloaders when preload_types is empty' do
        assert_equal [], FactoryDataPreloader.preload_types
        assert_equal [], FactoryDataPreloader.requested_preloaders
      end

      should 'return just the requested preloaders for FactoryDataPreloader.requested_preloaders' do
        FactoryDataPreloader.preload_types << :post_images
        FactoryDataPreloader.preload_types << :ip_addresses
        expected = [@ip_addresses, @users, @posts, @post_images]
        assert_equal expected.map(&:model_type), FactoryDataPreloader.requested_preloaders.dependency_order.map(&:model_type)
      end
    end
  end
end