require 'spec_helper'

module Beaker
  describe Vagrant do

    let( :options ) {
      make_opts.merge({
        'logger' => double().as_null_object,
        :hosts_file => 'sample.cfg',
        :forward_ssh_agent => true,
      })
    }

    let( :vagrant ) { Beaker::Vagrant.new( @hosts, options ) }

    before :each do
      @hosts = make_hosts({
        :mount_folders => {
          :test_temp => {:from => './', :to => '/temp'},
          :test_tmp => {:from => '../', :to => '/tmp'}
        },
        :forwarded_ports => {
          :http => {:from => 10080, :to => 80},
          :ssl  => {:from => 4443,  :to => 443},
          :tomcat => {:from => 8080, :to => 8080}
        }
      })
    end

    it "stores the vagrant file in $WORKINGDIR/.vagrant/beaker_vagrant_files/sample.cfg" do
      allow( vagrant ).to receive( :randmac ).and_return( "0123456789" )
      path = vagrant.instance_variable_get( :@vagrant_path )

      expect( path ).to be === File.join(Dir.pwd, '.vagrant', 'beaker_vagrant_files', 'sample.cfg')

    end

    it "can make a Vagrantfile for a set of hosts" do
      path = vagrant.instance_variable_get( :@vagrant_path )
      allow( vagrant ).to receive( :randmac ).and_return( "0123456789" )

      vagrant.make_vfile( @hosts )

      vagrantfile = File.read( File.expand_path( File.join( path, "Vagrantfile")))
      puts "file is #{path}\n"
      expect( vagrantfile ).to be === <<-EOF
Vagrant.configure("2") do |c|
  c.ssh.insert_key = false
  c.vm.define 'vm1' do |v|
    v.vm.hostname = 'vm1'
    v.vm.box = 'vm2vm1_of_my_box'
    v.vm.box_url = 'http://address.for.my.box.vm1'
    v.vm.box_check_update = 'true'
    v.vm.network :private_network, ip: "ip.address.for.vm1", :netmask => "255.255.0.0", :mac => "0123456789"
    v.vm.synced_folder './', '/temp', create: true
    v.vm.synced_folder '../', '/tmp', create: true
    v.vm.network :forwarded_port, guest: 80, host: 10080
    v.vm.network :forwarded_port, guest: 443, host: 4443
    v.vm.network :forwarded_port, guest: 8080, host: 8080
    v.vm.provider :virtualbox do |vb|
      vb.customize ['modifyvm', :id, '--memory', '1024', '--cpus', '1']
    end
  end
  c.vm.define 'vm2' do |v|
    v.vm.hostname = 'vm2'
    v.vm.box = 'vm2vm2_of_my_box'
    v.vm.box_url = 'http://address.for.my.box.vm2'
    v.vm.box_check_update = 'true'
    v.vm.network :private_network, ip: "ip.address.for.vm2", :netmask => "255.255.0.0", :mac => "0123456789"
    v.vm.synced_folder './', '/temp', create: true
    v.vm.synced_folder '../', '/tmp', create: true
    v.vm.network :forwarded_port, guest: 80, host: 10080
    v.vm.network :forwarded_port, guest: 443, host: 4443
    v.vm.network :forwarded_port, guest: 8080, host: 8080
    v.vm.provider :virtualbox do |vb|
      vb.customize ['modifyvm', :id, '--memory', '1024', '--cpus', '1']
    end
  end
  c.vm.define 'vm3' do |v|
    v.vm.hostname = 'vm3'
    v.vm.box = 'vm2vm3_of_my_box'
    v.vm.box_url = 'http://address.for.my.box.vm3'
    v.vm.box_check_update = 'true'
    v.vm.network :private_network, ip: "ip.address.for.vm3", :netmask => "255.255.0.0", :mac => "0123456789"
    v.vm.synced_folder './', '/temp', create: true
    v.vm.synced_folder '../', '/tmp', create: true
    v.vm.network :forwarded_port, guest: 80, host: 10080
    v.vm.network :forwarded_port, guest: 443, host: 4443
    v.vm.network :forwarded_port, guest: 8080, host: 8080
    v.vm.provider :virtualbox do |vb|
      vb.customize ['modifyvm', :id, '--memory', '1024', '--cpus', '1']
    end
  end
