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

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

describe Vagrant::Action::Warden do
  class ActionOne
    def initialize(app, env)
      @app = app
    end

    def call(env)
      env[:data] << 1 if env[:data]
      @app.call(env)
    end

    def recover(env)
      env[:recover] << 1
    end
  end

  class ActionTwo
    def initialize(app, env)
      @app = app
    end

    def call(env)
      env[:data] << 2 if env[:data]
      @app.call(env)
    end

    def recover(env)
      env[:recover] << 2
    end
  end

  class ExitAction
    def initialize(app, env)
      @app = app
    end

    def call(env)
      @app.call(env)
    end

    def recover(env)
      env[:recover] = true
    end
  end

  let(:data) { { data: [] } }
  let(:instance) { described_class.new }

  # This returns a proc that can be used with the builder
  # that simply appends data to an array in the env.
  def appender_proc(data)
    Proc.new { |env| env[:data] << data }
  end

  it "calls the actions like normal" do
    instance = described_class.new([appender_proc(1), appender_proc(2)], data)
    instance.call(data)

    expect(data[:data]).to eq([1, 2])
  end

  it "starts a recovery sequence when an exception is raised" do
    error_proc = Proc.new { raise "ERROR!" }

    data     = { recover: [] }
    instance = described_class.new([ActionOne, ActionTwo, error_proc], data)

    # The error should be raised back up
    expect { instance.call(data) }.
      to raise_error(RuntimeError)

    # Verify the recovery process goes in reverse order
    expect(data[:recover]).to eq([2, 1])

    # Verify that the error is available in the data
    expect(data["vagrant.error"]).to be_kind_of(RuntimeError)
  end

  it "does not do a recovery sequence if SystemExit is raised" do
    # Make a proc that just calls "abort" which raises a
    # SystemExit exception.
    error_proc = Proc.new { abort }

    instance = described_class.new([ExitAction, error_proc], data)

    # The SystemExit should come through
    expect { instance.call(data) }.to raise_error(SystemExit)

    # The recover should not have been called
    expect(data.key?(:recover)).not_to be
  end

  it "does not do a recovery sequence if NoMemoryError is raised" do
    error_proc = Proc.new { raise NoMemoryError }

    instance = described_class.new([ExitAction, error_proc], data)

    # The SystemExit should come through
    expect { instance.call(data) }.to raise_error(NoMemoryError)

    # The recover should not have been called
    expect(data.key?(:recover)).not_to be
  end
end
