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

require File.expand_path("../../../../base", __FILE__)

require Vagrant.source_root.join("plugins/kernel_v2/config/vm")

describe Vagrant::Action::Builtin::Provision do
  include_context "unit"

  let(:app) { lambda { |env| } }
  let(:env) {
    { machine: machine, ui: ui, hook: hook, provision_ignore_sentinel: false }
  }
  let(:hook){ double("hook") }

  let(:machine) do
    double("machine").tap do |machine|
      allow(machine).to receive(:id).and_return('machine-id')
      allow(machine).to receive(:data_dir).and_return(data_dir)
      allow(machine).to receive(:config).and_return(machine_config)
      allow(machine).to receive(:env).and_return(machine_env)
    end
  end

  let(:machine_config) do
    double("machine_config").tap do |config|
      allow(config).to receive(:vm).and_return(vm_config)
    end
  end

  let(:data_dir){ temporary_dir }

  let(:machine_env) do
    isolated_environment.tap do |i_env|
      allow(i_env).to receive(:data_dir).and_return(data_dir)
      allow(i_env).to receive(:lock).and_yield
    end
  end

  let(:vm_config) do
    double("machine_vm_config").tap do |config|
      allow(config).to receive(:provisioners).and_return([])
    end
  end

  let(:ui) { Vagrant::UI::Silent.new }

  let(:instance){ described_class.new(app, env) }

  describe "#call" do
    context "with no provisioners defined" do
      it "should process empty set of provisioners" do
        expect(instance.call(env)).to eq([])
      end

      context "with provisioning disabled" do
        before{ env[:provision_enabled] = false }
        after{ env.delete(:provision_enabled) }

        it "should not process any provisioners" do
          expect(instance.call(env)).to be_nil
        end
      end
    end

    context "with single provisioner defined" do
      let(:provisioner) do
        prov = VagrantPlugins::Kernel_V2::VagrantConfigProvisioner.new("spec-test", :shell)
        prov.config = provisioner_config
        prov
      end
      let(:provisioner_config){ double("provisioner_config", name: "spec-test") }

      before{ expect(vm_config).to receive(:provisioners).and_return([provisioner]) }

      it "should call the defined provisioner" do
        expect(hook).to receive(:call).with(:provisioner_run, anything)
        instance.call(env)
      end

      context "with provisioning disabled" do
        before{ env[:provision_enabled] = false }
        after{ env.delete(:provision_enabled) }

        it "should not process any provisioners" do
          expect(hook).not_to receive(:call).with(:provisioner_run, anything)
          expect(instance.call(env)).to be_nil
        end
      end

      context "with provisioner configured to run once" do
        before{ provisioner.run = :once }

        it "should run if machine is not provisioned" do
          expect(hook).to receive(:call).with(:provisioner_run, anything)
          instance.call(env)
        end

        it "should not run if machine is provisioned" do
          File.open(File.join(data_dir.to_s, "action_provision"), "w") do |file|
            file.write("1.5:machine-id")
          end
          expect(hook).not_to receive(:call).with(:provisioner_run, anything)
          instance.call(env)
        end

        it "should not run if provision types are set and provisioner is not included" do
          env[:provision_types] = [:"other-provisioner", :"other-test"]
          expect(hook).not_to receive(:call).with(:provisioner_run, anything)
          instance.call(env)
        end

        it "should run if provision types are set and include provisioner name" do
          env[:provision_types] = [:"spec-test"]
          expect(hook).to receive(:call).with(:provisioner_run, anything)
          instance.call(env)
        end

        it "should run if provision types are set and include provisioner type" do
          env[:provision_types] = [:shell]
          expect(hook).to receive(:call).with(:provisioner_run, anything)
          instance.call(env)
        end
      end

      context "with provisioner configured to run always" do
        before{ provisioner.run = :always }

        it "should run if machine is not provisioned" do
          expect(hook).to receive(:call).with(:provisioner_run, anything)
          instance.call(env)
        end

        it "should run if machine is provisioned" do
          File.open(File.join(data_dir.to_s, "action_provision"), "w") do |file|
            file.write("1.5:machine-id")
          end
          expect(hook).to receive(:call).with(:provisioner_run, anything)
          instance.call(env)
        end

        it "should not run if provision types are set and provisioner is not included" do
          env[:provision_types] = [:"other-provisioner", :"other-test"]
          expect(hook).not_to receive(:call).with(:provisioner_run, anything)
          instance.call(env)
        end

        it "should run if provision types are set and include provisioner name" do
          env[:provision_types] = [:"spec-test"]
          expect(hook).to receive(:call).with(:provisioner_run, anything)
          instance.call(env)
        end

        it "should run if provision types are set and include provisioner type" do
          env[:provision_types] = [:shell]
          expect(hook).to receive(:call).with(:provisioner_run, anything)
          instance.call(env)
        end
      end

      context "with provisioner configured to never run" do
        before{ provisioner.run = :never }

        it "should not run if machine is not provisioned" do
          expect(hook).not_to receive(:call).with(:provisioner_run, anything)
          instance.call(env)
        end

        it "should not run if machine is provisioned" do
          File.open(File.join(data_dir.to_s, "action_provision"), "w") do |file|
            file.write("1.5:machine-id")
          end
          expect(hook).not_to receive(:call).with(:provisioner_run, anything)
          instance.call(env)
        end

        it "should not run if provision types are set and provisioner is not included" do
          env[:provision_types] = [:"other-provisioner", :"other-test"]
          expect(hook).not_to receive(:call).with(:provisioner_run, anything)
          instance.call(env)
        end

        it "should run if provision types are set and include provisioner name" do
          env[:provision_types] = [:"spec-test"]
          expect(hook).to receive(:call).with(:provisioner_run, anything)
          instance.call(env)
        end

        it "should run if provision types are set and include provisioner name and machine is provisioned" do
          File.open(File.join(data_dir.to_s, "action_provision"), "w") do |file|
            file.write("1.5:machine-id")
          end
          env[:provision_types] = [:"spec-test"]
          expect(hook).to receive(:call).with(:provisioner_run, anything)
          instance.call(env)
        end

        it "should not run if provision types are set and include provisioner type" do
          env[:provision_types] = [:shell]
          expect(hook).not_to receive(:call).with(:provisioner_run, anything)
          instance.call(env)
        end
      end
    end
  end
end
