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

require "tmpdir"
require_relative "../base"

require "vagrant/bundler"

describe Vagrant::Bundler::SolutionFile do
  let(:plugin_path) { Pathname.new(tmpdir) + "plugin_file" }
  let(:solution_path) { Pathname.new(tmpdir) + "solution_file" }
  let(:tmpdir) { @tmpdir ||= Dir.mktmpdir("vagrant-bundler-test") }
  let(:subject) {
    described_class.new(
      plugin_file: plugin_path,
      solution_file: solution_path
    )
  }

  after do
    if @tmpdir
      FileUtils.rm_rf(@tmpdir)
      @tmpdir = nil
    end
  end

  describe "#initialize" do
    context "file paths" do
      context "with solution_file not provided" do
        let(:subject) { described_class.new(plugin_file: plugin_path) }

        it "should set the plugin_file" do
          expect(subject.plugin_file.to_s).to eq(plugin_path.to_s)
        end

        it "should set solution path to same directory" do
          expect(subject.solution_file.to_s).to eq(plugin_path.to_s + ".sol")
        end
      end

      context "with custom solution_file provided" do
        let(:subject) { described_class.
            new(plugin_file: plugin_path, solution_file: solution_path) }

        it "should set the plugin file path" do
          expect(subject.plugin_file.to_s).to eq(plugin_path.to_s)
        end

        it "should set the solution file path to given value" do
          expect(subject.solution_file.to_s).to eq(solution_path.to_s)
        end
      end
    end

    context "initialization behavior" do
      context "on creation" do
        before { expect_any_instance_of(described_class).to receive(:load) }

        it "should load solution file during initialization" do
          subject
        end
      end

      it "should be invalid by default" do
        expect(subject.valid?).to be_falsey
      end
    end
  end

  describe "#dependency_list=" do
    it "should accept a list of Gem::Dependency instances" do
      list = ["dep1", "dep2"].map{ |x| Gem::Dependency.new(x) }
      subject.dependency_list = list
      expect(subject.dependency_list.map(&:dependency)).to eq(list)
    end

    it "should error if list includes instance not Gem::Dependency" do
      list = ["dep1", "dep2"].map{ |x| Gem::Dependency.new(x) } << :invalid
      expect{ subject.dependency_list = list }.to raise_error(TypeError)
    end

    it "should convert list into resolver dependency request" do
      list = ["dep1", "dep2"].map{ |x| Gem::Dependency.new(x) }
      subject.dependency_list = list
      subject.dependency_list.each do |dep|
        expect(dep).to be_a(Gem::Resolver::DependencyRequest)
      end
    end

    it "should freeze the new dependency list" do
      list = ["dep1", "dep2"].map{ |x| Gem::Dependency.new(x) }
      subject.dependency_list = list
      expect(subject.dependency_list).to be_frozen
    end
  end

  describe "#delete!" do
    context "when file does not exist" do
      before { subject.solution_file.delete if subject.solution_file.exist? }

      it "should return false" do
        expect(subject.delete!).to be_falsey
      end

      it "should not exist" do
        subject.delete!
        expect(subject.solution_file.exist?).to be_falsey
      end
    end

    context "when file does exist" do
      before { subject.solution_file.write('x') }

      it "should return true" do
        expect(subject.delete!).to be_truthy
      end

      it "should not exist" do
        expect(subject.solution_file.exist?).to be_truthy
        subject.delete!
        expect(subject.solution_file.exist?).to be_falsey
      end
    end
  end

  describe "store!" do
    context "when plugin file does not exist" do
      before { subject.plugin_file.delete if subject.plugin_file.exist? }

      it "should return false" do
        expect(subject.store!).to be_falsey
      end

      it "should not create a solution file" do
        subject.store!
        expect(subject.solution_file.exist?).to be_falsey
      end
    end

    context "when plugin file does exist" do
      before { subject.plugin_file.write("x") }

      it "should return true" do
        expect(subject.store!).to be_truthy
      end

      it "should create a solution file" do
        expect(subject.solution_file.exist?).to be_falsey
        subject.store!
        expect(subject.solution_file.exist?).to be_truthy
      end

      context "stored file" do
        let(:content) {
          @content ||= JSON.load(subject.solution_file.read)
        }
        before { subject.store! }
        after { @content = nil }

        it "should store JSON hash" do
          expect(content).to be_a(Hash)
        end

        it "should include dependencies key as array value" do
          expect(content["dependencies"]).to be_a(Array)
        end

        it "should include checksum key as string value" do
          expect(content["checksum"]).to be_a(String)
        end

        it "should include vagrant_version key as string value" do
          expect(content["vagrant_version"]).to be_a(String)
        end

        it "should include vagrant_version key that matches current version" do
          expect(content["vagrant_version"]).to eq(Vagrant::VERSION)
        end
      end
    end
  end

  describe "behavior" do
    context "when storing new solution set" do
      let(:deps) { ["dep1", "dep2"].map{ |n| Gem::Dependency.new(n) } }

      context "when plugin file does not exist" do
        before { subject.solution_file.delete if subject.solution_file.exist? }

        it "should not create a solution file" do
          subject.dependency_list = deps
          subject.store!
          expect(subject.solution_file.exist?).to be_falsey
        end
      end

      context "when plugin file does exist" do
        before { subject.plugin_file.write("x") }

        it "should create a solution file" do
          subject.dependency_list = deps
          subject.store!
          expect(subject.solution_file.exist?).to be_truthy
        end

        it "should update solution file instance to valid" do
          expect(subject.valid?).to be_falsey
          subject.dependency_list = deps
          subject.store!
          expect(subject.valid?).to be_truthy
        end

        context "when solution file does exist" do
          before do
            subject.dependency_list = deps
            subject.store!
          end

          it "should be a valid solution" do
            subject = described_class.new(
              plugin_file: plugin_path,
              solution_file: solution_path
            )
            expect(subject.valid?).to be_truthy
          end

          it "should have expected dependency list" do
            subject = described_class.new(
              plugin_file: plugin_path,
              solution_file: solution_path
            )
            expect(subject.dependency_list).to eq(deps)
          end

          context "when plugin file has been changed" do
            before { subject.plugin_file.write("xy") }

            it "should not be a valid solution" do
              subject = described_class.new(
                plugin_file: plugin_path,
                solution_file: solution_path
              )
              expect(subject.valid?).to be_falsey
            end

            it "should have empty dependency list" do
              subject = described_class.new(
                plugin_file: plugin_path,
                solution_file: solution_path
              )
              expect(subject.dependency_list).to be_empty
            end
          end
        end
      end
    end
  end

  describe "#load" do
    let(:plugin_file_exists) { false }
    let(:solution_file_exists) { false }
    let(:plugin_file_path) { "PLUGIN_FILE_PATH" }
    let(:solution_file_path) { "SOLUTION_FILE_PATH" }
    let(:plugin_file) { double("plugin-file") }
    let(:solution_file) { double("solution-file") }

    subject do
      described_class.new(plugin_file: plugin_file_path, solution_file: solution_file_path)
    end

    before do
      allow(Pathname).to receive(:new).with(plugin_file_path).and_return(plugin_file)
      allow(Pathname).to receive(:new).with(solution_file_path).and_return(solution_file)
      allow(plugin_file).to receive(:exist?).and_return(plugin_file_exists)
      allow(solution_file).to receive(:exist?).and_return(solution_file_exists)
    end

    context "when plugin file and solution file do not exist" do
      it "should not attempt to read the solution" do
        expect_any_instance_of(described_class).not_to receive(:read_solution)
        subject
      end
    end

    context "when plugin file exists and solution file does not" do
      let(:plugin_file_exists) { true }

      it "should not attempt to read the solution" do
        expect_any_instance_of(described_class).not_to receive(:read_solution)
        subject
      end
    end

    context "when solution file exists and plugin file does not" do
      let(:solution_file_exists) { true }

      it "should not attempt to read the solution" do
        expect_any_instance_of(described_class).not_to receive(:read_solution)
        subject
      end
    end

    context "when solution file and plugin file exist" do
      let(:plugin_file_exists) { true }
      let(:solution_file_exists) { true }

      let(:solution_file_contents) { "" }

      before do
        allow(solution_file).to receive(:read).and_return(solution_file_contents)
        allow_any_instance_of(described_class).to receive(:plugin_file_checksum).and_return("VALID")
      end

      context "when solution file is empty" do
        it "should return false" do
          expect(subject.send(:load)).to be_falsey
        end
      end

      context "when solution file contains invalid checksum" do
        let(:solution_file_contents) { {checksum: "INVALID", vagrant_version: Vagrant::VERSION}.to_json }

        it "should return false" do
          expect(subject.send(:load)).to be_falsey
        end
      end

      context "when solution file contains different Vagrant version" do
        let(:solution_file_contents) { {checksum: "VALID", vagrant_version: "0.1"}.to_json }

        it "should return false" do
          expect(subject.send(:load)).to be_falsey
        end
      end

      context "when solution file contains valid Vagrant version and valid checksum" do
        let(:solution_file_contents) {
          {checksum: "VALID", vagrant_version: Vagrant::VERSION, dependencies: file_dependencies}.to_json
        }
        let(:file_dependencies) { dependency_list.map{|d| [d.name, d.requirements_list]} }
        let(:dependency_list) { [] }

        it "should return true" do
          expect(subject.send(:load)).to be_truthy
        end

        it "should be valid" do
          expect(subject).to be_valid
        end

        context "when solution file contains dependency list" do
          let(:dependency_list) { [
            Gem::Dependency.new("dep1", "> 0"),
            Gem::Dependency.new("dep2", "< 3")
          ] }

          it "should be valid" do
            expect(subject).to be_valid
          end

          it "should convert list into dependency requests" do
            subject.dependency_list.each do |d|
              expect(d).to be_a(Gem::Resolver::DependencyRequest)
            end
          end

          it "should include defined dependencies" do
            expect(subject.dependency_list.first).to eq(dependency_list.first)
            expect(subject.dependency_list.last).to eq(dependency_list.last)
          end

          it "should freeze the dependency list" do
            expect(subject.dependency_list).to be_frozen
          end
        end
      end
    end
  end

  describe "#read_solution" do
    let(:solution_file_contents) { "" }
    let(:plugin_file_path) { "PLUGIN_FILE_PATH" }
    let(:solution_file_path) { "SOLUTION_FILE_PATH" }
    let(:plugin_file) { double("plugin-file") }
    let(:solution_file) { double("solution-file") }

    subject do
      described_class.new(plugin_file: plugin_file_path, solution_file: solution_file_path)
    end

    before do
      allow(Pathname).to receive(:new).with(plugin_file_path).and_return(plugin_file)
      allow(Pathname).to receive(:new).with(solution_file_path).and_return(solution_file)
      allow(plugin_file).to receive(:exist?).and_return(false)
      allow(solution_file).to receive(:exist?).and_return(false)
      allow(solution_file).to receive(:read).and_return(solution_file_contents)
    end

    it "should return nil when file contents are empty" do
      expect(subject.send(:read_solution)).to be_nil
    end

    context "when file contents are hash" do
      let(:solution_file_contents) { {checksum: "VALID"}.to_json }

      it "should return a hash" do
        expect(subject.send(:read_solution)).to be_a(Hash)
      end

      it "should return a hash with indifferent access" do
        expect(subject.send(:read_solution)).to be_a(Vagrant::Util::HashWithIndifferentAccess)
      end
    end

    context "when file contents are array" do
      let(:solution_file_contents) { ["test"].to_json }

      it "should return a hash" do
        expect(subject.send(:read_solution)).to be_a(Hash)
      end

      it "should return a hash with indifferent access" do
        expect(subject.send(:read_solution)).to be_a(Vagrant::Util::HashWithIndifferentAccess)
      end
    end

    context "when file contents are null" do
      let(:solution_file_contents) { "null" }

      it "should return nil" do
        expect(subject.send(:read_solution)).to be_nil
      end
    end

    context "when file contents are invalid" do
      let(:solution_file_contents) { "{2dfwef" }

      it "should return nil" do
        expect(subject.send(:read_solution)).to be_nil
      end
    end
  end
