require 'spec_helper'
require 'r10k/settings'
require 'r10k/util/exec_env'

describe R10K::Settings do
  describe "git settings" do
    subject { described_class.git_settings }

    describe "provider" do
      it "normalizes valid values to a symbol" do
        output = subject.evaluate("provider" => "rugged")
        expect(output[:provider]).to eq(:rugged)
      end
    end

    describe "username" do
      it "defaults to the current user" do
        expect(Etc).to receive(:getlogin).and_return("puppet")
        output = subject.evaluate({})
        expect(output[:username]).to eq("puppet")
      end

      it "passes values through unchanged" do
        output = subject.evaluate("username" => "git")
        expect(output[:username]).to eq("git")
      end
    end

    describe "private_key" do
      it "passes values through unchanged" do
        output = subject.evaluate("private_key" => "/etc/puppetlabs/r10k/id_rsa")
        expect(output[:private_key]).to eq("/etc/puppetlabs/r10k/id_rsa")
      end
    end

    describe "proxy" do
      it "accepts valid URIs" do
        output = subject.evaluate("proxy" => "http://proxy.tessier-ashpool.freeside:3128")
        expect(output[:proxy]).to eq "http://proxy.tessier-ashpool.freeside:3128"
      end

      it "rejects invalid URIs" do
        expect {
          subject.evaluate("proxy" => "that's no proxy!")
        }.to raise_error do |err|
          expect(err.message).to match(/Validation failed for 'git' settings group/)
          expect(err.errors.size).to eq 1
          expect(err.errors[:proxy]).to be_a_kind_of(ArgumentError)
          expect(err.errors[:proxy].message).to match(/could not be parsed as a URL/)
        end
      end
    end
  end

  describe "forge settings" do
    subject { described_class.forge_settings }

    describe "proxy" do
      it "accepts valid URIs" do
        output = subject.evaluate("proxy" => "http://proxy.tessier-ashpool.freeside:3128")
        expect(output[:proxy]).to eq "http://proxy.tessier-ashpool.freeside:3128"
      end

      it "rejects invalid URIs" do
        expect {
          subject.evaluate("proxy" => "that's no proxy!")
        }.to raise_error do |err|
          expect(err.message).to match(/Validation failed for 'forge' settings group/)
          expect(err.errors.size).to eq 1
          expect(err.errors[:proxy]).to be_a_kind_of(ArgumentError)
          expect(err.errors[:proxy].message).to match(/could not be parsed as a URL/)
        end
      end
    end

    describe "baseurl" do
      it "accepts valid URIs" do
        output = subject.evaluate("baseurl" => "https://forge.tessier-ashpool.freeside")
        expect(output[:baseurl]).to eq "https://forge.tessier-ashpool.freeside"
      end

      it "rejects invalid URIs" do
        expect {
          subject.evaluate("baseurl" => "that's no forge!")
        }.to raise_error do |err|
          expect(err.message).to match(/Validation failed for 'forge' settings group/)
          expect(err.errors.size).to eq 1
          expect(err.errors[:baseurl]).to be_a_kind_of(ArgumentError)
          expect(err.errors[:baseurl].message).to match(/could not be parsed as a URL/)
        end
      end
    end

    describe "allow_puppetfile_override" do
      it 'is false by default' do
        expect(subject.evaluate({})[:allow_puppetfile_override]).to eq(false)
      end

      it 'can be set to true' do
        expect(subject.evaluate({"allow_puppetfile_override" => true})[:allow_puppetfile_override]).to eq(true)
      end

      it "raises an error for non-boolean values" do
        expect {
          subject.evaluate({"allow_puppetfile_override" => 'invalid_string'})
        }.to raise_error do |err|
          expect(err.message).to match(/Validation failed for 'forge' settings group/)
          expect(err.errors.size).to eq 1
          expect(err.errors[:allow_puppetfile_override]).to be_a_kind_of(ArgumentError)
          expect(err.errors[:allow_puppetfile_override].message).to match(/`allow_puppetfile_override` can only be a boolean value, not 'invalid_string'/)
        end
      end
    end
  end

  describe "deploy settings" do
    subject { described_class.deploy_settings }

    describe 'exclude_spec' do
      it 'is true by default' do
        expect(subject.evaluate({})[:exclude_spec]).to eq(true)
      end
      it 'can be set to false' do
        expect(subject.evaluate({"exclude_spec" => false})[:exclude_spec]).to eq(false)
      end
      it "raises an error for non-boolean values" do
        expect {
          subject.evaluate({"exclude_spec" => 'invalid_string'})
        }.to raise_error do |err|
          expect(err.message).to match(/Validation failed for 'deploy' settings group/)
          expect(err.errors.size).to eq 1
          expect(err.errors[:exclude_spec]).to be_a_kind_of(ArgumentError)
          expect(err.errors[:exclude_spec].message).to match(/`exclude_spec` can only be a boolean value, not 'invalid_string'/)
        end
      end
    end
    describe "write_lock" do
      it "accepts a string with a reason for the write lock" do
        output = subject.evaluate("write_lock" => "No maintenance window active, code freeze till 2038-01-19")
        expect(output[:write_lock]).to eq("No maintenance window active, code freeze till 2038-01-19")
      end

      it "accepts false and null values for the write lock" do
        output = subject.evaluate("write_lock" => false)
        expect(output[:write_lock]).to eq false
      end

      it "rejects non-string truthy values for the write lock" do
        expect {
          subject.evaluate("write_lock" => %w[list of reasons why deploys are locked])
        }.to raise_error do |err|
          expect(err.message).to match(/Validation failed for 'deploy' settings group/)
          expect(err.errors.size).to eq 1
          expect(err.errors[:write_lock]).to be_a_kind_of(ArgumentError)
          expect(err.errors[:write_lock].message).to match(/should be a string containing the reason/)
        end
      end
    end

    describe 'puppet_path' do
      it 'when executable raises no error' do
        expect(File).to receive(:executable?).with('/nonexistent').and_return(true)
        expect { subject.evaluate('puppet_path' => '/nonexistent') }.not_to raise_error
      end

      it 'when not executable raises error' do
        expect(File).to receive(:executable?).with('/nonexistent')
        expect { subject.evaluate('puppet_path' => '/nonexistent') }.to raise_error(R10K::Settings::Collection::ValidationError)
      end
    end

    describe 'puppet_conf' do
      it 'when file raises no error' do
        allow(File).to receive(:readable?).with('/nonexistent').and_return(true)
        expect { subject.evaluate('puppet_conf' => '/nonexistent') }.not_to raise_error
      end

      it 'when not file raises error' do
        allow(File).to receive(:readable?).with('/nonexistent').and_return(false)
        expect { subject.evaluate('puppet_conf' => '/nonexistent') }.to raise_error(R10K::Settings::Collection::ValidationError)
      end
    end
  end

  describe "global settings" do
    subject { described_class.global_settings }
    describe "sources" do
      it "passes values through unchanged" do
        output = subject.evaluate("sources" => {"puppet" => {"remote" => "https://git.tessier-ashpool.freeside"}})
        expect(output[:sources]).to eq({"puppet" => {"remote" => "https://git.tessier-ashpool.freeside"}})
      end
    end

    describe "cachedir" do
      it "passes values through unchanged" do
        output = subject.evaluate("cachedir" => "/srv/r10k/git")
        expect(output[:cachedir]).to eq("/srv/r10k/git")
      end
    end

    describe "postrun" do
      it "accepts an argument vector" do
        output = subject.evaluate("postrun" => ["curl", "-F", "deploy=done", "http://reporting.tessier-ashpool.freeside/r10k"])
        expect(output[:postrun]).to eq(["curl", "-F", "deploy=done", "http://reporting.tessier-ashpool.freeside/r10k"])
      end

      it "rejects a string command" do
        expect {
          subject.evaluate("postrun" => "curl -F 'deploy=done' https://reporting.tessier-ashpool.freeside/r10k")
        }.to raise_error do |err|
          expect(err.message).to match(/Validation failed for 'global' settings group/)
          expect(err.errors.size).to eq 1
          expect(err.errors[:postrun]).to be_a_kind_of(ArgumentError)
          expect(err.errors[:postrun].message).to eq("The postrun setting should be an array of strings, not a String")
        end
      end
    end

    describe "pool_size" do
      it "accepts integers greater than zero" do
        output = subject.evaluate("pool_size" => 5)
        expect(output[:pool_size]).to eq 5
      end

      it "rejects non integer values" do
        expect {
          subject.evaluate("pool_size" => "5")
        }.to raise_error do |err|
          expect(err.errors.size).to eq 1
          expect(err.errors[:pool_size]).to be_a_kind_of(ArgumentError)
          expect(err.errors[:pool_size].message).to match(/The pool_size setting should be an integer/)
        end
      end

      it "rejects integers smaller than one" do
        expect {
          subject.evaluate("pool_size" => 0)
        }.to raise_error do |err|
          expect(err.errors.size).to eq 1
          expect(err.errors[:pool_size]).to be_a_kind_of(ArgumentError)
          expect(err.errors[:pool_size].message).to match(/The pool_size setting should be greater than zero/)
        end

        expect {
          subject.evaluate("pool_size" => -3)
        }.to raise_error do |err|
          expect(err.errors.size).to eq 1
          expect(err.errors[:pool_size]).to be_a_kind_of(ArgumentError)
          expect(err.errors[:pool_size].message).to match(/The pool_size setting should be greater than zero/)
        end
      end
    end

    describe "proxy" do
      it "accepts valid URIs" do
        output = subject.evaluate("proxy" => "http://proxy.tessier-ashpool.freeside:3128")
        expect(output[:proxy]).to eq "http://proxy.tessier-ashpool.freeside:3128"
      end

      it "rejects invalid URIs" do
        expect {
          subject.evaluate("proxy" => "that's no proxy!")
        }.to raise_error do |err|
          expect(err.message).to match(/Validation failed for 'global' settings group/)
          expect(err.errors.size).to eq 1
          expect(err.errors[:proxy]).to be_a_kind_of(ArgumentError)
          expect(err.errors[:proxy].message).to match(/could not be parsed as a URL/)
        end
      end

      describe "setting a default value" do
        %w[HTTPS_PROXY https_proxy HTTP_PROXY http_proxy].each do |env_var|
          it "respects the #{env_var} environment variable" do
            R10K::Util::ExecEnv.withenv(env_var => "http://proxy.value/#{env_var}") do
              output = subject.evaluate({})
              expect(output[:proxy]).to eq("http://proxy.value/#{env_var}")
            end
          end
        end
      end
    end

    describe "git settings" do
      it "passes settings through to the git settings" do
        output = subject.evaluate("git" => {"provider" => "shellgit", "username" => "git"})
        expect(output[:git]).to include(:provider => :shellgit, :username => "git")
      end
    end

    describe "forge settings" do
      it "passes settings through to the forge settings" do
        output = subject.evaluate("forge" => {"baseurl" => "https://forge.tessier-ashpool.freeside",
                                              "proxy" => "https://proxy.tessier-ashpool.freesize:3128",
                                              "authorization_token" => "faketoken",
                                              "allow_puppetfile_override" => true})
        expect(output[:forge]).to eq(:baseurl => "https://forge.tessier-ashpool.freeside",
                                     :proxy => "https://proxy.tessier-ashpool.freesize:3128",
                                     :authorization_token => "faketoken",
                                     :allow_puppetfile_override => true)
      end
    end
  end
end
