require "vagrant-aws/config"
require 'rspec/its'

# remove deprecation warnings
# (until someone decides to update the whole spec file to rspec 3.4)
RSpec.configure do |config|
  # ...
  config.mock_with :rspec do |c|
    c.syntax = [:should, :expect]
  end
  config.expect_with :rspec do |c|
    c.syntax = [:should, :expect]
  end
end

describe VagrantPlugins::AWS::Config do
  let(:instance) { described_class.new }

  # Ensure tests are not affected by AWS credential environment variables
  before :each do
    ENV.stub(:[] => nil)
  end

  describe "defaults" do
    subject do
      instance.tap do |o|
        o.finalize!
      end
    end

    its("access_key_id")     { should be_nil }
    its("ami")               { should be_nil }
    its("availability_zone") { should be_nil }
    its("instance_ready_timeout") { should == 120 }
    its("instance_check_interval") { should == 2 }
    its("instance_package_timeout") { should == 600 }
    its("instance_type")     { should == "m3.medium" }
    its("keypair_name")      { should be_nil }
    its("private_ip_address") { should be_nil }
    its("region")            { should == "us-east-1" }
    its("secret_access_key") { should be_nil }
    its("session_token") { should be_nil }
    its("security_groups")   { should == [] }
    its("subnet_id")         { should be_nil }
    its("iam_instance_profile_arn") { should be_nil }
    its("iam_instance_profile_name") { should be_nil }
    its("tags")              { should == {} }
    its("package_tags")      { should == {} }
    its("user_data")         { should be_nil }
    its("use_iam_profile")   { should be false }
    its("block_device_mapping")  {should == [] }
    its("elastic_ip")        { should be_nil }
    its("terminate_on_shutdown") { should == false }
    its("ssh_host_attribute") { should be_nil }
    its("monitoring")        { should == false }
    its("ebs_optimized")     { should == false }
    its("source_dest_check")       { should be_nil }
    its("associate_public_ip")     { should == false }
    its("unregister_elb_from_az") { should == true }
    its("tenancy")     { should == "default" }
  end

  describe "overriding defaults" do
    # I typically don't meta-program in tests, but this is a very
    # simple boilerplate test, so I cut corners here. It just sets
    # each of these attributes to "foo" in isolation, and reads the value
    # and asserts the proper result comes back out.
    [:access_key_id, :ami, :availability_zone, :instance_ready_timeout,
      :instance_package_timeout, :instance_type, :keypair_name, :ssh_host_attribute,
      :ebs_optimized, :region, :secret_access_key, :session_token, :monitoring,
      :associate_public_ip, :subnet_id, :tags, :package_tags, :elastic_ip,
      :terminate_on_shutdown, :iam_instance_profile_arn, :iam_instance_profile_name,
      :use_iam_profile, :user_data, :block_device_mapping,
      :source_dest_check].each do |attribute|

      it "should not default #{attribute} if overridden" do
        # but these should always come together, so you need to set them all or nothing
        instance.send("access_key_id=".to_sym, "foo")
        instance.send("secret_access_key=".to_sym, "foo")
        instance.send("session_token=".to_sym, "foo")
        instance.send("#{attribute}=".to_sym, "foo")
        instance.finalize!
        instance.send(attribute).should == "foo"
      end
    end
    it "should not default security_groups if overridden" do
      instance.security_groups = "foo"
      instance.finalize!
      instance.security_groups.should == ["foo"]
    end
  end

  describe "getting credentials from environment" do
    context "without EC2 credential environment variables" do
      subject do
        instance.tap do |o|
          o.finalize!
        end
      end

      its("access_key_id")     { should be_nil }
      its("secret_access_key") { should be_nil }
      its("session_token")     { should be_nil }
    end

    context "with EC2 credential environment variables" do
      before :each do
        ENV.stub(:[]).with("AWS_ACCESS_KEY_ID").and_return("access_key")
        ENV.stub(:[]).with("AWS_SECRET_ACCESS_KEY").and_return("secret_key")
        ENV.stub(:[]).with("AWS_SESSION_TOKEN").and_return("session_token")
      end

      subject do
        instance.tap do |o|
          o.finalize!
        end
      end

      its("access_key_id")     { should == "access_key" }
      its("secret_access_key") { should == "secret_key" }
      its("session_token")     { should == "session_token" }
    end
  end


  describe "getting credentials when there is an AWS profile" do
    ## ENV has been nuked so ENV['HOME'] will be a empty string when Credentials#get_aws_info gets called
    let(:filename_cfg)  { "/.aws/config" }
    let(:filename_keys) { "/.aws/credentials" }
    let(:data_cfg)      {
"[default]
region=eu-west-1
output=json

[profile user1]
region=us-east-1
output=text

[profile user2]
region=us-east-1
output=text

[profile user3]
region=us-west-2
output=text
" }
    let(:data_keys)     {
"[default]
aws_access_key_id=AKIdefault
aws_secret_access_key=PASSdefault

[user1]
aws_access_key_id=AKIuser1
aws_secret_access_key=PASSuser1

[user2]
aws_access_key_id=AKIuser2
aws_secret_access_key=PASSuser2
aws_session_token=TOKuser2

[user3]
aws_access_key_id=AKIuser3
aws_secret_access_key=PASSuser3
aws_session_token= TOKuser3
" }
    # filenames and file data when using AWS_SHARED_CREDENTIALS_FILE and AWS_CONFIG_FILE
    let(:sh_dir)           { "/aws_shared/" }
    let(:sh_filename_cfg)  { sh_dir + "config" }
    let(:sh_filename_keys) { sh_dir + "credentials" }
    let(:sh_data_cfg)      { "[default]\nregion=sh-region\noutput=text" }
    let(:sh_data_keys)     { "[default]\naws_access_key_id=AKI_set_shared\naws_secret_access_key=set_shared_foobar" }

    context "with EC2 credential environment variables set" do
      subject do
        ENV.stub(:[]).with("AWS_ACCESS_KEY_ID").and_return("env_access_key")
        ENV.stub(:[]).with("AWS_SECRET_ACCESS_KEY").and_return("env_secret_key")
        ENV.stub(:[]).with("AWS_SESSION_TOKEN").and_return("env_session_token")
        ENV.stub(:[]).with("AWS_DEFAULT_REGION").and_return("env_region")
        allow(File).to receive(:read).with(filename_cfg).and_return(data_cfg)
        allow(File).to receive(:read).with(filename_keys).and_return(data_keys)
        instance.tap do |o|
          o.finalize!
        end
      end
      its("access_key_id")        { should == "env_access_key" }
      its("secret_access_key")    { should == "env_secret_key" }
      its("session_token")        { should == "env_session_token" }
      its("region")               { should == "env_region" }
    end

    context "without EC2 credential environment variables but with AWS_CONFIG_FILE and AWS_SHARED_CREDENTIALS_FILE set" do
      subject do
        allow(File).to receive(:exist?).and_return(true)
        allow(File).to receive(:read).with(filename_cfg).and_return(data_cfg)
        allow(File).to receive(:read).with(filename_keys).and_return(data_keys)
        ENV.stub(:[]).with("AWS_CONFIG_FILE").and_return(sh_filename_cfg)
        ENV.stub(:[]).with("AWS_SHARED_CREDENTIALS_FILE").and_return(sh_filename_keys)
        allow(File).to receive(:read).with(sh_filename_cfg).and_return(sh_data_cfg)
        allow(File).to receive(:read).with(sh_filename_keys).and_return(sh_data_keys)
        instance.tap do |o|
          o.finalize!
        end
      end
      its("access_key_id")         { should == "AKI_set_shared" }
      its("secret_access_key")     { should == "set_shared_foobar" }
      its("session_token")         { should be_nil }
      its("region")                { should == "sh-region" }
    end

    context "without any credential environment variables and fallback to default profile at default location" do
      subject do
        allow(File).to receive(:exist?).and_return(true)
        allow(File).to receive(:read).with(filename_cfg).and_return(data_cfg)
        allow(File).to receive(:read).with(filename_keys).and_return(data_keys)
        instance.tap do |o|
          o.finalize!
        end
      end
      its("access_key_id")         { should == "AKIdefault" }
      its("secret_access_key")     { should == "PASSdefault" }
      its("session_token")         { should be_nil }
    end

    context "without any credential environment variables and chosing a profile" do
      subject do
        allow(File).to receive(:exist?).and_return(true)
        allow(File).to receive(:read).with(filename_cfg).and_return(data_cfg)
        allow(File).to receive(:read).with(filename_keys).and_return(data_keys)
        instance.aws_profile = "user3"
        instance.tap do |o|
          o.finalize!
        end
      end
      its("access_key_id")         { should == "AKIuser3" }
      its("secret_access_key")     { should == "PASSuser3" }
      its("session_token")         { should == "TOKuser3" }
      its("region")                { should == "us-west-2" }
    end
  end



  describe "region config" do
    let(:config_access_key_id)     { "foo" }
    let(:config_ami)               { "foo" }
    let(:config_instance_type)     { "foo" }
    let(:config_keypair_name)      { "foo" }
    let(:config_region)            { "foo" }
    let(:config_secret_access_key) { "foo" }
    let(:config_session_token)     { "foo" }

    def set_test_values(instance)
      instance.access_key_id     = config_access_key_id
      instance.ami               = config_ami
      instance.instance_type     = config_instance_type
      instance.keypair_name      = config_keypair_name
      instance.region            = config_region
      instance.secret_access_key = config_secret_access_key
      instance.session_token     = config_session_token
    end

    it "should raise an exception if not finalized" do
      expect { instance.get_region_config("us-east-1") }.
        to raise_error
    end

    context "with no specific config set" do
      subject do
        # Set the values on the top-level object
        set_test_values(instance)

        # Finalize so we can get the region config
        instance.finalize!

        # Get a lower level region
        instance.get_region_config("us-east-1")
      end

      its("access_key_id")     { should == config_access_key_id }
      its("ami")               { should == config_ami }
      its("instance_type")     { should == config_instance_type }
      its("keypair_name")      { should == config_keypair_name }
      its("region")            { should == config_region }
      its("secret_access_key") { should == config_secret_access_key }
      its("session_token")     { should == config_session_token }
    end

    context "with a specific config set" do
      let(:region_name) { "hashi-region" }

      subject do
        # Set the values on a specific region
        instance.region_config region_name do |config|
          set_test_values(config)
        end

        # Finalize so we can get the region config
        instance.finalize!

        # Get the region
        instance.get_region_config(region_name)
      end

      its("access_key_id")     { should == config_access_key_id }
      its("ami")               { should == config_ami }
      its("instance_type")     { should == config_instance_type }
      its("keypair_name")      { should == config_keypair_name }
      its("region")            { should == region_name }
      its("secret_access_key") { should == config_secret_access_key }
      its("session_token")     { should == config_session_token }
    end

    describe "inheritance of parent config" do
      let(:region_name) { "hashi-region" }

      subject do
        # Set the values on a specific region
        instance.region_config region_name do |config|
          config.ami = "child"
        end

        # Set some top-level values
        instance.access_key_id = "parent"
        instance.secret_access_key = "parent"
        instance.ami = "parent"

        # Finalize and get the region
        instance.finalize!
        instance.get_region_config(region_name)
      end

      its("access_key_id") { should == "parent" }
      its("secret_access_key") { should == "parent" }
      its("ami")           { should == "child" }
    end

    describe "shortcut configuration" do
      subject do
        # Use the shortcut configuration to set some values
        instance.region_config "us-east-1", :ami => "child"
        instance.finalize!
        instance.get_region_config("us-east-1")
      end

      its("ami") { should == "child" }
    end

    describe "merging" do
      let(:first)  { described_class.new }
      let(:second) { described_class.new }

      it "should merge the tags and block_device_mappings" do
        first.tags["one"] = "one"
        second.tags["two"] = "two"
        first.package_tags["three"] = "three"
        second.package_tags["four"] = "four"
        first.block_device_mapping = [{:one => "one"}]
        second.block_device_mapping = [{:two => "two"}]

        third = first.merge(second)
        third.tags.should == {
          "one" => "one",
          "two" => "two"
        }
        third.package_tags.should == {
          "three" => "three",
          "four" => "four"
        }
        third.block_device_mapping.index({:one => "one"}).should_not be_nil
        third.block_device_mapping.index({:two => "two"}).should_not be_nil
      end
    end
  end
end