end
EOF
    end

    it "can make a Vagrantfile with ssh agent forwarding enabled" do
      path = vagrant.instance_variable_get( :@vagrant_path )
      allow( vagrant ).to receive( :randmac ).and_return( "0123456789" )

      hosts = make_hosts({},1)
      vagrant.make_vfile( hosts, options )

      vagrantfile = File.read( File.expand_path( File.join( path, "Vagrantfile")))
      expect( vagrantfile ).to match(/(ssh.forward_agent = true)/)
    end

    it "can make a Vagrantfile with synced_folder disabled" do
      path = vagrant.instance_variable_get( :@vagrant_path )
      allow( vagrant ).to receive( :randmac ).and_return( "0123456789" )

      hosts = make_hosts({:synced_folder => 'disabled'},1)
      vagrant.make_vfile( hosts, options )

      vagrantfile = File.read( File.expand_path( File.join( path, "Vagrantfile")))
      expect( vagrantfile ).to match(/v.vm.synced_folder .* disabled: true/)
    end

    it "can make a Vagrantfile with network mac autogenerated" do
      path = vagrant.instance_variable_get( :@vagrant_path )

      hosts = make_hosts({},1)
      vagrant.make_vfile( hosts, options )

      vagrantfile = File.read( File.expand_path( File.join( path, "Vagrantfile")))
      expect( vagrantfile ).to match(/v.vm.network :private_network, ip: "ip.address.for.vm1", :netmask => "255.255.0.0", :mac => ".+/)
    end

    it "can make a Vagrantfile with network mac specified" do
      path = vagrant.instance_variable_get( :@vagrant_path )

      hosts = make_hosts({:network_mac => 'b6:33:ae:19:48:f9'},1)
      vagrant.make_vfile( hosts, options )

      vagrantfile = File.read( File.expand_path( File.join( path, "Vagrantfile")))
      expect( vagrantfile ).to match(/v.vm.network :private_network, ip: "ip.address.for.vm1", :netmask => "255.255.0.0", :mac => "b6:33:ae:19:48:f9/)
    end

    it "can make a Vagrantfile with network mac disabled" do
      path = vagrant.instance_variable_get( :@vagrant_path )

      hosts = make_hosts({:network_mac => 'false'},1)
      vagrant.make_vfile( hosts, options )

      vagrantfile = File.read( File.expand_path( File.join( path, "Vagrantfile")))
      expect( vagrantfile ).to match(/v.vm.network :private_network, ip: "ip.address.for.vm1", :netmask => "255.255.0.0"/)
    end

    context "when generating a windows config" do
      before do
        path = vagrant.instance_variable_get( :@vagrant_path )
        allow( vagrant ).to receive( :randmac ).and_return( "0123456789" )
        @hosts[0][:platform] = 'windows'

        vagrant.make_vfile( @hosts )

        @generated_file = File.read( File.expand_path( File.join( path, "Vagrantfile") ) )
      end

      it 'has the proper port forwarding for RDP' do
        expect( @generated_file ).to match /v.vm.network :forwarded_port, guest: 3389, host: 3389, id: 'rdp', auto_correct: true/
      end

      it 'has the proper port forwarding for WinRM' do
        expect( @generated_file ).to match /v.vm.network :forwarded_port, guest: 5985, host: 5985, id: 'winrm', auto_correct: true/
      end

      it 'configures the guest type to windows' do
        expect( @generated_file ).to match /v.vm.guest = :windows/
      end

      it 'sets a non-default memsize' do
        expect( @generated_file ).to match /'--memory', '2048',/
      end
    end

    it "uses the memsize defined per vagrant host" do
      path = vagrant.instance_variable_get( :@vagrant_path )
      allow( vagrant ).to receive( :randmac ).and_return( "0123456789" )

      vagrant.make_vfile( @hosts, {'vagrant_memsize' => 'hello!'} )

      generated_file = File.read( File.expand_path( File.join( path, "Vagrantfile") ) )

      match = generated_file.match(/vb.customize \['modifyvm', :id, '--memory', 'hello!', '--cpus', '1'\]/)

      expect( match ).to_not be nil

    end

    it "uses the cpus defined per vagrant host" do
      path = vagrant.instance_variable_get( :@vagrant_path )
      allow( vagrant ).to receive( :randmac ).and_return( "0123456789" )

      vagrant.make_vfile( @hosts, {'vagrant_cpus' => 'goodbye!'} )

      generated_file = File.read( File.expand_path( File.join( path, "Vagrantfile") ) )

      match = generated_file.match(/vb.customize \['modifyvm', :id, '--memory', '1024', '--cpus', 'goodbye!'\]/)

      expect( match ).to_not be nil

    end

    context "port forwarding rules" do
      it "supports all Vagrant parameters" do
        path = vagrant.instance_variable_get( :@vagrant_path )
        allow( vagrant ).to receive( :randmac ).and_return( "0123456789" )

        hosts = make_hosts(
          {
            :forwarded_ports => {
              :http => {
                :from => 10080,
                :from_ip => '127.0.0.1',
                :to => 80,
                :to_ip => '0.0.0.0',
                :protocol => 'udp'
              }
            }
          },1)
        vagrant.make_vfile( hosts, options )

        vagrantfile = File.read( File.expand_path( File.join( path, "Vagrantfile")))
        expect( vagrantfile ).to match(/v.vm.network :forwarded_port, protocol: 'udp', guest_ip: '0.0.0.0', guest: 80, host_ip: '127.0.0.1', host: 10080/)
      end

      it "supports supports from_ip" do
        path = vagrant.instance_variable_get( :@vagrant_path )
        allow( vagrant ).to receive( :randmac ).and_return( "0123456789" )

        hosts = make_hosts(
            {
                :forwarded_ports => {
                    :http => {
                        :from => 10080,
                        :from_ip => '127.0.0.1',
                        :to => 80,
                    }
                }
            },1)
        vagrant.make_vfile( hosts, options )

        vagrantfile = File.read( File.expand_path( File.join( path, "Vagrantfile")))
        expect( vagrantfile ).to match(/v.vm.network :forwarded_port, guest: 80, host_ip: '127.0.0.1', host: 10080/)
      end

      it "supports all to_ip" do
        path = vagrant.instance_variable_get( :@vagrant_path )
        allow( vagrant ).to receive( :randmac ).and_return( "0123456789" )

        hosts = make_hosts(
            {
                :forwarded_ports => {
                    :http => {
                        :from => 10080,
                        :to => 80,
                        :to_ip => '0.0.0.0',
                    }
                }
            },1)
        vagrant.make_vfile( hosts, options )

        vagrantfile = File.read( File.expand_path( File.join( path, "Vagrantfile")))
        expect( vagrantfile ).to match(/v.vm.network :forwarded_port, guest_ip: '0.0.0.0', guest: 80, host: 10080/)
      end

      it "supports all protocol" do
        path = vagrant.instance_variable_get( :@vagrant_path )
        allow( vagrant ).to receive( :randmac ).and_return( "0123456789" )

        hosts = make_hosts(
            {
                :forwarded_ports => {
                    :http => {
                        :from => 10080,
                        :to => 80,
                        :protocol => 'udp'
                    }
                }
            },1)
        vagrant.make_vfile( hosts, options )

        vagrantfile = File.read( File.expand_path( File.join( path, "Vagrantfile")))
        expect( vagrantfile ).to match(/v.vm.network :forwarded_port, protocol: 'udp', guest: 80, host: 10080/)

      end
    end

    it "can generate a new /etc/hosts file referencing each host" do

      @hosts.each do |host|
        expect( vagrant ).to receive( :get_domain_name ).with( host ).and_return( 'labs.lan' )
        expect( vagrant ).to receive( :set_etc_hosts ).with( host, "127.0.0.1\tlocalhost localhost.localdomain\nip.address.for.vm1\tvm1.labs.lan vm1\nip.address.for.vm2\tvm2.labs.lan vm2\nip.address.for.vm3\tvm3.labs.lan vm3\n" ).once
      end

      vagrant.hack_etc_hosts( @hosts, options )

    end

    context "can copy vagrant's key to root .ssh on each host" do

      it "can copy to root on unix" do
        host = @hosts[0]
        host[:platform] = 'unix'

        expect( Command ).to receive( :new ).with("sudo su -c \"cp -r .ssh /root/.\"").once
        expect( Command ).to receive( :new ).with("sudo fixfiles restore /root").once
        expect( Command ).to receive( :new ).with("sudo selinuxenabled").once

        vagrant.copy_ssh_to_root( host, options )

      end

      it "can copy to Administrator on windows" do
        host = @hosts[0]
        host[:platform] = 'windows'
        expect( host ).to receive( :is_cygwin? ).and_return(true)

        expect( Command ).to_not receive( :new ).with("sudo fixfiles restore /root")
        expect( Command ).to receive( :new ).with("cp -r .ssh /cygdrive/c/Users/Administrator/.").once
        expect( Command ).to receive( :new ).with("chown -R Administrator /cygdrive/c/Users/Administrator/.ssh").once

        # This is checked on all platforms since Linux isn't called out specifically in the code
        # If this fails, nothing further is activated
        result = Beaker::Result.new(host, '')
        result.exit_code = 1
        expect( Command ).to receive( :new ).with("sudo selinuxenabled")
        allow( host ).to receive(:exec).and_return(result)

        vagrant.copy_ssh_to_root( host, options )

      end
    end

    it "can generate a ssh-config file" do
      host = @hosts[0]
      name = host.name
      allow( Dir ).to receive( :chdir ).and_yield()
      out = double( 'stdout' )
      allow( out ).to receive( :read ).and_return("Host #{name}
    HostName 127.0.0.1
    User vagrant
    Port 2222
    UserKnownHostsFile /dev/null
    StrictHostKeyChecking no
    PasswordAuthentication no
    IdentityFile /home/root/.vagrant.d/insecure_private_key
    IdentitiesOnly yes")

      wait_thr = OpenStruct.new
      state = double( 'state' )
      allow( state ).to receive( :success? ).and_return( true )
      wait_thr.value = state

      allow( Open3 ).to receive( :popen3 ).with( {"RUBYLIB"=>""}, 'vagrant', 'ssh-config', name ).and_return( [ "", out, "", wait_thr ])

      file = double( 'file' )
      allow( file ).to receive( :path ).and_return( '/path/sshconfig' )
      allow( file ).to receive( :rewind ).and_return( true )

      expect( Tempfile ).to receive( :new ).with( "#{host.name}").and_return( file )
      expect( file ).to receive( :write ).with("Host ip.address.for.#{name}\n    HostName 127.0.0.1\n    User root\n    Port 2222\n    UserKnownHostsFile /dev/null\n    StrictHostKeyChecking no\n    PasswordAuthentication no\n    IdentityFile /home/root/.vagrant.d/insecure_private_key\n    IdentitiesOnly yes")

      vagrant.set_ssh_config( host, 'root' )
      expect( host['ssh'] ).to be === { :config => file.path }
      expect( host['user']).to be === 'root'

    end

    describe "get_ip_from_vagrant_file" do
      before :each do
        allow( vagrant ).to receive( :randmac ).and_return( "0123456789" )
        vagrant.make_vfile( @hosts )
      end

      it "can find the correct ip for the provided hostname" do
        @hosts.each do |host|
          expect( vagrant.get_ip_from_vagrant_file(host.name) ).to be === host[:ip]
        end

      end

      it "returns nil if it is unable to find an ip" do
        expect( vagrant.get_ip_from_vagrant_file("unknown") ).to be_nil
      end

      it "raises an error if no Vagrantfile is present" do
        File.delete( vagrant.instance_variable_get( :@vagrant_file ) )
        @hosts.each do |host|
          expect{ vagrant.get_ip_from_vagrant_file(host.name) }.to raise_error RuntimeError, /No vagrant file found/
        end
      end
    end

    describe "provisioning and cleanup" do

      before :each do
        expect( vagrant ).to receive( :vagrant_cmd ).with( "up" ).once
        @hosts.each do |host|
          host_prev_name = host['user']
          expect( vagrant ).to receive( :set_ssh_config ).with( host, 'vagrant' ).once
          expect( vagrant ).to receive( :copy_ssh_to_root ).with( host, options ).once
          expect( vagrant ).to receive( :set_ssh_config ).with( host, host_prev_name ).once
        end
        expect( vagrant ).to receive( :hack_etc_hosts ).with( @hosts, options ).once
      end

      it "can provision a set of hosts" do
        options = vagrant.instance_variable_get( :@options )
        expect( vagrant ).to receive( :make_vfile ).with( @hosts, options ).once
        expect( vagrant ).to receive( :vagrant_cmd ).with( "destroy --force" ).never
        vagrant.provision
      end

      it "destroys an existing set of hosts before provisioning" do
        vagrant.make_vfile( @hosts )
        expect( vagrant ).to receive( :vagrant_cmd ).with( "destroy --force" ).once
        vagrant.provision
      end

      it "notifies user of failed provision" do
        vagrant.provision
        expect( vagrant ).to receive( :vagrant_cmd ).with( "destroy --force" ).and_raise( RuntimeError )
        expect( options['logger'] ).to receive( :debug ).with( /Vagrantfile/ )
        expect{ vagrant.provision }.to raise_error( RuntimeError )
      end

      it "can cleanup" do
        expect( vagrant ).to receive( :vagrant_cmd ).with( "destroy --force" ).once
        expect( FileUtils ).to receive( :rm_rf ).once

        vagrant.provision
        vagrant.cleanup

      end

    end

    describe "provisioning and cleanup on windows" do
      before :each do
        expect( vagrant ).to receive( :vagrant_cmd ).with( "up" ).once
        @hosts.each do |host|
          host_prev_name = host['user']
          expect( vagrant ).to receive( :set_ssh_config ).with( host, 'vagrant' ).once
          expect( vagrant ).not_to receive( :copy_ssh_to_root ).with( host, options ).once
          expect( vagrant ).not_to receive( :set_ssh_config ).with( host, host_prev_name ).once
        end
        expect( vagrant ).to receive( :hack_etc_hosts ).with( @hosts, options ).once
      end

      it "can provision a set of hosts" do
        options = vagrant.instance_variable_get( :@options )
        expect( vagrant ).to receive( :make_vfile ).with( @hosts, options ).once
        expect( vagrant ).to receive( :vagrant_cmd ).with( "destroy --force" ).never
        vagrant.provision
      end

      it "destroys an existing set of hosts before provisioning" do
        vagrant.make_vfile( @hosts )
        expect( vagrant ).to receive( :vagrant_cmd ).with( "destroy --force" ).once
        vagrant.provision
      end

      it "can cleanup" do
        expect( vagrant ).to receive( :vagrant_cmd ).with( "destroy --force" ).once
        expect( FileUtils ).to receive( :rm_rf ).once

        vagrant.provision
        vagrant.cleanup

      end

    end

  end

end