require 'spec_helper'

describe Docker do
  subject { Docker }

  it { should be_a Module }

  context 'default url and connection' do
    context "when the DOCKER_* ENV variables aren't set" do
      before do
        allow(ENV).to receive(:[]).with('DOCKER_URL').and_return(nil)
        allow(ENV).to receive(:[]).with('DOCKER_HOST').and_return(nil)
        allow(ENV).to receive(:[]).with('DOCKER_CERT_PATH').and_return(nil)
        Docker.reset!
      end

      its(:options) { should == {} }
      its(:url) { should == 'unix:///var/run/docker.sock' }
      its(:connection) { should be_a Docker::Connection }
    end

    context "when the DOCKER_* ENV variables are set" do
      before do
        allow(ENV).to receive(:[]).with('DOCKER_URL')
          .and_return('unixs:///var/run/not-docker.sock')
        allow(ENV).to receive(:[]).with('DOCKER_HOST').and_return(nil)
        allow(ENV).to receive(:[]).with('DOCKER_CERT_PATH').and_return(nil)
        Docker.reset!
      end

      its(:options) { should == {} }
      its(:url) { should == 'unixs:///var/run/not-docker.sock' }
      its(:connection) { should be_a Docker::Connection }
    end

    context "when the DOCKER_HOST is set and uses default tcp://" do
      before do
        allow(ENV).to receive(:[]).with('DOCKER_URL').and_return(nil)
        allow(ENV).to receive(:[]).with('DOCKER_HOST').and_return('tcp://')
        allow(ENV).to receive(:[]).with('DOCKER_CERT_PATH').and_return(nil)
        Docker.reset!
      end

      its(:options) { should == {} }
      its(:url) { should == 'tcp://localhost:2375' }
      its(:connection) { should be_a Docker::Connection }
    end

    context "when the DOCKER_HOST ENV variable is set" do
      before do
        allow(ENV).to receive(:[]).with('DOCKER_URL').and_return(nil)
        allow(ENV).to receive(:[]).with('DOCKER_HOST')
          .and_return('tcp://someserver:8103')
        allow(ENV).to receive(:[]).with('DOCKER_CERT_PATH').and_return(nil)
        Docker.reset!
      end

      its(:options) { should == {} }
      its(:url) { should == 'tcp://someserver:8103' }
      its(:connection) { should be_a Docker::Connection }
    end

    context "DOCKER_URL should take precedence over DOCKER_HOST" do
      before do
        allow(ENV).to receive(:[]).with('DOCKER_URL')
          .and_return('tcp://someotherserver:8103')
        allow(ENV).to receive(:[]).with('DOCKER_HOST')
          .and_return('tcp://someserver:8103')
        allow(ENV).to receive(:[]).with('DOCKER_CERT_PATH').and_return(nil)
        Docker.reset!
      end

      its(:options) { should == {} }
      its(:url) { should == 'tcp://someotherserver:8103' }
      its(:connection) { should be_a Docker::Connection }
    end

    context "when the DOCKER_CERT_PATH and DOCKER_HOST ENV variables are set" do
      before do
        allow(ENV).to receive(:[]).with('DOCKER_URL').and_return(nil)
        allow(ENV).to receive(:[]).with('DOCKER_HOST')
          .and_return('tcp://someserver:8103')
        allow(ENV).to receive(:[]).with('DOCKER_CERT_PATH')
          .and_return('/boot2dockert/cert/path')
        allow(ENV).to receive(:[]).with('DOCKER_SSL_VERIFY').and_return(nil)
        Docker.reset!
      end

      its(:options) {
        should == {
          client_cert: '/boot2dockert/cert/path/cert.pem',
          client_key: '/boot2dockert/cert/path/key.pem',
          ssl_ca_file: '/boot2dockert/cert/path/ca.pem',
          scheme: 'https'
        }
      }
      its(:url) { should == 'tcp://someserver:8103' }
      its(:connection) { should be_a Docker::Connection }
    end

    context "when the DOCKER_CERT_PATH and DOCKER_SSL_VERIFY ENV variables are set" do
      before do
        allow(ENV).to receive(:[]).with('DOCKER_URL').and_return(nil)
        allow(ENV).to receive(:[]).with('DOCKER_HOST')
          .and_return('tcp://someserver:8103')
        allow(ENV).to receive(:[]).with('DOCKER_CERT_PATH')
          .and_return('/boot2dockert/cert/path')
        allow(ENV).to receive(:[]).with('DOCKER_SSL_VERIFY')
          .and_return('false')
        Docker.reset!
      end

      its(:options) {
        should == {
          client_cert: '/boot2dockert/cert/path/cert.pem',
          client_key: '/boot2dockert/cert/path/key.pem',
          ssl_ca_file: '/boot2dockert/cert/path/ca.pem',
          scheme: 'https',
          ssl_verify_peer: false
        }
      }
      its(:url) { should == 'tcp://someserver:8103' }
      its(:connection) { should be_a Docker::Connection }
    end

  end

  describe '#reset_connection!' do
    before { subject.connection }
    it 'sets the @connection to nil' do
      expect { subject.reset_connection! }
          .to change { subject.instance_variable_get(:@connection) }
          .to nil
    end
  end

  [:options=, :url=].each do |method|
    describe "##{method}" do
      before do
        subject.url = nil
        subject.options = nil
      end

      it 'calls #reset_connection!' do
        expect(subject).to receive(:reset_connection!)
        subject.public_send(method, {})
      end
    end
  end

  describe '#version' do
    before do
      subject.url = nil
      subject.options = nil
    end
    let(:expected) {
      %w[ApiVersion Arch GitCommit GoVersion KernelVersion Os Version]
    }

    let(:version) { subject.version }
    it 'returns the version as a Hash', :vcr do
      expect(version).to be_a Hash
      expect(version.keys.sort).to eq expected
    end
  end

  describe '#info' do
    before do
      subject.url = nil
      subject.options = nil
    end

    let(:info) { subject.info }
    let(:keys) do
      %w(Containers Debug DockerRootDir Driver DriverStatus ExecutionDriver ID
         IPv4Forwarding Images IndexServerAddress InitPath InitSha1
         KernelVersion Labels MemTotal MemoryLimit NCPU NEventsListener NFd
         NGoroutines Name OperatingSystem SwapLimit)
    end

    it 'returns the info as a Hash', :vcr do
      expect(info).to be_a Hash
      expect(info.keys.sort).to eq keys
    end
  end

  describe '#authenticate!' do
    subject { described_class }

    let(:authentication) {
      subject.authenticate!(credentials)
    }

    after do
      Docker.creds = nil
    end

    context 'with valid credentials' do
      # Used valid credentials to record VCR and then changed
      # cassette to match these credentials
      let(:credentials) {
        {
          :username      => ENV['DOCKER_API_USER'],
          :password      => ENV['DOCKER_API_PASS'],
          :email         => ENV['DOCKER_API_EMAIL'],
          :serveraddress => 'https://index.docker.io/v1/'
        }
      }

      it 'logs in and sets the creds', :vcr do
        expect(authentication).to be true
        expect(Docker.creds).to eq(credentials.to_json)
      end
    end

    context 'with invalid credentials' do
      # Recorded the VCR with these credentials
      # to purposely fail
      let(:credentials) {
        {
          :username      => 'test',
          :password      => 'password',
          :email         => 'test@example.com',
          :serveraddress => 'https://index.docker.io/v1/'
        }
      }

      it "raises an error and doesn't set the creds", :vcr do
        skip "VCR won't record when Excon::Expects fail"
        expect {
          authentication
        }.to raise_error(Docker::Error::AuthenticationError)
        expect(Docker.creds).to be_nil
      end
    end
  end

  describe '#validate_version' do
    before do
      subject.url = nil
      subject.options = nil
    end

    context 'when a Docker Error is raised' do
      before do
        allow(Docker).to receive(:info).and_raise(Docker::Error::ClientError)
      end

      it 'raises a Version Error' do
        expect { subject.validate_version! }
            .to raise_error(Docker::Error::VersionError)
      end
    end

    context 'when nothing is raised', :vcr do
      its(:validate_version!) { should be true }
    end
  end
end
