require 'spec_helper'
require 'puppet/module_tool'
require 'tmpdir'
require 'puppet_spec/module_tool/shared_functions'
require 'puppet_spec/module_tool/stub_source'

describe Puppet::ModuleTool::Applications::Uninstaller do
  include PuppetSpec::ModuleTool::SharedFunctions
  include PuppetSpec::Files

  before do
    FileUtils.mkdir_p(primary_dir)
    FileUtils.mkdir_p(secondary_dir)
  end

  let(:environment) do
    Puppet.lookup(:current_environment).override_with(
      :vardir     => vardir,
      :modulepath => [ primary_dir, secondary_dir ]
    )
  end

  let(:vardir)   { tmpdir('uninstaller') }
  let(:primary_dir) { File.join(vardir, "primary") }
  let(:secondary_dir) { File.join(vardir, "secondary") }
  let(:remote_source) { PuppetSpec::ModuleTool::StubSource.new }

  let(:module) { 'module-not_installed' }
  let(:application) do
    opts = options
    Puppet::ModuleTool.set_option_defaults(opts)
    Puppet::ModuleTool::Applications::Uninstaller.new(self.module, opts)
  end

  def options
    { :environment => environment }
  end

  subject { application.run }

  context "when the module is not installed" do
    it "should fail" do
      expect(subject).to include :result => :failure
    end
  end

  context "when the module is installed" do
    let(:module) { 'pmtacceptance-stdlib' }

    before { preinstall('pmtacceptance-stdlib', '1.0.0') }
    before { preinstall('pmtacceptance-apache', '0.0.4') }

    it "should uninstall the module" do
      expect(subject[:affected_modules].first.forge_name).to eq("pmtacceptance/stdlib")
    end

    it "should only uninstall the requested module" do
      subject[:affected_modules].length == 1
    end

    it 'should refuse to uninstall in FIPS mode' do
      allow(Facter).to receive(:value).with(:fips_enabled).and_return(true)
      err = subject[:error][:oneline]
      expect(err).to eq("Either the `--ignore_changes` or `--force` argument must be specified to uninstall modules when running in FIPS mode.")
    end

    context 'in two modulepaths' do
      before { preinstall('pmtacceptance-stdlib', '2.0.0', :into => secondary_dir) }

      it "should fail if a module exists twice in the modpath" do
        expect(subject).to include :result => :failure
      end
    end

    context "when options[:version] is specified" do
      def options
        super.merge(:version => '1.0.0')
      end

      it "should uninstall the module if the version matches" do
        expect(subject[:affected_modules].length).to eq(1)
        expect(subject[:affected_modules].first.version).to eq("1.0.0")
      end

      context 'but not matched' do
        def options
          super.merge(:version => '2.0.0')
        end

        it "should not uninstall the module if the version does not match" do
          expect(subject).to include :result => :failure
        end
      end
    end

    context "when the module metadata is missing" do
      before { File.unlink(File.join(primary_dir, 'stdlib', 'metadata.json')) }

      it "should not uninstall the module" do
        expect(application.run[:result]).to eq(:failure)
      end
    end

    context "when the module has local changes" do
      before do
        mark_changed(File.join(primary_dir, 'stdlib'))
      end

      it "should not uninstall the module" do
        expect(subject).to include :result => :failure
      end
    end

    context "when uninstalling the module will cause broken dependencies" do
      before { preinstall('pmtacceptance-apache', '0.10.0') }

      it "should not uninstall the module" do
        expect(subject).to include :result => :failure
      end
    end

    context 'with --ignore-changes' do
      def options
        super.merge(:ignore_changes => true)
      end

      context 'with local changes' do
        before do
          mark_changed(File.join(primary_dir, 'stdlib'))
        end

        it 'overwrites the installed module with the greatest version matching that range' do
          expect(subject).to include :result => :success
        end

        it 'uninstalls in FIPS mode' do
          allow(Facter).to receive(:value).with(:fips_enabled).and_return(true)
          expect(subject).to include :result => :success
        end
      end

      context 'without local changes' do
        it 'overwrites the installed module with the greatest version matching that range' do
          expect(subject).to include :result => :success
        end
      end
    end

    context "when using the --force flag" do

      def options
        super.merge(:force => true)
      end

      context "with local changes" do
        before do
          mark_changed(File.join(primary_dir, 'stdlib'))
        end

        it "should ignore local changes" do
          expect(subject[:affected_modules].length).to eq(1)
          expect(subject[:affected_modules].first.forge_name).to eq("pmtacceptance/stdlib")
        end

        it 'uninstalls in FIPS mode' do
          allow(Facter).to receive(:value).with(:fips_enabled).and_return(true)
          expect(subject).to include :result => :success
        end
      end

      context "while depended upon" do
        before { preinstall('pmtacceptance-apache', '0.10.0') }

        it "should ignore broken dependencies" do
          expect(subject[:affected_modules].length).to eq(1)
          expect(subject[:affected_modules].first.forge_name).to eq("pmtacceptance/stdlib")
        end
      end
    end
  end
end
