require 'spec_helper'

require 'aws/codedeploy/local/cli_validator'

describe AWS::CodeDeploy::Local::CLIValidator do
  FAKE_DIRECTORY = "/path/directory"
  let(:validator) { AWS::CodeDeploy::Local::CLIValidator.new }

  describe 'validate' do
    context 'when type is invalid' do
      INVALID_TYPE = 'invalid-type'

      let(:args) do
        {"--type"=>INVALID_TYPE}
      end

      it 'throws a ValidationError' do
        expect{validator.validate(args)}.to raise_error(AWS::CodeDeploy::Local::CLIValidator::ValidationError, "type #{INVALID_TYPE} is not a valid type. Must be one of #{AWS::CodeDeploy::Local::CLIValidator::VALID_TYPES.join(',')}")
      end
    end

    context 'when location is valid file' do
      VALID_FILE = "/path/test"

      let(:args) do
        {"deploy"=>true,
         "--location"=>true,
         "--bundle-location"=>VALID_FILE,
         "--type"=>'tgz',
         "--help"=>false,
         "--version"=>false}
      end

      it 'returns the same arguments' do
        allow(File).to receive(:exist?).with(VALID_FILE).and_return(true)
        expect(validator.validate(args)).to equal(args)
      end
    end

    context 'when location is valid https location' do
      let(:args) do
        {"deploy"=>true,
         "--location"=>true,
         "--bundle-location"=>"https://example.com/file",
         "--type"=>'tgz',
         "--help"=>false,
         "--version"=>false}
      end

      it 'returns the same arguments' do
        expect(validator.validate(args)).to equal(args)
      end
    end

    context 'when location is not a valid uri' do
      INVALID_URI = "https://invalidurl.com/file[/].html"

      let(:args) do
        {"deploy"=>true,
         "--location"=>true,
         "--bundle-location"=>INVALID_URI,
         "--type"=>'tgz',
         "--help"=>false,
         "--version"=>false}
      end

      it 'throws a ValidationError' do
        expect{validator.validate(args)}.to raise_error(AWS::CodeDeploy::Local::CLIValidator::ValidationError, "location #{INVALID_URI} is not a valid uri")
      end
    end

    context 'when location url is http' do
      HTTP_URL = "http://example.com/file"

      let(:args) do
        {"deploy"=>true,
         "--location"=>true,
         "--bundle-location"=>HTTP_URL,
         "--type"=>'tgz',
         "--help"=>false,
         "--version"=>false}
      end

      it 'throws a ValidationError since unencrypted urls are not supported' do
        expect{validator.validate(args)}.to raise_error(AWS::CodeDeploy::Local::CLIValidator::ValidationError, "location #{HTTP_URL} cannot be http, only encrypted (https) url endpoints supported")
      end
    end

    context 'when location is directory but appspec is missing' do
      let(:args) do
        {"--bundle-location"=>FAKE_DIRECTORY,
         "--type"=>'directory'}
      end

      it 'throws a ValidationError' do
        allow(File).to receive(:exist?).with(FAKE_DIRECTORY).and_return(true)
        allow(File).to receive(:directory?).with(FAKE_DIRECTORY).and_return(true)
        expect(File).to receive(:exist?).with("#{FAKE_DIRECTORY}/appspec.yml").and_return(false)
        expect(File).to receive(:exist?).with("#{FAKE_DIRECTORY}/appspec.yaml").and_return(false)
        expect{validator.validate(args)}.to raise_error(AWS::CodeDeploy::Local::CLIValidator::ValidationError, "Expecting appspec file at location #{FAKE_DIRECTORY}/appspec.yml or #{FAKE_DIRECTORY}/appspec.yaml but it is not found there. Please either run the CLI from within a directory containing the appspec.yml or appspec.yaml file or specify a bundle location containing an appspec.yml or appspec.yaml file in its root directory")
      end
    end

    context 'when location is directory and --appspec-filename is specified (but not existing)' do
      let(:args) do
        {"--bundle-location"=>FAKE_DIRECTORY,
         "--type"=>'directory',
         "--appspec-filename"=>"appspec-override.yaml"}
      end

      it 'throws a ValidationError' do
        allow(File).to receive(:exist?).with(FAKE_DIRECTORY).and_return(true)
        allow(File).to receive(:directory?).with(FAKE_DIRECTORY).and_return(true)
        expect(File).to receive(:exist?).with("#{FAKE_DIRECTORY}/appspec-override.yaml").and_return(false)
        expect{validator.validate(args)}.to raise_error(AWS::CodeDeploy::Local::CLIValidator::ValidationError, "Expecting appspec file at location #{FAKE_DIRECTORY}/appspec-override.yaml but it is not found there. Please either run the CLI from within a directory containing the appspec-override.yaml file or specify a bundle location containing an appspec-override.yaml file in its root directory")
      end
    end

    context 'when location is a file which does not exists' do
      FAKE_FILE_WHICH_DOES_NOT_EXIST = "/path/directory/file-does-not-exist.zip"

      let(:args) do
        {"deploy"=>true,
         "--location"=>true,
         "--bundle-location"=>FAKE_FILE_WHICH_DOES_NOT_EXIST,
         "--type"=>'zip',
         "--help"=>false,
         "--version"=>false}
      end

      it 'throws a ValidationError' do
        allow(File).to receive(:exist?).with(FAKE_FILE_WHICH_DOES_NOT_EXIST).and_return(false)
        expect{validator.validate(args)}.to raise_error(AWS::CodeDeploy::Local::CLIValidator::ValidationError, "location #{FAKE_FILE_WHICH_DOES_NOT_EXIST} is specified as a file or directory which does not exist")
      end
    end

    context 'when type is directory and location is a file' do
      FAKE_FILE = "/path/directory/file.zip"

      let(:args) do
        {"deploy"=>true,
         "--location"=>true,
         "--bundle-location"=>FAKE_FILE,
         "--type"=>'directory',
         "--help"=>false,
         "--version"=>false}
      end

      it 'throws a ValidationError' do
        allow(File).to receive(:exist?).with(FAKE_FILE).and_return(true)
        allow(File).to receive(:file?).with(FAKE_FILE).and_return(true)
        expect{validator.validate(args)}.to raise_error(AWS::CodeDeploy::Local::CLIValidator::ValidationError, "location #{FAKE_FILE} is specified with type directory but it is a file")
      end
    end

    context 'when type is zip or tgz and location is a directory' do
      let(:argszip) do
        {"deploy"=>true,
         "--location"=>true,
         "--bundle-location"=>FAKE_DIRECTORY,
         "--type"=>'zip',
         "--help"=>false,
         "--version"=>false}
      end

      let(:argstgz) do
        {"deploy"=>true,
         "--location"=>true,
         "--bundle-location"=>FAKE_DIRECTORY,
         "--type"=>'tgz',
         "--help"=>false,
         "--version"=>false}
      end

      it 'throws a ValidationError' do
        allow(File).to receive(:exist?).with(FAKE_DIRECTORY).and_return(true)
        allow(File).to receive(:directory?).with(FAKE_DIRECTORY).and_return(true)
        expect{validator.validate(argszip)}.to raise_error(AWS::CodeDeploy::Local::CLIValidator::ValidationError, "location #{FAKE_DIRECTORY} is specified as a compressed local file but it is a directory")
        expect{validator.validate(argstgz)}.to raise_error(AWS::CodeDeploy::Local::CLIValidator::ValidationError, "location #{FAKE_DIRECTORY} is specified as a compressed local file but it is a directory")
      end
    end

    context 'when previous revision event specified before DownloadBundle' do
      let(:args) do
        {"--bundle-location"=>FAKE_DIRECTORY,
         "--type"=>'directory',
         '--events'=>'ApplicationStart,DownloadBundle'
        }
      end

      it 'throws a ValidationError' do
        allow(File).to receive(:exist?).with(FAKE_DIRECTORY).and_return(true)
        allow(File).to receive(:directory?).with(FAKE_DIRECTORY).and_return(true)
        expect(File).to receive(:exist?).with("#{FAKE_DIRECTORY}/appspec.yml").and_return(true)
        expect{validator.validate(args)}.to raise_error(AWS::CodeDeploy::Local::CLIValidator::ValidationError, "The only events that can be specified before DownloadBundle are BeforeBlockTraffic,AfterBlockTraffic,ApplicationStop. Please fix the order of your specified events: #{args['--events']}")
      end
    end

    context 'when Install specified before DownloadBundle' do
      let(:args) do
        {"--bundle-location"=>FAKE_DIRECTORY,
         "--type"=>'directory',
         '--events'=>'Install,DownloadBundle'
        }
      end

      it 'throws a ValidationError' do
        allow(File).to receive(:exist?).with(FAKE_DIRECTORY).and_return(true)
        allow(File).to receive(:directory?).with(FAKE_DIRECTORY).and_return(true)
        expect(File).to receive(:exist?).with("#{FAKE_DIRECTORY}/appspec.yml").and_return(true)
        expect{validator.validate(args)}.to raise_error(AWS::CodeDeploy::Local::CLIValidator::ValidationError, "The only events that can be specified before DownloadBundle are BeforeBlockTraffic,AfterBlockTraffic,ApplicationStop. Please fix the order of your specified events: #{args['--events']}")
      end
    end

    context 'when previous revision event specified before Install' do
      let(:args) do
        {"--bundle-location"=>FAKE_DIRECTORY,
         "--type"=>'directory',
         '--events'=>'ApplicationStart,Install'
        }
      end

      it 'throws a ValidationError' do
        allow(File).to receive(:exist?).with(FAKE_DIRECTORY).and_return(true)
        allow(File).to receive(:directory?).with(FAKE_DIRECTORY).and_return(true)
        expect(File).to receive(:exist?).with("#{FAKE_DIRECTORY}/appspec.yml").and_return(true)
        expect{validator.validate(args)}.to raise_error(AWS::CodeDeploy::Local::CLIValidator::ValidationError, "The only events that can be specified before Install are BeforeBlockTraffic,AfterBlockTraffic,ApplicationStop,DownloadBundle,BeforeInstall. Please fix the order of your specified events: #{args['--events']}")
      end
    end

    context 'when BeforeInstall event specified before Install' do
      let(:args) do
        {"--bundle-location"=>FAKE_DIRECTORY,
         "--type"=>'directory',
         '--events'=>'BeforeInstall,Install'
        }
      end

      it 'returns the same arguments' do
        allow(File).to receive(:exist?).with(FAKE_DIRECTORY).and_return(true)
        allow(File).to receive(:directory?).with(FAKE_DIRECTORY).and_return(true)
        expect(File).to receive(:exist?).with("#{FAKE_DIRECTORY}/appspec.yml").and_return(true)
        expect(validator.validate(args)).to equal(args)
      end
    end
  end
end
