diff options
| author | Michael Stahnke <stahnma@puppetlabs.com> | 2011-08-05 09:23:49 -0700 |
|---|---|---|
| committer | Michael Stahnke <stahnma@puppetlabs.com> | 2011-08-05 09:23:49 -0700 |
| commit | 3daea902b29cfd8e126ed64247ddf28aa5ad3d76 (patch) | |
| tree | cda7fff4d06c7f3607a84b260fd71adfd9704e3b /spec | |
| parent | c8835ad0275c350b57884b81e485d9fc16699a21 (diff) | |
| parent | 2185bb2804aeef6b419667951b2157b01404c694 (diff) | |
| download | puppet-3daea902b29cfd8e126ed64247ddf28aa5ad3d76.tar.gz puppet-3daea902b29cfd8e126ed64247ddf28aa5ad3d76.tar.xz puppet-3daea902b29cfd8e126ed64247ddf28aa5ad3d76.zip | |
Merge branch '2.7.x' into 2.7rc
Diffstat (limited to 'spec')
32 files changed, 1604 insertions, 120 deletions
diff --git a/spec/integration/defaults_spec.rb b/spec/integration/defaults_spec.rb index 9bec769ab..8cf0e3e7b 100755 --- a/spec/integration/defaults_spec.rb +++ b/spec/integration/defaults_spec.rb @@ -275,6 +275,6 @@ describe "Puppet defaults" do describe "reporturl" do subject { Puppet.settings[:reporturl] } - it { should == "http://localhost:3000/reports" } + it { should == "http://localhost:3000/reports/upload" } end end diff --git a/spec/integration/network/rest_authconfig_spec.rb b/spec/integration/network/rest_authconfig_spec.rb new file mode 100644 index 000000000..d2f539cd4 --- /dev/null +++ b/spec/integration/network/rest_authconfig_spec.rb @@ -0,0 +1,145 @@ +require 'spec_helper' + +require 'puppet/network/rest_authconfig' + +RSpec::Matchers.define :allow do |params| + + match do |auth| + begin + auth.check_authorization(params[0], params[1], params[2], params[3]) + true + rescue Puppet::Network::AuthorizationError + false + end + end + + failure_message_for_should do |instance| + "expected #{params[3][:node]}/#{params[3][:ip]} to be allowed" + end + + failure_message_for_should_not do |instance| + "expected #{params[3][:node]}/#{params[3][:ip]} to be forbidden" + end +end + +describe Puppet::Network::RestAuthConfig do + include PuppetSpec::Files + + before(:each) do + Puppet[:rest_authconfig] = tmpfile('auth.conf') + end + + def add_rule(rule) + File.open(Puppet[:rest_authconfig],"w+") do |f| + f.print "path /test\n#{rule}\n" + end + @auth = Puppet::Network::RestAuthConfig.new(Puppet[:rest_authconfig], true) + end + + def add_regex_rule(regex, rule) + File.open(Puppet[:rest_authconfig],"w+") do |f| + f.print "path ~ #{regex}\n#{rule}\n" + end + @auth = Puppet::Network::RestAuthConfig.new(Puppet[:rest_authconfig], true) + end + + def request(args = {}) + { :ip => '10.1.1.1', :node => 'host.domain.com', :key => 'key', :authenticated => true }.each do |k,v| + args[k] ||= v + end + ['test', :find, args[:key], args] + end + + it "should support IPv4 address" do + add_rule("allow 10.1.1.1") + + @auth.should allow(request) + end + + it "should support CIDR IPv4 address" do + add_rule("allow 10.0.0.0/8") + + @auth.should allow(request) + end + + it "should support wildcard IPv4 address" do + add_rule("allow 10.1.1.*") + + @auth.should allow(request) + end + + it "should support IPv6 address" do + add_rule("allow 2001:DB8::8:800:200C:417A") + + @auth.should allow(request(:ip => '2001:DB8::8:800:200C:417A')) + end + + it "should support hostname" do + add_rule("allow host.domain.com") + + @auth.should allow(request) + end + + it "should support wildcard host" do + add_rule("allow *.domain.com") + + @auth.should allow(request) + end + + it "should support hostname backreferences" do + add_regex_rule('^/test/([^/]+)$', "allow $1.domain.com") + + @auth.should allow(request(:key => 'host')) + end + + it "should support opaque strings" do + add_rule("allow this-is-opaque@or-not") + + @auth.should allow(request(:node => 'this-is-opaque@or-not')) + end + + it "should support opaque strings and backreferences" do + add_regex_rule('^/test/([^/]+)$', "allow $1") + + @auth.should allow(request(:key => 'this-is-opaque@or-not', :node => 'this-is-opaque@or-not')) + end + + it "should support hostname ending with '.'" do + pending('bug #7589') + add_rule("allow host.domain.com.") + + @auth.should allow(request(:node => 'host.domain.com.')) + end + + it "should support hostname ending with '.' and backreferences" do + pending('bug #7589') + add_regex_rule('^/test/([^/]+)$',"allow $1") + + @auth.should allow(request(:node => 'host.domain.com.')) + end + + it "should support trailing whitespace" do + add_rule('allow host.domain.com ') + + @auth.should allow(request) + end + + it "should support inlined comments" do + add_rule('allow host.domain.com # will it work?') + + @auth.should allow(request) + end + + it "should deny non-matching host" do + add_rule("allow inexistant") + + @auth.should_not allow(request) + end + + it "should deny denied hosts" do + add_rule("deny host.domain.com") + + @auth.should_not allow(request) + end + +end
\ No newline at end of file diff --git a/spec/integration/node/facts_spec.rb b/spec/integration/node/facts_spec.rb index f54d7f9aa..e87a0bdeb 100755 --- a/spec/integration/node/facts_spec.rb +++ b/spec/integration/node/facts_spec.rb @@ -7,7 +7,7 @@ require 'spec_helper' describe Puppet::Node::Facts do describe "when using the indirector" do - after { Puppet::Util::Cacher.expire } + after(:each) { Puppet::Util::Cacher.expire } it "should expire any cached node instances when it is saved" do Puppet::Node::Facts.indirection.stubs(:terminus_class).returns :yaml diff --git a/spec/lib/puppet/face/1.0.0/huzzah.rb b/spec/lib/puppet/face/1.0.0/huzzah.rb new file mode 100755 index 000000000..8a1311704 --- /dev/null +++ b/spec/lib/puppet/face/1.0.0/huzzah.rb @@ -0,0 +1,8 @@ +require 'puppet/face' +Puppet::Face.define(:huzzah, '1.0.0') do + copyright "Puppet Labs", 2011 + license "Apache 2 license; see COPYING" + summary "life is a thing for celebration" + script :obsolete_in_core do |_| "you are in obsolete core now!" end + script :call_newer do |_| method_on_newer end +end diff --git a/spec/lib/puppet/face/huzzah.rb b/spec/lib/puppet/face/huzzah.rb index ab465d9e0..f3d18a797 100755 --- a/spec/lib/puppet/face/huzzah.rb +++ b/spec/lib/puppet/face/huzzah.rb @@ -4,4 +4,5 @@ Puppet::Face.define(:huzzah, '2.0.1') do license "Apache 2 license; see COPYING" summary "life is a thing for celebration" script :bar do |options| "is where beer comes from" end + script :call_older do |_| method_on_older end end diff --git a/spec/lib/puppet/face/huzzah/obsolete.rb b/spec/lib/puppet/face/huzzah/obsolete.rb new file mode 100644 index 000000000..1f717ea2f --- /dev/null +++ b/spec/lib/puppet/face/huzzah/obsolete.rb @@ -0,0 +1,6 @@ +Puppet::Face.define(:huzzah, '1.0.0') do + action :obsolete do + summary "This is an action on version 1.0.0 of the face" + when_invoked do |options| options end + end +end diff --git a/spec/shared_behaviours/things_that_declare_options.rb b/spec/shared_behaviours/things_that_declare_options.rb index ebf1b2071..ecdbfcaea 100755 --- a/spec/shared_behaviours/things_that_declare_options.rb +++ b/spec/shared_behaviours/things_that_declare_options.rb @@ -43,7 +43,7 @@ shared_examples_for "things that declare options" do option "-f" option "--baz" end - thing.options.should == [:foo, :bar, :b, :q, :quux, :f, :baz] + thing.options.should == [:foo, :bar, :quux, :f, :baz] end it "should detect conflicts in long options" do @@ -146,4 +146,117 @@ shared_examples_for "things that declare options" do end end end + + describe "#default_to" do + it "should not have a default value by default" do + option = add_options_to do option "--foo" end.get_option(:foo) + option.should_not be_has_default + end + + it "should accept a block for the default value" do + option = add_options_to do + option "--foo" do + default_to do + 12 + end + end + end.get_option(:foo) + + option.should be_has_default + end + + it "should invoke the block when asked for the default value" do + invoked = false + option = add_options_to do + option "--foo" do + default_to do + invoked = true + end + end + end.get_option(:foo) + + option.should be_has_default + option.default.should be_true + invoked.should be_true + end + + it "should return the value of the block when asked for the default" do + option = add_options_to do + option "--foo" do + default_to do + 12 + end + end + end.get_option(:foo) + + option.should be_has_default + option.default.should == 12 + end + + it "should invoke the block every time the default is requested" do + option = add_options_to do + option "--foo" do + default_to do + {} + end + end + end.get_option(:foo) + + first = option.default.object_id + second = option.default.object_id + third = option.default.object_id + + first.should_not == second + first.should_not == third + second.should_not == third + end + + it "should fail if the option has a default and is required" do + expect { + add_options_to do + option "--foo" do + required + default_to do 12 end + end + end + }.to raise_error ArgumentError, /can't be optional and have a default value/ + + expect { + add_options_to do + option "--foo" do + default_to do 12 end + required + end + end + }.to raise_error ArgumentError, /can't be optional and have a default value/ + end + + it "should fail if default_to has no block" do + expect { add_options_to do option "--foo" do default_to end end }. + to raise_error ArgumentError, /default_to requires a block/ + end + + it "should fail if default_to is invoked twice" do + expect { + add_options_to do + option "--foo" do + default_to do 12 end + default_to do "fun" end + end + end + }.to raise_error ArgumentError, /already has a default value/ + end + + [ "one", "one, two", "one, *two" ].each do |input| + it "should fail if the block has the wrong arity (#{input})" do + expect { + add_options_to do + option "--foo" do + eval "default_to do |#{input}| 12 end" + end + end + }.to raise_error ArgumentError, /should not take any arguments/ + end + end + end end diff --git a/spec/unit/application/apply_spec.rb b/spec/unit/application/apply_spec.rb index c9555157c..489f4db36 100755 --- a/spec/unit/application/apply_spec.rb +++ b/spec/unit/application/apply_spec.rb @@ -134,7 +134,9 @@ describe Puppet::Application::Apply do Puppet[:postrun_command] = '' Puppet::Node::Facts.indirection.terminus_class = :memory + Puppet::Node::Facts.indirection.cache_class = :memory Puppet::Node.indirection.terminus_class = :memory + Puppet::Node.indirection.cache_class = :memory @facts = Puppet::Node::Facts.new(Puppet[:node_name_value]) Puppet::Node::Facts.indirection.save(@facts) diff --git a/spec/unit/application/cert_spec.rb b/spec/unit/application/cert_spec.rb index 7510f0783..300234c2b 100755 --- a/spec/unit/application/cert_spec.rb +++ b/spec/unit/application/cert_spec.rb @@ -208,5 +208,15 @@ describe Puppet::Application::Cert, :'fails_on_ruby_1.9.2' => true do args.should == ["fun.example.com"] end end + + it "should print help and exit if there is no subcommand" do + args = [] + @cert_app.command_line.stubs(:args).returns(args) + @cert_app.stubs(:help).returns("I called for help!") + @cert_app.expects(:puts).with("I called for help!") + + expect { @cert_app.parse_options }.to exit_with 0 + @cert_app.subcommand.should be_nil + end end end diff --git a/spec/unit/application/face_base_spec.rb b/spec/unit/application/face_base_spec.rb index 0a4a86be6..bebc26210 100755 --- a/spec/unit/application/face_base_spec.rb +++ b/spec/unit/application/face_base_spec.rb @@ -55,6 +55,7 @@ describe Puppet::Application::FaceBase do it "should stop if the first thing found is not an action" do app.command_line.stubs(:args).returns %w{banana count_args} expect { app.run }.to exit_with 1 + @logs.first.should_not be_nil @logs.first.message.should =~ /has no 'banana' action/ end diff --git a/spec/unit/application/inspect_spec.rb b/spec/unit/application/inspect_spec.rb index 571683f37..77f36f438 100755 --- a/spec/unit/application/inspect_spec.rb +++ b/spec/unit/application/inspect_spec.rb @@ -12,6 +12,11 @@ describe Puppet::Application::Inspect do before :each do @inspect = Puppet::Application[:inspect] + @inspect.preinit + end + + it "should operate in agent run_mode" do + @inspect.class.run_mode.name.should == :agent end describe "during setup" do diff --git a/spec/unit/face/ca_spec.rb b/spec/unit/face/ca_spec.rb new file mode 100755 index 000000000..b8c82ce99 --- /dev/null +++ b/spec/unit/face/ca_spec.rb @@ -0,0 +1,355 @@ +#!/usr/bin/env rspec +require 'spec_helper' +require 'puppet/face' + +describe Puppet::Face[:ca, '0.1.0'] do + include PuppetSpec::Files + + before :each do + Puppet.run_mode.stubs(:master?).returns(true) + Puppet[:ca] = true + Puppet[:ssldir] = tmpdir("face-ca-ssldir") + + Puppet::SSL::Host.ca_location = :only + Puppet[:certificate_revocation] = true + + # This is way more intimate than I want to be with the implementation, but + # there doesn't seem any other way to test this. --daniel 2011-07-18 + Puppet::SSL::CertificateAuthority.stubs(:instance).returns( + # ...and this actually does the directory creation, etc. + Puppet::SSL::CertificateAuthority.new + ) + end + + def make_certs(csr_names, crt_names) + Array(csr_names).map do |name| + Puppet::SSL::Host.new(name).generate_certificate_request + end + + Array(crt_names).map do |name| + Puppet::SSL::Host.new(name).generate + end + end + + context "#verify" do + let :action do Puppet::Face[:ca, '0.1.0'].get_action(:verify) end + + it "should not explode if there is no certificate" do + expect { + subject.verify('random-host').should == { + :host => 'random-host', :valid => false, + :error => 'Could not find a certificate for random-host' + } + }.should_not raise_error + end + + it "should not explode if there is only a CSR" do + make_certs('random-host', []) + expect { + subject.verify('random-host').should == { + :host => 'random-host', :valid => false, + :error => 'Could not find a certificate for random-host' + } + }.should_not raise_error + end + + it "should verify a signed certificate" do + make_certs([], 'random-host') + subject.verify('random-host').should == { + :host => 'random-host', :valid => true + } + end + + it "should not verify a revoked certificate" do + make_certs([], 'random-host') + subject.revoke('random-host') + + expect { + subject.verify('random-host').should == { + :host => 'random-host', :valid => false, + :error => 'certificate revoked' + } + }.should_not raise_error + end + + it "should verify a revoked certificate if CRL use was turned off" do + make_certs([], 'random-host') + subject.revoke('random-host') + + Puppet[:certificate_revocation] = false + subject.verify('random-host').should == { + :host => 'random-host', :valid => true + } + end + end + + context "#fingerprint" do + let :action do Puppet::Face[:ca, '0.1.0'].get_action(:fingerprint) end + + it "should have a 'digest' option" do + action.should be_option :digest + end + + it "should not explode if there is no certificate" do + expect { + subject.fingerprint('random-host').should be_nil + }.should_not raise_error + end + + it "should fingerprint a CSR" do + make_certs('random-host', []) + expect { + subject.fingerprint('random-host').should =~ /^[0-9A-F:]+$/ + }.should_not raise_error + end + + it "should fingerprint a certificate" do + make_certs([], 'random-host') + subject.fingerprint('random-host').should =~ /^[0-9A-F:]+$/ + end + + %w{md5 MD5 sha1 ShA1 SHA1 RIPEMD160 sha256 sha512}.each do |digest| + it "should fingerprint with #{digest.inspect}" do + make_certs([], 'random-host') + subject.fingerprint('random-host', :digest => digest).should =~ /^[0-9A-F:]+$/ + end + + it "should fingerprint with #{digest.to_sym} as a symbol" do + make_certs([], 'random-host') + subject.fingerprint('random-host', :digest => digest.to_sym). + should =~ /^[0-9A-F:]+$/ + end + end + end + + context "#print" do + let :action do Puppet::Face[:ca, '0.1.0'].get_action(:print) end + + it "should not explode if there is no certificate" do + expect { + subject.print('random-host').should be_nil + }.should_not raise_error + end + + it "should return nothing if there is only a CSR" do + make_certs('random-host', []) + expect { + subject.print('random-host').should be_nil + }.should_not raise_error + end + + it "should return the certificate content if there is a cert" do + make_certs([], 'random-host') + text = subject.print('random-host') + text.should be_an_instance_of String + text.should =~ /^Certificate:/ + text.should =~ /Issuer: CN=Puppet CA: / + text.should =~ /Subject: CN=random-host$/ + end + end + + context "#sign" do + let :action do Puppet::Face[:ca, '0.1.0'].get_action(:sign) end + + it "should not explode if there is no CSR" do + expect { + subject.sign('random-host'). + should == 'Could not find certificate request for random-host' + }.should_not raise_error + end + + it "should not explode if there is a signed cert" do + make_certs([], 'random-host') + expect { + subject.sign('random-host'). + should == 'Could not find certificate request for random-host' + }.should_not raise_error + end + + it "should sign a CSR if one exists" do + make_certs('random-host', []) + subject.sign('random-host').should be_an_instance_of Puppet::SSL::Certificate + + list = subject.list(:signed => true) + list.length.should == 1 + list.first.name.should == 'random-host' + end + end + + context "#generate" do + let :action do Puppet::Face[:ca, '0.1.0'].get_action(:generate) end + + it "should generate a certificate if requested" do + subject.list(:all => true).should == [] + + subject.generate('random-host') + + list = subject.list(:signed => true) + list.length.should == 1 + list.first.name.should == 'random-host' + end + + it "should not explode if a CSR with that name already exists" do + make_certs('random-host', []) + expect { + subject.generate('random-host').should =~ /already has a certificate request/ + }.should_not raise_error + end + + it "should not explode if the certificate with that name already exists" do + make_certs([], 'random-host') + expect { + subject.generate('random-host').should =~ /already has a certificate/ + }.should_not raise_error + end + end + + context "#revoke" do + let :action do Puppet::Face[:ca, '0.1.0'].get_action(:revoke) end + + it "should not explode when asked to revoke something that doesn't exist" do + expect { subject.revoke('nonesuch') }.should_not raise_error + end + + it "should let the user know what went wrong" do + subject.revoke('nonesuch').should == 'Nothing was revoked' + end + + it "should revoke a certificate" do + make_certs([], 'random-host') + found = subject.list(:all => true, :subject => 'random-host') + subject.get_action(:list).when_rendering(:console).call(found). + should =~ /^\+ random-host/ + + subject.revoke('random-host') + + found = subject.list(:all => true, :subject => 'random-host') + subject.get_action(:list).when_rendering(:console).call(found). + should =~ /^- random-host \([:0-9A-F]+\) \(certificate revoked\)/ + end + end + + context "#destroy" do + let :action do Puppet::Face[:ca, '0.1.0'].get_action(:destroy) end + + it "should not explode when asked to delete something that doesn't exist" do + expect { subject.destroy('nonesuch') }.should_not raise_error + end + + it "should let the user know if nothing was deleted" do + subject.destroy('nonesuch').should == "Nothing was deleted" + end + + it "should destroy a CSR, if we have one" do + make_certs('random-host', []) + subject.list(:pending => true, :subject => 'random-host').should_not == [] + + subject.destroy('random-host') + + subject.list(:pending => true, :subject => 'random-host').should == [] + end + + it "should destroy a certificate, if we have one" do + make_certs([], 'random-host') + subject.list(:signed => true, :subject => 'random-host').should_not == [] + + subject.destroy('random-host') + + subject.list(:signed => true, :subject => 'random-host').should == [] + end + + it "should tell the user something was deleted" do + make_certs([], 'random-host') + subject.list(:signed => true, :subject => 'random-host').should_not == [] + subject.destroy('random-host'). + should == "Deleted for random-host: Puppet::SSL::Certificate, Puppet::SSL::Key" + end + end + + context "#list" do + let :action do Puppet::Face[:ca, '0.1.0'].get_action(:list) end + + context "options" do + subject { Puppet::Face[:ca, '0.1.0'].get_action(:list) } + it { should be_option :pending } + it { should be_option :signed } + it { should be_option :all } + it { should be_option :subject } + end + + context "with no hosts in CA" do + [:pending, :signed, :all].each do |type| + it "should return nothing for #{type}" do + subject.list(type => true).should == [] + end + + it "should not fail when a matcher is passed" do + expect { + subject.list(type => true, :subject => '.').should == [] + }.should_not raise_error + end + end + end + + context "with some hosts" do + csr_names = (1..3).map {|n| "csr-#{n}" } + crt_names = (1..3).map {|n| "crt-#{n}" } + all_names = csr_names + crt_names + + { + {} => csr_names, + { :pending => true } => csr_names, + + { :signed => true } => crt_names, + + { :all => true } => all_names, + { :pending => true, :signed => true } => all_names, + }.each do |input, expect| + it "should map #{input.inspect} to #{expect.inspect}" do + make_certs(csr_names, crt_names) + subject.list(input).map(&:name).should =~ expect + end + + ['', '.', '2', 'none'].each do |pattern| + filtered = expect.select {|x| Regexp.new(pattern).match(x) } + + it "should filter all hosts matching #{pattern.inspect} to #{filtered.inspect}" do + make_certs(csr_names, crt_names) + subject.list(input.merge :subject => pattern).map(&:name).should =~ filtered + end + end + end + + context "when_rendering :console" do + { [["csr1.local"], []] => '^ csr1.local ', + [[], ["crt1.local"]] => '^\+ crt1.local ', + [["csr2"], ["crt2"]] => ['^ csr2 ', '^\+ crt2 '] + }.each do |input, pattern| + it "should render #{input.inspect} to match #{pattern.inspect}" do + make_certs(*input) + text = action.when_rendering(:console).call(subject.list(:all => true)) + Array(pattern).each do |item| + text.should =~ Regexp.new(item) + end + end + end + end + end + end + + actions = %w{destroy list revoke generate sign print verify fingerprint} + actions.each do |action| + it { should be_action action } + it "should fail #{action} when not a CA" do + Puppet[:ca] = false + expect { + case subject.method(action).arity + when -1 then subject.send(action) + when -2 then subject.send(action, 'dummy') + else + raise "#{action} has arity #{subject.method(action).arity}" + end + }.should raise_error(/Not a CA/) + end + end +end diff --git a/spec/unit/face/certificate_spec.rb b/spec/unit/face/certificate_spec.rb index 0cb905b75..9291d7523 100755 --- a/spec/unit/face/certificate_spec.rb +++ b/spec/unit/face/certificate_spec.rb @@ -10,14 +10,26 @@ describe Puppet::Face[:certificate, '0.0.1'] do end it "should set the ca location when invoked" do - Puppet::SSL::Host.expects(:ca_location=).with(:foo) + Puppet::SSL::Host.expects(:ca_location=).with(:local) Puppet::SSL::Host.indirection.expects(:save) - subject.sign "hello, friend", :ca_location => :foo + subject.sign "hello, friend", :ca_location => :local end it "(#7059) should set the ca location when an inherited action is invoked" do - Puppet::SSL::Host.expects(:ca_location=).with(:foo) + Puppet::SSL::Host.expects(:ca_location=).with(:local) subject.indirection.expects(:find) - subject.find "hello, friend", :ca_location => :foo + subject.find "hello, friend", :ca_location => :local + end + + it "should validate the option as required" do + expect do + subject.find 'hello, friend' + end.to raise_exception ArgumentError, /required/i + end + + it "should validate the option as a supported value" do + expect do + subject.find 'hello, friend', :ca_location => :foo + end.to raise_exception ArgumentError, /valid values/i end end diff --git a/spec/unit/face/node_spec.rb b/spec/unit/face/node_spec.rb index 027a4cce0..6f6edc6cc 100755 --- a/spec/unit/face/node_spec.rb +++ b/spec/unit/face/node_spec.rb @@ -3,5 +3,265 @@ require 'spec_helper' require 'puppet/face' describe Puppet::Face[:node, '0.0.1'] do - it "REVISIT: really should have some tests" + describe '#cleanup' do + it "should clean everything" do + { + "cert" => ['hostname'], + "cached_facts" => ['hostname'], + "cached_node" => ['hostname'], + "reports" => ['hostname'], + + # Support for cleaning storeconfigs has been temporarily suspended. + # "storeconfigs" => ['hostname', :unexport] + }.each { |k, v| subject.expects("clean_#{k}".to_sym).with(*v) } + subject.cleanup('hostname', :unexport) + end + end + + describe 'when running #clean' do + before :each do + Puppet::Node::Facts.indirection.stubs(:terminus_class=) + Puppet::Node::Facts.indirection.stubs(:cache_class=) + Puppet::Node.stubs(:terminus_class=) + Puppet::Node.stubs(:cache_class=) + end + + it 'should invoke #cleanup' do + subject.expects(:cleanup).with('hostname', nil) + subject.clean('hostname') + end + end + + describe "clean action" do + before :each do + Puppet::Node::Facts.indirection.stubs(:terminus_class=) + Puppet::Node::Facts.indirection.stubs(:cache_class=) + Puppet::Node.stubs(:terminus_class=) + Puppet::Node.stubs(:cache_class=) + subject.stubs(:cleanup) + end + + it "should have a clean action" do + subject.should be_action :clean + end + + it "should not accept a call with no arguments" do + expect { subject.clean() }.should raise_error + end + + it "should accept a node name" do + expect { subject.clean('hostname') }.should_not raise_error + end + + it "should accept more than one node name" do + expect do + subject.clean('hostname', 'hostname2', {}) + end.should_not raise_error + + expect do + subject.clean('hostname', 'hostname2', 'hostname3', { :unexport => true }) + end.should_not raise_error + end + + it "should accept the option --unexport" do + expect { subject.help('hostname', :unexport => true) }. + should_not raise_error ArgumentError + end + + context "clean action" do + subject { Puppet::Face[:node, :current] } + before :each do + Puppet::Util::Log.stubs(:newdestination) + Puppet::Util::Log.stubs(:level=) + end + + describe "during setup" do + it "should set facts terminus and cache class to yaml" do + Puppet::Node::Facts.indirection.expects(:terminus_class=).with(:yaml) + Puppet::Node::Facts.indirection.expects(:cache_class=).with(:yaml) + + subject.clean('hostname') + end + + it "should run in master mode" do + subject.clean('hostname') + $puppet_application_mode.name.should == :master + end + + it "should set node cache as yaml" do + Puppet::Node.indirection.expects(:terminus_class=).with(:yaml) + Puppet::Node.indirection.expects(:cache_class=).with(:yaml) + + subject.clean('hostname') + end + + it "should manage the certs if the host is a CA" do + Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true) + Puppet::SSL::Host.expects(:ca_location=).with(:local) + subject.clean('hostname') + end + + it "should not manage the certs if the host is not a CA" do + Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(false) + Puppet::SSL::Host.expects(:ca_location=).with(:none) + subject.clean('hostname') + end + end + + describe "when cleaning certificate" do + before :each do + Puppet::SSL::Host.stubs(:destroy) + @ca = mock() + Puppet::SSL::CertificateAuthority.stubs(:instance).returns(@ca) + end + + it "should send the :destroy order to the ca if we are a CA" do + Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true) + @ca.expects(:revoke).with(@host) + @ca.expects(:destroy).with(@host) + subject.clean_cert(@host) + end + + it "should not destroy the certs if we are not a CA" do + Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(false) + @ca.expects(:revoke).never + @ca.expects(:destroy).never + subject.clean_cert(@host) + end + end + + describe "when cleaning cached facts" do + it "should destroy facts" do + @host = 'node' + Puppet::Node::Facts.indirection.expects(:destroy).with(@host) + + subject.clean_cached_facts(@host) + end + end + + describe "when cleaning cached node" do + it "should destroy the cached node" do + Puppet::Node::Yaml.any_instance.expects(:destroy) + subject.clean_cached_node(@host) + end + end + + describe "when cleaning archived reports" do + it "should tell the reports to remove themselves" do + Puppet::Transaction::Report.indirection.stubs(:destroy).with(@host) + + subject.clean_reports(@host) + end + end + + # describe "when cleaning storeconfigs entries for host", :if => Puppet.features.rails? do + # before :each do + # # Stub this so we don't need access to the DB + # require 'puppet/rails/host' + # + # Puppet.stubs(:[]).with(:storeconfigs).returns(true) + # + # Puppet::Rails.stubs(:connect) + # @rails_node = stub_everything 'rails_node' + # Puppet::Rails::Host.stubs(:find_by_name).returns(@rails_node) + # end + # + # it "should connect to the database" do + # Puppet::Rails.expects(:connect) + # subject.clean_storeconfigs(@host, false) + # end + # + # it "should find the right host entry" do + # Puppet::Rails::Host.expects(:find_by_name).with(@host).returns(@rails_node) + # subject.clean_storeconfigs(@host, false) + # end + # + # describe "without unexport" do + # it "should remove the host and it's content" do + # @rails_node.expects(:destroy) + # subject.clean_storeconfigs(@host, false) + # end + # end + # + # describe "with unexport" do + # before :each do + # @rails_node.stubs(:id).returns(1234) + # + # @type = stub_everything 'type' + # @type.stubs(:validattr?).with(:ensure).returns(true) + # + # @ensure_name = stub_everything 'ensure_name', :id => 23453 + # Puppet::Rails::ParamName.stubs(:find_or_create_by_name).returns(@ensure_name) + # + # @param_values = stub_everything 'param_values' + # @resource = stub_everything 'resource', :param_values => @param_values, :restype => "File" + # Puppet::Rails::Resource.stubs(:find).returns([@resource]) + # end + # + # it "should find all resources" do + # Puppet::Rails::Resource.expects(:find).with(:all, {:include => {:param_values => :param_name}, :conditions => ["exported=? AND host_id=?", true, 1234]}).returns([]) + # + # subject.clean_storeconfigs(@host, true) + # end + # + # describe "with an exported native type" do + # before :each do + # Puppet::Type.stubs(:type).returns(@type) + # @type.expects(:validattr?).with(:ensure).returns(true) + # end + # + # it "should test a native type for ensure as an attribute" do + # subject.clean_storeconfigs(@host, true) + # end + # + # it "should delete the old ensure parameter" do + # ensure_param = stub 'ensure_param', :id => 12345, :line => 12 + # @param_values.stubs(:find).returns(ensure_param) + # Puppet::Rails::ParamValue.expects(:delete).with(12345); + # subject.clean_storeconfigs(@host, true) + # end + # + # it "should add an ensure => absent parameter" do + # @param_values.expects(:create).with(:value => "absent", + # :line => 0, + # :param_name => @ensure_name) + # subject.clean_storeconfigs(@host, true) + # end + # end + # + # describe "with an exported definition" do + # it "should try to lookup a definition and test it for the ensure argument" do + # Puppet::Type.stubs(:type).returns(nil) + # definition = stub_everything 'definition', :arguments => { 'ensure' => 'present' } + # Puppet::Resource::TypeCollection.any_instance.expects(:find_definition).with('', "File").returns(definition) + # subject.clean_storeconfigs(@host, true) + # end + # end + # + # it "should not unexport the resource of an unknown type" do + # Puppet::Type.stubs(:type).returns(nil) + # Puppet::Resource::TypeCollection.any_instance.expects(:find_definition).with('', "File").returns(nil) + # Puppet::Rails::ParamName.expects(:find_or_create_by_name).never + # subject.clean_storeconfigs(@host) + # end + # + # it "should not unexport the resource of a not ensurable native type" do + # Puppet::Type.stubs(:type).returns(@type) + # @type.expects(:validattr?).with(:ensure).returns(false) + # Puppet::Resource::TypeCollection.any_instance.expects(:find_definition).with('', "File").returns(nil) + # Puppet::Rails::ParamName.expects(:find_or_create_by_name).never + # subject.clean_storeconfigs(@host, true) + # end + # + # it "should not unexport the resource of a not ensurable definition" do + # Puppet::Type.stubs(:type).returns(nil) + # definition = stub_everything 'definition', :arguments => { 'foobar' => 'someValue' } + # Puppet::Resource::TypeCollection.any_instance.expects(:find_definition).with('', "File").returns(definition) + # Puppet::Rails::ParamName.expects(:find_or_create_by_name).never + # subject.clean_storeconfigs(@host, true) + # end + # end + # end + end + end end diff --git a/spec/unit/file_serving/configuration/parser_spec.rb b/spec/unit/file_serving/configuration/parser_spec.rb index 3d6b3e234..5ccfc5075 100755 --- a/spec/unit/file_serving/configuration/parser_spec.rb +++ b/spec/unit/file_serving/configuration/parser_spec.rb @@ -118,6 +118,14 @@ describe Puppet::FileServing::Configuration::Parser do @parser.parse end + it "should support inline comments" do + mock_file_content "[one]\nallow something \# will it work?\n" + + @mount.expects(:info) + @mount.expects(:allow).with("something") + @parser.parse + end + it "should tell the mount to deny any deny values from the section" do mock_file_content "[one]\ndeny something\n" diff --git a/spec/unit/indirector/certificate_status/file_spec.rb b/spec/unit/indirector/certificate_status/file_spec.rb index ae03aa9cb..5451913a1 100755 --- a/spec/unit/indirector/certificate_status/file_spec.rb +++ b/spec/unit/indirector/certificate_status/file_spec.rb @@ -7,6 +7,10 @@ require 'tempfile' describe "Puppet::Indirector::CertificateStatus::File" do include PuppetSpec::Files + before :all do + Puppet::SSL::Host.configure_indirection(:file) + end + before do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns true @terminus = Puppet::SSL::Host.indirection.terminus(:file) diff --git a/spec/unit/indirector/face_spec.rb b/spec/unit/indirector/face_spec.rb index 943ff7991..8e324f019 100755 --- a/spec/unit/indirector/face_spec.rb +++ b/spec/unit/indirector/face_spec.rb @@ -12,6 +12,8 @@ describe Puppet::Indirector::Face do instance end + it { should be_option :extra } + it "should be able to return a list of indirections" do Puppet::Indirector::Face.indirections.should be_include("catalog") end @@ -48,7 +50,7 @@ describe Puppet::Indirector::Face do end it "should forward passed options" do subject.indirection.expects(method).with(:test, {'one'=>'1'}) - subject.send(method, :test, {'one'=>'1'}) + subject.send(method, :test, :extra => {'one'=>'1'}) end end diff --git a/spec/unit/indirector/report/processor_spec.rb b/spec/unit/indirector/report/processor_spec.rb index bafbe6ee7..c64cc7eff 100755 --- a/spec/unit/indirector/report/processor_spec.rb +++ b/spec/unit/indirector/report/processor_spec.rb @@ -15,39 +15,62 @@ describe Puppet::Transaction::Report::Processor do it "should provide a method for saving reports" do Puppet::Transaction::Report::Processor.new.should respond_to(:save) end + + it "should provide a method for cleaning reports" do + Puppet::Transaction::Report::Processor.new.should respond_to(:destroy) + end + end -describe Puppet::Transaction::Report::Processor, " when saving a report" do +describe Puppet::Transaction::Report::Processor, " when processing a report" do before do Puppet.settings.stubs(:use) @reporter = Puppet::Transaction::Report::Processor.new + @request = stub 'request', :instance => stub("report", :host => 'hostname'), :key => 'node' end - it "should not process the report if reports are set to 'none'" do + it "should not save the report if reports are set to 'none'" do Puppet::Reports.expects(:report).never - Puppet.settings.expects(:value).with(:reports).returns("none") + Puppet[:reports] = 'none' - request = stub 'request', :instance => mock("report") + request = Puppet::Indirector::Request.new(:indirection_name, :head, "key") + report = Puppet::Transaction::Report.new('apply') + request.instance = report @reporter.save(request) end - it "should process the report with each configured report type" do + it "should save the report with each configured report type" do Puppet.settings.stubs(:value).with(:reports).returns("one,two") @reporter.send(:reports).should == %w{one two} + + Puppet::Reports.expects(:report).with('one') + Puppet::Reports.expects(:report).with('two') + + @reporter.save(@request) + end + + it "should destroy reports for each processor that responds to destroy" do + Puppet.settings.stubs(:value).with(:reports).returns("http,store") + http_report = mock() + store_report = mock() + store_report.expects(:destroy).with(@request.key) + Puppet::Reports.expects(:report).with('http').returns(http_report) + Puppet::Reports.expects(:report).with('store').returns(store_report) + @reporter.destroy(@request) end end describe Puppet::Transaction::Report::Processor, " when processing a report" do before do - Puppet.settings.stubs(:value).with(:reports).returns("one") + Puppet[:reports] = "one" Puppet.settings.stubs(:use) @reporter = Puppet::Transaction::Report::Processor.new @report_type = mock 'one' @dup_report = mock 'dupe report' @dup_report.stubs(:process) - @report = mock 'report' + @report = Puppet::Transaction::Report.new('apply') @report.expects(:dup).returns(@dup_report) @request = stub 'request', :instance => @report @@ -74,7 +97,7 @@ describe Puppet::Transaction::Report::Processor, " when processing a report" do end it "should not raise exceptions" do - Puppet.settings.stubs(:value).with(:trace).returns(false) + Puppet[:trace] = false @dup_report.expects(:process).raises(ArgumentError) proc { @reporter.save(@request) }.should_not raise_error end diff --git a/spec/unit/indirector/rest_spec.rb b/spec/unit/indirector/rest_spec.rb index ee0111a77..042b7ca16 100755 --- a/spec/unit/indirector/rest_spec.rb +++ b/spec/unit/indirector/rest_spec.rb @@ -90,42 +90,53 @@ describe Puppet::Indirector::REST do @rest_class.port.should == 543 end - describe "when making http requests" do - it "should provide a helpful error message when hostname was not match with server certificate" do - Puppet[:certdnsnames] = 'foo:bar:baz' - csr = OpenSSL::X509::Request.new - csr.subject = OpenSSL::X509::Name.new([['CN', 'not_my_server']]) - csr.public_key = OpenSSL::PKey::RSA.generate(Puppet[:keylength]).public_key - cert = Puppet::SSL::CertificateFactory.new('server', csr, csr, 14).result - - connection = Net::HTTP.new('my_server', 8140) - @searcher.stubs(:network).returns(connection) - ssl_context = OpenSSL::SSL::SSLContext.new - ssl_context.stubs(:current_cert).returns(cert) - connection.stubs(:get).with do - connection.verify_callback.call(true, ssl_context) - end.raises(OpenSSL::SSL::SSLError.new('hostname was not match with server certificate')) - - msg = /Server hostname 'my_server' did not match server certificate; expected one of (.+)/ - expect { @searcher.http_request(:get, stub('request')) }.to( - raise_error(Puppet::Error, msg) do |error| - error.message =~ msg - $1.split(', ').should =~ ['foo', 'bar', 'baz', 'not_my_server'] - end - ) - end - - it "should pass along the error message otherwise" do - connection = Net::HTTP.new('my_server', 8140) - @searcher.stubs(:network).returns(connection) - - connection.stubs(:get).raises(OpenSSL::SSL::SSLError.new('certificate verify failed')) - - expect do - @searcher.http_request(:get, stub('request')) - end.to raise_error(/certificate verify failed/) - end - end + describe "when making http requests" do + it "should provide a suggestive error message when certificate verify failed" do + connection = Net::HTTP.new('my_server', 8140) + @searcher.stubs(:network).returns(connection) + + connection.stubs(:get).raises(OpenSSL::SSL::SSLError.new('certificate verify failed')) + + expect do + @searcher.http_request(:get, stub('request')) + end.to raise_error(/This is often because the time is out of sync on the server or client/) + end + + it "should provide a helpful error message when hostname was not match with server certificate" do + Puppet[:certdnsnames] = 'foo:bar:baz' + csr = OpenSSL::X509::Request.new + csr.subject = OpenSSL::X509::Name.new([['CN', 'not_my_server']]) + csr.public_key = OpenSSL::PKey::RSA.generate(Puppet[:keylength]).public_key + cert = Puppet::SSL::CertificateFactory.new('server', csr, csr, 14).result + + connection = Net::HTTP.new('my_server', 8140) + @searcher.stubs(:network).returns(connection) + ssl_context = OpenSSL::SSL::SSLContext.new + ssl_context.stubs(:current_cert).returns(cert) + connection.stubs(:get).with do + connection.verify_callback.call(true, ssl_context) + end.raises(OpenSSL::SSL::SSLError.new('hostname was not match with server certificate')) + + msg = /Server hostname 'my_server' did not match server certificate; expected one of (.+)/ + expect { @searcher.http_request(:get, stub('request')) }.to( + raise_error(Puppet::Error, msg) do |error| + error.message =~ msg + $1.split(', ').should =~ ['foo', 'bar', 'baz', 'not_my_server'] + end + ) + end + + it "should pass along the error message otherwise" do + connection = Net::HTTP.new('my_server', 8140) + @searcher.stubs(:network).returns(connection) + + connection.stubs(:get).raises(OpenSSL::SSL::SSLError.new('some other message')) + + expect do + @searcher.http_request(:get, stub('request')) + end.to raise_error(/some other message/) + end + end describe "when deserializing responses" do it "should return nil if the response code is 404" do diff --git a/spec/unit/indirector/yaml_spec.rb b/spec/unit/indirector/yaml_spec.rb index c43dbcaf6..29f6d466f 100755 --- a/spec/unit/indirector/yaml_spec.rb +++ b/spec/unit/indirector/yaml_spec.rb @@ -154,5 +154,23 @@ describe Puppet::Indirector::Yaml, " when choosing file location" do Dir.expects(:glob).with(:glob).returns [] @store.search(@request).should == [] end + + describe Puppet::Indirector::Yaml, " when destroying" do + it "should unlink the right yaml file if it exists" do + path = File.join("/what/ever", @store.class.indirection_name.to_s, @request.key.to_s + ".yaml") + File.expects(:exists?).with(path).returns true + File.expects(:unlink).with(path) + + @store.destroy(@request) + end + + it "should not unlink the yaml file if it does not exists" do + path = File.join("/what/ever", @store.class.indirection_name.to_s, @request.key.to_s + ".yaml") + File.expects(:exists?).with(path).returns false + File.expects(:unlink).with(path).never + + @store.destroy(@request) + end + end end end diff --git a/spec/unit/interface/action_spec.rb b/spec/unit/interface/action_spec.rb index cf8d61d51..6b68eb149 100755 --- a/spec/unit/interface/action_spec.rb +++ b/spec/unit/interface/action_spec.rb @@ -121,6 +121,7 @@ describe Puppet::Interface::Action do let :face do Puppet::Interface.new(:ruby_api, '1.0.0') do action :bar do + option "--bar" when_invoked do |*args| args.last end @@ -138,8 +139,8 @@ describe Puppet::Interface::Action do options.should == { :bar => "beer" } end - it "should call #validate_args on the action when invoked" do - face.get_action(:bar).expects(:validate_args).with([1, :two, 'three', {}]) + it "should call #validate_and_clean on the action when invoked" do + face.get_action(:bar).expects(:validate_and_clean).with({}).returns({}) face.bar 1, :two, 'three' end end @@ -171,6 +172,30 @@ describe Puppet::Interface::Action do face.get_action(:foo).options.should =~ [:bar] end + describe "option aliases" do + let :option do action.get_option :bar end + let :action do face.get_action :foo end + let :face do + Puppet::Interface.new(:action_level_options, '0.0.1') do + action :foo do + when_invoked do |options| options end + option "--bar", "--foo", "-b" + end + end + end + + it "should only list options and not aliases" do + action.options.should =~ [:bar] + end + + it "should use the canonical option name when passed aliases" do + name = option.name + option.aliases.each do |input| + face.foo(input => 1).should == { name => 1 } + end + end + end + describe "with both face and action options" do let :face do Puppet::Interface.new(:action_level_options, '0.0.1') do @@ -426,12 +451,12 @@ describe Puppet::Interface::Action do end it "should be invoked when calling a child action" do - subject.on_child(:foo => true, :bar => true).should == :on_child + subject.on_child(:foo => true).should == :on_child subject.reported.should == [ :child_before ] end it "should be invoked when calling a parent action" do - subject.on_parent(:foo => true, :bar => true).should == :on_parent + subject.on_parent(:foo => true).should == :on_parent subject.reported.should == [ :child_before ] end end @@ -443,12 +468,12 @@ describe Puppet::Interface::Action do end it "should be invoked when calling a child action" do - subject.on_child(:foo => true, :bar => true).should == :on_child + subject.on_child(:foo => true).should == :on_child subject.reported.should == [ :parent_before ] end it "should be invoked when calling a parent action" do - subject.on_parent(:foo => true, :bar => true).should == :on_parent + subject.on_parent(:foo => true).should == :on_parent subject.reported.should == [ :parent_before ] end end @@ -524,10 +549,10 @@ describe Puppet::Interface::Action do it "should return the block if asked" end - context "#validate_args" do + context "#validate_and_clean" do subject do Puppet::Interface.new(:validate_args, '1.0.0') do - script :test do |options| true end + script :test do |options| options end end end @@ -541,5 +566,84 @@ describe Puppet::Interface::Action do expect { subject.test :foo => true, :f => true }. to raise_error ArgumentError, /Multiple aliases for the same option/ end + + it "should fail if an unknown option is passed" do + expect { subject.test :unknown => true }. + to raise_error ArgumentError, /Unknown options passed: unknown/ + end + + it "should report all the unknown options passed" do + expect { subject.test :unknown => true, :unseen => false }. + to raise_error ArgumentError, /Unknown options passed: unknown, unseen/ + end + + it "should accept 'global' options from settings" do + expect { + subject.test(:certname => "true").should == { :certname => "true" } + }.not_to raise_error + end + end + + context "default option values" do + subject do + Puppet::Interface.new(:default_option_values, '1.0.0') do + action :foo do + option "--foo" do end + option "--bar" do end + when_invoked do |options| options end + end + end + end + + let :action do subject.get_action :foo end + let :option do action.get_option :foo end + + it "should not add options without defaults" do + subject.foo.should == {} + end + + it "should not add options without defaults, if options are given" do + subject.foo(:bar => 1).should == { :bar => 1 } + end + + it "should add the option default value when set" do + option.default = proc { 12 } + subject.foo.should == { :foo => 12 } + end + + it "should add the option default value when set, if other options are given" do + option.default = proc { 12 } + subject.foo(:bar => 1).should == { :foo => 12, :bar => 1 } + end + + it "should invoke the same default proc every time called" do + option.default = proc { @foo ||= {} } + subject.foo[:foo].object_id.should == subject.foo[:foo].object_id + end + + [nil, 0, 1, true, false, {}, []].each do |input| + it "should not override a passed option (#{input.inspect})" do + option.default = proc { :fail } + subject.foo(:foo => input).should == { :foo => input } + end + end + end + + context "runtime manipulations" do + subject do + Puppet::Interface.new(:runtime_manipulations, '1.0.0') do + action :foo do + when_invoked do |options| options end + end + end + end + + let :action do subject.get_action :foo end + + it "should be the face default action if default is set true" do + subject.get_default_action.should be_nil + action.default = true + subject.get_default_action.should == action + end end end diff --git a/spec/unit/interface/face_collection_spec.rb b/spec/unit/interface/face_collection_spec.rb index 4ad8787c5..514a624b1 100755 --- a/spec/unit/interface/face_collection_spec.rb +++ b/spec/unit/interface/face_collection_spec.rb @@ -25,39 +25,9 @@ describe Puppet::Interface::FaceCollection do @original_required.each {|f| $".push f unless $".include? f } end - describe "::prefix_match?" do - # want have - { ['1.0.0', '1.0.0'] => true, - ['1.0', '1.0.0'] => true, - ['1', '1.0.0'] => true, - ['1.0.0', '1.1.0'] => false, - ['1.0', '1.1.0'] => false, - ['1', '1.1.0'] => true, - ['1.0.1', '1.0.0'] => false, - }.each do |data, result| - it "should return #{result.inspect} for prefix_match?(#{data.join(', ')})" do - subject.prefix_match?(*data).should == result - end - end - end - - describe "::validate_version" do - { '10.10.10' => true, - '1.2.3.4' => false, - '10.10.10beta' => true, - '10.10' => false, - '123' => false, - 'v1.1.1' => false, - }.each do |input, result| - it "should#{result ? '' : ' not'} permit #{input.inspect}" do - subject.validate_version(input).should(result ? be_true : be_false) - end - end - end - describe "::[]" do before :each do - subject.instance_variable_get("@faces")[:foo]['0.0.1'] = 10 + subject.instance_variable_get("@faces")[:foo][SemVer.new('0.0.1')] = 10 end it "should return the face with the given name" do @@ -65,7 +35,8 @@ describe Puppet::Interface::FaceCollection do end it "should attempt to load the face if it isn't found" do - subject.expects(:require).with('puppet/face/bar') + subject.expects(:require).once.with('puppet/face/bar') + subject.expects(:require).once.with('puppet/face/0.0.1/bar') subject["bar", '0.0.1'] end @@ -75,13 +46,13 @@ describe Puppet::Interface::FaceCollection do end it "should return true if the face specified is registered" do - subject.instance_variable_get("@faces")[:foo]['0.0.1'] = 10 + subject.instance_variable_get("@faces")[:foo][SemVer.new('0.0.1')] = 10 subject["foo", '0.0.1'].should == 10 end it "should attempt to require the face if it is not registered" do subject.expects(:require).with do |file| - subject.instance_variable_get("@faces")[:bar]['0.0.1'] = true + subject.instance_variable_get("@faces")[:bar][SemVer.new('0.0.1')] = true file == 'puppet/face/bar' end subject["bar", '0.0.1'].should be_true @@ -94,7 +65,8 @@ describe Puppet::Interface::FaceCollection do it "should return false if the face file itself is missing" do subject.stubs(:require). - raises(LoadError, 'no such file to load -- puppet/face/bar') + raises(LoadError, 'no such file to load -- puppet/face/bar').then. + raises(LoadError, 'no such file to load -- puppet/face/0.0.1/bar') subject["bar", '0.0.1'].should be_false end @@ -127,11 +99,49 @@ describe Puppet::Interface::FaceCollection do end end + describe "::get_action_for_face" do + it "should return an action on the current face" do + Puppet::Face::FaceCollection.get_action_for_face(:huzzah, :bar, :current). + should be_an_instance_of Puppet::Interface::Action + end + + it "should return an action on an older version of a face" do + action = Puppet::Face::FaceCollection. + get_action_for_face(:huzzah, :obsolete, :current) + + action.should be_an_instance_of Puppet::Interface::Action + action.face.version.should == SemVer.new('1.0.0') + end + + it "should load the full older version of a face" do + action = Puppet::Face::FaceCollection. + get_action_for_face(:huzzah, :obsolete, :current) + + action.face.version.should == SemVer.new('1.0.0') + action.face.should be_action :obsolete_in_core + end + + it "should not add obsolete actions to the current version" do + action = Puppet::Face::FaceCollection. + get_action_for_face(:huzzah, :obsolete, :current) + + action.face.version.should == SemVer.new('1.0.0') + action.face.should be_action :obsolete_in_core + + current = Puppet::Face[:huzzah, :current] + current.version.should == SemVer.new('2.0.1') + current.should_not be_action :obsolete_in_core + current.should_not be_action :obsolete + end + end + describe "::register" do it "should store the face by name" do face = Puppet::Face.new(:my_face, '0.0.1') subject.register(face) - subject.instance_variable_get("@faces").should == {:my_face => {'0.0.1' => face}} + subject.instance_variable_get("@faces").should == { + :my_face => { face.version => face } + } end end diff --git a/spec/unit/interface/option_spec.rb b/spec/unit/interface/option_spec.rb index e77b46e79..e73561fba 100755 --- a/spec/unit/interface/option_spec.rb +++ b/spec/unit/interface/option_spec.rb @@ -97,4 +97,48 @@ describe Puppet::Interface::Option do end end end + + context "defaults" do + subject { Puppet::Interface::Option.new(face, "--foo") } + + it "should work sanely if member variables are used for state" do + subject.default = proc { @foo ||= 0; @foo += 1 } + subject.default.should == 1 + subject.default.should == 2 + subject.default.should == 3 + end + + context "with no default" do + it { should_not be_has_default } + its :default do should be_nil end + + it "should set a proc as default" do + expect { subject.default = proc { 12 } }.should_not raise_error + end + + [1, {}, [], Object.new, "foo"].each do |input| + it "should reject anything but a proc (#{input.class})" do + expect { subject.default = input }.to raise_error ArgumentError, /not a proc/ + end + end + end + + context "with a default" do + before :each do subject.default = proc { [:foo] } end + + it { should be_has_default } + its :default do should == [:foo] end + + it "should invoke the block every time" do + subject.default.object_id.should_not == subject.default.object_id + subject.default.should == subject.default + end + + it "should allow replacing the default proc" do + subject.default.should == [:foo] + subject.default = proc { :bar } + subject.default.should == :bar + end + end + end end diff --git a/spec/unit/interface_spec.rb b/spec/unit/interface_spec.rb index 8bbbcc857..4ff71ac3d 100755 --- a/spec/unit/interface_spec.rb +++ b/spec/unit/interface_spec.rb @@ -126,14 +126,11 @@ describe Puppet::Interface do end it "should try to require faces that are not known" do - pending "mocking require causes random stack overflow" - subject::FaceCollection.expects(:require).with "puppet/face/foo" - subject[:foo, '0.0.1'] + subject::FaceCollection.expects(:load_face).with(:foo, :current) + subject::FaceCollection.expects(:load_face).with(:foo, '0.0.1') + expect { subject[:foo, '0.0.1'] }.to raise_error Puppet::Error end - it "should be able to load all actions in all search paths" - - it_should_behave_like "things that declare options" do def add_options_to(&block) subject.new(:with_options, '0.0.1', &block) @@ -174,6 +171,14 @@ describe Puppet::Interface do face.get_action(:foo).options.should =~ [:quux] face.get_action(:bar).options.should =~ [:quux] end + + it "should only list options and not aliases" do + face = subject.new(:face_options, '0.0.1') do + option "--bar", "-b", "--foo-bar" + end + face.options.should =~ [:bar] + end + end describe "with inherited options" do diff --git a/spec/unit/module_spec.rb b/spec/unit/module_spec.rb index 8d38657f9..822c76296 100755 --- a/spec/unit/module_spec.rb +++ b/spec/unit/module_spec.rb @@ -505,12 +505,38 @@ describe Puppet::Module do mod.metadata_file.should == mod.metadata_file end - it "should know if it has a metadata file" do + it "should have metadata if it has a metadata file and its data is not empty" do FileTest.expects(:exist?).with(@module.metadata_file).returns true + File.stubs(:read).with(@module.metadata_file).returns "{\"foo\" : \"bar\"}" @module.should be_has_metadata end + it "should have metadata if it has a metadata file and its data is not empty" do + FileTest.expects(:exist?).with(@module.metadata_file).returns true + File.stubs(:read).with(@module.metadata_file).returns "{\"foo\" : \"bar\"}" + + @module.should be_has_metadata + end + + it "should not have metadata if has a metadata file and its data is empty" do + FileTest.expects(:exist?).with(@module.metadata_file).returns true + File.stubs(:read).with(@module.metadata_file).returns "/* ++-----------------------------------------------------------------------+ +| | +| ==> DO NOT EDIT THIS FILE! <== | +| | +| You should edit the `Modulefile` and run `puppet-module build` | +| to generate the `metadata.json` file for your releases. | +| | ++-----------------------------------------------------------------------+ +*/ + +{}" + + @module.should_not be_has_metadata + end + it "should know if it is missing a metadata file" do FileTest.expects(:exist?).with(@module.metadata_file).returns false @@ -528,16 +554,16 @@ describe Puppet::Module do Puppet::Module.new("yay") end - describe "when loading the medatada file", :if => Puppet.features.json? do + describe "when loading the medatada file", :if => Puppet.features.pson? do before do @data = { - :license => "GPL2", - :author => "luke", - :version => "1.0", - :source => "http://foo/", + :license => "GPL2", + :author => "luke", + :version => "1.0", + :source => "http://foo/", :puppetversion => "0.25" } - @text = @data.to_json + @text = @data.to_pson @module = Puppet::Module.new("foo") @module.stubs(:metadata_file).returns "/my/file" @@ -552,9 +578,12 @@ describe Puppet::Module do it "should fail if #{attr} is not present in the metadata file" do @data.delete(attr.to_sym) - @text = @data.to_json + @text = @data.to_pson File.stubs(:read).with("/my/file").returns @text - lambda { @module.load_metadata }.should raise_error(Puppet::Module::MissingMetadata) + lambda { @module.load_metadata }.should raise_error( + Puppet::Module::MissingMetadata, + "No #{attr} module metadata provided for foo" + ) end end diff --git a/spec/unit/network/authconfig_spec.rb b/spec/unit/network/authconfig_spec.rb index c47b2e0c5..ca94cc1ab 100755 --- a/spec/unit/network/authconfig_spec.rb +++ b/spec/unit/network/authconfig_spec.rb @@ -184,6 +184,29 @@ describe Puppet::Network::AuthConfig do @authconfig.read end + it "should strip whitespace around ACE" do + acl = stub 'acl', :info + + @fd.stubs(:each).multiple_yields('[puppetca]', ' allow 127.0.0.1 , 172.16.10.0 ') + @rights.stubs(:newright).with("[puppetca]", 1, 'dummy').returns(acl) + + acl.expects(:allow).with('127.0.0.1') + acl.expects(:allow).with('172.16.10.0') + + @authconfig.read + end + + it "should allow ACE inline comments" do + acl = stub 'acl', :info + + @fd.stubs(:each).multiple_yields('[puppetca]', ' allow 127.0.0.1 # will it work?') + @rights.stubs(:newright).with("[puppetca]", 1, 'dummy').returns(acl) + + acl.expects(:allow).with('127.0.0.1') + + @authconfig.read + end + it "should create an allow ACE on each subsequent allow" do acl = stub 'acl', :info diff --git a/spec/unit/network/handler/fileserver_spec.rb b/spec/unit/network/handler/fileserver_spec.rb index 08852634d..851736e76 100755 --- a/spec/unit/network/handler/fileserver_spec.rb +++ b/spec/unit/network/handler/fileserver_spec.rb @@ -25,6 +25,38 @@ describe Puppet::Network::Handler::FileServer do @mount = Puppet::Network::Handler::FileServer::Mount.new("some_path", @basedir) end + describe "when parsing the fileserver.conf" do + it "should create a valid mount when a valid conf is read" do + config_file = tmpfile('fileserver.conf') + mountdir = tmpdir('mountdir') + + conf_text = <<-HEREDOC + [mymount] + path #{mountdir} + allow anyone.com + deny nobody.com + HEREDOC + File.open(config_file, 'w') { |f| f.write conf_text } + + fs = Puppet::Network::Handler::FileServer.new(:Config => config_file) + mounts = fs.instance_variable_get(:@mounts) + mount = mounts["mymount"] + mount.path == mountdir + mount.instance_variable_get(:@declarations).map {|d| d.pattern}.should =~ [["com", "nobody"], ["com", "anyone"]] + end + + ['path', 'allow', 'deny'].each do |arg| + it "should error if config file doesn't specify a mount for #{arg} argument" do + config_file = tmpfile('fileserver.conf') + File.open(config_file, 'w') { |f| f.puts "#{arg} 127.0.0.1/24" } + + expect { + Puppet::Network::Handler::FileServer.new(:Config => config_file) + }.should raise_error(Puppet::Network::Handler::FileServerError, "No mount specified for argument #{arg} 127.0.0.1/24") + end + end + end + it "should list a single directory" do @mount.list("/", false, false).should == [["/", "directory"]] end diff --git a/spec/unit/network/rest_authconfig_spec.rb b/spec/unit/network/rest_authconfig_spec.rb index e1403997f..bebbb874f 100755 --- a/spec/unit/network/rest_authconfig_spec.rb +++ b/spec/unit/network/rest_authconfig_spec.rb @@ -29,7 +29,7 @@ describe Puppet::Network::RestAuthConfig do params = {:ip => "127.0.0.1", :node => "me", :environment => :env, :authenticated => true} @acl.expects(:is_request_forbidden_and_why?).with("path", :save, "to/resource", params).returns(nil) - @authconfig.allowed?("path", :save, "to/resource", params) + @authconfig.check_authorization("path", :save, "to/resource", params) end describe "when defining an acl with mk_acl" do diff --git a/spec/unit/resource/catalog_spec.rb b/spec/unit/resource/catalog_spec.rb index 8f4910af6..896167a2b 100755 --- a/spec/unit/resource/catalog_spec.rb +++ b/spec/unit/resource/catalog_spec.rb @@ -500,7 +500,7 @@ describe Puppet::Resource::Catalog, "when compiling" do lambda { @catalog.alias(@one, "other") }.should_not raise_error end - it "should create aliases for resources isomorphic resources whose names do not match their titles" do + it "should create aliases for isomorphic resources whose names do not match their titles" do resource = Puppet::Type::File.new(:title => "testing", :path => @basepath+"/something") @catalog.add_resource(resource) @@ -508,7 +508,7 @@ describe Puppet::Resource::Catalog, "when compiling" do @catalog.resource(:file, @basepath+"/something").should equal(resource) end - it "should not create aliases for resources non-isomorphic resources whose names do not match their titles" do + it "should not create aliases for non-isomorphic resources whose names do not match their titles" do resource = Puppet::Type.type(:exec).new(:title => "testing", :command => "echo", :path => %w{/bin /usr/bin /usr/local/bin}) @catalog.add_resource(resource) @@ -524,11 +524,6 @@ describe Puppet::Resource::Catalog, "when compiling" do @catalog.resource("notify", "other").should equal(@one) end - it "should ignore conflicting aliases that point to the aliased resource" do - @catalog.alias(@one, "other") - lambda { @catalog.alias(@one, "other") }.should_not raise_error - end - it "should fail to add an alias if the aliased name already exists" do @catalog.add_resource @one proc { @catalog.alias @two, "one" }.should raise_error(ArgumentError) @@ -582,6 +577,58 @@ describe Puppet::Resource::Catalog, "when compiling" do @catalog.create_resource :file, args @catalog.resource("File[/yay]").should equal(resource) end + + describe "when adding resources with multiple namevars" do + before :each do + Puppet::Type.newtype(:multiple) do + newparam(:color, :namevar => true) + newparam(:designation, :namevar => true) + + def self.title_patterns + [ [ + /^(\w+) (\w+)$/, + [ + [:color, lambda{|x| x}], + [:designation, lambda{|x| x}] + ] + ] ] + end + end + end + + it "should add an alias using the uniqueness key" do + @resource = Puppet::Type.type(:multiple).new(:title => "some resource", :color => "red", :designation => "5") + + @catalog.add_resource(@resource) + @catalog.resource(:multiple, "some resource").must == @resource + @catalog.resource("Multiple[some resource]").must == @resource + @catalog.resource("Multiple[red 5]").must == @resource + end + + it "should conflict with a resource with the same uniqueness key" do + @resource = Puppet::Type.type(:multiple).new(:title => "some resource", :color => "red", :designation => "5") + @other = Puppet::Type.type(:multiple).new(:title => "another resource", :color => "red", :designation => "5") + + @catalog.add_resource(@resource) + expect { @catalog.add_resource(@other) }.to raise_error(ArgumentError, /Cannot alias Multiple\[another resource\] to \["red", "5"\].*resource \["Multiple", "red", "5"\] already defined/) + end + + it "should conflict when its uniqueness key matches another resource's title" do + @resource = Puppet::Type.type(:file).new(:title => "/tmp/foo") + @other = Puppet::Type.type(:file).new(:title => "another file", :path => "/tmp/foo") + + @catalog.add_resource(@resource) + expect { @catalog.add_resource(@other) }.to raise_error(ArgumentError, /Cannot alias File\[another file\] to \["\/tmp\/foo"\].*resource \["File", "\/tmp\/foo"\] already defined/) + end + + it "should conflict when its uniqueness key matches the uniqueness key derived from another resource's title" do + @resource = Puppet::Type.type(:multiple).new(:title => "red leader") + @other = Puppet::Type.type(:multiple).new(:title => "another resource", :color => "red", :designation => "leader") + + @catalog.add_resource(@resource) + expect { @catalog.add_resource(@other) }.to raise_error(ArgumentError, /Cannot alias Multiple\[another resource\] to \["red", "leader"\].*resource \["Multiple", "red", "leader"\] already defined/) + end + end end describe "when applying" do diff --git a/spec/unit/semver_spec.rb b/spec/unit/semver_spec.rb new file mode 100644 index 000000000..0e0457b6e --- /dev/null +++ b/spec/unit/semver_spec.rb @@ -0,0 +1,187 @@ +require 'spec_helper' +require 'semver' + +describe SemVer do + describe '::valid?' do + it 'should validate basic version strings' do + %w[ 0.0.0 999.999.999 v0.0.0 v999.999.999 ].each do |vstring| + SemVer.valid?(vstring).should be_true + end + end + + it 'should validate special version strings' do + %w[ 0.0.0foo 999.999.999bar v0.0.0a v999.999.999beta ].each do |vstring| + SemVer.valid?(vstring).should be_true + end + end + + it 'should fail to validate invalid version strings' do + %w[ nope 0.0foo 999.999 x0.0.0 z.z.z 1.2.3-beta 1.x.y ].each do |vstring| + SemVer.valid?(vstring).should be_false + end + end + end + + describe '::find_matching' do + before :all do + @versions = %w[ + 0.0.1 + 0.0.2 + 1.0.0rc1 + 1.0.0rc2 + 1.0.0 + 1.0.1 + 1.1.0 + 1.1.1 + 1.1.2 + 1.1.3 + 1.1.4 + 1.2.0 + 1.2.1 + 2.0.0rc1 + ].map { |v| SemVer.new(v) } + end + + it 'should match exact versions by string' do + @versions.each do |version| + SemVer.find_matching(version, @versions).should == version + end + end + + it 'should return nil if no versions match' do + %w[ 3.0.0 2.0.0rc2 1.0.0alpha ].each do |v| + SemVer.find_matching(v, @versions).should be_nil + end + end + + it 'should find the greatest match for partial versions' do + SemVer.find_matching('1.0', @versions).should == 'v1.0.1' + SemVer.find_matching('1.1', @versions).should == 'v1.1.4' + SemVer.find_matching('1', @versions).should == 'v1.2.1' + SemVer.find_matching('2', @versions).should == 'v2.0.0rc1' + SemVer.find_matching('2.1', @versions).should == nil + end + + + it 'should find the greatest match for versions with placeholders' do + SemVer.find_matching('1.0.x', @versions).should == 'v1.0.1' + SemVer.find_matching('1.1.x', @versions).should == 'v1.1.4' + SemVer.find_matching('1.x', @versions).should == 'v1.2.1' + SemVer.find_matching('1.x.x', @versions).should == 'v1.2.1' + SemVer.find_matching('2.x', @versions).should == 'v2.0.0rc1' + SemVer.find_matching('2.x.x', @versions).should == 'v2.0.0rc1' + SemVer.find_matching('2.1.x', @versions).should == nil + end + end + + describe 'instantiation' do + it 'should raise an exception when passed an invalid version string' do + expect { SemVer.new('invalidVersion') }.to raise_exception ArgumentError + end + + it 'should populate the appropriate fields for a basic version string' do + version = SemVer.new('1.2.3') + version.major.should == 1 + version.minor.should == 2 + version.tiny.should == 3 + version.special.should == '' + end + + it 'should populate the appropriate fields for a special version string' do + version = SemVer.new('3.4.5beta6') + version.major.should == 3 + version.minor.should == 4 + version.tiny.should == 5 + version.special.should == 'beta6' + end + end + + describe '#matched_by?' do + subject { SemVer.new('v1.2.3beta') } + + describe 'should match against' do + describe 'literal version strings' do + it { should be_matched_by('1.2.3beta') } + + it { should_not be_matched_by('1.2.3alpha') } + it { should_not be_matched_by('1.2.4beta') } + it { should_not be_matched_by('1.3.3beta') } + it { should_not be_matched_by('2.2.3beta') } + end + + describe 'partial version strings' do + it { should be_matched_by('1.2.3') } + it { should be_matched_by('1.2') } + it { should be_matched_by('1') } + end + + describe 'version strings with placeholders' do + it { should be_matched_by('1.2.x') } + it { should be_matched_by('1.x.3') } + it { should be_matched_by('1.x.x') } + it { should be_matched_by('1.x') } + end + end + end + + describe 'comparisons' do + describe 'against a string' do + it 'should just work' do + SemVer.new('1.2.3').should == '1.2.3' + end + end + + describe 'against a symbol' do + it 'should just work' do + SemVer.new('1.2.3').should == :'1.2.3' + end + end + + describe 'on a basic version (v1.2.3)' do + subject { SemVer.new('v1.2.3') } + + it { should == SemVer.new('1.2.3') } + + # Different major versions + it { should > SemVer.new('0.2.3') } + it { should < SemVer.new('2.2.3') } + + # Different minor versions + it { should > SemVer.new('1.1.3') } + it { should < SemVer.new('1.3.3') } + + # Different tiny versions + it { should > SemVer.new('1.2.2') } + it { should < SemVer.new('1.2.4') } + + # Against special versions + it { should > SemVer.new('1.2.3beta') } + it { should < SemVer.new('1.2.4beta') } + end + + describe 'on a special version (v1.2.3beta)' do + subject { SemVer.new('v1.2.3beta') } + + it { should == SemVer.new('1.2.3beta') } + + # Same version, final release + it { should < SemVer.new('1.2.3') } + + # Different major versions + it { should > SemVer.new('0.2.3') } + it { should < SemVer.new('2.2.3') } + + # Different minor versions + it { should > SemVer.new('1.1.3') } + it { should < SemVer.new('1.3.3') } + + # Different tiny versions + it { should > SemVer.new('1.2.2') } + it { should < SemVer.new('1.2.4') } + + # Against special versions + it { should > SemVer.new('1.2.3alpha') } + it { should < SemVer.new('1.2.3beta2') } + end + end +end diff --git a/spec/unit/type/user_spec.rb b/spec/unit/type/user_spec.rb index 71c9e1857..823b12f27 100755 --- a/spec/unit/type/user_spec.rb +++ b/spec/unit/type/user_spec.rb @@ -289,6 +289,14 @@ describe user do @password.change_to_s("other", "mypass").should_not be_include("mypass") end + it "should redact the password when displaying the old value" do + @password.is_to_s("currentpassword").should =~ /^\[old password hash redacted\]$/ + end + + it "should redact the password when displaying the new value" do + @password.should_to_s("newpassword").should =~ /^\[new password hash redacted\]$/ + end + it "should fail if a ':' is included in the password" do lambda { @password.should = "some:thing" }.should raise_error(Puppet::Error) end diff --git a/spec/unit/util/settings_spec.rb b/spec/unit/util/settings_spec.rb index aa50c8f3a..a2cd57a0c 100755 --- a/spec/unit/util/settings_spec.rb +++ b/spec/unit/util/settings_spec.rb @@ -1,5 +1,6 @@ #!/usr/bin/env rspec require 'spec_helper' +require 'ostruct' describe Puppet::Util::Settings do describe "when specifying defaults" do @@ -1104,4 +1105,14 @@ describe Puppet::Util::Settings do it "should cache the result" end + + describe "#writesub" do + it "should only pass valid arguments to File.open" do + settings = Puppet::Util::Settings.new + settings.stubs(:get_config_file_default).with(:privatekeydir).returns(OpenStruct.new(:mode => "750")) + + File.expects(:open).with("/path/to/keydir", "w", 750).returns true + settings.writesub(:privatekeydir, "/path/to/keydir") + end + end end |
