# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1

require File.expand_path("../../../../base", __FILE__)
require Vagrant.source_root.join("plugins/commands/upload/command")

describe VagrantPlugins::CommandUpload::Command do
  include_context "unit"
  include_context "virtualbox"

  let(:iso_env) do
    # We have to create a Vagrantfile so there is a root path
    env = isolated_environment
    env.vagrantfile("")
    env.create_vagrant_env
  end

  let(:guest)   { double("guest", capability_host_chain: guest_chain) }
  let(:host)    { double("host", capability_host_chain: host_chain) }
  let(:machine) { iso_env.machine(iso_env.machine_names[0], :dummy) }
  let(:communicator) { double("communicator") }
  let(:host_chain){ [[]] }
  let(:guest_chain){ [[]] }

  let(:argv)     { [] }
  let(:config) {
    double("config")
  }

  subject { described_class.new(argv, iso_env) }

  before do
    allow(machine).to receive(:communicate).and_return(communicator)
    allow(machine).to receive(:config).and_return(config)
    allow(subject).to receive(:with_target_vms)
  end

  it "should raise invalid usage error by default" do
    expect { subject.execute }.to raise_error(Vagrant::Errors::CLIInvalidUsage)
  end

  context "when three arguments are provided" do
    let(:argv) { ["source", "destination", "guest"] }

    before { allow(File).to receive(:file?).and_return(true) }

    it "should use third argument as name of guest" do
      expect(subject).to receive(:with_target_vms).with("guest", any_args)
      subject.execute
    end

    it "should use first argument as source and second as destination" do
      allow(subject).to receive(:with_target_vms) { |&block| block.call machine }
      expect(communicator).to receive(:upload).with("source", "destination")
      subject.execute
    end
  end

  context "when two arguments are provided" do
    let(:argv) { ["source", "ambiguous"] }
    let(:active_machines) { [] }

    before do
      allow(File).to receive(:file?).and_return(true)
      allow(iso_env).to receive(:active_machines).and_return(active_machines)
    end

    it "should use the the second argument as destination when not a machine name" do
      allow(subject).to receive(:with_target_vms) { |&block| block.call machine }
      expect(communicator).to receive(:upload).with("source", "ambiguous")
      subject.execute
    end

    context "when active machine matches second argument" do
      let(:active_machines) { [["ambiguous"]] }

      it "should use second argument as guest name and generate destination" do
        allow(subject).to receive(:with_target_vms).with("ambiguous", any_args) { |&block| block.call machine }
        expect(communicator).to receive(:upload).with("source", "source")
        subject.execute
      end
    end
  end

  context "when single argument is provided" do
    let(:argv) { ["item"] }

    before do
      allow(File).to receive(:file?)
      allow(File).to receive(:directory?)
    end

    it "should check if source is a file" do
      expect(File).to receive(:file?).with("item").and_return(true)
      subject.execute
    end

    it "should check if source is a directory" do
      expect(File).to receive(:directory?).with("item").and_return(true)
      subject.execute
    end

    it "should raise error if source is not a directory or file" do
      expect { subject.execute }.to raise_error(Vagrant::Errors::UploadSourceMissing)
    end

    context "when source path ends with double quote" do
      let(:argv) { [".\\item\""] }

      it "should remove trailing quote" do
        expect(File).to receive(:file?).with(".\\item").and_return(true)
        subject.execute
      end
    end

    context "when source path ends with single quote" do
      let(:argv) { ['.\item\''] }

      it "should remove trailing quote" do
        expect(File).to receive(:file?).with(".\\item").and_return(true)
        subject.execute
      end
    end

    context "when source is a directory" do
      before do
        allow(File).to receive(:file?).with("item").and_return(false)
        allow(File).to receive(:directory?).with("item").and_return(true)
        allow(communicator).to receive(:upload)
        allow(subject).to receive(:with_target_vms) { |&block| block.call machine }
      end

      it "should upload the directory" do
        expect(communicator).to receive(:upload).with(/item/, anything)
        subject.execute
      end

      it "should append separator and dot to source path for upload" do
        expect(communicator).to receive(:upload).with("item/.", anything)
        subject.execute
      end
    end

    context "when source is a file" do
      before do
        allow(File).to receive(:file?).with("item").and_return(true)
        allow(communicator).to receive(:upload)
        allow(subject).to receive(:with_target_vms) { |&block| block.call machine }
        allow(machine).to receive(:guest).and_return(guest)
        allow(machine).to receive(:env).and_return(double("env", host: host))
        allow(guest).to receive(:capability?).and_return(true)
        allow(guest).to receive(:capability)
      end

      it "should upload the file" do
        expect(communicator).to receive(:upload).with("item", anything)
        subject.execute
      end

      it "should name destination after the source" do
        expect(communicator).to receive(:upload).with("item", "item")
        subject.execute
      end

      context "when temporary option is set" do
        before { argv << "-t" }

        it "should get temporary path for destination from guest" do
          expect(guest).to receive(:capability).with(:create_tmp_path, any_args).and_return("TMP_PATH")
          expect(communicator).to receive(:upload).with("item", "TMP_PATH")
          subject.execute
        end
      end

      context "when compress option is set" do
        before do
          argv << "-c"
          allow(guest).to receive(:capability).with(:create_tmp_path, any_args).and_return("TMP")
          allow(subject).to receive(:compress_source_zip).and_return("COMPRESS_SOURCE")
          allow(subject).to receive(:compress_source_tgz).and_return("COMPRESS_SOURCE")
          allow(FileUtils).to receive(:rm).with("COMPRESS_SOURCE")
        end

        it "should check for guest decompression" do
          expect(guest).to receive(:capability?).with(:decompress_tgz).and_return(true)
          subject.execute
        end

        it "should compress the source" do
          expect(subject).to receive(:compress_source_tgz).with("item").and_return("COMPRESS_SOURCE")
          subject.execute
        end

        it "should cleanup compressed source" do
          expect(FileUtils).to receive(:rm).with("COMPRESS_SOURCE")
          subject.execute
        end

        it "should upload the compressed source" do
          expect(communicator).to receive(:upload).with("COMPRESS_SOURCE", anything)
          subject.execute
        end

        it "should upload compressed source to temporary location" do
          expect(communicator).to receive(:upload).with("COMPRESS_SOURCE", "TMP")
          subject.execute
        end

        it "should have guest decompress file" do
          expect(guest).to receive(:capability).with(:decompress_tgz, "TMP", any_args)
          subject.execute
        end

        it "should provide destination for guest decompression of file" do
          expect(guest).to receive(:capability).with(:decompress_tgz, "TMP", "item", any_args)
          subject.execute
        end

        it "should provide the destination type for guest decompression" do
          expect(guest).to receive(:capability).with(:decompress_tgz, "TMP", "item", hash_including(type: :file))
          subject.execute
        end

        context "with compression type set to zip" do
          before { argv << "-C" << "zip" }

          it "should test guest for decompression capability" do
            expect(guest).to receive(:capability?).with(:decompress_zip).and_return(true)
            subject.execute
          end

          it "should compress source using zip" do
            expect(subject).to receive(:compress_source_zip)
            subject.execute
          end

          it "should have guest decompress file using zip" do
            expect(guest).to receive(:capability).with(:decompress_zip, any_args)
            subject.execute
          end
        end
      end
    end
  end
end
