require File.expand_path("../../../base", __FILE__) describe Vagrant::Action::Builder do let(:data) { { data: [] } } # 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) result = Proc.new { |env| env[:data] << data } # Define a to_s on it for helpful output result.define_singleton_method(:to_s) do "" end result end def wrapper_proc(data) Class.new do def initialize(app, env) @app = app end define_method(:call) do |env| env[:data] << "#{data}_in" @app.call(env) env[:data] << "#{data}_out" end end end context "copying" do it "should copy the stack" do copy = subject.dup expect(copy.stack.object_id).not_to eq(subject.stack.object_id) end end context "build" do it "should provide build as a shortcut for basic sequences" do data = {} proc = Proc.new { |env| env[:data] = true } subject = described_class.build(proc) subject.call(data) expect(data[:data]).to eq(true) end end context "basic `use`" do it "should add items to the stack and make them callable" do data = {} proc = Proc.new { |env| env[:data] = true } subject.use proc subject.call(data) expect(data[:data]).to eq(true) end it "should be able to add multiple items" do data = {} proc1 = Proc.new { |env| env[:one] = true } proc2 = Proc.new { |env| env[:two] = true } subject.use proc1 subject.use proc2 subject.call(data) expect(data[:one]).to eq(true) expect(data[:two]).to eq(true) end it "should be able to add another builder" do data = {} proc1 = Proc.new { |env| env[:one] = true } # Build the first builder one = described_class.new one.use proc1 # Add it to this builder two = described_class.new two.use one # Call the 2nd and verify results two.call(data) expect(data[:one]).to eq(true) end end context "inserting" do it "can insert at an index" do subject.use appender_proc(1) subject.insert(0, appender_proc(2)) subject.call(data) expect(data[:data]).to eq([2, 1]) end it "can insert by name" do # Create the proc then make sure it has a name bar_proc = appender_proc(2) def bar_proc.name; :bar; end subject.use appender_proc(1) subject.use bar_proc subject.insert_before :bar, appender_proc(3) subject.call(data) expect(data[:data]).to eq([1, 3, 2]) end it "can insert next to a previous object" do proc2 = appender_proc(2) subject.use appender_proc(1) subject.use proc2 subject.insert(proc2, appender_proc(3)) subject.call(data) expect(data[:data]).to eq([1, 3, 2]) end it "can insert before" do subject.use appender_proc(1) subject.insert_before 0, appender_proc(2) subject.call(data) expect(data[:data]).to eq([2, 1]) end it "can insert after" do subject.use appender_proc(1) subject.use appender_proc(3) subject.insert_after 0, appender_proc(2) subject.call(data) expect(data[:data]).to eq([1, 2, 3]) end it "merges middleware stacks of other builders" do wrapper_class = Proc.new do |letter| Class.new do def initialize(app, env) @app = app end define_method(:call) do |env| env[:data] << "#{letter}1" @app.call(env) env[:data] << "#{letter}2" end end end proc2 = appender_proc(2) subject.use appender_proc(1) subject.use proc2 builder = described_class.new builder.use wrapper_class.call("A") builder.use wrapper_class.call("B") subject.insert(proc2, builder) subject.call(data) expect(data[:data]).to eq([1, "A1", "B1", 2, "B2", "A2"]) end it "raises an exception if an invalid object given for insert" do expect { subject.insert "object", appender_proc(1) }. to raise_error(RuntimeError) end it "raises an exception if an invalid object given for insert_after" do expect { subject.insert_after "object", appender_proc(1) }. to raise_error(RuntimeError) end end context "replace" do it "can replace an object" do proc1 = appender_proc(1) proc2 = appender_proc(2) subject.use proc1 subject.replace proc1, proc2 subject.call(data) expect(data[:data]).to eq([2]) end it "can replace by index" do proc1 = appender_proc(1) proc2 = appender_proc(2) subject.use proc1 subject.replace 0, proc2 subject.call(data) expect(data[:data]).to eq([2]) end end context "deleting" do it "can delete by object" do proc1 = appender_proc(1) subject.use proc1 subject.use appender_proc(2) subject.delete proc1 subject.call(data) expect(data[:data]).to eq([2]) end it "can delete by index" do proc1 = appender_proc(1) subject.use proc1 subject.use appender_proc(2) subject.delete 0 subject.call(data) expect(data[:data]).to eq([2]) end end describe "action hooks" do it "applies them properly" do hook = double("hook") allow(hook).to receive(:apply) do |builder| builder.use appender_proc(2) end data[:action_hooks] = [hook] subject.use appender_proc(1) subject.call(data) expect(data[:data]).to eq([1, 2]) expect(data[:action_hooks_already_ran]).to eq(true) end it "applies without prepend/append if it has already" do hook = double("hook") expect(hook).to receive(:apply).with(anything, { no_prepend_or_append: true }).once data[:action_hooks] = [hook] data[:action_hooks_already_ran] = true subject.call(data) end end describe "calling another app later" do it "calls in the proper order" do # We have to do this because inside the Class.new, it can't see these # rspec methods... described_klass = described_class wrapper_proc = self.method(:wrapper_proc) wrapper = Class.new do def initialize(app, env) @app = app end define_method(:call) do |env| inner = described_klass.new inner.use wrapper_proc[2] inner.use @app inner.call(env) end end subject.use wrapper_proc(1) subject.use wrapper subject.use wrapper_proc(3) subject.call(data) expect(data[:data]).to eq([ "1_in", "2_in", "3_in", "3_out", "2_out", "1_out"]) end end end