diff --git a/spec/shared_behaviours/iterative_functions.rb b/spec/shared_behaviours/iterative_functions.rb index d3975a194..1910a3c80 100644 --- a/spec/shared_behaviours/iterative_functions.rb +++ b/spec/shared_behaviours/iterative_functions.rb @@ -1,69 +1,69 @@ shared_examples_for 'all iterative functions hash handling' do |func| it 'passes a hash entry as an array of the key and value' do catalog = compile_to_catalog(<<-MANIFEST) {a=>1}.#{func} |$v| { notify { "${v[0]} ${v[1]}": } } MANIFEST catalog.resource(:notify, "a 1").should_not be_nil end end shared_examples_for 'all iterative functions argument checks' do |func| it 'raises an error when used against an unsupported type' do expect do compile_to_catalog(<<-MANIFEST) 3.14.#{func} |$k, $v| { } MANIFEST end.to raise_error(Puppet::Error, /must be something enumerable/) end it 'raises an error when called with any parameters besides a block' do expect do compile_to_catalog(<<-MANIFEST) [1].#{func}(1) |$v| { } MANIFEST end.to raise_error(Puppet::Error, /mis-matched arguments.*expected.*arg count \{2\}.*actual.*arg count \{3\}/m) end it 'raises an error when called without a block' do expect do compile_to_catalog(<<-MANIFEST) [1].#{func}() MANIFEST end.to raise_error(Puppet::Error, /mis-matched arguments.*expected.*arg count \{2\}.*actual.*arg count \{1\}/m) end it 'raises an error when called with something that is not a block' do expect do compile_to_catalog(<<-MANIFEST) [1].#{func}(1) MANIFEST end.to raise_error(Puppet::Error, /mis-matched arguments.*expected.*Callable.*actual(?!Callable\)).*/m) end it 'raises an error when called with a block with too many required parameters' do expect do compile_to_catalog(<<-MANIFEST) [1].#{func}() |$v1, $v2, $v3| { } MANIFEST end.to raise_error(Puppet::Error, /mis-matched arguments.*expected.*arg count \{2\}.*actual.*Callable\[Any, Any, Any\]/m) end it 'raises an error when called with a block with too few parameters' do expect do compile_to_catalog(<<-MANIFEST) [1].#{func}() | | { } MANIFEST end.to raise_error(Puppet::Error, /mis-matched arguments.*expected.*arg count \{2\}.*actual.*Callable\[0, 0\]/m) end it 'does not raise an error when called with a block with too many but optional arguments' do expect do compile_to_catalog(<<-MANIFEST) [1].#{func}() |$v1, $v2, $v3=extra| { } MANIFEST - end.to_not raise_error(Puppet::Error) + end.to_not raise_error end end diff --git a/spec/unit/functions/assert_type_spec.rb b/spec/unit/functions/assert_type_spec.rb index 05f0fbe0f..13b353401 100644 --- a/spec/unit/functions/assert_type_spec.rb +++ b/spec/unit/functions/assert_type_spec.rb @@ -1,78 +1,78 @@ require 'spec_helper' require 'puppet/pops' require 'puppet/loaders' describe 'the assert_type function' do after(:all) { Puppet::Pops::Loaders.clear } around(:each) do |example| loaders = Puppet::Pops::Loaders.new(Puppet::Node::Environment.create(:testing, [])) Puppet.override({:loaders => loaders}, "test-example") do example.run end end let(:func) do Puppet.lookup(:loaders).puppet_system_loader.load(:function, 'assert_type') end it 'asserts compliant type by returning the value' do expect(func.call({}, type(String), 'hello world')).to eql('hello world') end it 'accepts type given as a String' do expect(func.call({}, 'String', 'hello world')).to eql('hello world') end it 'asserts non compliant type by raising an error' do expect do func.call({}, type(Integer), 'hello world') end.to raise_error(Puppet::ParseError, /does not match actual/) end it 'checks that first argument is a type' do expect do func.call({}, 10, 10) end.to raise_error(ArgumentError, Regexp.new(Regexp.escape( "function 'assert_type' called with mis-matched arguments expected one of: assert_type(Type type, Any value, Callable[Type, Type] block {0,1}) - arg count {2,3} assert_type(String type_string, Any value, Callable[Type, Type] block {0,1}) - arg count {2,3} actual: assert_type(Integer, Integer) - arg count {2}"))) end it 'allows the second arg to be undef/nil)' do expect do func.call({}, optional(String), nil) - end.to_not raise_error(ArgumentError) + end.to_not raise_error end it 'can be called with a callable that receives a specific type' do expected, actual = func.call({}, optional(String), 1, create_callable_2_args_unit) expect(expected.to_s).to eql('Optional[String]') expect(actual.to_s).to eql('Integer[1, 1]') end def optional(type_ref) Puppet::Pops::Types::TypeFactory.optional(type(type_ref)) end def type(type_ref) Puppet::Pops::Types::TypeFactory.type_of(type_ref) end def create_callable_2_args_unit() Puppet::Functions.create_function(:func) do dispatch :func do param 'Type', 'expected' param 'Type', 'actual' end def func(expected, actual) [expected, actual] end end.new({}, nil) end end diff --git a/spec/unit/pops/binder/injector_spec.rb b/spec/unit/pops/binder/injector_spec.rb index 8fa3bfbed..b62ceaeb9 100644 --- a/spec/unit/pops/binder/injector_spec.rb +++ b/spec/unit/pops/binder/injector_spec.rb @@ -1,786 +1,786 @@ require 'spec_helper' require 'puppet/pops' module InjectorSpecModule def injector(binder) Puppet::Pops::Binder::Injector.new(binder) end def factory Puppet::Pops::Binder::BindingsFactory end def test_layer_with_empty_bindings factory.named_layer('test-layer', factory.named_bindings('test').model) end def test_layer_with_bindings(*bindings) factory.named_layer('test-layer', *bindings) end def null_scope() nil end def type_calculator Puppet::Pops::Types::TypeCalculator end def type_factory Puppet::Pops::Types::TypeFactory end # Returns a binder # def configured_binder b = Puppet::Pops::Binder::Binder.new() b end class TestDuck end class Daffy < TestDuck end class AngryDuck < TestDuck # Supports assisted inject, returning a Donald duck as the default impl of Duck def self.inject(injector, scope, binding, *args) Donald.new() end end class Donald < AngryDuck end class ArneAnka < AngryDuck attr_reader :label def initialize() @label = 'A Swedish angry cartoon duck' end end class ScroogeMcDuck < TestDuck attr_reader :fortune # Supports assisted inject, returning an ScroogeMcDuck with 1$ fortune or first arg in args # Note that when injected (via instance producer, or implict assisted inject, the inject method # always wins. def self.inject(injector, scope, binding, *args) self.new(args[0].nil? ? 1 : args[0]) end def initialize(fortune) @fortune = fortune end end class NamedDuck < TestDuck attr_reader :name def initialize(name) @name = name end end # Test custom producer that on each produce returns a duck that is twice as rich as its predecessor class ScroogeProducer < Puppet::Pops::Binder::Producers::Producer attr_reader :next_capital def initialize @next_capital = 100 end def produce(scope) ScroogeMcDuck.new(@next_capital *= 2) end end end describe 'Injector' do include InjectorSpecModule let(:bindings) { factory.named_bindings('test') } let(:scope) { null_scope()} let(:binder) { Puppet::Pops::Binder::Binder } let(:lbinder) do binder.new(layered_bindings) end def duck_type # create distinct instances type_factory.ruby(InjectorSpecModule::TestDuck) end let(:layered_bindings) { factory.layered_bindings(test_layer_with_bindings(bindings.model)) } context 'When created' do it 'should not raise an error if binder is configured' do expect { injector(lbinder) }.to_not raise_error end it 'should create an empty injector given an empty binder' do expect { binder.new(layered_bindings) }.to_not raise_exception end it "should be possible to reference the TypeCalculator" do injector(lbinder).type_calculator.is_a?(Puppet::Pops::Types::TypeCalculator).should == true end it "should be possible to reference the KeyFactory" do injector(lbinder).key_factory.is_a?(Puppet::Pops::Binder::KeyFactory).should == true end it "can be created using a model" do bindings.bind.name('a_string').to('42') injector = Puppet::Pops::Binder::Injector.create_from_model(layered_bindings) injector.lookup(scope, 'a_string').should == '42' end it 'can be created using a block' do injector = Puppet::Pops::Binder::Injector.create('test') do bind.name('a_string').to('42') end injector.lookup(scope, 'a_string').should == '42' end it 'can be created using a hash' do injector = Puppet::Pops::Binder::Injector.create_from_hash('test', 'a_string' => '42') injector.lookup(scope, 'a_string').should == '42' end it 'can be created using an overriding injector with block' do injector = Puppet::Pops::Binder::Injector.create('test') do bind.name('a_string').to('42') end injector2 = injector.override('override') do bind.name('a_string').to('43') end injector.lookup(scope, 'a_string').should == '42' injector2.lookup(scope, 'a_string').should == '43' end it 'can be created using an overriding injector with hash' do injector = Puppet::Pops::Binder::Injector.create_from_hash('test', 'a_string' => '42') injector2 = injector.override_with_hash('override', 'a_string' => '43') injector.lookup(scope, 'a_string').should == '42' injector2.lookup(scope, 'a_string').should == '43' end it "can be created using an overriding injector with a model" do injector = Puppet::Pops::Binder::Injector.create_from_hash('test', 'a_string' => '42') bindings.bind.name('a_string').to('43') injector2 = injector.override_with_model(layered_bindings) injector.lookup(scope, 'a_string').should == '42' injector2.lookup(scope, 'a_string').should == '43' end end context "When looking up objects" do it 'lookup(scope, name) finds bound object of type Data with given name' do bindings.bind().name('a_string').to('42') injector(lbinder).lookup(scope, 'a_string').should == '42' end context 'a block transforming the result can be given' do it 'that transform a found value given scope and value' do bindings.bind().name('a_string').to('42') injector(lbinder).lookup(scope, 'a_string') {|zcope, val| val + '42' }.should == '4242' end it 'that transform a found value given only value' do bindings.bind().name('a_string').to('42') injector(lbinder).lookup(scope, 'a_string') {|val| val + '42' }.should == '4242' end it 'that produces a default value when entry is missing' do bindings.bind().name('a_string').to('42') injector(lbinder).lookup(scope, 'a_non_existing_string') {|val| val ? (raise Error, "Should not happen") : '4242' }.should == '4242' end end context "and class is not bound" do it "assisted inject kicks in for classes with zero args constructor" do duck_type = type_factory.ruby(InjectorSpecModule::Daffy) injector = injector(lbinder) injector.lookup(scope, duck_type).is_a?(InjectorSpecModule::Daffy).should == true injector.lookup_producer(scope, duck_type).produce(scope).is_a?(InjectorSpecModule::Daffy).should == true end it "assisted inject produces same instance on lookup but not on lookup producer" do duck_type = type_factory.ruby(InjectorSpecModule::Daffy) injector = injector(lbinder) d1 = injector.lookup(scope, duck_type) d2 = injector.lookup(scope, duck_type) d1.equal?(d2).should == true d1 = injector.lookup_producer(scope, duck_type).produce(scope) d2 = injector.lookup_producer(scope, duck_type).produce(scope) d1.equal?(d2).should == false end it "assisted inject kicks in for classes with a class inject method" do duck_type = type_factory.ruby(InjectorSpecModule::ScroogeMcDuck) injector = injector(lbinder) # Do not pass any arguments, the ScroogeMcDuck :inject method should pick 1 by default # This tests zero args passed injector.lookup(scope, duck_type).fortune.should == 1 injector.lookup_producer(scope, duck_type).produce(scope).fortune.should == 1 end it "assisted inject selects the inject method if it exists over a zero args constructor" do injector = injector(lbinder) duck_type = type_factory.ruby(InjectorSpecModule::AngryDuck) injector.lookup(scope, duck_type).is_a?(InjectorSpecModule::Donald).should == true injector.lookup_producer(scope, duck_type).produce(scope).is_a?(InjectorSpecModule::Donald).should == true end it "assisted inject selects the zero args constructor if injector is from a superclass" do injector = injector(lbinder) duck_type = type_factory.ruby(InjectorSpecModule::ArneAnka) injector.lookup(scope, duck_type).is_a?(InjectorSpecModule::ArneAnka).should == true injector.lookup_producer(scope, duck_type).produce(scope).is_a?(InjectorSpecModule::ArneAnka).should == true end end context "and multiple layers are in use" do it "a higher layer shadows anything in a lower layer" do bindings1 = factory.named_bindings('test1') bindings1.bind().name('a_string').to('bad stuff') lower_layer = factory.named_layer('lower-layer', bindings1.model) bindings2 = factory.named_bindings('test2') bindings2.bind().name('a_string').to('good stuff') higher_layer = factory.named_layer('higher-layer', bindings2.model) injector = injector(binder.new(factory.layered_bindings(higher_layer, lower_layer))) injector.lookup(scope,'a_string').should == 'good stuff' end it "a higher layer may not shadow a lower layer binding that is final" do bindings1 = factory.named_bindings('test1') bindings1.bind().final.name('a_string').to('required stuff') lower_layer = factory.named_layer('lower-layer', bindings1.model) bindings2 = factory.named_bindings('test2') bindings2.bind().name('a_string').to('contraband') higher_layer = factory.named_layer('higher-layer', bindings2.model) expect { injector = injector(binder.new(factory.layered_bindings(higher_layer, lower_layer))) }.to raise_error(/Override of final binding not allowed/) end end context "and dealing with Data types" do let(:lbinder) { binder.new(layered_bindings) } it "should treat all data as same type w.r.t. key" do bindings.bind().name('a_string').to('42') bindings.bind().name('an_int').to(43) bindings.bind().name('a_float').to(3.14) bindings.bind().name('a_boolean').to(true) bindings.bind().name('an_array').to([1,2,3]) bindings.bind().name('a_hash').to({'a'=>1,'b'=>2,'c'=>3}) injector = injector(lbinder) injector.lookup(scope,'a_string').should == '42' injector.lookup(scope,'an_int').should == 43 injector.lookup(scope,'a_float').should == 3.14 injector.lookup(scope,'a_boolean').should == true injector.lookup(scope,'an_array').should == [1,2,3] injector.lookup(scope,'a_hash').should == {'a'=>1,'b'=>2,'c'=>3} end it "should provide type-safe lookup of given type/name" do bindings.bind().string().name('a_string').to('42') bindings.bind().integer().name('an_int').to(43) bindings.bind().float().name('a_float').to(3.14) bindings.bind().boolean().name('a_boolean').to(true) bindings.bind().array_of_data().name('an_array').to([1,2,3]) bindings.bind().hash_of_data().name('a_hash').to({'a'=>1,'b'=>2,'c'=>3}) injector = injector(lbinder) # Check lookup using implied Data type injector.lookup(scope,'a_string').should == '42' injector.lookup(scope,'an_int').should == 43 injector.lookup(scope,'a_float').should == 3.14 injector.lookup(scope,'a_boolean').should == true injector.lookup(scope,'an_array').should == [1,2,3] injector.lookup(scope,'a_hash').should == {'a'=>1,'b'=>2,'c'=>3} # Check lookup using expected type injector.lookup(scope,type_factory.string(), 'a_string').should == '42' injector.lookup(scope,type_factory.integer(), 'an_int').should == 43 injector.lookup(scope,type_factory.float(),'a_float').should == 3.14 injector.lookup(scope,type_factory.boolean(),'a_boolean').should == true injector.lookup(scope,type_factory.array_of_data(),'an_array').should == [1,2,3] injector.lookup(scope,type_factory.hash_of_data(),'a_hash').should == {'a'=>1,'b'=>2,'c'=>3} # Check lookup using wrong type expect { injector.lookup(scope,type_factory.integer(), 'a_string')}.to raise_error(/Type error/) expect { injector.lookup(scope,type_factory.string(), 'an_int')}.to raise_error(/Type error/) expect { injector.lookup(scope,type_factory.string(),'a_float')}.to raise_error(/Type error/) expect { injector.lookup(scope,type_factory.string(),'a_boolean')}.to raise_error(/Type error/) expect { injector.lookup(scope,type_factory.string(),'an_array')}.to raise_error(/Type error/) expect { injector.lookup(scope,type_factory.string(),'a_hash')}.to raise_error(/Type error/) end end end context "When looking up producer" do it 'the value is produced by calling produce(scope)' do bindings.bind().name('a_string').to('42') injector(lbinder).lookup_producer(scope, 'a_string').produce(scope).should == '42' end context 'a block transforming the result can be given' do it 'that transform a found value given scope and producer' do bindings.bind().name('a_string').to('42') injector(lbinder).lookup_producer(scope, 'a_string') {|zcope, p| p.produce(zcope) + '42' }.should == '4242' end it 'that transform a found value given only producer' do bindings.bind().name('a_string').to('42') injector(lbinder).lookup_producer(scope, 'a_string') {|p| p.produce(scope) + '42' }.should == '4242' end it 'that can produce a default value when entry is not found' do bindings.bind().name('a_string').to('42') injector(lbinder).lookup_producer(scope, 'a_non_existing_string') {|p| p ? (raise Error,"Should not happen") : '4242' }.should == '4242' end end end context "When dealing with singleton vs. non singleton" do it "should produce the same instance when producer is a singleton" do bindings.bind().name('a_string').to('42') injector = injector(lbinder) a = injector.lookup(scope, 'a_string') b = injector.lookup(scope, 'a_string') a.equal?(b).should == true end it "should produce different instances when producer is a non singleton producer" do bindings.bind().name('a_string').to_series_of('42') injector = injector(lbinder) a = injector.lookup(scope, 'a_string') b = injector.lookup(scope, 'a_string') a.should == '42' b.should == '42' a.equal?(b).should == false end end context "When using the lookup producer" do it "should lookup again to produce a value" do bindings.bind().name('a_string').to_lookup_of('another_string') bindings.bind().name('another_string').to('hello') injector(lbinder).lookup(scope, 'a_string').should == 'hello' end it "should produce nil if looked up key does not exist" do bindings.bind().name('a_string').to_lookup_of('non_existing') injector(lbinder).lookup(scope, 'a_string').should == nil end it "should report an error if lookup loop is detected" do bindings.bind().name('a_string').to_lookup_of('a_string') expect { injector(lbinder).lookup(scope, 'a_string') }.to raise_error(/Lookup loop/) end end context "When using the hash lookup producer" do it "should lookup a key in looked up hash" do data_hash = type_factory.hash_of_data() bindings.bind().name('a_string').to_hash_lookup_of(data_hash, 'a_hash', 'huey') bindings.bind().name('a_hash').to({'huey' => 'red', 'dewey' => 'blue', 'louie' => 'green'}) injector(lbinder).lookup(scope, 'a_string').should == 'red' end it "should produce nil if looked up entry does not exist" do data_hash = type_factory.hash_of_data() bindings.bind().name('a_string').to_hash_lookup_of(data_hash, 'non_existing_entry', 'huey') bindings.bind().name('a_hash').to({'huey' => 'red', 'dewey' => 'blue', 'louie' => 'green'}) injector(lbinder).lookup(scope, 'a_string').should == nil end end context "When using the first found producer" do it "should lookup until it finds a value, but not further" do bindings.bind().name('a_string').to_first_found('b_string', 'c_string', 'g_string') bindings.bind().name('c_string').to('hello') bindings.bind().name('g_string').to('Oh, mrs. Smith...') injector(lbinder).lookup(scope, 'a_string').should == 'hello' end it "should lookup until it finds a value using mix of type and name, but not further" do bindings.bind().name('a_string').to_first_found('b_string', [type_factory.string, 'c_string'], 'g_string') bindings.bind().name('c_string').to('hello') bindings.bind().name('g_string').to('Oh, mrs. Smith...') injector(lbinder).lookup(scope, 'a_string').should == 'hello' end end context "When producing instances" do it "should lookup an instance of a class without arguments" do bindings.bind().type(duck_type).name('the_duck').to(InjectorSpecModule::Daffy) injector(lbinder).lookup(scope, duck_type, 'the_duck').is_a?(InjectorSpecModule::Daffy).should == true end it "should lookup an instance of a class with arguments" do bindings.bind().type(duck_type).name('the_duck').to(InjectorSpecModule::ScroogeMcDuck, 1234) injector = injector(lbinder) the_duck = injector.lookup(scope, duck_type, 'the_duck') the_duck.is_a?(InjectorSpecModule::ScroogeMcDuck).should == true the_duck.fortune.should == 1234 end it "singleton producer should not be recreated between lookups" do bindings.bind().type(duck_type).name('the_duck').to_producer(InjectorSpecModule::ScroogeProducer) injector = injector(lbinder) the_duck = injector.lookup(scope, duck_type, 'the_duck') the_duck.is_a?(InjectorSpecModule::ScroogeMcDuck).should == true the_duck.fortune.should == 200 # singleton, do it again to get next value in series - it is the producer that is a singleton # not the produced value the_duck = injector.lookup(scope, duck_type, 'the_duck') the_duck.is_a?(InjectorSpecModule::ScroogeMcDuck).should == true the_duck.fortune.should == 400 duck_producer = injector.lookup_producer(scope, duck_type, 'the_duck') duck_producer.produce(scope).fortune.should == 800 end it "series of producers should recreate producer on each lookup and lookup_producer" do bindings.bind().type(duck_type).name('the_duck').to_producer_series(InjectorSpecModule::ScroogeProducer) injector = injector(lbinder) duck_producer = injector.lookup_producer(scope, duck_type, 'the_duck') duck_producer.produce(scope).fortune().should == 200 duck_producer.produce(scope).fortune().should == 400 # series, each lookup gets a new producer (initialized to produce 200) duck_producer = injector.lookup_producer(scope, duck_type, 'the_duck') duck_producer.produce(scope).fortune().should == 200 duck_producer.produce(scope).fortune().should == 400 injector.lookup(scope, duck_type, 'the_duck').fortune().should == 200 injector.lookup(scope, duck_type, 'the_duck').fortune().should == 200 end end context "When working with multibind" do context "of hash kind" do it "a multibind produces contributed items keyed by their bound key-name" do hash_of_duck = type_factory.hash_of(duck_type) multibind_id = "ducks" bindings.multibind(multibind_id).type(hash_of_duck).name('donalds_nephews') bindings.bind.in_multibind(multibind_id).type(duck_type).name('nephew1').to(InjectorSpecModule::NamedDuck, 'Huey') bindings.bind.in_multibind(multibind_id).type(duck_type).name('nephew2').to(InjectorSpecModule::NamedDuck, 'Dewey') bindings.bind.in_multibind(multibind_id).type(duck_type).name('nephew3').to(InjectorSpecModule::NamedDuck, 'Louie') injector = injector(lbinder) the_ducks = injector.lookup(scope, hash_of_duck, "donalds_nephews") the_ducks.size.should == 3 the_ducks['nephew1'].name.should == 'Huey' the_ducks['nephew2'].name.should == 'Dewey' the_ducks['nephew3'].name.should == 'Louie' end it "is an error to not bind contribution with a name" do hash_of_duck = type_factory.hash_of(duck_type) multibind_id = "ducks" bindings.multibind(multibind_id).type(hash_of_duck).name('donalds_nephews') # missing name bindings.bind.in_multibind(multibind_id).type(duck_type).to(InjectorSpecModule::NamedDuck, 'Huey') bindings.bind.in_multibind(multibind_id).type(duck_type).to(InjectorSpecModule::NamedDuck, 'Dewey') expect { the_ducks = injector(lbinder).lookup(scope, hash_of_duck, "donalds_nephews") }.to raise_error(/must have a name/) end it "is an error to bind with duplicate key when using default (priority) conflict resolution" do hash_of_duck = type_factory.hash_of(duck_type) multibind_id = "ducks" bindings.multibind(multibind_id).type(hash_of_duck).name('donalds_nephews') # missing name bindings.bind.in_multibind(multibind_id).type(duck_type).name('foo').to(InjectorSpecModule::NamedDuck, 'Huey') bindings.bind.in_multibind(multibind_id).type(duck_type).name('foo').to(InjectorSpecModule::NamedDuck, 'Dewey') expect { the_ducks = injector(lbinder).lookup(scope, hash_of_duck, "donalds_nephews") }.to raise_error(/Duplicate key/) end it "is not an error to bind with duplicate key when using (ignore) conflict resolution" do hash_of_duck = type_factory.hash_of(duck_type) multibind_id = "ducks" bindings.multibind(multibind_id).type(hash_of_duck).name('donalds_nephews').producer_options(:conflict_resolution => :ignore) bindings.bind.in_multibind(multibind_id).type(duck_type).name('foo').to(InjectorSpecModule::NamedDuck, 'Huey') bindings.bind.in_multibind(multibind_id).type(duck_type).name('foo').to(InjectorSpecModule::NamedDuck, 'Dewey') expect { the_ducks = injector(lbinder).lookup(scope, hash_of_duck, "donalds_nephews") - }.to_not raise_error(/Duplicate key/) + }.to_not raise_error end it "should produce detailed type error message" do hash_of_integer = type_factory.hash_of(type_factory.integer()) multibind_id = "ints" mb = bindings.multibind(multibind_id).type(hash_of_integer).name('donalds_family') bindings.bind.in_multibind(multibind_id).name('nephew').to('Huey') expect { ducks = injector(lbinder).lookup(scope, 'donalds_family') }.to raise_error(%r{expected: Integer, got: String}) end it "should be possible to combine hash multibind contributions with append on conflict" do # This case uses a multibind of individual strings, but combines them # into an array bound to a hash key # (There are other ways to do this - e.g. have the multibind lookup a multibind # of array type to which nephews are contributed). # hash_of_data = type_factory.hash_of_data() multibind_id = "ducks" mb = bindings.multibind(multibind_id).type(hash_of_data).name('donalds_family') mb.producer_options(:conflict_resolution => :append) bindings.bind.in_multibind(multibind_id).name('nephews').to('Huey') bindings.bind.in_multibind(multibind_id).name('nephews').to('Dewey') bindings.bind.in_multibind(multibind_id).name('nephews').to('Louie') bindings.bind.in_multibind(multibind_id).name('uncles').to('Scrooge McDuck') bindings.bind.in_multibind(multibind_id).name('uncles').to('Ludwig Von Drake') ducks = injector(lbinder).lookup(scope, 'donalds_family') ducks['nephews'].should == ['Huey', 'Dewey', 'Louie'] ducks['uncles'].should == ['Scrooge McDuck', 'Ludwig Von Drake'] end it "should be possible to combine hash multibind contributions with append, flat, and uniq, on conflict" do # This case uses a multibind of individual strings, but combines them # into an array bound to a hash key # (There are other ways to do this - e.g. have the multibind lookup a multibind # of array type to which nephews are contributed). # hash_of_data = type_factory.hash_of_data() multibind_id = "ducks" mb = bindings.multibind(multibind_id).type(hash_of_data).name('donalds_family') mb.producer_options(:conflict_resolution => :append, :flatten => true, :uniq => true) bindings.bind.in_multibind(multibind_id).name('nephews').to('Huey') bindings.bind.in_multibind(multibind_id).name('nephews').to('Huey') bindings.bind.in_multibind(multibind_id).name('nephews').to('Dewey') bindings.bind.in_multibind(multibind_id).name('nephews').to(['Huey', ['Louie'], 'Dewey']) bindings.bind.in_multibind(multibind_id).name('uncles').to('Scrooge McDuck') bindings.bind.in_multibind(multibind_id).name('uncles').to('Ludwig Von Drake') ducks = injector(lbinder).lookup(scope, 'donalds_family') ducks['nephews'].should == ['Huey', 'Dewey', 'Louie'] ducks['uncles'].should == ['Scrooge McDuck', 'Ludwig Von Drake'] end it "should fail attempts to append, perform uniq or flatten on type incompatible multibind hash" do hash_of_integer = type_factory.hash_of(type_factory.integer()) ids = ["ducks1", "ducks2", "ducks3"] mb = bindings.multibind(ids[0]).type(hash_of_integer.copy).name('broken_family0') mb.producer_options(:conflict_resolution => :append) mb = bindings.multibind(ids[1]).type(hash_of_integer.copy).name('broken_family1') mb.producer_options(:flatten => :true) mb = bindings.multibind(ids[2]).type(hash_of_integer.copy).name('broken_family2') mb.producer_options(:uniq => :true) injector = injector(binder.new(factory.layered_bindings(test_layer_with_bindings(bindings.model)))) expect { injector.lookup(scope, 'broken_family0')}.to raise_error(/:conflict_resolution => :append/) expect { injector.lookup(scope, 'broken_family1')}.to raise_error(/:flatten/) expect { injector.lookup(scope, 'broken_family2')}.to raise_error(/:uniq/) end it "a higher priority contribution is selected when resolution is :priority" do hash_of_duck = type_factory.hash_of(duck_type) multibind_id = "ducks" bindings.multibind(multibind_id).type(hash_of_duck).name('donalds_nephews') mb1 = bindings.bind.in_multibind(multibind_id) pending 'priority based on layers not added, and priority on category removed' mb1.type(duck_type).name('nephew').to(InjectorSpecModule::NamedDuck, 'Huey') mb2 = bindings.bind.in_multibind(multibind_id) mb2.type(duck_type).name('nephew').to(InjectorSpecModule::NamedDuck, 'Dewey') binder.define_layers(layered_bindings) injector(binder).lookup(scope, hash_of_duck, "donalds_nephews")['nephew'].name.should == 'Huey' end it "a higher priority contribution wins when resolution is :merge" do # THIS TEST MAY DEPEND ON HASH ORDER SINCE PRIORITY BASED ON CATEGORY IS REMOVED hash_of_data = type_factory.hash_of_data() multibind_id = "hashed_ducks" bindings.multibind(multibind_id).type(hash_of_data).name('donalds_nephews').producer_options(:conflict_resolution => :merge) mb1 = bindings.bind.in_multibind(multibind_id) mb1.name('nephew').to({'name' => 'Huey', 'is' => 'winner'}) mb2 = bindings.bind.in_multibind(multibind_id) mb2.name('nephew').to({'name' => 'Dewey', 'is' => 'looser', 'has' => 'cap'}) the_ducks = injector(binder.new(layered_bindings)).lookup(scope, "donalds_nephews"); the_ducks['nephew']['name'].should == 'Huey' the_ducks['nephew']['is'].should == 'winner' the_ducks['nephew']['has'].should == 'cap' end end context "of array kind" do it "an array multibind produces contributed items, names are allowed but ignored" do array_of_duck = type_factory.array_of(duck_type) multibind_id = "ducks" bindings.multibind(multibind_id).type(array_of_duck).name('donalds_nephews') # one with name (ignored, expect no error) bindings.bind.in_multibind(multibind_id).type(duck_type).name('nephew1').to(InjectorSpecModule::NamedDuck, 'Huey') # two without name bindings.bind.in_multibind(multibind_id).type(duck_type).to(InjectorSpecModule::NamedDuck, 'Dewey') bindings.bind.in_multibind(multibind_id).type(duck_type).to(InjectorSpecModule::NamedDuck, 'Louie') the_ducks = injector(lbinder).lookup(scope, array_of_duck, "donalds_nephews") the_ducks.size.should == 3 the_ducks.collect {|d| d.name }.sort.should == ['Dewey', 'Huey', 'Louie'] end it "should be able to make result contain only unique entries" do # This case uses a multibind of individual strings, and combines them # into an array of unique values # array_of_data = type_factory.array_of_data() multibind_id = "ducks" mb = bindings.multibind(multibind_id).type(array_of_data).name('donalds_family') # turn off priority on named to not trigger conflict as all additions have the same precedence # (could have used the default for unnamed and add unnamed entries). mb.producer_options(:priority_on_named => false, :uniq => true) bindings.bind.in_multibind(multibind_id).name('nephews').to('Huey') bindings.bind.in_multibind(multibind_id).name('nephews').to('Dewey') bindings.bind.in_multibind(multibind_id).name('nephews').to('Dewey') # duplicate bindings.bind.in_multibind(multibind_id).name('nephews').to('Louie') bindings.bind.in_multibind(multibind_id).name('nephews').to('Louie') # duplicate bindings.bind.in_multibind(multibind_id).name('nephews').to('Louie') # duplicate ducks = injector(lbinder).lookup(scope, 'donalds_family') ducks.should == ['Huey', 'Dewey', 'Louie'] end it "should be able to contribute elements and arrays of elements and flatten 1 level" do # This case uses a multibind of individual strings and arrays, and combines them # into an array of flattened # array_of_string = type_factory.array_of(type_factory.string()) multibind_id = "ducks" mb = bindings.multibind(multibind_id).type(array_of_string).name('donalds_family') # flatten one level mb.producer_options(:flatten => 1) bindings.bind.in_multibind(multibind_id).to('Huey') bindings.bind.in_multibind(multibind_id).to('Dewey') bindings.bind.in_multibind(multibind_id).to('Louie') # duplicate bindings.bind.in_multibind(multibind_id).to(['Huey', 'Dewey', 'Louie']) ducks = injector(lbinder).lookup(scope, 'donalds_family') ducks.should == ['Huey', 'Dewey', 'Louie', 'Huey', 'Dewey', 'Louie'] end it "should produce detailed type error message" do array_of_integer = type_factory.array_of(type_factory.integer()) multibind_id = "ints" mb = bindings.multibind(multibind_id).type(array_of_integer).name('donalds_family') bindings.bind.in_multibind(multibind_id).to('Huey') expect { ducks = injector(lbinder).lookup(scope, 'donalds_family') }.to raise_error(%r{expected: Integer, or Array\[Integer\], got: String}) end end context "When using multibind in multibind" do it "a hash multibind can be contributed to another" do hash_of_data = type_factory.hash_of_data() mb1_id = 'data1' mb2_id = 'data2' top = bindings.multibind(mb1_id).type(hash_of_data).name("top") detail = bindings.multibind(mb2_id).type(hash_of_data).name("detail").in_multibind(mb1_id) bindings.bind.in_multibind(mb1_id).name('a').to(10) bindings.bind.in_multibind(mb1_id).name('b').to(20) bindings.bind.in_multibind(mb2_id).name('a').to(30) bindings.bind.in_multibind(mb2_id).name('b').to(40) expect( injector(lbinder).lookup(scope, "top") ).to eql({'detail' => {'a' => 30, 'b' => 40}, 'a' => 10, 'b' => 20}) end end context "When looking up entries requiring evaluation" do let(:node) { Puppet::Node.new('localhost') } let(:compiler) { Puppet::Parser::Compiler.new(node)} let(:scope) { Puppet::Parser::Scope.new(compiler) } let(:parser) { Puppet::Pops::Parser::Parser.new() } it "should be possible to lookup a concatenated string" do scope['duck'] = 'Donald Fauntleroy Duck' expr = parser.parse_string('"Hello $duck"').current() bindings.bind.name('the_duck').to(expr) injector(lbinder).lookup(scope, 'the_duck').should == 'Hello Donald Fauntleroy Duck' end it "should be possible to post process lookup with a puppet lambda" do model = parser.parse_string('fake() |$value| {$value + 1 }').current bindings.bind.name('an_int').to(42).producer_options( :transformer => model.body.lambda) injector(lbinder).lookup(scope, 'an_int').should == 43 end it "should be possible to post process lookup with a ruby proc" do transformer = lambda {|scope, value| value + 1 } bindings.bind.name('an_int').to(42).producer_options( :transformer => transformer) injector(lbinder).lookup(scope, 'an_int').should == 43 end end end context "When there are problems with configuration" do let(:lbinder) { binder.new(layered_bindings) } it "reports error for surfacing abstract bindings" do bindings.bind.abstract.name('an_int') expect{injector(lbinder).lookup(scope, 'an_int') }.to raise_error(/The abstract binding .* was not overridden/) end it "does not report error for abstract binding that is ovrridden" do bindings.bind.abstract.name('an_int') bindings.bind.override.name('an_int').to(142) expect{ injector(lbinder).lookup(scope, 'an_int') }.to_not raise_error end it "reports error for overriding binding that does not override" do bindings.bind.override.name('an_int').to(42) expect{injector(lbinder).lookup(scope, 'an_int') }.to raise_error(/Binding with unresolved 'override' detected/) end it "reports error for binding without producer" do bindings.bind.name('an_int') expect{injector(lbinder).lookup(scope, 'an_int') }.to raise_error(/Binding without producer/) end end end \ No newline at end of file diff --git a/spec/unit/provider/nameservice/directoryservice_spec.rb b/spec/unit/provider/nameservice/directoryservice_spec.rb index 53e0f5ae0..3427a8675 100755 --- a/spec/unit/provider/nameservice/directoryservice_spec.rb +++ b/spec/unit/provider/nameservice/directoryservice_spec.rb @@ -1,197 +1,197 @@ #! /usr/bin/env ruby require 'spec_helper' # We use this as a reasonable way to obtain all the support infrastructure. [:group].each do |type_for_this_round| provider_class = Puppet::Type.type(type_for_this_round).provider(:directoryservice) describe provider_class do before do @resource = stub("resource") @provider = provider_class.new(@resource) end it "[#6009] should handle nested arrays of members" do current = ["foo", "bar", "baz"] desired = ["foo", ["quux"], "qorp"] group = 'example' @resource.stubs(:[]).with(:name).returns(group) @resource.stubs(:[]).with(:auth_membership).returns(true) @provider.instance_variable_set(:@property_value_cache_hash, { :members => current }) %w{bar baz}.each do |del| @provider.expects(:execute).once. with([:dseditgroup, '-o', 'edit', '-n', '.', '-d', del, group]) end %w{quux qorp}.each do |add| @provider.expects(:execute).once. with([:dseditgroup, '-o', 'edit', '-n', '.', '-a', add, group]) end expect { @provider.set(:members, desired) }.to_not raise_error end end end describe 'DirectoryService.single_report' do it 'should fail on OS X < 10.5' do Puppet::Provider::NameService::DirectoryService.stubs(:get_macosx_version_major).returns("10.4") expect { Puppet::Provider::NameService::DirectoryService.single_report('resource_name') }.to raise_error(RuntimeError, "Puppet does not support OS X versions < 10.5") end it 'should succeed on OS X >= 10.10' do Puppet::Provider::NameService::DirectoryService.stubs(:get_macosx_version_major).returns("10.10") expect { Puppet::Provider::NameService::DirectoryService.single_report('resource_name') - }.to_not raise_error(RuntimeError) + }.to_not raise_error end it 'should use plist data on >= 10.5' do Puppet::Provider::NameService::DirectoryService.stubs(:get_macosx_version_major).returns("10.5") Puppet::Provider::NameService::DirectoryService.stubs(:get_ds_path).returns('Users') Puppet::Provider::NameService::DirectoryService.stubs(:list_all_present).returns( ['root', 'user1', 'user2', 'resource_name'] ) Puppet::Provider::NameService::DirectoryService.stubs(:generate_attribute_hash) Puppet::Provider::NameService::DirectoryService.stubs(:execute) Puppet::Provider::NameService::DirectoryService.expects(:parse_dscl_plist_data) Puppet::Provider::NameService::DirectoryService.single_report('resource_name') end end describe 'DirectoryService.get_exec_preamble' do it 'should fail on OS X < 10.5' do Puppet::Provider::NameService::DirectoryService.stubs(:get_macosx_version_major).returns("10.4") expect { Puppet::Provider::NameService::DirectoryService.get_exec_preamble('-list') }.to raise_error(RuntimeError, "Puppet does not support OS X versions < 10.5") end it 'should use plist data on >= 10.5' do Puppet::Provider::NameService::DirectoryService.stubs(:get_macosx_version_major).returns("10.5") Puppet::Provider::NameService::DirectoryService.stubs(:get_ds_path).returns('Users') Puppet::Provider::NameService::DirectoryService.get_exec_preamble('-list').should include("-plist") end end describe 'DirectoryService password behavior' do # The below is a binary plist containing a ShadowHashData key which CONTAINS # another binary plist. The nested binary plist contains a 'SALTED-SHA512' # key that contains a base64 encoded salted-SHA512 password hash... let (:binary_plist) { "bplist00\324\001\002\003\004\005\006\a\bXCRAM-MD5RNT]SALTED-SHA512[RECOVERABLEO\020 \231k2\3360\200GI\201\355J\216\202\215y\243\001\206J\300\363\032\031\022\006\2359\024\257\217<\361O\020\020F\353\at\377\277\226\276c\306\254\031\037J(\235O\020D\335\006{\3744g@\377z\204\322\r\332t\021\330\n\003\246K\223\356\034!P\261\305t\035\346\352p\206\003n\247MMA\310\301Z<\366\246\023\0161W3\340\357\000\317T\t\301\311+\204\246L7\276\370\320*\245O\021\002\000k\024\221\270x\353\001\237\346D}\377?\265]\356+\243\v[\350\316a\340h\376<\322\266\327\016\306n\272r\t\212A\253L\216\214\205\016\241 [\360/\335\002#\\A\372\241a\261\346\346\\\251\330\312\365\016\n\341\017\016\225&;\322\\\004*\ru\316\372\a \362?8\031\247\231\030\030\267\315\023\v\343{@\227\301s\372h\212\000a\244&\231\366\nt\277\2036,\027bZ+\223W\212g\333`\264\331N\306\307\362\257(^~ b\262\247&\231\261t\341\231%\244\247\203eOt\365\271\201\273\330\350\363C^A\327F\214!\217hgf\e\320k\260n\315u~\336\371M\t\235k\230S\375\311\303\240\351\037d\273\321y\335=K\016`_\317\230\2612_\023K\036\350\v\232\323Y\310\317_\035\227%\237\v\340\023\016\243\233\025\306:\227\351\370\364x\234\231\266\367\016w\275\333-\351\210}\375x\034\262\272kRuHa\362T/F!\347B\231O`K\304\037'k$$\245h)e\363\365mT\b\317\\2\361\026\351\254\375Jl1~\r\371\267\352\2322I\341\272\376\243^Un\266E7\230[VocUJ\220N\2116D/\025f=\213\314\325\vG}\311\360\377DT\307m\261&\263\340\272\243_\020\271rG^BW\210\030l\344\0324\335\233\300\023\272\225Im\330\n\227*Yv[\006\315\330y'\a\321\373\273A\240\305F{S\246I#/\355\2425\031\031GGF\270y\n\331\004\023G@\331\000\361\343\350\264$\032\355_\210y\000\205\342\375\212q\024\004\026W:\205 \363v?\035\270L-\270=\022\323\2003\v\336\277\t\237\356\374\n\267n\003\367\342\330;\371S\326\016`B6@Njm>\240\021%\336\345\002(P\204Yn\3279l\0228\264\254\304\2528t\372h\217\347sA\314\345\245\337)]\000\b\000\021\000\032\000\035\000+\0007\000Z\000m\000\264\000\000\000\000\000\000\002\001\000\000\000\000\000\000\000\t\000\000\000\000\000\000\000\000\000\000\000\000\000\000\002\270" } # The below is a base64 encoded salted-SHA512 password hash. let (:pw_string) { "\335\006{\3744g@\377z\204\322\r\332t\021\330\n\003\246K\223\356\034!P\261\305t\035\346\352p\206\003n\247MMA\310\301Z<\366\246\023\0161W3\340\357\000\317T\t\301\311+\204\246L7\276\370\320*\245" } # The below is a salted-SHA512 password hash in hex. let (:sha512_hash) { 'dd067bfc346740ff7a84d20dda7411d80a03a64b93ee1c2150b1c5741de6ea7086036ea74d4d41c8c15a3cf6a6130e315733e0ef00cf5409c1c92b84a64c37bef8d02aa5' } let :plist_path do '/var/db/dslocal/nodes/Default/users/jeff.plist' end let :ds_provider do Puppet::Provider::NameService::DirectoryService end let :shadow_hash_data do {'ShadowHashData' => [StringIO.new(binary_plist)]} end subject do Puppet::Provider::NameService::DirectoryService end before :each do subject.expects(:get_macosx_version_major).returns("10.7") end it 'should execute convert_binary_to_xml once when getting the password on >= 10.7' do subject.expects(:convert_binary_to_xml).returns({'SALTED-SHA512' => StringIO.new(pw_string)}) Puppet::FileSystem.expects(:exist?).with(plist_path).once.returns(true) Plist.expects(:parse_xml).returns(shadow_hash_data) # On Mac OS X 10.7 we first need to convert to xml when reading the password subject.expects(:plutil).with('-convert', 'xml1', '-o', '/dev/stdout', plist_path) subject.get_password('uid', 'jeff') end it 'should fail if a salted-SHA512 password hash is not passed in >= 10.7' do expect { subject.set_password('jeff', 'uid', 'badpassword') }.to raise_error(RuntimeError, /OS X 10.7 requires a Salted SHA512 hash password of 136 characters./) end it 'should convert xml-to-binary and binary-to-xml when setting the pw on >= 10.7' do subject.expects(:convert_binary_to_xml).returns({'SALTED-SHA512' => StringIO.new(pw_string)}) subject.expects(:convert_xml_to_binary).returns(binary_plist) Puppet::FileSystem.expects(:exist?).with(plist_path).once.returns(true) Plist.expects(:parse_xml).returns(shadow_hash_data) # On Mac OS X 10.7 we first need to convert to xml subject.expects(:plutil).with('-convert', 'xml1', '-o', '/dev/stdout', plist_path) # And again back to a binary plist or DirectoryService will complain subject.expects(:plutil).with('-convert', 'binary1', plist_path) Plist::Emit.expects(:save_plist).with(shadow_hash_data, plist_path) subject.set_password('jeff', 'uid', sha512_hash) end it '[#13686] should handle an empty ShadowHashData field in the users plist' do subject.expects(:convert_xml_to_binary).returns(binary_plist) Puppet::FileSystem.expects(:exist?).with(plist_path).once.returns(true) Plist.expects(:parse_xml).returns({'ShadowHashData' => nil}) subject.expects(:plutil).with('-convert', 'xml1', '-o', '/dev/stdout', plist_path) subject.expects(:plutil).with('-convert', 'binary1', plist_path) Plist::Emit.expects(:save_plist) subject.set_password('jeff', 'uid', sha512_hash) end end describe '(#4855) directoryservice group resource failure' do let :provider_class do Puppet::Type.type(:group).provider(:directoryservice) end let :group_members do ['root','jeff'] end let :user_account do ['root'] end let :stub_resource do stub('resource') end subject do provider_class.new(stub_resource) end before :each do @resource = stub("resource") @provider = provider_class.new(@resource) end it 'should delete a group member if the user does not exist' do stub_resource.stubs(:[]).with(:name).returns('fake_group') stub_resource.stubs(:name).returns('fake_group') subject.expects(:execute).with([:dseditgroup, '-o', 'edit', '-n', '.', '-d', 'jeff', 'fake_group']).raises(Puppet::ExecutionFailure, 'it broke') subject.expects(:execute).with([:dscl, '.', '-delete', '/Groups/fake_group', 'GroupMembership', 'jeff']) subject.remove_unwanted_members(group_members, user_account) end end diff --git a/spec/unit/type/cron_spec.rb b/spec/unit/type/cron_spec.rb index b4a853173..82f646290 100755 --- a/spec/unit/type/cron_spec.rb +++ b/spec/unit/type/cron_spec.rb @@ -1,543 +1,543 @@ #! /usr/bin/env ruby require 'spec_helper' describe Puppet::Type.type(:cron), :unless => Puppet.features.microsoft_windows? do let(:simple_provider) do @provider_class = described_class.provide(:simple) { mk_resource_methods } @provider_class.stubs(:suitable?).returns true @provider_class end before :each do described_class.stubs(:defaultprovider).returns @provider_class end after :each do described_class.unprovide(:simple) end it "should have :name be its namevar" do described_class.key_attributes.should == [:name] end describe "when validating attributes" do [:name, :provider].each do |param| it "should have a #{param} parameter" do described_class.attrtype(param).should == :param end end [:command, :special, :minute, :hour, :weekday, :month, :monthday, :environment, :user, :target].each do |property| it "should have a #{property} property" do described_class.attrtype(property).should == :property end end [:command, :minute, :hour, :weekday, :month, :monthday].each do |cronparam| it "should have #{cronparam} of type CronParam" do described_class.attrclass(cronparam).ancestors.should include CronParam end end end describe "when validating values" do describe "ensure" do it "should support present as a value for ensure" do expect { described_class.new(:name => 'foo', :ensure => :present) }.to_not raise_error end it "should support absent as a value for ensure" do expect { described_class.new(:name => 'foo', :ensure => :present) }.to_not raise_error end it "should not support other values" do expect { described_class.new(:name => 'foo', :ensure => :foo) }.to raise_error(Puppet::Error, /Invalid value/) end end describe "command" do it "should discard leading spaces" do described_class.new(:name => 'foo', :command => " /bin/true")[:command].should_not match Regexp.new(" ") end it "should discard trailing spaces" do described_class.new(:name => 'foo', :command => "/bin/true ")[:command].should_not match Regexp.new(" ") end end describe "minute" do it "should support absent" do expect { described_class.new(:name => 'foo', :minute => 'absent') }.to_not raise_error end it "should support *" do expect { described_class.new(:name => 'foo', :minute => '*') }.to_not raise_error end it "should translate absent to :absent" do described_class.new(:name => 'foo', :minute => 'absent')[:minute].should == :absent end it "should translate * to :absent" do described_class.new(:name => 'foo', :minute => '*')[:minute].should == :absent end it "should support valid single values" do expect { described_class.new(:name => 'foo', :minute => '0') }.to_not raise_error expect { described_class.new(:name => 'foo', :minute => '1') }.to_not raise_error expect { described_class.new(:name => 'foo', :minute => '59') }.to_not raise_error end it "should not support non numeric characters" do expect { described_class.new(:name => 'foo', :minute => 'z59') }.to raise_error(Puppet::Error, /z59 is not a valid minute/) expect { described_class.new(:name => 'foo', :minute => '5z9') }.to raise_error(Puppet::Error, /5z9 is not a valid minute/) expect { described_class.new(:name => 'foo', :minute => '59z') }.to raise_error(Puppet::Error, /59z is not a valid minute/) end it "should not support single values out of range" do expect { described_class.new(:name => 'foo', :minute => '-1') }.to raise_error(Puppet::Error, /-1 is not a valid minute/) expect { described_class.new(:name => 'foo', :minute => '60') }.to raise_error(Puppet::Error, /60 is not a valid minute/) expect { described_class.new(:name => 'foo', :minute => '61') }.to raise_error(Puppet::Error, /61 is not a valid minute/) expect { described_class.new(:name => 'foo', :minute => '120') }.to raise_error(Puppet::Error, /120 is not a valid minute/) end it "should support valid multiple values" do expect { described_class.new(:name => 'foo', :minute => ['0','1','59'] ) }.to_not raise_error expect { described_class.new(:name => 'foo', :minute => ['40','30','20'] ) }.to_not raise_error expect { described_class.new(:name => 'foo', :minute => ['10','30','20'] ) }.to_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid expect { described_class.new(:name => 'foo', :minute => ['0','1','60'] ) }.to raise_error(Puppet::Error, /60 is not a valid minute/) expect { described_class.new(:name => 'foo', :minute => ['0','120','59'] ) }.to raise_error(Puppet::Error, /120 is not a valid minute/) expect { described_class.new(:name => 'foo', :minute => ['-1','1','59'] ) }.to raise_error(Puppet::Error, /-1 is not a valid minute/) # two invalid expect { described_class.new(:name => 'foo', :minute => ['0','61','62'] ) }.to raise_error(Puppet::Error, /(61|62) is not a valid minute/) # all invalid expect { described_class.new(:name => 'foo', :minute => ['-1','61','62'] ) }.to raise_error(Puppet::Error, /(-1|61|62) is not a valid minute/) end it "should support valid step syntax" do expect { described_class.new(:name => 'foo', :minute => '*/2' ) }.to_not raise_error expect { described_class.new(:name => 'foo', :minute => '10-16/2' ) }.to_not raise_error end it "should not support invalid steps" do expect { described_class.new(:name => 'foo', :minute => '*/A' ) }.to raise_error(Puppet::Error, /\*\/A is not a valid minute/) expect { described_class.new(:name => 'foo', :minute => '*/2A' ) }.to raise_error(Puppet::Error, /\*\/2A is not a valid minute/) # As it turns out cron does not complaining about steps that exceed the valid range # expect { described_class.new(:name => 'foo', :minute => '*/120' ) }.to raise_error(Puppet::Error, /is not a valid minute/) end end describe "hour" do it "should support absent" do expect { described_class.new(:name => 'foo', :hour => 'absent') }.to_not raise_error end it "should support *" do expect { described_class.new(:name => 'foo', :hour => '*') }.to_not raise_error end it "should translate absent to :absent" do described_class.new(:name => 'foo', :hour => 'absent')[:hour].should == :absent end it "should translate * to :absent" do described_class.new(:name => 'foo', :hour => '*')[:hour].should == :absent end it "should support valid single values" do expect { described_class.new(:name => 'foo', :hour => '0') }.to_not raise_error expect { described_class.new(:name => 'foo', :hour => '11') }.to_not raise_error expect { described_class.new(:name => 'foo', :hour => '12') }.to_not raise_error expect { described_class.new(:name => 'foo', :hour => '13') }.to_not raise_error expect { described_class.new(:name => 'foo', :hour => '23') }.to_not raise_error end it "should not support non numeric characters" do expect { described_class.new(:name => 'foo', :hour => 'z15') }.to raise_error(Puppet::Error, /z15 is not a valid hour/) expect { described_class.new(:name => 'foo', :hour => '1z5') }.to raise_error(Puppet::Error, /1z5 is not a valid hour/) expect { described_class.new(:name => 'foo', :hour => '15z') }.to raise_error(Puppet::Error, /15z is not a valid hour/) end it "should not support single values out of range" do expect { described_class.new(:name => 'foo', :hour => '-1') }.to raise_error(Puppet::Error, /-1 is not a valid hour/) expect { described_class.new(:name => 'foo', :hour => '24') }.to raise_error(Puppet::Error, /24 is not a valid hour/) expect { described_class.new(:name => 'foo', :hour => '120') }.to raise_error(Puppet::Error, /120 is not a valid hour/) end it "should support valid multiple values" do expect { described_class.new(:name => 'foo', :hour => ['0','1','23'] ) }.to_not raise_error expect { described_class.new(:name => 'foo', :hour => ['5','16','14'] ) }.to_not raise_error expect { described_class.new(:name => 'foo', :hour => ['16','13','9'] ) }.to_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid expect { described_class.new(:name => 'foo', :hour => ['0','1','24'] ) }.to raise_error(Puppet::Error, /24 is not a valid hour/) expect { described_class.new(:name => 'foo', :hour => ['0','-1','5'] ) }.to raise_error(Puppet::Error, /-1 is not a valid hour/) expect { described_class.new(:name => 'foo', :hour => ['-1','1','23'] ) }.to raise_error(Puppet::Error, /-1 is not a valid hour/) # two invalid expect { described_class.new(:name => 'foo', :hour => ['0','25','26'] ) }.to raise_error(Puppet::Error, /(25|26) is not a valid hour/) # all invalid expect { described_class.new(:name => 'foo', :hour => ['-1','24','120'] ) }.to raise_error(Puppet::Error, /(-1|24|120) is not a valid hour/) end it "should support valid step syntax" do expect { described_class.new(:name => 'foo', :hour => '*/2' ) }.to_not raise_error expect { described_class.new(:name => 'foo', :hour => '10-18/4' ) }.to_not raise_error end it "should not support invalid steps" do expect { described_class.new(:name => 'foo', :hour => '*/A' ) }.to raise_error(Puppet::Error, /\*\/A is not a valid hour/) expect { described_class.new(:name => 'foo', :hour => '*/2A' ) }.to raise_error(Puppet::Error, /\*\/2A is not a valid hour/) # As it turns out cron does not complaining about steps that exceed the valid range # expect { described_class.new(:name => 'foo', :hour => '*/26' ) }.to raise_error(Puppet::Error, /is not a valid hour/) end end describe "weekday" do it "should support absent" do expect { described_class.new(:name => 'foo', :weekday => 'absent') }.to_not raise_error end it "should support *" do expect { described_class.new(:name => 'foo', :weekday => '*') }.to_not raise_error end it "should translate absent to :absent" do described_class.new(:name => 'foo', :weekday => 'absent')[:weekday].should == :absent end it "should translate * to :absent" do described_class.new(:name => 'foo', :weekday => '*')[:weekday].should == :absent end it "should support valid numeric weekdays" do expect { described_class.new(:name => 'foo', :weekday => '0') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => '1') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => '6') }.to_not raise_error # According to http://www.manpagez.com/man/5/crontab 7 is also valid (Sunday) expect { described_class.new(:name => 'foo', :weekday => '7') }.to_not raise_error end it "should support valid weekdays as words (long version)" do expect { described_class.new(:name => 'foo', :weekday => 'Monday') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Tuesday') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Wednesday') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Thursday') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Friday') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Saturday') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Sunday') }.to_not raise_error end it "should support valid weekdays as words (3 character version)" do expect { described_class.new(:name => 'foo', :weekday => 'Mon') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Tue') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Wed') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Thu') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Fri') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Sat') }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => 'Sun') }.to_not raise_error end it "should not support numeric values out of range" do expect { described_class.new(:name => 'foo', :weekday => '-1') }.to raise_error(Puppet::Error, /-1 is not a valid weekday/) expect { described_class.new(:name => 'foo', :weekday => '8') }.to raise_error(Puppet::Error, /8 is not a valid weekday/) end it "should not support invalid weekday names" do expect { described_class.new(:name => 'foo', :weekday => 'Sar') }.to raise_error(Puppet::Error, /Sar is not a valid weekday/) end it "should support valid multiple values" do expect { described_class.new(:name => 'foo', :weekday => ['0','1','6'] ) }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => ['Mon','Wed','Friday'] ) }.to_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid expect { described_class.new(:name => 'foo', :weekday => ['0','1','8'] ) }.to raise_error(Puppet::Error, /8 is not a valid weekday/) expect { described_class.new(:name => 'foo', :weekday => ['Mon','Fii','Sat'] ) }.to raise_error(Puppet::Error, /Fii is not a valid weekday/) # two invalid expect { described_class.new(:name => 'foo', :weekday => ['Mos','Fii','Sat'] ) }.to raise_error(Puppet::Error, /(Mos|Fii) is not a valid weekday/) # all invalid expect { described_class.new(:name => 'foo', :weekday => ['Mos','Fii','Saa'] ) }.to raise_error(Puppet::Error, /(Mos|Fii|Saa) is not a valid weekday/) expect { described_class.new(:name => 'foo', :weekday => ['-1','8','11'] ) }.to raise_error(Puppet::Error, /(-1|8|11) is not a valid weekday/) end it "should support valid step syntax" do expect { described_class.new(:name => 'foo', :weekday => '*/2' ) }.to_not raise_error expect { described_class.new(:name => 'foo', :weekday => '0-4/2' ) }.to_not raise_error end it "should not support invalid steps" do expect { described_class.new(:name => 'foo', :weekday => '*/A' ) }.to raise_error(Puppet::Error, /\*\/A is not a valid weekday/) expect { described_class.new(:name => 'foo', :weekday => '*/2A' ) }.to raise_error(Puppet::Error, /\*\/2A is not a valid weekday/) # As it turns out cron does not complaining about steps that exceed the valid range # expect { described_class.new(:name => 'foo', :weekday => '*/9' ) }.to raise_error(Puppet::Error, /is not a valid weekday/) end end describe "month" do it "should support absent" do expect { described_class.new(:name => 'foo', :month => 'absent') }.to_not raise_error end it "should support *" do expect { described_class.new(:name => 'foo', :month => '*') }.to_not raise_error end it "should translate absent to :absent" do described_class.new(:name => 'foo', :month => 'absent')[:month].should == :absent end it "should translate * to :absent" do described_class.new(:name => 'foo', :month => '*')[:month].should == :absent end it "should support valid numeric values" do expect { described_class.new(:name => 'foo', :month => '1') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => '12') }.to_not raise_error end it "should support valid months as words" do expect { described_class.new(:name => 'foo', :month => 'January') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'February') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'March') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'April') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'May') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'June') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'July') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'August') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'September') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'October') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'November') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'December') }.to_not raise_error end it "should support valid months as words (3 character short version)" do expect { described_class.new(:name => 'foo', :month => 'Jan') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'Feb') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'Mar') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'Apr') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'May') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'Jun') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'Jul') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'Aug') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'Sep') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'Oct') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'Nov') }.to_not raise_error expect { described_class.new(:name => 'foo', :month => 'Dec') }.to_not raise_error end it "should not support numeric values out of range" do expect { described_class.new(:name => 'foo', :month => '-1') }.to raise_error(Puppet::Error, /-1 is not a valid month/) expect { described_class.new(:name => 'foo', :month => '0') }.to raise_error(Puppet::Error, /0 is not a valid month/) expect { described_class.new(:name => 'foo', :month => '13') }.to raise_error(Puppet::Error, /13 is not a valid month/) end it "should not support words that are not valid months" do expect { described_class.new(:name => 'foo', :month => 'Jal') }.to raise_error(Puppet::Error, /Jal is not a valid month/) end it "should not support single values out of range" do expect { described_class.new(:name => 'foo', :month => '-1') }.to raise_error(Puppet::Error, /-1 is not a valid month/) expect { described_class.new(:name => 'foo', :month => '60') }.to raise_error(Puppet::Error, /60 is not a valid month/) expect { described_class.new(:name => 'foo', :month => '61') }.to raise_error(Puppet::Error, /61 is not a valid month/) expect { described_class.new(:name => 'foo', :month => '120') }.to raise_error(Puppet::Error, /120 is not a valid month/) end it "should support valid multiple values" do expect { described_class.new(:name => 'foo', :month => ['1','9','12'] ) }.to_not raise_error expect { described_class.new(:name => 'foo', :month => ['Jan','March','Jul'] ) }.to_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid expect { described_class.new(:name => 'foo', :month => ['0','1','12'] ) }.to raise_error(Puppet::Error, /0 is not a valid month/) expect { described_class.new(:name => 'foo', :month => ['1','13','10'] ) }.to raise_error(Puppet::Error, /13 is not a valid month/) expect { described_class.new(:name => 'foo', :month => ['Jan','Feb','Jxx'] ) }.to raise_error(Puppet::Error, /Jxx is not a valid month/) # two invalid expect { described_class.new(:name => 'foo', :month => ['Jan','Fex','Jux'] ) }.to raise_error(Puppet::Error, /(Fex|Jux) is not a valid month/) # all invalid expect { described_class.new(:name => 'foo', :month => ['-1','0','13'] ) }.to raise_error(Puppet::Error, /(-1|0|13) is not a valid month/) expect { described_class.new(:name => 'foo', :month => ['Jax','Fex','Aux'] ) }.to raise_error(Puppet::Error, /(Jax|Fex|Aux) is not a valid month/) end it "should support valid step syntax" do expect { described_class.new(:name => 'foo', :month => '*/2' ) }.to_not raise_error expect { described_class.new(:name => 'foo', :month => '1-12/3' ) }.to_not raise_error end it "should not support invalid steps" do expect { described_class.new(:name => 'foo', :month => '*/A' ) }.to raise_error(Puppet::Error, /\*\/A is not a valid month/) expect { described_class.new(:name => 'foo', :month => '*/2A' ) }.to raise_error(Puppet::Error, /\*\/2A is not a valid month/) # As it turns out cron does not complaining about steps that exceed the valid range # expect { described_class.new(:name => 'foo', :month => '*/13' ) }.to raise_error(Puppet::Error, /is not a valid month/) end end describe "monthday" do it "should support absent" do expect { described_class.new(:name => 'foo', :monthday => 'absent') }.to_not raise_error end it "should support *" do expect { described_class.new(:name => 'foo', :monthday => '*') }.to_not raise_error end it "should translate absent to :absent" do described_class.new(:name => 'foo', :monthday => 'absent')[:monthday].should == :absent end it "should translate * to :absent" do described_class.new(:name => 'foo', :monthday => '*')[:monthday].should == :absent end it "should support valid single values" do expect { described_class.new(:name => 'foo', :monthday => '1') }.to_not raise_error expect { described_class.new(:name => 'foo', :monthday => '30') }.to_not raise_error expect { described_class.new(:name => 'foo', :monthday => '31') }.to_not raise_error end it "should not support non numeric characters" do expect { described_class.new(:name => 'foo', :monthday => 'z23') }.to raise_error(Puppet::Error, /z23 is not a valid monthday/) expect { described_class.new(:name => 'foo', :monthday => '2z3') }.to raise_error(Puppet::Error, /2z3 is not a valid monthday/) expect { described_class.new(:name => 'foo', :monthday => '23z') }.to raise_error(Puppet::Error, /23z is not a valid monthday/) end it "should not support single values out of range" do expect { described_class.new(:name => 'foo', :monthday => '-1') }.to raise_error(Puppet::Error, /-1 is not a valid monthday/) expect { described_class.new(:name => 'foo', :monthday => '0') }.to raise_error(Puppet::Error, /0 is not a valid monthday/) expect { described_class.new(:name => 'foo', :monthday => '32') }.to raise_error(Puppet::Error, /32 is not a valid monthday/) end it "should support valid multiple values" do expect { described_class.new(:name => 'foo', :monthday => ['1','23','31'] ) }.to_not raise_error expect { described_class.new(:name => 'foo', :monthday => ['31','23','1'] ) }.to_not raise_error expect { described_class.new(:name => 'foo', :monthday => ['1','31','23'] ) }.to_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid expect { described_class.new(:name => 'foo', :monthday => ['1','23','32'] ) }.to raise_error(Puppet::Error, /32 is not a valid monthday/) expect { described_class.new(:name => 'foo', :monthday => ['-1','12','23'] ) }.to raise_error(Puppet::Error, /-1 is not a valid monthday/) expect { described_class.new(:name => 'foo', :monthday => ['13','32','30'] ) }.to raise_error(Puppet::Error, /32 is not a valid monthday/) # two invalid expect { described_class.new(:name => 'foo', :monthday => ['-1','0','23'] ) }.to raise_error(Puppet::Error, /(-1|0) is not a valid monthday/) # all invalid expect { described_class.new(:name => 'foo', :monthday => ['-1','0','32'] ) }.to raise_error(Puppet::Error, /(-1|0|32) is not a valid monthday/) end it "should support valid step syntax" do expect { described_class.new(:name => 'foo', :monthday => '*/2' ) }.to_not raise_error expect { described_class.new(:name => 'foo', :monthday => '10-16/2' ) }.to_not raise_error end it "should not support invalid steps" do expect { described_class.new(:name => 'foo', :monthday => '*/A' ) }.to raise_error(Puppet::Error, /\*\/A is not a valid monthday/) expect { described_class.new(:name => 'foo', :monthday => '*/2A' ) }.to raise_error(Puppet::Error, /\*\/2A is not a valid monthday/) # As it turns out cron does not complaining about steps that exceed the valid range # expect { described_class.new(:name => 'foo', :monthday => '*/32' ) }.to raise_error(Puppet::Error, /is not a valid monthday/) end end describe "special" do %w(reboot yearly annually monthly weekly daily midnight hourly).each do |value| it "should support the value '#{value}'" do - expect { described_class.new(:name => 'foo', :special => value ) }.to_not raise_error(Puppet::Error, /cannot specify both a special schedule and a value/) + expect { described_class.new(:name => 'foo', :special => value ) }.to_not raise_error end end context "when combined with numeric schedule fields" do context "which are 'absent'" do [ %w(reboot yearly annually monthly weekly daily midnight hourly), :absent ].flatten.each { |value| it "should accept the value '#{value}' for special" do expect { described_class.new(:name => 'foo', :minute => :absent, :special => value ) - }.to_not raise_error(Puppet::Error, /cannot specify both a special schedule and a value/) + }.to_not raise_error end } end context "which are not absent" do %w(reboot yearly annually monthly weekly daily midnight hourly).each { |value| it "should not accept the value '#{value}' for special" do expect { described_class.new(:name => 'foo', :minute => "1", :special => value ) }.to raise_error(Puppet::Error, /cannot specify both a special schedule and a value/) end } it "should accept the 'absent' value for special" do expect { described_class.new(:name => 'foo', :minute => "1", :special => :absent ) - }.to_not raise_error(Puppet::Error, /cannot specify both a special schedule and a value/) + }.to_not raise_error end end end end describe "environment" do it "it should accept an :environment that looks like a path" do expect do described_class.new(:name => 'foo',:environment => 'PATH=/bin:/usr/bin:/usr/sbin') end.to_not raise_error end it "should not accept environment variables that do not contain '='" do expect do described_class.new(:name => 'foo',:environment => 'INVALID') end.to raise_error(Puppet::Error, /Invalid environment setting "INVALID"/) end it "should accept empty environment variables that do not contain '='" do expect do described_class.new(:name => 'foo',:environment => 'MAILTO=') end.to_not raise_error end it "should accept 'absent'" do expect do described_class.new(:name => 'foo',:environment => 'absent') end.to_not raise_error end end end describe "when autorequiring resources" do before :each do @user_bob = Puppet::Type.type(:user).new(:name => 'bob', :ensure => :present) @user_alice = Puppet::Type.type(:user).new(:name => 'alice', :ensure => :present) @catalog = Puppet::Resource::Catalog.new @catalog.add_resource @user_bob, @user_alice end it "should autorequire the user" do @resource = described_class.new(:name => 'dummy', :command => '/usr/bin/uptime', :user => 'alice') @catalog.add_resource @resource req = @resource.autorequire req.size.should == 1 req[0].target.must == @resource req[0].source.must == @user_alice end end it "should not require a command when removing an entry" do entry = described_class.new(:name => "test_entry", :ensure => :absent) entry.value(:command).should == nil end it "should default to user => root if Etc.getpwuid(Process.uid) returns nil (#12357)" do Etc.expects(:getpwuid).returns(nil) entry = described_class.new(:name => "test_entry", :ensure => :present) entry.value(:user).should eql "root" end end diff --git a/spec/unit/type/nagios_spec.rb b/spec/unit/type/nagios_spec.rb index bc96c26d4..f82b760e4 100755 --- a/spec/unit/type/nagios_spec.rb +++ b/spec/unit/type/nagios_spec.rb @@ -1,284 +1,284 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/external/nagios' describe "Nagios parser" do NONESCAPED_SEMICOLON_COMMENT = <<-'EOL' define host{ use linux-server ; Name of host template to use host_name localhost alias localhost address 127.0.0.1 } define command{ command_name notify-host-by-email command_line /usr/bin/printf "%b" "***** Nagios *****\n\nNotification Type: $NOTIFICATIONTYPE$\nHost: $HOSTNAME$\nState: $HOSTSTATE$\nAddress: $HOSTADDRESS$\nInfo: $HOSTOUTPUT$\n\nDate/Time: $LONGDATETIME$\n" | /usr/bin/mail -s "** $NOTIFICATIONTYPE$ Host Alert: $HOSTNAME$ is $HOSTSTATE$ **" $CONTACTEMAIL$ } EOL LINE_COMMENT_SNIPPET = <<-'EOL' # This is a comment starting at the beginning of a line define command{ # This is a comment starting at the beginning of a line command_name command_name # This is a comment starting at the beginning of a line ## --PUPPET_NAME-- (called '_naginator_name' in the manifest) command_name command_line command_line # This is a comment starting at the beginning of a line } # This is a comment starting at the beginning of a line EOL LINE_COMMENT_SNIPPET2 = <<-'EOL' define host{ use linux-server ; Name of host template to use host_name localhost alias localhost address 127.0.0.1 } define command{ command_name command_name2 command_line command_line2 } EOL UNKNOWN_NAGIOS_OBJECT_DEFINITION = <<-'EOL' define command2{ command_name notify-host-by-email command_line /usr/bin/printf "%b" "***** Nagios *****\n\nNotification Type: $NOTIFICATIONTYPE$\nHost: $HOSTNAME$\nState: $HOSTSTATE$\nAddress: $HOSTADDRESS$\nInfo: $HOSTOUTPUT$\n\nDate/Time: $LONGDATETIME$\n" | /usr/bin/mail -s "** $NOTIFICATIONTYPE$ Host Alert: $HOSTNAME$ is $HOSTSTATE$ **" $CONTACTEMAIL$ } EOL MISSING_CLOSING_CURLY_BRACKET = <<-'EOL' define command{ command_name notify-host-by-email command_line /usr/bin/printf "%b" "***** Nagios *****\n\nNotification Type: $NOTIFICATIONTYPE$\nHost: $HOSTNAME$\nState: $HOSTSTATE$\nAddress: $HOSTADDRESS$\nInfo: $HOSTOUTPUT$\n\nDate/Time: $LONGDATETIME$\n" | /usr/bin/mail -s "** $NOTIFICATIONTYPE$ Host Alert: $HOSTNAME$ is $HOSTSTATE$ **" $CONTACTEMAIL$ EOL ESCAPED_SEMICOLON = <<-'EOL' define command { command_name nagios_table_size command_line $USER3$/check_mysql_health --hostname localhost --username nagioschecks --password nagiosCheckPWD --mode sql --name "SELECT ROUND(Data_length/1024) as Data_kBytes from INFORMATION_SCHEMA.TABLES where TABLE_NAME=\"$ARG1$\"\;" --name2 "table size" --units kBytes -w $ARG2$ -c $ARG3$ } EOL POUND_SIGN_HASH_SYMBOL_NOT_IN_FIRST_COLUMN = <<-'EOL' define command { command_name notify-by-irc command_line /usr/local/bin/riseup-nagios-client.pl "$HOSTNAME$ ($SERVICEDESC$) $NOTIFICATIONTYPE$ #$SERVICEATTEMPT$ $SERVICESTATETYPE$ $SERVICEEXECUTIONTIME$s $SERVICELATENCY$s $SERVICEOUTPUT$ $SERVICEPERFDATA$" } EOL ANOTHER_ESCAPED_SEMICOLON = <<-EOL define command { \tcommand_line LC_ALL=en_US.UTF-8 /usr/lib/nagios/plugins/check_haproxy -u 'http://blah:blah@$HOSTADDRESS$:8080/haproxy?stats\\;csv' \tcommand_name check_haproxy } EOL it "should parse without error" do parser = Nagios::Parser.new expect { results = parser.parse(NONESCAPED_SEMICOLON_COMMENT) }.to_not raise_error end describe "when parsing a statement" do parser = Nagios::Parser.new results = parser.parse(NONESCAPED_SEMICOLON_COMMENT) results.each do |obj| it "should have the proper base type" do obj.should be_a_kind_of(Nagios::Base) end end end it "should raise an error when an incorrect object definition is present" do parser = Nagios::Parser.new expect { results = parser.parse(UNKNOWN_NAGIOS_OBJECT_DEFINITION) }.to raise_error Nagios::Base::UnknownNagiosType end it "should raise an error when syntax is not correct" do parser = Nagios::Parser.new expect { results = parser.parse(MISSING_CLOSING_CURLY_BRACKET) }.to raise_error Nagios::Parser::SyntaxError end describe "when encoutering ';'" do it "should not throw an exception" do parser = Nagios::Parser.new expect { results = parser.parse(ESCAPED_SEMICOLON) - }.to_not raise_error Nagios::Parser::SyntaxError + }.to_not raise_error end it "should ignore it if it is a comment" do parser = Nagios::Parser.new results = parser.parse(NONESCAPED_SEMICOLON_COMMENT) results[0].use.should eql("linux-server") end it "should parse correctly if it is escaped" do parser = Nagios::Parser.new results = parser.parse(ESCAPED_SEMICOLON) results[0].command_line.should eql("$USER3$/check_mysql_health --hostname localhost --username nagioschecks --password nagiosCheckPWD --mode sql --name \"SELECT ROUND(Data_length/1024) as Data_kBytes from INFORMATION_SCHEMA.TABLES where TABLE_NAME=\\\"$ARG1$\\\";\" --name2 \"table size\" --units kBytes -w $ARG2$ -c $ARG3$") end end describe "when encountering '#'" do it "should not throw an exception" do parser = Nagios::Parser.new expect { results = parser.parse(POUND_SIGN_HASH_SYMBOL_NOT_IN_FIRST_COLUMN) - }.to_not raise_error Nagios::Parser::SyntaxError + }.to_not raise_error end it "should ignore it at the beginning of a line" do parser = Nagios::Parser.new results = parser.parse(LINE_COMMENT_SNIPPET) results[0].command_line.should eql("command_line") end it "should let it go anywhere else" do parser = Nagios::Parser.new results = parser.parse(POUND_SIGN_HASH_SYMBOL_NOT_IN_FIRST_COLUMN) results[0].command_line.should eql("/usr/local/bin/riseup-nagios-client.pl \"$HOSTNAME$ ($SERVICEDESC$) $NOTIFICATIONTYPE$ \#$SERVICEATTEMPT$ $SERVICESTATETYPE$ $SERVICEEXECUTIONTIME$s $SERVICELATENCY$s $SERVICEOUTPUT$ $SERVICEPERFDATA$\"") end end describe "when encountering ';' again" do it "should not throw an exception" do parser = Nagios::Parser.new expect { results = parser.parse(ANOTHER_ESCAPED_SEMICOLON) - }.to_not raise_error Nagios::Parser::SyntaxError + }.to_not raise_error end it "should parse correctly" do parser = Nagios::Parser.new results = parser.parse(ANOTHER_ESCAPED_SEMICOLON) results[0].command_line.should eql("LC_ALL=en_US.UTF-8 /usr/lib/nagios/plugins/check_haproxy -u 'http://blah:blah@$HOSTADDRESS$:8080/haproxy?stats;csv'") end end it "should be idempotent" do parser = Nagios::Parser.new src = ANOTHER_ESCAPED_SEMICOLON.dup results = parser.parse(src) nagios_type = Nagios::Base.create(:command) nagios_type.command_name = results[0].command_name nagios_type.command_line = results[0].command_line nagios_type.to_s.should eql(ANOTHER_ESCAPED_SEMICOLON) end end describe "Nagios generator" do it "should escape ';'" do param = '$USER3$/check_mysql_health --hostname localhost --username nagioschecks --password nagiosCheckPWD --mode sql --name "SELECT ROUND(Data_length/1024) as Data_kBytes from INFORMATION_SCHEMA.TABLES where TABLE_NAME=\"$ARG1$\";" --name2 "table size" --units kBytes -w $ARG2$ -c $ARG3$' nagios_type = Nagios::Base.create(:command) nagios_type.command_line = param nagios_type.to_s.should eql("define command {\n\tcommand_line $USER3$/check_mysql_health --hostname localhost --username nagioschecks --password nagiosCheckPWD --mode sql --name \"SELECT ROUND(Data_length/1024) as Data_kBytes from INFORMATION_SCHEMA.TABLES where TABLE_NAME=\\\"$ARG1$\\\"\\;\" --name2 \"table size\" --units kBytes -w $ARG2$ -c $ARG3$\n}\n") end it "should escape ';' if it is not already the case" do param = "LC_ALL=en_US.UTF-8 /usr/lib/nagios/plugins/check_haproxy -u 'http://blah:blah@$HOSTADDRESS$:8080/haproxy?stats;csv'" nagios_type = Nagios::Base.create(:command) nagios_type.command_line = param nagios_type.to_s.should eql("define command {\n\tcommand_line LC_ALL=en_US.UTF-8 /usr/lib/nagios/plugins/check_haproxy -u 'http://blah:blah@$HOSTADDRESS$:8080/haproxy?stats\\;csv'\n}\n") end it "should be idempotent" do param = '$USER3$/check_mysql_health --hostname localhost --username nagioschecks --password nagiosCheckPWD --mode sql --name "SELECT ROUND(Data_length/1024) as Data_kBytes from INFORMATION_SCHEMA.TABLES where TABLE_NAME=\"$ARG1$\";" --name2 "table size" --units kBytes -w $ARG2$ -c $ARG3$' nagios_type = Nagios::Base.create(:command) nagios_type.command_line = param parser = Nagios::Parser.new results = parser.parse(nagios_type.to_s) results[0].command_line.should eql(param) end end describe "Nagios resource types" do Nagios::Base.eachtype do |name, nagios_type| puppet_type = Puppet::Type.type("nagios_#{name}") it "should have a valid type for #{name}" do puppet_type.should_not be_nil end next unless puppet_type describe puppet_type do it "should be defined as a Puppet resource type" do puppet_type.should_not be_nil end it "should have documentation" do puppet_type.instance_variable_get("@doc").should_not == "" end it "should have #{nagios_type.namevar} as its key attribute" do puppet_type.key_attributes.should == [nagios_type.namevar] end it "should have documentation for its #{nagios_type.namevar} parameter" do puppet_type.attrclass(nagios_type.namevar).instance_variable_get("@doc").should_not be_nil end it "should have an ensure property" do puppet_type.should be_validproperty(:ensure) end it "should have a target property" do puppet_type.should be_validproperty(:target) end it "should have documentation for its target property" do puppet_type.attrclass(:target).instance_variable_get("@doc").should_not be_nil end [ :owner, :group, :mode ].each do |fileprop| it "should have a #{fileprop} parameter" do puppet_type.parameters.should be_include(fileprop) end end nagios_type.parameters.reject { |param| param == nagios_type.namevar or param.to_s =~ /^[0-9]/ }.each do |param| it "should have a #{param} property" do puppet_type.should be_validproperty(param) end it "should have documentation for its #{param} property" do puppet_type.attrclass(param).instance_variable_get("@doc").should_not be_nil end end nagios_type.parameters.find_all { |param| param.to_s =~ /^[0-9]/ }.each do |param| it "should have not have a #{param} property" do puppet_type.should_not be_validproperty(:param) end end end end end diff --git a/spec/unit/type/resources_spec.rb b/spec/unit/type/resources_spec.rb index d2e259042..e985b9752 100755 --- a/spec/unit/type/resources_spec.rb +++ b/spec/unit/type/resources_spec.rb @@ -1,318 +1,318 @@ #! /usr/bin/env ruby require 'spec_helper' resources = Puppet::Type.type(:resources) # There are still plenty of tests to port over from test/. describe resources do before :each do described_class.reset_system_users_max_uid! end describe "when initializing" do it "should fail if the specified resource type does not exist" do Puppet::Type.stubs(:type).with { |x| x.to_s.downcase == "resources"}.returns resources Puppet::Type.expects(:type).with("nosuchtype").returns nil lambda { resources.new :name => "nosuchtype" }.should raise_error(Puppet::Error) end it "should not fail when the specified resource type exists" do lambda { resources.new :name => "file" }.should_not raise_error end it "should set its :resource_type attribute" do resources.new(:name => "file").resource_type.should == Puppet::Type.type(:file) end end describe :purge do let (:instance) { described_class.new(:name => 'file') } it "defaults to false" do instance[:purge].should be_false end it "can be set to false" do instance[:purge] = 'false' end it "cannot be set to true for a resource type that does not accept ensure" do instance.resource_type.stubs(:respond_to?).returns true instance.resource_type.stubs(:validproperty?).returns false expect { instance[:purge] = 'yes' }.to raise_error Puppet::Error end it "cannot be set to true for a resource type that does not have instances" do instance.resource_type.stubs(:respond_to?).returns false instance.resource_type.stubs(:validproperty?).returns true expect { instance[:purge] = 'yes' }.to raise_error Puppet::Error end it "can be set to true for a resource type that has instances and can accept ensure" do instance.resource_type.stubs(:respond_to?).returns true instance.resource_type.stubs(:validproperty?).returns true - expect { instance[:purge] = 'yes' }.not_to raise_error Puppet::Error + expect { instance[:purge] = 'yes' }.to_not raise_error end end describe "#check_user purge behaviour" do describe "with unless_system_user => true" do before do @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => true @res.catalog = Puppet::Resource::Catalog.new Puppet::FileSystem.stubs(:exist?).with('/etc/login.defs').returns false end it "should never purge hardcoded system users" do %w{root nobody bin noaccess daemon sys}.each do |sys_user| @res.user_check(Puppet::Type.type(:user).new(:name => sys_user)).should be_false end end it "should not purge system users if unless_system_user => true" do user_hash = {:name => 'system_user', :uid => 125, :system => true} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_false end it "should purge non-system users if unless_system_user => true" do user_hash = {:name => 'system_user', :uid => described_class.system_users_max_uid + 1, :system => true} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_true end it "should not purge system users under 600 if unless_system_user => 600" do res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => 600 res.catalog = Puppet::Resource::Catalog.new user_hash = {:name => 'system_user', :uid => 500, :system => true} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) res.user_check(user).should be_false end end %w(FreeBSD OpenBSD).each do |os| describe "on #{os}" do before :each do Facter.stubs(:value).with(:kernel).returns(os) Facter.stubs(:value).with(:operatingsystem).returns(os) Facter.stubs(:value).with(:osfamily).returns(os) Puppet::FileSystem.stubs(:exist?).with('/etc/login.defs').returns false @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => true @res.catalog = Puppet::Resource::Catalog.new end it "should not purge system users under 1000" do user_hash = {:name => 'system_user', :uid => 999} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_false end it "should purge users over 999" do user_hash = {:name => 'system_user', :uid => 1000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_true end end end describe 'with login.defs present' do before :each do Puppet::FileSystem.expects(:exist?).with('/etc/login.defs').returns true Puppet::FileSystem.expects(:each_line).with('/etc/login.defs').yields(' UID_MIN 1234 # UID_MIN comment ') @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_system_user => true @res.catalog = Puppet::Resource::Catalog.new end it 'should not purge a system user' do user_hash = {:name => 'system_user', :uid => 1233} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_false end it 'should purge a non-system user' do user_hash = {:name => 'system_user', :uid => 1234} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_true end end describe "with unless_uid" do describe "with a uid array" do before do @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => [15_000, 15_001, 15_002] @res.catalog = Puppet::Resource::Catalog.new end it "should purge uids that are not in a specified array" do user_hash = {:name => 'special_user', :uid => 25_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_true end it "should not purge uids that are in a specified array" do user_hash = {:name => 'special_user', :uid => 15000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_false end end describe "with a single integer uid" do before do @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => 15_000 @res.catalog = Puppet::Resource::Catalog.new end it "should purge uids that are not specified" do user_hash = {:name => 'special_user', :uid => 25_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_true end it "should not purge uids that are specified" do user_hash = {:name => 'special_user', :uid => 15_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_false end end describe "with a single string uid" do before do @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => '15000' @res.catalog = Puppet::Resource::Catalog.new end it "should purge uids that are not specified" do user_hash = {:name => 'special_user', :uid => 25_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_true end it "should not purge uids that are specified" do user_hash = {:name => 'special_user', :uid => 15_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_false end end describe "with a mixed uid array" do before do @res = Puppet::Type.type(:resources).new :name => :user, :purge => true, :unless_uid => ['15000', 16_666] @res.catalog = Puppet::Resource::Catalog.new end it "should not purge ids in the range" do user_hash = {:name => 'special_user', :uid => 15_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_false end it "should not purge specified ids" do user_hash = {:name => 'special_user', :uid => 16_666} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_false end it "should purge unspecified ids" do user_hash = {:name => 'special_user', :uid => 17_000} user = Puppet::Type.type(:user).new(user_hash) user.stubs(:retrieve_resource).returns Puppet::Resource.new("user", user_hash[:name], :parameters => user_hash) @res.user_check(user).should be_true end end end end describe "#generate" do before do @host1 = Puppet::Type.type(:host).new(:name => 'localhost', :ip => '127.0.0.1') @catalog = Puppet::Resource::Catalog.new end describe "when dealing with non-purging resources" do before do @resources = Puppet::Type.type(:resources).new(:name => 'host') end it "should not generate any resource" do @resources.generate.should be_empty end end describe "when the catalog contains a purging resource" do before do @resources = Puppet::Type.type(:resources).new(:name => 'host', :purge => true) @purgeable_resource = Puppet::Type.type(:host).new(:name => 'localhost', :ip => '127.0.0.1') @catalog.add_resource @resources end it "should not generate a duplicate of that resource" do Puppet::Type.type(:host).stubs(:instances).returns [@host1] @catalog.add_resource @host1 @resources.generate.collect { |r| r.ref }.should_not include(@host1.ref) end it "should not include the skipped system users" do res = Puppet::Type.type(:resources).new :name => :user, :purge => true res.catalog = Puppet::Resource::Catalog.new root = Puppet::Type.type(:user).new(:name => "root") Puppet::Type.type(:user).expects(:instances).returns [ root ] list = res.generate names = list.collect { |r| r[:name] } names.should_not be_include("root") end describe "when generating a purgeable resource" do it "should be included in the generated resources" do Puppet::Type.type(:host).stubs(:instances).returns [@purgeable_resource] @resources.generate.collect { |r| r.ref }.should include(@purgeable_resource.ref) end end describe "when the instance's do not have an ensure property" do it "should not be included in the generated resources" do @no_ensure_resource = Puppet::Type.type(:exec).new(:name => "#{File.expand_path('/usr/bin/env')} echo") Puppet::Type.type(:host).stubs(:instances).returns [@no_ensure_resource] @resources.generate.collect { |r| r.ref }.should_not include(@no_ensure_resource.ref) end end describe "when the instance's ensure property does not accept absent" do it "should not be included in the generated resources" do @no_absent_resource = Puppet::Type.type(:service).new(:name => 'foobar') Puppet::Type.type(:host).stubs(:instances).returns [@no_absent_resource] @resources.generate.collect { |r| r.ref }.should_not include(@no_absent_resource.ref) end end describe "when checking the instance fails" do it "should not be included in the generated resources" do @purgeable_resource = Puppet::Type.type(:host).new(:name => 'foobar') Puppet::Type.type(:host).stubs(:instances).returns [@purgeable_resource] @resources.expects(:check).with(@purgeable_resource).returns(false) @resources.generate.collect { |r| r.ref }.should_not include(@purgeable_resource.ref) end end end end end diff --git a/spec/unit/util/tagging_spec.rb b/spec/unit/util/tagging_spec.rb index c2ebeaab3..53bb39d7a 100755 --- a/spec/unit/util/tagging_spec.rb +++ b/spec/unit/util/tagging_spec.rb @@ -1,162 +1,162 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/util/tagging' describe Puppet::Util::Tagging do let(:tagger) { Object.new.extend(Puppet::Util::Tagging) } it "should add tags to the returned tag list" do tagger.tag("one") expect(tagger.tags).to include("one") end it "should return a duplicate of the tag list, rather than the original" do tagger.tag("one") tags = tagger.tags tags << "two" expect(tagger.tags).to_not include("two") end it "should add all provided tags to the tag list" do tagger.tag("one", "two") expect(tagger.tags).to include("one") expect(tagger.tags).to include("two") end it "should fail on tags containing '*' characters" do expect { tagger.tag("bad*tag") }.to raise_error(Puppet::ParseError) end it "should fail on tags starting with '-' characters" do expect { tagger.tag("-badtag") }.to raise_error(Puppet::ParseError) end it "should fail on tags containing ' ' characters" do expect { tagger.tag("bad tag") }.to raise_error(Puppet::ParseError) end it "should allow alpha tags" do expect { tagger.tag("good_tag") }.not_to raise_error end it "should allow tags containing '.' characters" do - expect { tagger.tag("good.tag") }.to_not raise_error(Puppet::ParseError) + expect { tagger.tag("good.tag") }.to_not raise_error end it "should add qualified classes as tags" do tagger.tag("one::two") expect(tagger.tags).to include("one::two") end it "should add each part of qualified classes as tags" do tagger.tag("one::two::three") expect(tagger.tags).to include('one') expect(tagger.tags).to include("two") expect(tagger.tags).to include("three") end it "should indicate when the object is tagged with a provided tag" do tagger.tag("one") expect(tagger).to be_tagged("one") end it "should indicate when the object is not tagged with a provided tag" do expect(tagger).to_not be_tagged("one") end it "should indicate when the object is tagged with any tag in an array" do tagger.tag("one") expect(tagger).to be_tagged("one","two","three") end it "should indicate when the object is not tagged with any tag in an array" do tagger.tag("one") expect(tagger).to_not be_tagged("two","three") end context "when tagging" do it "converts symbols to strings" do tagger.tag(:hello) expect(tagger.tags).to include('hello') end it "downcases tags" do tagger.tag(:HEllO) tagger.tag("GooDByE") expect(tagger).to be_tagged("hello") expect(tagger).to be_tagged("goodbye") end it "accepts hyphenated tags" do tagger.tag("my-tag") expect(tagger).to be_tagged("my-tag") end end context "when querying if tagged" do it "responds true if queried on the entire set" do tagger.tag("one", "two") expect(tagger).to be_tagged("one", "two") end it "responds true if queried on a subset" do tagger.tag("one", "two", "three") expect(tagger).to be_tagged("two", "one") end it "responds true if queried on an overlapping but not fully contained set" do tagger.tag("one", "two") expect(tagger).to be_tagged("zero", "one") end it "responds false if queried on a disjoint set" do tagger.tag("one", "two", "three") expect(tagger).to_not be_tagged("five") end it "responds false if queried on the empty set" do expect(tagger).to_not be_tagged end end context "when assigning tags" do it "splits a string on ','" do tagger.tags = "one, two, three" expect(tagger).to be_tagged("one") expect(tagger).to be_tagged("two") expect(tagger).to be_tagged("three") end it "protects against empty tags" do expect { tagger.tags = "one,,two"}.to raise_error(/Invalid tag ''/) end it "takes an array of tags" do tagger.tags = ["one", "two"] expect(tagger).to be_tagged("one") expect(tagger).to be_tagged("two") end it "removes any existing tags when reassigning" do tagger.tags = "one, two" tagger.tags = "three, four" expect(tagger).to_not be_tagged("one") expect(tagger).to_not be_tagged("two") expect(tagger).to be_tagged("three") expect(tagger).to be_tagged("four") end it "allows empty tags that are generated from :: separated tags" do tagger.tags = "one::::two::three" expect(tagger).to be_tagged("one") expect(tagger).to be_tagged("") expect(tagger).to be_tagged("two") expect(tagger).to be_tagged("three") end end end