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

require_relative "../../../base"
require "fake_ftp"

require Vagrant.source_root.join("plugins/pushes/ftp/push")

describe VagrantPlugins::FTPPush::Push do
  include_context "unit"

  let(:env) { isolated_environment }
  let(:config) do
    double("config",
      host:        "127.0.0.1:#{@port}",
      username:    "sethvargo",
      password:    "bacon",
      passive:     false,
      secure:      false,
      destination: "/var/www/site",
    )
  end
  let(:ui) { Vagrant::UI::Silent.new }

  subject { described_class.new(env, config) }

  before do
    allow(env).to receive(:root_path)
      .and_return(File.expand_path("..", __FILE__))
    allow(env).to receive(:ui)
      .and_return(ui)
  end

  describe "#push" do
    before(:all) do
      @server = nil
      with_random_port do |port1, port2|
        @port = port1
        @server = FakeFtp::Server.new(port1, port2)
      end
      @server.start

      @dir = Dir.mktmpdir("vagrant-ftp-push")
      FileUtils.touch("#{@dir}/.hidden.rb")
      FileUtils.touch("#{@dir}/application.rb")
      FileUtils.touch("#{@dir}/config.rb")
      FileUtils.touch("#{@dir}/Gemfile")
      FileUtils.touch("#{@dir}/data.txt")
      FileUtils.mkdir("#{@dir}/empty_folder")
    end

    after(:all) do
      FileUtils.rm_rf(@dir)
      @server.stop
    end

    let(:server) { @server }

    before do
      allow(config).to receive(:dir)
        .and_return(@dir)

      allow(config).to receive(:includes)
        .and_return([])

      allow(config).to receive(:excludes)
        .and_return(%w(*.rb))
    end

    after do
      server.reset
    end

    it "pushes the files to the server" do
      subject.push
      expect(server.files).to eq(%w[/var/www/site/Gemfile /var/www/site/data.txt])
    end

    it "raises informative exception when too many files to process" do
      expect(subject).to receive(:all_files).and_raise(SystemStackError)
      expect{ subject.push }.to raise_error(VagrantPlugins::FTPPush::Errors::TooManyFiles)
    end

    context "when VAGRANT_CWD is set to something relative" do
      # this will be the PWD for the test context
      let(:pwd) { Pathname.new(Dir.mktmpdir("vagrant-ftp-push-pwd")) }

      before do
        # this path should have a ../ in it since the pwd is another temp dir
        # and measuring path from one to the other. they're most likely to be
        # siblings
        relative_path = Pathname.new(@dir).relative_path_from(pwd)
        allow(env).to receive(:root_path) { relative_path.to_s }
        # reset config.dir to its default since it was set absolute above
        allow(config).to receive(:dir) { "." }
      end

      it "properly paths out the files to upload" do
        Vagrant::Util::SafeChdir.safe_chdir(pwd) do
          subject.push
        end

        expect(server.files).to eq(%w(/var/www/site/Gemfile /var/www/site/data.txt))
      ensure
        FileUtils.rm_rf(pwd)
      end
    end
  end

  describe "#connect" do
    before do
      allow_any_instance_of(VagrantPlugins::FTPPush::FTPAdapter)
        .to receive(:connect)
        .and_yield(:ftp)
      allow_any_instance_of(VagrantPlugins::FTPPush::SFTPAdapter)
        .to receive(:connect)
        .and_yield(:sftp)
    end

    context "when secure is requested" do
      before do
        allow(config).to receive(:secure)
          .and_return(true)
      end

      it "yields a new SFTPAdapter" do
        expect { |b| subject.connect(&b) }.to yield_with_args(:sftp)
      end
    end

    context "when secure is not requested" do
      before do
        allow(config).to receive(:secure)
          .and_return(false)
      end

      it "yields a new FTPAdapter" do
        expect { |b| subject.connect(&b) }.to yield_with_args(:ftp)
      end
    end
  end

  describe "#all_files" do
    before(:all) do
      @dir = Dir.mktmpdir("vagrant-ftp-push-push-all-files")

      FileUtils.touch("#{@dir}/.hidden.rb")
      FileUtils.touch("#{@dir}/application.rb")
      FileUtils.touch("#{@dir}/config.rb")
      FileUtils.touch("#{@dir}/Gemfile")
      FileUtils.mkdir("#{@dir}/empty_folder")
      FileUtils.mkdir("#{@dir}/folder")
      FileUtils.mkdir("#{@dir}/folder/.git")
      FileUtils.touch("#{@dir}/folder/.git/config")
      FileUtils.touch("#{@dir}/folder/server.rb")
    end

    after(:all) do
      FileUtils.rm_rf(@dir)
    end

    let(:files) do
      subject.all_files.map do |file|
        file.sub("#{@dir}/", "")
      end
    end

    before do
      allow(config).to receive(:dir)
        .and_return(@dir)

      allow(config).to receive(:includes)
        .and_return(%w(not_a_file.rb still_not_a_file.rb))

      allow(config).to receive(:excludes)
        .and_return(%w(*.rb))
    end

    it "returns the list of real files + includes, without excludes" do
      expect(files).to eq(%w(
        Gemfile
        folder/.git/config
      ))
    end
  end

  describe "#includes_files" do
    before(:all) do
      @dir = Dir.mktmpdir("vagrant-ftp-push-includes-files")

      FileUtils.touch("#{@dir}/.hidden.rb")
      FileUtils.touch("#{@dir}/application.rb")
      FileUtils.touch("#{@dir}/config.rb")
      FileUtils.touch("#{@dir}/Gemfile")
      FileUtils.mkdir("#{@dir}/folder")
      FileUtils.mkdir("#{@dir}/folder/.git")
      FileUtils.touch("#{@dir}/folder/.git/config")
      FileUtils.touch("#{@dir}/folder/server.rb")
    end

    after(:all) do
      FileUtils.rm_rf(@dir)
    end

    let(:files) do
      subject.includes_files.map do |file|
        file.sub("#{@dir}/", "")
      end
    end

    before do
      allow(config).to receive(:dir)
        .and_return(@dir)
    end

    def set_includes(value)
      allow(config).to receive(:includes)
        .and_return(value)
    end

    it "includes the file" do
      set_includes(["Gemfile"])
      expect(files).to eq(%w(
        Gemfile
      ))
    end

    it "includes the files that are subdirectories" do
      set_includes(["folder"])
      expect(files).to eq(%w(
        folder
        folder/.git
        folder/.git/config
        folder/server.rb
      ))
    end

    it "includes files that match a pattern" do
      set_includes(["*.rb"])
      expect(files).to eq(%w(
        .hidden.rb
        application.rb
        config.rb
      ))
    end
  end

  describe "#filter_excludes" do
    let(:dir) { "/root/dir" }

    let(:list) do
      %W(
        #{dir}/.hidden.rb
        #{dir}/application.rb
        #{dir}/config.rb
        #{dir}/Gemfile
        #{dir}/folder
        #{dir}/folder/.git
        #{dir}/folder/.git/config
        #{dir}/folder/server.rb

        /path/outside/you.rb
        /path/outside/me.rb
        /path/outside/folder/bacon.rb
      )
    end

    before do
      allow(config).to receive(:dir)
        .and_return(dir)
    end

    it "excludes files" do
      subject.filter_excludes!(list, %w(*.rb))

      expect(list).to eq(%W(
        #{dir}/Gemfile
        #{dir}/folder
        #{dir}/folder/.git
        #{dir}/folder/.git/config
      ))
    end

    it "excludes files in a directory" do
      subject.filter_excludes!(list, %w(folder))

      expect(list).to eq(%W(
        #{dir}/.hidden.rb
        #{dir}/application.rb
        #{dir}/config.rb
        #{dir}/Gemfile

        /path/outside/you.rb
        /path/outside/me.rb
        /path/outside/folder/bacon.rb
      ))
    end

    it "excludes specific files in a directory" do
      subject.filter_excludes!(list, %w(/path/outside/folder/*.rb))

      expect(list).to eq(%W(
        #{dir}/.hidden.rb
        #{dir}/application.rb
        #{dir}/config.rb
        #{dir}/Gemfile
        #{dir}/folder
        #{dir}/folder/.git
        #{dir}/folder/.git/config
        #{dir}/folder/server.rb

        /path/outside/you.rb
        /path/outside/me.rb
      ))
    end

    it "excludes files outside the #dir" do
      subject.filter_excludes!(list, %w(/path/outside))

      expect(list).to eq(%W(
        #{dir}/.hidden.rb
        #{dir}/application.rb
        #{dir}/config.rb
        #{dir}/Gemfile
        #{dir}/folder
        #{dir}/folder/.git
        #{dir}/folder/.git/config
        #{dir}/folder/server.rb
      ))
    end
  end
end
