require 'spec_helper' require 'puppet/defaults' require 'puppet/indirector' require 'puppet/indirector/memory' describe Puppet::Indirector::Terminus do before :all do class Puppet::AbstractConcept extend Puppet::Indirector indirects :abstract_concept attr_accessor :name def initialize(name = "name") @name = name end end class Puppet::AbstractConcept::Freedom < Puppet::Indirector::Code end end after :all do # Remove the class, unlinking it from the rest of the system. Puppet.send(:remove_const, :AbstractConcept) if Puppet.const_defined?(:AbstractConcept) end let :terminus_class do Puppet::AbstractConcept::Freedom end let :terminus do terminus_class.new end let :indirection do Puppet::AbstractConcept.indirection end let :model do Puppet::AbstractConcept end it "should provide a method for setting terminus class documentation" do expect(terminus_class).to respond_to(:desc) end it "should support a class-level name attribute" do expect(terminus_class).to respond_to(:name) end it "should support a class-level indirection attribute" do expect(terminus_class).to respond_to(:indirection) end it "should support a class-level terminus-type attribute" do expect(terminus_class).to respond_to(:terminus_type) end it "should support a class-level model attribute" do expect(terminus_class).to respond_to(:model) end it "should accept indirection instances as its indirection" do # The test is that this shouldn't raise, and should preserve the object # instance exactly, hence "equal", not just "==". terminus_class.indirection = indirection expect(terminus_class.indirection).to equal indirection end it "should look up indirection instances when only a name has been provided" do terminus_class.indirection = :abstract_concept expect(terminus_class.indirection).to equal indirection end it "should fail when provided a name that does not resolve to an indirection" do expect { terminus_class.indirection = :exploding_whales }.to raise_error(ArgumentError, /Could not find indirection instance/) # We should still have the default indirection. expect(terminus_class.indirection).to equal indirection end describe "when a terminus instance" do it "should return the class's name as its name" do expect(terminus.name).to eq(:freedom) end it "should return the class's indirection as its indirection" do expect(terminus.indirection).to equal indirection end it "should set the instances's type to the abstract terminus type's name" do expect(terminus.terminus_type).to eq(:code) end it "should set the instances's model to the indirection's model" do expect(terminus.model).to equal indirection.model end end describe "when managing terminus classes" do it "should provide a method for registering terminus classes" do expect(Puppet::Indirector::Terminus).to respond_to(:register_terminus_class) end it "should provide a method for returning terminus classes by name and type" do terminus = double('terminus_type', :name => :abstract, :indirection_name => :whatever) Puppet::Indirector::Terminus.register_terminus_class(terminus) expect(Puppet::Indirector::Terminus.terminus_class(:whatever, :abstract)).to equal(terminus) end it "should set up autoloading for any terminus class types requested" do expect(Puppet::Indirector::Terminus).to receive(:instance_load).with(:test2, "puppet/indirector/test2") Puppet::Indirector::Terminus.terminus_class(:test2, :whatever) end it "should load terminus classes that are not found" do # Set up instance loading; it would normally happen automatically Puppet::Indirector::Terminus.instance_load :test1, "puppet/indirector/test1" expect(Puppet::Indirector::Terminus.instance_loader(:test1)).to receive(:load).with(:yay, anything) Puppet::Indirector::Terminus.terminus_class(:test1, :yay) end it "should fail when no indirection can be found" do expect(Puppet::Indirector::Indirection).to receive(:instance).with(:abstract_concept).and_return(nil) expect { class Puppet::AbstractConcept::Physics < Puppet::Indirector::Code end }.to raise_error(ArgumentError, /Could not find indirection instance/) end it "should register the terminus class with the terminus base class" do expect(Puppet::Indirector::Terminus).to receive(:register_terminus_class) do |type| expect(type.indirection_name).to eq(:abstract_concept) expect(type.name).to eq(:intellect) end begin class Puppet::AbstractConcept::Intellect < Puppet::Indirector::Code end ensure Puppet::AbstractConcept.send(:remove_const, :Intellect) rescue nil end end end describe "when parsing class constants for indirection and terminus names" do before :each do allow(Puppet::Indirector::Terminus).to receive(:register_terminus_class) end let :subclass do subclass = double('subclass') allow(subclass).to receive(:to_s).and_return("TestInd::OneTwo") allow(subclass).to receive(:mark_as_abstract_terminus) subclass end it "should fail when anonymous classes are used" do expect { Puppet::Indirector::Terminus.inherited(Class.new) }.to raise_error(Puppet::DevError, /Terminus subclasses must have associated constants/) end it "should use the last term in the constant for the terminus class name" do expect(subclass).to receive(:name=).with(:one_two) allow(subclass).to receive(:indirection=) Puppet::Indirector::Terminus.inherited(subclass) end it "should convert the terminus name to a downcased symbol" do expect(subclass).to receive(:name=).with(:one_two) allow(subclass).to receive(:indirection=) Puppet::Indirector::Terminus.inherited(subclass) end it "should use the second to last term in the constant for the indirection name" do expect(subclass).to receive(:indirection=).with(:test_ind) allow(subclass).to receive(:name=) allow(subclass).to receive(:terminus_type=) Puppet::Indirector::Memory.inherited(subclass) end it "should convert the indirection name to a downcased symbol" do expect(subclass).to receive(:indirection=).with(:test_ind) allow(subclass).to receive(:name=) allow(subclass).to receive(:terminus_type=) Puppet::Indirector::Memory.inherited(subclass) end it "should convert camel case to lower case with underscores as word separators" do expect(subclass).to receive(:name=).with(:one_two) allow(subclass).to receive(:indirection=) Puppet::Indirector::Terminus.inherited(subclass) end end describe "when creating terminus class types" do before :each do allow(Puppet::Indirector::Terminus).to receive(:register_terminus_class) class Puppet::Indirector::Terminus::TestTerminusType < Puppet::Indirector::Terminus end end after :each do Puppet::Indirector::Terminus.send(:remove_const, :TestTerminusType) end let :subclass do Puppet::Indirector::Terminus::TestTerminusType end it "should set the name of the abstract subclass to be its class constant" do expect(subclass.name).to eq(:test_terminus_type) end it "should mark abstract terminus types as such" do expect(subclass).to be_abstract_terminus end it "should not allow instances of abstract subclasses to be created" do expect { subclass.new }.to raise_error(Puppet::DevError) end end describe "when listing terminus classes" do it "should list the terminus files available to load" do allow_any_instance_of(Puppet::Util::Autoload).to receive(:files_to_load).and_return(["/foo/bar/baz", "/max/runs/marathon"]) expect(Puppet::Indirector::Terminus.terminus_classes('my_stuff')).to eq([:baz, :marathon]) end end describe "when validating a request" do let :request do Puppet::Indirector::Request.new(indirection.name, :find, "the_key", instance) end describe "`instance.name` does not match the key in the request" do let(:instance) { model.new("wrong_key") } it "raises an error " do expect { terminus.validate(request) }.to raise_error( Puppet::Indirector::ValidationError, /Instance name .* does not match requested key/ ) end end describe "`instance` is not an instance of the model class" do let(:instance) { double("instance") } it "raises an error" do expect { terminus.validate(request) }.to raise_error( Puppet::Indirector::ValidationError, /Invalid instance type/ ) end end describe "the instance key and class match the request key and model class" do let(:instance) { model.new("the_key") } it "passes" do terminus.validate(request) end end end end