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

describe Sprinkle::Policy do
  include Sprinkle::Policy

  before do
    @name = 'a policy'
  end

  describe 'when created' do

    it 'should be invalid without a name' do
      lambda { policy nil }.should raise_error
    end

    it 'should be invalid without role definitions' do
      lambda { policy @name do; end }.should raise_error
      lambda { policy @name, :roles => :app do; end }.should_not raise_error
    end

    it 'should optionally accept package dependencies' do
      p = policy @name, :roles => :app do; end
      p.should respond_to(:requires)
      p.requires :appserver
      p.packages.should == [ :appserver ]
    end
    
    it 'should accept package dependencies using the configures alias' do
      p = policy @name, :roles => :app do; end
      p.should respond_to(:configures)
      p.configures :appserver
      p.packages.should == [ :appserver ]
    end

    it 'should optionally accept package dependencies with versions' do
      p = policy @name, :roles => :app do; end
      p.requires :appserver, :version => 2
      p.packages.should == [ :appserver ]
      pending 'requires version checking implementation'
    end

    it 'should add itself to the global policy list' do
      sz = Sprinkle::Policy::POLICIES.size
      p = policy @name, :roles => :app do; end
      Sprinkle::Policy::POLICIES.size.should == sz + 1
      Sprinkle::Policy::POLICIES.last.should == p
    end

  end

  describe 'with packages' do
    include Sprinkle::Package

    before do
      @deployment = mock(Sprinkle::Deployment)
      Sprinkle::Package::PACKAGES.clear # reset full package list before each spec is run

      @a = package :a do; requires :b; requires :c; end
      @b = package :b, :provides => :xyz do; end
      @c = package :c, :provides => :abc do; end
      @d = package :d, :provides => :abc do; end

      @policy = policy :test, :roles => :app do; requires :a; end
      $terminal.stub!(:choose).and_return(:c) # stub out highline asking questions
    end

    describe 'when applying' do
      include Sprinkle::Package

      it 'should determine the packages to install via the hierarchy dependency tree of each package in the policy' do
        @a.should_receive(:process).and_return
        @b.should_receive(:process).and_return
        @c.should_receive(:process).and_return
        @d.should_not_receive(:process)
      end

      it 'should normalize (ie remove duplicates from) the installation order of all packages including dependencies' do
        @e = package :e do; requires :b; end
        @policy.requires :e

        @a.should_receive(:process).once.and_return
        @b.should_receive(:process).once.and_return
        @c.should_receive(:process).once.and_return
        @d.should_not_receive(:process)
        @e.should_receive(:process).once.and_return
      end
    end

    describe 'containing package dependencies with versions' do

      it 'should be invalid if the specified package does not exist'
      it 'should ignore any packages of the same name that have other versions'
      it 'should select the correct package version when applying'

    end

    describe 'containing virtual packages' do

      it 'should automatically select a concrete package implementation for a virtual one when there exists only one possible selection' do
        @policy = policy :virtual, :roles => :app do; requires :xyz; end
        Sprinkle::Package::PACKAGES[:xyz].should == [ @b ]
      end

      it 'should ask the user for the concrete package implementation to use for a virtual one when more than one possible choice exists' do
        @policy = policy :virtual, :roles => :app do; requires :abc; end
        Sprinkle::Package::PACKAGES[:abc].should include(@c)
        Sprinkle::Package::PACKAGES[:abc].should include(@d)
        $terminal.should_receive(:choose).and_return(:c)
      end

    end

    after do
      @policy.process(@deployment)
    end
  end
end

describe Sprinkle::Policy, 'with missing packages' do

  before do
    @deployment = mock(Sprinkle::Deployment)
    Sprinkle::Package::PACKAGES.clear # reset full package list before each spec is run

    @policy = policy :test, :roles => :app do; requires :z; end
    $terminal.stub!(:choose).and_return(:c) # stub out highline asking questions
  end

  it 'should raise an error if a package is missing' do
    lambda { @policy.process(@deployment) }.should raise_error
  end

end