end

describe Vagrant::Bundler do
  include_context "unit"

  let(:iso_env) { isolated_environment }
  let(:env) { iso_env.create_vagrant_env }
  let(:tmpdir) { @v_tmpdir ||= Pathname.new(Dir.mktmpdir("vagrant-bundler-test")) }

  before do
    @tmpdir = Dir.mktmpdir("vagrant-bundler-test")
    @vh = ENV["VAGRANT_HOME"]
    ENV["VAGRANT_HOME"] = @tmpdir
  end

  after do
    ENV["VAGRANT_HOME"] = @vh
    FileUtils.rm_rf(@tmpdir)
    FileUtils.rm_rf(@v_tmpdir) if @v_tmpdir
  end

  it "should isolate gem path based on Ruby version" do
    expect(subject.plugin_gem_path.to_s).to end_with(RUBY_VERSION)
  end

  it "should not have an env_plugin_gem_path by default" do
    expect(subject.env_plugin_gem_path).to be_nil
  end

  describe "#initialize" do
    it "should automatically set the plugin gem path" do
      expect(subject.plugin_gem_path).not_to be_nil
    end

    it "should add current ruby version to plugin gem path suffix" do
      expect(subject.plugin_gem_path.to_s).to end_with(RUBY_VERSION)
    end

    it "should freeze the plugin gem path" do
      expect(subject.plugin_gem_path).to be_frozen
    end
  end

  describe "#environment_path=" do
    it "should error if not given Pathname" do
      expect { subject.environment_path = :value }.
        to raise_error(TypeError)
    end

    context "when set with Pathname" do
      let(:env_path) { Pathname.new("/dev/null") }
      before { subject.environment_path = env_path }

      it "should set the environment_data_path" do
        expect(subject.environment_data_path).to eq(env_path)
      end

      it "should set the env_plugin_gem_path" do
        expect(subject.env_plugin_gem_path).not_to be_nil
      end

      it "should suffix current ruby version to env_plugin_gem_path" do
        expect(subject.env_plugin_gem_path.to_s).to end_with(RUBY_VERSION)
      end

      it "should base env_plugin_gem_path on environment_path value" do
        expect(subject.env_plugin_gem_path.to_s).to start_with(env_path.to_s)
      end

      it "should freeze the env_plugin_gem_path" do
        expect(subject.env_plugin_gem_path).to be_frozen
      end
    end
  end

  describe "#load_solution_file" do
    let(:local_opt) { nil }
    let(:global_opt) { nil }
    let(:options) { {local: local_opt, global: global_opt} }

    it "should return nil when local and global options are blank" do
      expect(subject.load_solution_file(options)).to be_nil
    end

    context "when environment data path is set" do
      let(:env_path) { "/dev/null" }
      before { subject.environment_path = Pathname.new(env_path) }

      context "when local option is set" do
        let(:local_opt) { tmpdir + "local" }

        it "should return a SolutionFile instance" do
          expect(subject.load_solution_file(options)).to be_a(Vagrant::Bundler::SolutionFile)
        end

        it "should be located in the environment data path" do
          file = subject.load_solution_file(options)
          expect(file.solution_file.to_s).to start_with(env_path)
        end

        it "should have a local.sol solution file" do
          file = subject.load_solution_file(options)
          expect(file.solution_file.to_s).to end_with("local.sol")
        end

        it "should have plugin file set to local value" do
          file = subject.load_solution_file(options)
          expect(file.plugin_file.to_s).to eq(local_opt.to_s)
        end
      end

      context "when global option is set" do
        let(:global_opt) { tmpdir + "global" }

        it "should return a SolutionFile instance" do
          expect(subject.load_solution_file(options)).to be_a(Vagrant::Bundler::SolutionFile)
        end

        it "should be located in the environment data path" do
          file = subject.load_solution_file(options)
          expect(file.solution_file.to_s).to start_with(env_path)
        end

        it "should have a global.sol solution file" do
          file = subject.load_solution_file(options)
          expect(file.solution_file.to_s).to end_with("global.sol")
        end

        it "should have plugin file set to global value" do
          file = subject.load_solution_file(options)
          expect(file.plugin_file.to_s).to eq(global_opt.to_s)
        end
      end

      context "when local and global option is set" do
        let(:global_opt) { tmpdir + "global" }
        let(:local_opt) { tmpdir + "local" }

        it "should return nil" do
          expect(subject.load_solution_file(options)).to be_nil
        end
      end
    end

    context "when environment data path is unset" do
      context "when local option is set" do
        let(:local_opt) { tmpdir + "local" }

        it "should return nil" do
          expect(subject.load_solution_file(options)).to be_nil
        end
      end

      context "when global option is set" do
        let(:global_opt) { tmpdir + "global" }

        it "should return a SolutionFile instance" do
          expect(subject.load_solution_file(options)).to be_a(Vagrant::Bundler::SolutionFile)
        end

        it "should be located in the vagrant user data path" do
          file = subject.load_solution_file(options)
          expect(file.solution_file.to_s).to start_with(Vagrant.user_data_path.to_s)
        end

        it "should have a global.sol solution file" do
          file = subject.load_solution_file(options)
          expect(file.solution_file.to_s).to end_with("global.sol")
        end

        it "should have plugin file set to global value" do
          file = subject.load_solution_file(options)
          expect(file.plugin_file.to_s).to eq(global_opt.to_s)
        end
      end
    end
  end

  describe "#deinit" do
    it "should provide method for backwards compatibility" do
      subject.deinit
    end
  end

  describe "DEFAULT_GEM_SOURCES" do
    it "should list hashicorp gemstore first" do
      expect(described_class.const_get(:DEFAULT_GEM_SOURCES).first).to eq(
        described_class.const_get(:HASHICORP_GEMSTORE))
    end
  end

  describe "#init!" do
    context "Gem.sources" do
      before {
        Gem.sources.clear
        Gem.sources << "https://rubygems.org/" }

      it "should add hashicorp gem store" do
        subject.init!([])
        expect(Gem.sources).to include(described_class.const_get(:HASHICORP_GEMSTORE))
      end

      it "should add hashicorp gem store to start of sources list" do
        subject.init!([])
        expect(Gem.sources.sources.first.uri.to_s).to eq(described_class.const_get(:HASHICORP_GEMSTORE))
      end
    end

    context "multiple specs" do
      let(:solution_file) { double('solution_file') }
      let(:vagrant_set)   { double('vagrant_set') }

      before do
        allow(subject).to receive(:load_solution_file).and_return(solution_file)
        allow(subject).to receive(:generate_vagrant_set).and_return(vagrant_set)
        allow(solution_file).to receive(:valid?).and_return(true)
      end

      it "should activate spec of deps already loaded" do
        spec = Gem.loaded_specs.first
        deps = [spec[0]]
        specs = [spec[1].dup, spec[1].dup]
        specs[0].version = Gem::Version::new('0.0.1')
        # make sure haven't accidentally modified both
        expect(specs[0].version).to_not eq(specs[1].version)

        expect(solution_file).to receive(:dependency_list).and_return(deps)
        expect(vagrant_set).to receive(:find_all).and_return(specs)
        expect(subject).to receive(:activate_solution) do |activate_specs|
          expect(activate_specs.length()).to eq(1)
          expect(activate_specs[0].full_spec()).to eq(specs[1])
        end
        subject.init!([])
      end
    end
  end

  describe "#install" do
    let(:plugins){ {"my-plugin" => {"gem_version" => "> 0"}} }

    it "should pass plugin information hash to internal install" do
      expect(subject).to receive(:internal_install).with(plugins, any_args)
      subject.install(plugins)
    end

    it "should not include any update plugins" do
      expect(subject).to receive(:internal_install).with(anything, nil, any_args)
      subject.install(plugins)
    end

    it "should flag local when local is true" do
      expect(subject).to receive(:internal_install).with(any_args, env_local: true)
      subject.install(plugins, true)
    end

    it "should not flag local when local is not set" do
      expect(subject).to receive(:internal_install).with(any_args, env_local: false)
      subject.install(plugins)
    end
  end

  describe "#install_local" do
    let(:plugin_source){ double("plugin_source", spec: plugin_spec) }
    let(:plugin_spec){ double("plugin_spec", name: plugin_name, version: plugin_version) }
    let(:plugin_name){ "PLUGIN_NAME" }
    let(:plugin_version){ "1.0.0" }
    let(:plugin_path){ "PLUGIN_PATH" }
    let(:sources){ "SOURCES" }

    before do
      allow(Gem::Source::SpecificFile).to receive(:new).and_return(plugin_source)
      allow(subject).to receive(:internal_install)
    end

    it "should return plugin gem specification" do
      expect(subject.install_local(plugin_path)).to eq(plugin_spec)
    end

    it "should set custom sources" do
      expect(subject).to receive(:internal_install) do |info, update, opts|
        expect(info[plugin_name]["sources"]).to eq(sources)
      end
      subject.install_local(plugin_path, sources: sources)
    end

    it "should not set the update parameter" do
      expect(subject).to receive(:internal_install) do |info, update, opts|
        expect(update).to be_nil
      end
      subject.install_local(plugin_path)
    end

    it "should not set plugin as environment local by default" do
      expect(subject).to receive(:internal_install) do |info, update, opts|
        expect(opts[:env_local]).to be_falsey
      end
      subject.install_local(plugin_path)
    end

    it "should set if plugin is environment local" do
      expect(subject).to receive(:internal_install) do |info, update, opts|
        expect(opts[:env_local]).to be_truthy
      end
      subject.install_local(plugin_path, env_local: true)
    end
  end

  describe "#update" do
    let(:plugins){ :plugins }
    let(:specific){ [] }

    after{ subject.update(plugins, specific) }

    it "should mark update as true" do
      expect(subject).to receive(:internal_install) do |info, update, opts|
        expect(update).to be_truthy
      end
    end

    context "with specific plugins named" do
      let(:specific){ ["PLUGIN_NAME"] }

      it "should set update to specific names" do
        expect(subject).to receive(:internal_install) do |info, update, opts|
          expect(update[:gems]).to eq(specific)
        end
      end
    end
  end

  describe "#vagrant_internal_specs" do
    let(:vagrant_spec) { double("vagrant_spec", name: "vagrant", version: Gem::Version.new(Vagrant::VERSION),
      activated?: vagrant_spec_activated, activate: nil, runtime_dependencies: vagrant_dep_specs) }
    let(:spec_list) { [] }
    let(:spec_dirs) { [] }
    let(:spec_default_dir) { "/dev/null" }
    let(:dir_spec_list) { [] }
    let(:vagrant_spec_activated) { true }
    let(:vagrant_dep_specs) { [] }

    before do
      allow(Gem::Specification).to receive(:find) { |&b| vagrant_spec if b.call(vagrant_spec) }
      allow(Gem::Specification).to receive(:find_all).and_return(spec_list)
      allow(Gem::Specification).to receive(:dirs).and_return(spec_dirs)
      allow(Gem::Specification).to receive(:default_specifications_dir).and_return(spec_default_dir)
      allow(Gem::Specification).to receive(:each_spec).and_return(dir_spec_list)
    end

    it "should return an empty list" do
      expect(subject.send(:vagrant_internal_specs)).to eq([])
    end

    context "when vagrant specification is not activated" do
      let(:vagrant_spec_activated) { false }

      it "should activate the specification" do
        expect(vagrant_spec).to receive(:activate)
        subject.send(:vagrant_internal_specs)
      end
    end

    context "when vagrant specification is not found" do
      before { allow(Gem::Specification).to receive(:find).and_return(nil) }

      it "should raise not found error" do
        expect { subject.send(:vagrant_internal_specs) }.to raise_error(Vagrant::Errors::SourceSpecNotFound)
      end
    end

    context "when bundler is not defined" do
      before { expect(Vagrant).to receive(:in_bundler?).and_return(false) }

      context "when running inside the installer" do
        before { expect(Vagrant).to receive(:in_installer?).and_return(true) }

        it "should load gem specification directories" do
          expect(Gem::Specification).to receive(:dirs).and_return(spec_dirs)
          subject.send(:vagrant_internal_specs)
        end

        context "when checking paths" do
          let(:spec_dirs) { [double("spec-dir", start_with?: in_user_dir)] }
          let(:in_user_dir) { true }
          let(:user_dir) { double("user-dir") }

          before { allow(Gem).to receive(:user_dir).and_return(user_dir) }

          it "should check if path is within local user directory" do
            expect(spec_dirs.first).to receive(:start_with?).with(user_dir).and_return(false)
            subject.send(:vagrant_internal_specs)
          end

          context "when path is not within user directory" do
            let(:in_user_dir) { false }

            it "should use path when loading specs" do
              expect(Gem::Specification).to receive(:each_spec) { |arg| expect(arg).to include(spec_dirs.first) }
              subject.send(:vagrant_internal_specs)
            end
          end
        end
      end

      context "when running outside the installer" do
        before { expect(Vagrant).to receive(:in_installer?).and_return(false) }

        it "should not load gem specification directories" do
          expect(Gem::Specification).not_to receive(:dirs)
          subject.send(:vagrant_internal_specs)
        end
      end
    end
  end

  describe Vagrant::Bundler::PluginSet do
    let(:name) { "test-gem" }
    let(:version) { "1.0.0" }
    let(:directory) { @directory ||= Dir.mktmpdir("vagrant-bundler-test") }

    after do
      FileUtils.rm_rf(@directory) if @directory
      @directory = nil
    end

    describe "#add_vendor_gem" do
      context "when spec file does not exist" do
        it "should raise a not found error" do
          expect { subject.add_vendor_gem(name, directory) }.to raise_error(Gem::GemNotFoundException)
        end
      end

      context "when spec file exists" do
        before do
          spec = Gem::Specification.new(name, version)
          File.write(File.join(directory, "#{name}.gemspec"), spec.to_ruby)
        end

        it "should load the specification" do
          expect(subject.add_vendor_gem(name, directory)).to be_a(Gem::Specification)
        end

        it "should set the full path in specification" do
          spec = subject.add_vendor_gem(name, directory)
          expect(spec.full_gem_path).to eq(directory)
        end
      end
    end

    describe "#find_all" do
      let(:request) { Gem::Resolver::DependencyRequest.new(dependency, nil) }
      let(:dependency) { Gem::Dependency.new("test-gem", requirement) }
      let(:requirement) { Gem::Requirement.new(version) }

      context "when specification is not included in set" do
        it "should return empty array" do
          expect(subject.find_all(request)).to eq([])
        end
      end

      context "when specification is included in set" do
        before do
          spec = Gem::Specification.new(name, version)
          File.write(File.join(directory, "#{name}.gemspec"), spec.to_ruby)
          subject.add_vendor_gem(name, directory)
        end

        it "should return a vendor specification instance" do
          expect(subject.find_all(request).first).to be_a(Gem::Resolver::VendorSpecification)
        end
      end
    end
  end
end
