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

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

describe Vagrant::Action::Builtin::Lock do
  let(:app) { lambda { |env| } }
  let(:env) { {} }
  let(:lock_path) do
    Dir::Tmpname.create("vagrant-test-lock") {}
  end

  let(:options) do
    {
      exception: Class.new(StandardError),
      path:      lock_path
    }
  end

  after do
    File.unlink(lock_path) if File.file?(lock_path)
  end

  it "should require a path" do
    expect { described_class.new(app, env) }.
      to raise_error(ArgumentError)

    expect { described_class.new(app, env, path: "foo") }.
      to raise_error(ArgumentError)

    expect { described_class.new(app, env, exception: "foo") }.
      to raise_error(ArgumentError)

    expect { described_class.new(app, env, path: "bar", exception: "foo") }.
      to_not raise_error
  end

  it "should allow the path to be a proc" do
    inner_acquire = true
    app = lambda do |env|
      File.open(lock_path, "w+") do |f|
        inner_acquire = f.flock(File::LOCK_EX | File::LOCK_NB)
      end
    end

    options[:path] = lambda { |env| lock_path }

    instance = described_class.new(app, env, options)
    instance.call(env)

    expect(inner_acquire).to eq(false)
  end

  it "should allow the exception to be a proc" do
    exception = options[:exception]
    options[:exception] = lambda { |env| exception }

    File.open(lock_path, "w+") do |f|
      # Acquire lock
      expect(f.flock(File::LOCK_EX | File::LOCK_NB)).to eq(0)

      # Test!
      instance = described_class.new(app, env, options)
      expect { instance.call(env) }.
        to raise_error(exception)
    end
  end

  it "should call the middleware with the lock held" do
    inner_acquire = true
    app = lambda do |env|
      File.open(lock_path, "w+") do |f|
        inner_acquire = f.flock(File::LOCK_EX | File::LOCK_NB)
      end
    end

    instance = described_class.new(app, env, options)
    instance.call(env)

    expect(inner_acquire).to eq(false)
  end

  it "should raise an exception if the lock is already held" do
    File.open(lock_path, "w+") do |f|
      # Acquire lock
      expect(f.flock(File::LOCK_EX | File::LOCK_NB)).to eq(0)

      # Test!
      instance = described_class.new(app, env, options)
      expect { instance.call(env) }.
        to raise_error(options[:exception])
    end
  end

  it "should allow nesting locks within the same middleware sequence" do
    called = false
    app = lambda { |env| called = true }
    inner = described_class.new(app, env, options)
    outer = described_class.new(inner, env, options)
    outer.call(env)

    expect(called).to eq(true)
  end
end
