require 'spec_helper'

describe SimpleDeploy::Stack::Deployment do
  include_context 'stubbed config'
  include_context 'double stubbed logger'
  include_context 'stubbed stack', :name        => 'my_stack',
                                   :environment => 'my_env'

  before do
    @attributes = { 'key'                     => 'val',
                    'chef_repo'               => 'chef_repo',
                    'chef_repo_bucket_prefix' => 'chef_repo_bp',
                    'chef_repo_domain'        => 'chef_repo_d',
                    'app'                     => 'app',
                    'app_bucket_prefix'       => 'app_bp',
                    'app_domain'              => 'app_d',
                    'cookbooks'               => 'cookbooks',
                    'cookbooks_bucket_prefix' => 'cookbooks_bp',
                    'cookbooks_domain'        => 'cookbooks_d' }

    @config_mock.stub(:region) { 'test-us-west-1' }

    @stack_mock.stub(:attributes) { @attributes }

    @status_mock = mock 'status mock'

    options = { :instances   => ['1.2.3.4', '4.3.2.1'],
                :environment => 'test-us-west-1',
                :ssh_user    => 'user',
                :ssh_key     => 'key',
                :stack       => @stack_mock,
                :name        => 'stack-name' }
    @deployment = SimpleDeploy::Stack::Deployment.new options
    @deployment.stub(:sleep) { false }
  end

  after do
    SimpleDeploy.release_config
  end

  context "manage locks" do
    before do
      status_options = { :name        => 'stack-name',
                         :environment => 'test-us-west-1',
                         :stack       => @stack_mock,
                         :ssh_user    => 'user' }
      SimpleDeploy::Stack::Deployment::Status.should_receive(:new).
                                              with(status_options).
                                              and_return @status_mock
    end

    describe "clear_for_deployment?" do
      it "should test the clear_for_deployment method" do
        @status_mock.stub :clear_for_deployment? => true
        @deployment.clear_for_deployment?.should be_true
      end
    end

    describe "clear_deployment_lock" do
      it "should test the clear_deployment_lock" do
        @status_mock.should_receive(:clear_deployment_lock).
                     with(true).
                     and_return true
        @deployment.clear_deployment_lock(true).should be_true
      end
    end

  end

  describe "executing a deploy" do
    before do
      @config_mock.stub(:artifacts) { ['chef_repo', 'cookbooks', 'app'] }
      @config_mock.stub(:deploy_script) { '/tmp/script' }
      @stack_mock.stub(:instances) { [ { 'instancesSet' =>
                                     [ { 'privateIpAddress' => '10.1.2.3' } ] } ] }
 
      status_options = { :name        => 'stack-name',
                         :environment => 'test-us-west-1',
                         :stack       => @stack_mock,
                         :ssh_user    => 'user' }
      SimpleDeploy::Stack::Deployment::Status.should_receive(:new).
                                              with(status_options).
                                              and_return @status_mock
    end

    describe "when successful" do
        before do
          @execute_mock = mock "execute"
          execute_options = { :name        => 'stack-name',
                              :environment => 'test-us-west-1',
                              :instances   => ['1.2.3.4', '4.3.2.1'],
                              :stack       => @stack_mock,
                              :ssh_user    => 'user',
                              :ssh_key     => 'key' }
          SimpleDeploy::Stack::Execute.should_receive(:new).
                                       with(execute_options).
                                       and_return @execute_mock
          @config_mock.should_receive(:artifact_deploy_variable).
                       with("cookbooks").
                       and_return('CHEF_REPO_URL')
          @config_mock.should_receive(:artifact_deploy_variable).
                       with("app").
                       and_return('APP_URL')
          @config_mock.should_receive(:artifact_deploy_variable).
                       with("chef_repo").
                       and_return('CHEF_REPO_URL')
        end

      context "when app encrypted" do
        before do
          @attributes['app_encrypted'] = 'true'
          @execute_mock.should_receive(:execute).
                        with( {:sudo=>true, :command=>"env CHEF_REPO_URL=s3://cookbooks_bp-test-us-west-1/cookbooks_d/cookbooks.tar.gz APP_URL=s3://app_bp-test-us-west-1/app_d/app.tar.gz.gpg PRIMARY_HOST=10.1.2.3 /tmp/script"} ).and_return(true)
        end

        it "should deploy if the stack is clear to deploy" do
          @status_mock.stub :clear_for_deployment? => true
          @status_mock.should_receive(:set_deployment_in_progress)
          @status_mock.should_receive(:unset_deployment_in_progress)
          @stack_mock.should_receive(:raw_instances).at_least(:once).and_return(
                      [{ 'instancesSet' => [ { 'privateIpAddress' => '10.1.2.3' } ] }])
          @deployment.execute(false).should be_true
        end

        it "should deploy if the stack is not clear to deploy but forced and clear in time" do
          @status_mock.stub :clear_for_deployment? => false,
                            :clear_deployment_lock => true,
                            :clear_for_deployment? => true
          @status_mock.should_receive(:set_deployment_in_progress)
          @status_mock.should_receive(:unset_deployment_in_progress)
          @stack_mock.should_receive(:raw_instances).at_least(:once).and_return(
                      [{ 'instancesSet' => [ { 'privateIpAddress' => '10.1.2.3' } ] }])
          @deployment.execute(true).should be_true
        end
      end

      context "when unencrypted" do
        before do
          @execute_mock.should_receive(:execute).
                        with( {:sudo=>true, :command=>"env CHEF_REPO_URL=s3://cookbooks_bp-test-us-west-1/cookbooks_d/cookbooks.tar.gz APP_URL=s3://app_bp-test-us-west-1/app_d/app.tar.gz PRIMARY_HOST=10.1.2.3 /tmp/script"} ).and_return(true)
        end

        it "should deploy if the stack is clear to deploy" do
          @status_mock.stub :clear_for_deployment? => true
          @status_mock.should_receive(:set_deployment_in_progress)
          @status_mock.should_receive(:unset_deployment_in_progress)
          @stack_mock.should_receive(:raw_instances).at_least(:once).and_return(
                      [{ 'instancesSet' => [ { 'privateIpAddress' => '10.1.2.3' } ] }])
          @deployment.execute(false).should be_true
        end

        it "should deploy if the stack is not clear to deploy but forced and clear in time" do
          @status_mock.stub :clear_for_deployment? => false,
                            :clear_deployment_lock => true,
                            :clear_for_deployment? => true
          @status_mock.should_receive(:set_deployment_in_progress)
          @status_mock.should_receive(:unset_deployment_in_progress)
          @stack_mock.should_receive(:raw_instances).at_least(:once).and_return(
                      [{ 'instancesSet' => [ { 'privateIpAddress' => '10.1.2.3' } ] }])
          @deployment.execute(true).should be_true
        end
      end
    end

    describe "when unsuccessful" do
      it "should not deploy if the stack is not clear to deploy but forced however does not clear in time" do
        @status_mock.stub(:clear_for_deployment?) { false }
        @status_mock.should_receive(:clear_deployment_lock).
                     with(true)
        @deployment.execute(true).should be_false
      end

      it "should not deploy if the stack is not clear to deploy and not forced" do
        @status_mock.should_receive(:clear_deployment_lock).never
        @status_mock.stub :clear_for_deployment? => false
        @deployment.execute(false).should be_false
      end
    end
  end

  describe "get_artifact_endpoints" do
    before do
      @config_mock.stub(:artifacts) { ['chef_repo', 'cookbooks', 'app'] }
      @config_mock.should_receive(:artifact_deploy_variable).
                   with('chef_repo').and_return('CHEF_REPO_URL')
      @config_mock.should_receive(:artifact_deploy_variable).
                   with('app').and_return('APP_URL')
      @config_mock.should_receive(:artifact_deploy_variable).
                   with('cookbooks').and_return('COOKBOOKS_URL')
    end

    it "should create S3 endpoints" do
      endpoints = @deployment.send :get_artifact_endpoints

      endpoints['CHEF_REPO_URL'].should == 's3://chef_repo_bp-test-us-west-1/chef_repo_d/chef_repo.tar.gz'
      endpoints['APP_URL'].should == 's3://app_bp-test-us-west-1/app_d/app.tar.gz'
      endpoints['COOKBOOKS_URL'].should == 's3://cookbooks_bp-test-us-west-1/cookbooks_d/cookbooks.tar.gz'
    end

  end
end
