diff options
author | Josh Cooper <josh@puppet.com> | 2018-07-13 09:38:02 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-07-13 09:38:02 -0700 |
commit | bd20d920a19e9b011dfd027a802985cea46ae04f (patch) | |
tree | f882cd2b7c31ae08cd242882b0f7f1d1ed89cbbd /spec | |
parent | 9d96ddecea6367abee1aa77859848b0c158fca80 (diff) | |
parent | c957642d70f4b778736a9b49cf1ef9702e42c4f1 (diff) | |
download | puppet-hosts_core-bd20d920a19e9b011dfd027a802985cea46ae04f.tar.gz puppet-hosts_core-bd20d920a19e9b011dfd027a802985cea46ae04f.tar.bz2 |
Merge pull request #1 from puppetlabs/extraction
Initial host module extraction
Diffstat (limited to 'spec')
-rw-r--r-- | spec/acceptance/nodesets/default.yml | 13 | ||||
-rw-r--r-- | spec/acceptance/tests/create_spec.rb | 56 | ||||
-rw-r--r-- | spec/acceptance/tests/destroy_spec.rb | 36 | ||||
-rw-r--r-- | spec/acceptance/tests/modify_spec.rb | 35 | ||||
-rw-r--r-- | spec/acceptance/tests/query_all_spec.rb | 37 | ||||
-rw-r--r-- | spec/default_facts.yml | 8 | ||||
-rw-r--r-- | spec/fixtures/unit/provider/host/parsed/valid_hosts | 19 | ||||
-rw-r--r-- | spec/lib/puppet_spec/files.rb | 44 | ||||
-rw-r--r-- | spec/shared_behaviours/all_parsedfile_providers.rb | 21 | ||||
-rw-r--r-- | spec/spec_helper.rb | 45 | ||||
-rw-r--r-- | spec/spec_helper_acceptance.rb | 13 | ||||
-rw-r--r-- | spec/spec_helper_local.rb | 19 | ||||
-rw-r--r-- | spec/unit/provider/host/parsed_spec.rb | 210 | ||||
-rw-r--r-- | spec/unit/type/host_spec.rb | 665 |
14 files changed, 1221 insertions, 0 deletions
diff --git a/spec/acceptance/nodesets/default.yml b/spec/acceptance/nodesets/default.yml new file mode 100644 index 0000000..6323005 --- /dev/null +++ b/spec/acceptance/nodesets/default.yml @@ -0,0 +1,13 @@ +--- +HOSTS: + centos7-64-1: + hypervisor: vmpooler + platform: el-7-x86_64 + packaging_platform: el-7-x86_64 + template: centos-7-x86_64 + roles: + - agent + - database +CONFIG: + type: agent + pooling_api: http://vmpooler.delivery.puppetlabs.net/ diff --git a/spec/acceptance/tests/create_spec.rb b/spec/acceptance/tests/create_spec.rb new file mode 100644 index 0000000..40267fd --- /dev/null +++ b/spec/acceptance/tests/create_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper_acceptance' + +RSpec.context 'when creating host files' do + agents.each do |agent| + context "on #{agent}" do + let(:target) { agent.tmpfile('host-create') } + + after(:each) do + on(agent, "test #{target} && rm -f #{target}") + end + + it 'creates a host record' do + on(agent, puppet_resource('host', 'test', 'ensure=present', + 'ip=127.0.0.1', "target=#{target}")) + on(agent, "cat #{target}") do |result| + fail_test 'record was not present' if result.stdout !~ %r{^127\.0\.0\.1[[:space:]]+test} + end + end + + it 'creates host aliases' do + on(agent, puppet_resource('host', 'test', 'ensure=present', + 'ip=127.0.0.7', "target=#{target}", 'host_aliases=alias')) + + on(agent, "cat #{target}") do |result| + fail_test 'alias was missing' unless + result.stdout =~ %r{^127\.0\.0\.7[[:space:]]+test[[:space:]]alias} + end + end + + it "doesn't create the entry if it already exists" do + on agent, "printf '127.0.0.2 test alias\n' > #{target}" + + step 'tell puppet to ensure the host exists' + on(agent, puppet_resource('host', 'test', "target=#{target}", + 'ensure=present', 'ip=127.0.0.2', 'host_aliases=alias')) do |result| + fail_test 'darn, we created the host record' if + result.stdout.include? '/Host[test1]/ensure: created' + end + end + + it 'requires an ipaddress' do + skip_test if agent['locale'] == 'ja' + + on(agent, puppet_resource('host', 'test', "target=#{target}", + 'host_aliases=alias')) do |result| + fail_test "puppet didn't complain about the missing attribute" unless + result.stderr.include? 'ip is a required attribute for hosts' + end + + on(agent, "cat #{target}") do |result| + fail_test 'the host was apparently added to the file' if result.stdout.include? 'test' + end + end + end + end +end diff --git a/spec/acceptance/tests/destroy_spec.rb b/spec/acceptance/tests/destroy_spec.rb new file mode 100644 index 0000000..b00e03a --- /dev/null +++ b/spec/acceptance/tests/destroy_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper_acceptance' + +RSpec.context 'when managing host files' do + agents.each do |agent| + context "on #{agent}" do + let(:target) { agent.tmpfile('host-destroy') } + + after(:each) do + on(agent, "test #{target} && rm -f #{target}") + end + + it 'deletes a host record' do + line = '127.0.0.7 test1' + + on agent, "printf '#{line}\n' > #{target}" + on(agent, puppet_resource('host', 'test1', "target=#{target}", + 'ensure=absent', 'ip=127.0.0.7')) + on(agent, "cat #{target}") do |result| + fail_test 'the content was still present' if result.stdout.include? line + end + end + + it 'does not purge valid host records if file contains malformed content' do + on(agent, "printf '127.0.0.2 existing alias\n' > #{target}") + on(agent, "printf '==\n' >> #{target}") + + on(agent, puppet_resource('host', 'test', "target=#{target}", + 'ensure=present', 'ip=127.0.0.3', 'host_aliases=foo')) + + on(agent, "cat #{target}") do |result| + fail_test 'existing host data was deleted' unless result.stdout.include? 'existing' + end + end + end + end +end diff --git a/spec/acceptance/tests/modify_spec.rb b/spec/acceptance/tests/modify_spec.rb new file mode 100644 index 0000000..6498ee0 --- /dev/null +++ b/spec/acceptance/tests/modify_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper_acceptance' + +RSpec.context 'when modifying host files' do + agents.each do |agent| + context "on #{agent}" do + let(:target) { agent.tmpfile('host-modify') } + + after(:each) do + on(agent, "test #{target} && rm -f #{target}") + end + + it 'modifies a host address' do + on agent, "printf '127.0.0.9 test alias\n' > #{target}" + on(agent, puppet_resource('host', 'test', "target=#{target}", + 'ensure=present', 'ip=127.0.0.10', 'host_aliases=alias')) + + on(agent, "cat #{target}") do |result| + fail_test 'the address was not updated' unless + result.stdout =~ %r{^127\.0\.0\.10[[:space:]]+test[[:space:]]+alias[[:space:]]*$} + end + end + + it 'modifies a host alias' do + on agent, "printf '127.0.0.8 test alias\n' > #{target}" + on(agent, puppet_resource('host', 'test', "target=#{target}", + 'ensure=present', 'ip=127.0.0.8', 'host_aliases=banzai')) + + on(agent, "cat #{target}") do |result| + fail_test 'the alias was not updated' unless + result.stdout =~ %r{^127\.0\.0\.8[[:space:]]+test[[:space:]]+banzai[[:space:]]*$} + end + end + end + end +end diff --git a/spec/acceptance/tests/query_all_spec.rb b/spec/acceptance/tests/query_all_spec.rb new file mode 100644 index 0000000..1653ab0 --- /dev/null +++ b/spec/acceptance/tests/query_all_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper_acceptance' + +RSpec.context 'when querying all hosts from a host file' do + agents.each do |agent| + context "on #{agent}" do + let(:backup) { agent.tmpfile('host-query') } + let(:content) do + <<END +127.0.0.1 test1 test1.local +127.0.0.2 test2 test2.local +127.0.0.3 test3 test3.local +127.0.0.4 test4 test4.local +END + end + + before(:each) do + on(agent, "cp /etc/hosts #{backup}") + on agent, 'cat > /etc/hosts', stdin: content + end + + after(:each) do + on agent, "cat #{backup} > /etc/hosts && rm -f #{backup}" + end + + it 'returns 4 host records' do + on(agent, puppet_resource('host')) do |result| + found = result.stdout.scan(%r{host \{ '([^']+)'}).flatten.sort + fail_test "the list of returned hosts was wrong: #{found.join(', ')}" unless + found == ['test1', 'test2', 'test3', 'test4'] + + count = result.stdout.scan(%r{ensure\s+=>\s+'present'}).length + fail_test "found #{count} records, wanted 4" unless count == 4 + end + end + end + end +end diff --git a/spec/default_facts.yml b/spec/default_facts.yml new file mode 100644 index 0000000..3248be5 --- /dev/null +++ b/spec/default_facts.yml @@ -0,0 +1,8 @@ +# Use default_module_facts.yml for module specific facts. +# +# Facts specified here will override the values provided by rspec-puppet-facts. +--- +concat_basedir: "/tmp" +ipaddress: "172.16.254.254" +is_pe: false +macaddress: "AA:AA:AA:AA:AA:AA" diff --git a/spec/fixtures/unit/provider/host/parsed/valid_hosts b/spec/fixtures/unit/provider/host/parsed/valid_hosts new file mode 100644 index 0000000..2463629 --- /dev/null +++ b/spec/fixtures/unit/provider/host/parsed/valid_hosts @@ -0,0 +1,19 @@ +# Some leading comment, that should be ignored +# The next line is empty so it should be ignored + +::1 localhost + +# We now try another delimiter: Several tabs +127.0.0.1 localhost + +# No test trailing spaces +10.0.0.1 host1 + +# Ok its time to test aliases +2001:252:0:1::2008:8 ipv6host alias1 +192.168.0.1 ipv4host alias2 alias3 + +# Testing inlinecomments now +192.168.0.2 host3 # This is host3 +192.168.0.3 host4 alias10 # This is host4 +192.168.0.4 host5 alias11 alias12 # This is host5 diff --git a/spec/lib/puppet_spec/files.rb b/spec/lib/puppet_spec/files.rb new file mode 100644 index 0000000..fa774ef --- /dev/null +++ b/spec/lib/puppet_spec/files.rb @@ -0,0 +1,44 @@ +require 'fileutils' +require 'tempfile' +require 'tmpdir' + +# A support module for testing files. +module PuppetSpec::Files + @global_tempfiles = [] + + def self.cleanup + until @global_tempfiles.empty? + path = @global_tempfiles.pop + Dir.unstub(:entries) + FileUtils.rm_rf path, secure: true + end + end + + module_function + + def tmpfile(name, dir = nil) + dir ||= Dir.tmpdir + path = Puppet::FileSystem.expand_path(make_tmpname(name, nil).encode(Encoding::UTF_8), dir) + PuppetSpec::Files.record_tmp(File.expand_path(path)) + + path + end + + # Copied from ruby 2.4 source + def make_tmpname((prefix, suffix), n) + prefix = (String.try_convert(prefix) || + raise(ArgumentError, "unexpected prefix: #{prefix.inspect}")) + suffix &&= (String.try_convert(suffix) || + raise(ArgumentError, "unexpected suffix: #{suffix.inspect}")) + t = Time.now.strftime('%Y%m%d') + path = "#{prefix}#{t}-#{$PROCESS_ID}-#{rand(0x100000000).to_s(36)}".dup + path << "-#{n}" if n + path << suffix if suffix + path + end + + def self.record_tmp(tmp) + # ...record it for cleanup, + @global_tempfiles << tmp + end +end diff --git a/spec/shared_behaviours/all_parsedfile_providers.rb b/spec/shared_behaviours/all_parsedfile_providers.rb new file mode 100644 index 0000000..d697a14 --- /dev/null +++ b/spec/shared_behaviours/all_parsedfile_providers.rb @@ -0,0 +1,21 @@ +shared_examples_for 'all parsedfile providers' do |provider, *files| + if files.empty? + files = my_fixtures + end + + files.flatten.each do |file| + it "should rewrite #{file} reasonably unchanged" do + provider.stubs(:default_target).returns(file) + provider.prefetch + + text = provider.to_file(provider.target_records(file)) + text.gsub!(%r{^# HEADER.+\n}, '') + + oldlines = File.readlines(file) + newlines = text.chomp.split "\n" + oldlines.zip(newlines).each do |old, new| + expect(new.gsub(%r{\s+}, '')).to eq(old.chomp.gsub(%r{\s+}, '')) + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..e69d11d --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,45 @@ + +require 'puppetlabs_spec_helper/module_spec_helper' +require 'rspec-puppet-facts' + +begin + require 'spec_helper_local' if File.file?(File.join(File.dirname(__FILE__), 'spec_helper_local.rb')) +rescue LoadError => loaderror + warn "Could not require spec_helper_local: #{loaderror.message}" +end + +include RspecPuppetFacts + +default_facts = { + puppetversion: Puppet.version, + facterversion: Facter.version, +} + +default_facts_path = File.expand_path(File.join(File.dirname(__FILE__), 'default_facts.yml')) +default_module_facts_path = File.expand_path(File.join(File.dirname(__FILE__), 'default_module_facts.yml')) + +if File.exist?(default_facts_path) && File.readable?(default_facts_path) + default_facts.merge!(YAML.safe_load(File.read(default_facts_path))) +end + +if File.exist?(default_module_facts_path) && File.readable?(default_module_facts_path) + default_facts.merge!(YAML.safe_load(File.read(default_module_facts_path))) +end + +RSpec.configure do |c| + c.default_facts = default_facts + c.before :each do + # set to strictest setting for testing + # by default Puppet runs at warning level + Puppet.settings[:strict] = :warning + end +end + +def ensure_module_defined(module_name) + module_name.split('::').reduce(Object) do |last_module, next_module| + last_module.const_set(next_module, Module.new) unless last_module.const_defined?(next_module) + last_module.const_get(next_module) + end +end + +# 'spec_overrides' from sync.yml will appear below this line diff --git a/spec/spec_helper_acceptance.rb b/spec/spec_helper_acceptance.rb new file mode 100644 index 0000000..848f7d9 --- /dev/null +++ b/spec/spec_helper_acceptance.rb @@ -0,0 +1,13 @@ +require 'beaker-rspec' +require 'beaker/module_install_helper' +require 'beaker/puppet_install_helper' + +RSpec.configure do |c| + c.before :suite do + unless ENV['BEAKER_provision'] == 'no' + run_puppet_install_helper + install_module_on(hosts_as('default')) + install_module_dependencies_on(hosts) + end + end +end diff --git a/spec/spec_helper_local.rb b/spec/spec_helper_local.rb new file mode 100644 index 0000000..86c87f0 --- /dev/null +++ b/spec/spec_helper_local.rb @@ -0,0 +1,19 @@ +dir = File.expand_path(File.dirname(__FILE__)) +$LOAD_PATH.unshift File.join(dir, 'lib') + +# So everyone else doesn't have to include this base constant. +module PuppetSpec + FIXTURE_DIR = File.join(File.expand_path(File.dirname(__FILE__)), 'fixtures') unless defined?(FIXTURE_DIR) +end + +require 'puppet_spec/files' + +Pathname.glob("#{dir}/shared_behaviours/**/*.rb") do |behaviour| + require behaviour.relative_path_from(Pathname.new(dir)) +end + +RSpec.configure do |c| + c.after :each do + PuppetSpec::Files.cleanup + end +end diff --git a/spec/unit/provider/host/parsed_spec.rb b/spec/unit/provider/host/parsed_spec.rb new file mode 100644 index 0000000..e07c83d --- /dev/null +++ b/spec/unit/provider/host/parsed_spec.rb @@ -0,0 +1,210 @@ +require 'spec_helper' +require 'shared_behaviours/all_parsedfile_providers' + +require 'puppet_spec/files' + +describe Puppet::Type.type(:host).provider(:parsed) do + include PuppetSpec::Files + + let(:provider) { described_class } + let(:hostfile) { tmpfile('hosts') } + + after :each do + provider.initvars + end + + def mkhost(args) + hostresource = Puppet::Type::Host.new(name: args[:name], target: hostfile) + + # Using setters of provider to build our testobject + # Note: We already proved, that in case of host_aliases + # the provider setter "host_aliases=(value)" will be + # called with the joined array, so we just simulate that + host = provider.new(hostresource) + args.each do |property, value| + value = value.join(' ') if property == :host_aliases && value.is_a?(Array) + host.send("#{property}=", value) + end + host + end + + def genhost(host) + provider.stubs(:filetype).returns(Puppet::Util::FileType::FileTypeRam) + File.stubs(:chown) + File.stubs(:chmod) + Puppet::Util::SUIDManager.stubs(:asuser).yields + host.flush + provider.target_object(hostfile).read + end + + describe 'when parsing on incomplete line' do + it 'works for only ip' do + expect(provider.parse_line('127.0.0.1')[:line]).to eq('127.0.0.1') + end + + it 'works for only hostname' do + expect(provider.parse_line('www.example.com')[:line]).to eq('www.example.com') + end + + it 'works for ip and space' do + expect(provider.parse_line('127.0.0.1 ')[:line]).to eq('127.0.0.1 ') + end + + it 'works for hostname and space' do + expect(provider.parse_line('www.example.com ')[:line]).to eq('www.example.com ') + end + + it 'works for hostname and host_aliases' do + expect(provider.parse_line('www.example.com www xyz')[:line]).to eq('www.example.com www xyz') + end + + it 'works for ip and comment' do + expect(provider.parse_line('127.0.0.1 #www xyz')[:line]).to eq('127.0.0.1 #www xyz') + end + + it 'works for hostname and comment' do + expect(provider.parse_line('xyz #www test123')[:line]).to eq('xyz #www test123') + end + + it 'works for crazy incomplete lines' do + expect(provider.parse_line("%th1s is a\t cr$zy !incompl1t line")[:line]).to eq("%th1s is a\t cr$zy !incompl1t line") + end + end + + describe 'when parsing a line with ip and hostname' do + it 'parses an ipv4 from the first field' do + expect(provider.parse_line('127.0.0.1 localhost')[:ip]).to eq('127.0.0.1') + end + + it 'parses an ipv6 from the first field' do + expect(provider.parse_line('::1 localhost')[:ip]).to eq('::1') + end + + it 'parses the name from the second field' do + expect(provider.parse_line('::1 localhost')[:name]).to eq('localhost') + end + + it 'sets an empty comment' do + expect(provider.parse_line('::1 localhost')[:comment]).to eq('') + end + + it 'sets host_aliases to :absent' do + expect(provider.parse_line('::1 localhost')[:host_aliases]).to eq(:absent) + end + end + + describe 'when parsing a line with ip, hostname and comment' do + let(:testline) { '127.0.0.1 localhost # A comment with a #-char' } + + it 'parses the ip from the first field' do + expect(provider.parse_line(testline)[:ip]).to eq('127.0.0.1') + end + + it 'parses the hostname from the second field' do + expect(provider.parse_line(testline)[:name]).to eq('localhost') + end + + it "parses the comment after the first '#' character" do + expect(provider.parse_line(testline)[:comment]).to eq('A comment with a #-char') + end + end + + describe 'when parsing a line with ip, hostname and aliases' do + it 'parses alias from the third field' do + expect(provider.parse_line('127.0.0.1 localhost localhost.localdomain')[:host_aliases]).to eq('localhost.localdomain') + end + + it 'parses multiple aliases' do + expect(provider.parse_line('127.0.0.1 host alias1 alias2')[:host_aliases]).to eq('alias1 alias2') + expect(provider.parse_line("127.0.0.1 host alias1\talias2")[:host_aliases]).to eq('alias1 alias2') + expect(provider.parse_line("127.0.0.1 host alias1\talias2 alias3")[:host_aliases]).to eq('alias1 alias2 alias3') + end + end + + describe 'when parsing a line with ip, hostname, aliases and comment' do + # Just playing with a few different delimiters + let(:testline) { "127.0.0.1\t host alias1\talias2 alias3 # A comment with a #-char" } + + it 'parses the ip from the first field' do + expect(provider.parse_line(testline)[:ip]).to eq('127.0.0.1') + end + + it 'parses the hostname from the second field' do + expect(provider.parse_line(testline)[:name]).to eq('host') + end + + it 'parses all host_aliases from the third field' do + expect(provider.parse_line(testline)[:host_aliases]).to eq('alias1 alias2 alias3') + end + + it "parses the comment after the first '#' character" do + expect(provider.parse_line(testline)[:comment]).to eq('A comment with a #-char') + end + end + + describe 'when operating on /etc/hosts like files' do + it_behaves_like 'all parsedfile providers', + described_class, my_fixtures('valid*') + + it 'is able to generate a simple hostfile entry' do + host = mkhost( + name: 'localhost', + ip: '127.0.0.1', + ensure: :present, + ) + expect(genhost(host)).to eq("127.0.0.1\tlocalhost\n") + end + + it 'is able to generate an entry with one alias' do + host = mkhost( + name: 'localhost.localdomain', + ip: '127.0.0.1', + host_aliases: 'localhost', + ensure: :present, + ) + expect(genhost(host)).to eq("127.0.0.1\tlocalhost.localdomain\tlocalhost\n") + end + + it 'is able to generate an entry with more than one alias' do + host = mkhost( + name: 'host', + ip: '192.0.0.1', + host_aliases: ['a1', 'a2', 'a3', 'a4'], + ensure: :present, + ) + expect(genhost(host)).to eq("192.0.0.1\thost\ta1 a2 a3 a4\n") + end + + it 'is able to generate a simple hostfile entry with comments' do + host = mkhost( + name: 'localhost', + ip: '127.0.0.1', + comment: 'Bazinga!', + ensure: :present, + ) + expect(genhost(host)).to eq("127.0.0.1\tlocalhost\t# Bazinga!\n") + end + + it 'is able to generate an entry with one alias and a comment' do + host = mkhost( + name: 'localhost.localdomain', + ip: '127.0.0.1', + host_aliases: 'localhost', + comment: 'Bazinga!', + ensure: :present, + ) + expect(genhost(host)).to eq("127.0.0.1\tlocalhost.localdomain\tlocalhost\t# Bazinga!\n") + end + + it 'is able to generate an entry with more than one alias and a comment' do + host = mkhost( + name: 'host', + ip: '192.0.0.1', + host_aliases: ['a1', 'a2', 'a3', 'a4'], + comment: 'Bazinga!', + ensure: :present, + ) + expect(genhost(host)).to eq("192.0.0.1\thost\ta1 a2 a3 a4\t# Bazinga!\n") + end + end +end diff --git a/spec/unit/type/host_spec.rb b/spec/unit/type/host_spec.rb new file mode 100644 index 0000000..426657a --- /dev/null +++ b/spec/unit/type/host_spec.rb @@ -0,0 +1,665 @@ +require 'spec_helper' + +FakeHostProvider = Struct.new(:ip, :host_aliases, :comment) + +describe Puppet::Type.type(:host) do + let(:provider) { FakeHostProvider.new } + let(:resource) { stub('resource', resource: nil, provider: provider) } + + it 'has :name be its namevar' do + expect(described_class.key_attributes).to eq([:name]) + end + + describe 'when validating attributes' do + [:name, :provider].each do |param| + it "should have a #{param} parameter" do + expect(described_class.attrtype(param)).to eq(:param) + end + end + + [:ip, :target, :host_aliases, :comment, :ensure].each do |property| + it "should have a #{property} property" do + expect(described_class.attrtype(property)).to eq(:property) + end + end + + it 'has a list host_aliases' do + expect(described_class.attrclass(:host_aliases).ancestors).to be_include(Puppet::Property::OrderedList) + end + end + + describe 'when validating values' do + it 'supports present as a value for ensure' do + expect { described_class.new(name: 'foo', ensure: :present) }.not_to raise_error + end + + it 'supports absent as a value for ensure' do + expect { described_class.new(name: 'foo', ensure: :absent) }.not_to raise_error + end + + it 'accepts IPv4 addresses' do + expect { described_class.new(name: 'foo', ip: '10.96.0.1') }.not_to raise_error + end + + it 'accepts long IPv6 addresses' do + # Taken from wikipedia article about ipv6 + expect { described_class.new(name: 'foo', ip: '2001:0db8:85a3:08d3:1319:8a2e:0370:7344') }.not_to raise_error + end + + it 'accepts one host_alias' do + expect { described_class.new(name: 'foo', host_aliases: 'alias1') }.not_to raise_error + end + + it 'accepts multiple host_aliases' do + expect { described_class.new(name: 'foo', host_aliases: ['alias1', 'alias2']) }.not_to raise_error + end + + it 'accepts shortened IPv6 addresses' do + expect { described_class.new(name: 'foo', ip: '2001:db8:0:8d3:0:8a2e:70:7344') }.not_to raise_error + expect { described_class.new(name: 'foo', ip: '::ffff:192.0.2.128') }.not_to raise_error + expect { described_class.new(name: 'foo', ip: '::1') }.not_to raise_error + end + + it 'does not accept malformed IPv4 addresses like 192.168.0.300' do + expect { described_class.new(name: 'foo', ip: '192.168.0.300') }.to raise_error(Puppet::ResourceError, %r{Parameter ip failed}) + end + + it 'rejects over-long IPv4 addresses' do + expect { described_class.new(name: 'foo', ip: '10.10.10.10.10') }.to raise_error(Puppet::ResourceError, %r{Parameter ip failed}) + end + + it 'does not accept malformed IP addresses like 2001:0dg8:85a3:08d3:1319:8a2e:0370:7344' do + expect { described_class.new(name: 'foo', ip: '2001:0dg8:85a3:08d3:1319:8a2e:0370:7344') }.to raise_error(Puppet::ResourceError, %r{Parameter ip failed}) + end + + # Assorted, annotated IPv6 passes. + ['::1', # loopback, compressed, non-routable + '::', # unspecified, compressed, non-routable + '0:0:0:0:0:0:0:1', # loopback, full + '0:0:0:0:0:0:0:0', # unspecified, full + '2001:DB8:0:0:8:800:200C:417A', # unicast, full + 'FF01:0:0:0:0:0:0:101', # multicast, full + '2001:DB8::8:800:200C:417A', # unicast, compressed + 'FF01::101', # multicast, compressed + # Some more test cases that should pass. + '2001:0000:1234:0000:0000:C1C0:ABCD:0876', + '3ffe:0b00:0000:0000:0001:0000:0000:000a', + 'FF02:0000:0000:0000:0000:0000:0000:0001', + '0000:0000:0000:0000:0000:0000:0000:0001', + '0000:0000:0000:0000:0000:0000:0000:0000', + # Assorted valid, compressed IPv6 addresses. + '2::10', + 'ff02::1', + 'fe80::', + '2002::', + '2001:db8::', + '2001:0db8:1234::', + '::ffff:0:0', + '::1', + '1:2:3:4:5:6:7:8', + '1:2:3:4:5:6::8', + '1:2:3:4:5::8', + '1:2:3:4::8', + '1:2:3::8', + '1:2::8', + '1::8', + '1::2:3:4:5:6:7', + '1::2:3:4:5:6', + '1::2:3:4:5', + '1::2:3:4', + '1::2:3', + '1::8', + '::2:3:4:5:6:7:8', + '::2:3:4:5:6:7', + '::2:3:4:5:6', + '::2:3:4:5', + '::2:3:4', + '::2:3', + '::8', + '1:2:3:4:5:6::', + '1:2:3:4:5::', + '1:2:3:4::', + '1:2:3::', + '1:2::', + '1::', + '1:2:3:4:5::7:8', + '1:2:3:4::7:8', + '1:2:3::7:8', + '1:2::7:8', + '1::7:8', + # IPv4 addresses as dotted-quads + '1:2:3:4:5:6:1.2.3.4', + '1:2:3:4:5::1.2.3.4', + '1:2:3:4::1.2.3.4', + '1:2:3::1.2.3.4', + '1:2::1.2.3.4', + '1::1.2.3.4', + '1:2:3:4::5:1.2.3.4', + '1:2:3::5:1.2.3.4', + '1:2::5:1.2.3.4', + '1::5:1.2.3.4', + '1::5:11.22.33.44', + 'fe80::217:f2ff:254.7.237.98', + '::ffff:192.168.1.26', + '::ffff:192.168.1.1', + '0:0:0:0:0:0:13.1.68.3', # IPv4-compatible IPv6 address, full, deprecated + '0:0:0:0:0:FFFF:129.144.52.38', # IPv4-mapped IPv6 address, full + '::13.1.68.3', # IPv4-compatible IPv6 address, compressed, deprecated + '::FFFF:129.144.52.38', # IPv4-mapped IPv6 address, compressed + 'fe80:0:0:0:204:61ff:254.157.241.86', + 'fe80::204:61ff:254.157.241.86', + '::ffff:12.34.56.78', + '::ffff:192.0.2.128', # this is OK, since there's a single zero digit in IPv4 + 'fe80:0000:0000:0000:0204:61ff:fe9d:f156', + 'fe80:0:0:0:204:61ff:fe9d:f156', + 'fe80::204:61ff:fe9d:f156', + '::1', + 'fe80::', + 'fe80::1', + '::ffff:c000:280', + + # Additional test cases from http://rt.cpan.org/Public/Bug/Display.html?id=50693 + '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + '2001:db8:85a3:0:0:8a2e:370:7334', + '2001:db8:85a3::8a2e:370:7334', + '2001:0db8:0000:0000:0000:0000:1428:57ab', + '2001:0db8:0000:0000:0000::1428:57ab', + '2001:0db8:0:0:0:0:1428:57ab', + '2001:0db8:0:0::1428:57ab', + '2001:0db8::1428:57ab', + '2001:db8::1428:57ab', + '0000:0000:0000:0000:0000:0000:0000:0001', + '::1', + '::ffff:0c22:384e', + '2001:0db8:1234:0000:0000:0000:0000:0000', + '2001:0db8:1234:ffff:ffff:ffff:ffff:ffff', + '2001:db8:a::123', + 'fe80::', + + '1111:2222:3333:4444:5555:6666:7777:8888', + '1111:2222:3333:4444:5555:6666:7777::', + '1111:2222:3333:4444:5555:6666::', + '1111:2222:3333:4444:5555::', + '1111:2222:3333:4444::', + '1111:2222:3333::', + '1111:2222::', + '1111::', + '1111:2222:3333:4444:5555:6666::8888', + '1111:2222:3333:4444:5555::8888', + '1111:2222:3333:4444::8888', + '1111:2222:3333::8888', + '1111:2222::8888', + '1111::8888', + '::8888', + '1111:2222:3333:4444:5555::7777:8888', + '1111:2222:3333:4444::7777:8888', + '1111:2222:3333::7777:8888', + '1111:2222::7777:8888', + '1111::7777:8888', + '::7777:8888', + '1111:2222:3333:4444::6666:7777:8888', + '1111:2222:3333::6666:7777:8888', + '1111:2222::6666:7777:8888', + '1111::6666:7777:8888', + '::6666:7777:8888', + '1111:2222:3333::5555:6666:7777:8888', + '1111:2222::5555:6666:7777:8888', + '1111::5555:6666:7777:8888', + '::5555:6666:7777:8888', + '1111:2222::4444:5555:6666:7777:8888', + '1111::4444:5555:6666:7777:8888', + '::4444:5555:6666:7777:8888', + '1111::3333:4444:5555:6666:7777:8888', + '::3333:4444:5555:6666:7777:8888', + '::2222:3333:4444:5555:6666:7777:8888', + '1111:2222:3333:4444:5555:6666:123.123.123.123', + '1111:2222:3333:4444:5555::123.123.123.123', + '1111:2222:3333:4444::123.123.123.123', + '1111:2222:3333::123.123.123.123', + '1111:2222::123.123.123.123', + '1111::123.123.123.123', + '::123.123.123.123', + '1111:2222:3333:4444::6666:123.123.123.123', + '1111:2222:3333::6666:123.123.123.123', + '1111:2222::6666:123.123.123.123', + '1111::6666:123.123.123.123', + '::6666:123.123.123.123', + '1111:2222:3333::5555:6666:123.123.123.123', + '1111:2222::5555:6666:123.123.123.123', + '1111::5555:6666:123.123.123.123', + '::5555:6666:123.123.123.123', + '1111:2222::4444:5555:6666:123.123.123.123', + '1111::4444:5555:6666:123.123.123.123', + '::4444:5555:6666:123.123.123.123', + '1111::3333:4444:5555:6666:123.123.123.123', + '::2222:3333:4444:5555:6666:123.123.123.123', + + # Playing with combinations of "0" and "::"; these are all sytactically + # correct, but are bad form because "0" adjacent to "::" should be + # combined into "::" + '::0:0:0:0:0:0:0', + '::0:0:0:0:0:0', + '::0:0:0:0:0', + '::0:0:0:0', + '::0:0:0', + '::0:0', + '::0', + '0:0:0:0:0:0:0::', + '0:0:0:0:0:0::', + '0:0:0:0:0::', + '0:0:0:0::', + '0:0:0::', + '0:0::', + '0::', + + # Additional cases: http://crisp.tweakblogs.net/blog/2031/ipv6-validation-%28and-caveats%29.html + '0:a:b:c:d:e:f::', + '::0:a:b:c:d:e:f', # syntactically correct, but bad form (::0:... could be combined) + 'a:b:c:d:e:f:0::'].each do |ip| + it "should accept #{ip.inspect} as an IPv6 address" do + expect { described_class.new(name: 'foo', ip: ip) }.not_to raise_error + end + end + + # ...aaaand, some failure cases. + [':', + '02001:0000:1234:0000:0000:C1C0:ABCD:0876', # extra 0 not allowed! + '2001:0000:1234:0000:00001:C1C0:ABCD:0876', # extra 0 not allowed! + '2001:0000:1234:0000:0000:C1C0:ABCD:0876 0', # junk after valid address + '2001:0000:1234: 0000:0000:C1C0:ABCD:0876', # internal space + '3ffe:0b00:0000:0001:0000:0000:000a', # seven segments + 'FF02:0000:0000:0000:0000:0000:0000:0000:0001', # nine segments + '3ffe:b00::1::a', # double "::" + '::1111:2222:3333:4444:5555:6666::', # double "::" + '1:2:3::4:5::7:8', # Double "::" + '12345::6:7:8', + # IPv4 embedded, but bad... + '1::5:400.2.3.4', '1::5:260.2.3.4', '1::5:256.2.3.4', '1::5:1.256.3.4', + '1::5:1.2.256.4', '1::5:1.2.3.256', '1::5:300.2.3.4', '1::5:1.300.3.4', + '1::5:1.2.300.4', '1::5:1.2.3.300', '1::5:900.2.3.4', '1::5:1.900.3.4', + '1::5:1.2.900.4', '1::5:1.2.3.900', '1::5:300.300.300.300', '1::5:3000.30.30.30', + '1::400.2.3.4', '1::260.2.3.4', '1::256.2.3.4', '1::1.256.3.4', + '1::1.2.256.4', '1::1.2.3.256', '1::300.2.3.4', '1::1.300.3.4', + '1::1.2.300.4', '1::1.2.3.300', '1::900.2.3.4', '1::1.900.3.4', + '1::1.2.900.4', '1::1.2.3.900', '1::300.300.300.300', '1::3000.30.30.30', + '::400.2.3.4', '::260.2.3.4', '::256.2.3.4', '::1.256.3.4', + '::1.2.256.4', '::1.2.3.256', '::300.2.3.4', '::1.300.3.4', + '::1.2.300.4', '::1.2.3.300', '::900.2.3.4', '::1.900.3.4', + '::1.2.900.4', '::1.2.3.900', '::300.300.300.300', '::3000.30.30.30', + '2001:1:1:1:1:1:255Z255X255Y255', # garbage instead of "." in IPv4 + '::ffff:192x168.1.26', # ditto + '::ffff:2.3.4', + '::ffff:257.1.2.3', + '1.2.3.4:1111:2222:3333:4444::5555', + '1.2.3.4:1111:2222:3333::5555', + '1.2.3.4:1111:2222::5555', + '1.2.3.4:1111::5555', + '1.2.3.4::5555', + '1.2.3.4::', + + # Testing IPv4 addresses represented as dotted-quads Leading zero's in + # IPv4 addresses not allowed: some systems treat the leading "0" in + # ".086" as the start of an octal number Update: The BNF in RFC-3986 + # explicitly defines the dec-octet (for IPv4 addresses) not to have a + # leading zero + 'fe80:0000:0000:0000:0204:61ff:254.157.241.086', + 'XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:1.2.3.4', + '1111:2222:3333:4444:5555:6666:00.00.00.00', + '1111:2222:3333:4444:5555:6666:000.000.000.000', + '1111:2222:3333:4444:5555:6666:256.256.256.256', + + '1111:2222:3333:4444::5555:', + '1111:2222:3333::5555:', + '1111:2222::5555:', + '1111::5555:', + '::5555:', + ':::', + '1111:', + ':', + + ':1111:2222:3333:4444::5555', + ':1111:2222:3333::5555', + ':1111:2222::5555', + ':1111::5555', + ':::5555', + ':::', + + # Additional test cases from http://rt.cpan.org/Public/Bug/Display.html?id=50693 + '123', + 'ldkfj', + '2001::FFD3::57ab', + '2001:db8:85a3::8a2e:37023:7334', + '2001:db8:85a3::8a2e:370k:7334', + '1:2:3:4:5:6:7:8:9', + '1::2::3', + '1:::3:4:5', + '1:2:3::4:5:6:7:8:9', + + # Invalid data + 'XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX', + + # Too many components + '1111:2222:3333:4444:5555:6666:7777:8888:9999', + '1111:2222:3333:4444:5555:6666:7777:8888::', + '::2222:3333:4444:5555:6666:7777:8888:9999', + + # Too few components + '1111:2222:3333:4444:5555:6666:7777', + '1111:2222:3333:4444:5555:6666', + '1111:2222:3333:4444:5555', + '1111:2222:3333:4444', + '1111:2222:3333', + '1111:2222', + '1111', + + # Missing : + '11112222:3333:4444:5555:6666:7777:8888', + '1111:22223333:4444:5555:6666:7777:8888', + '1111:2222:33334444:5555:6666:7777:8888', + '1111:2222:3333:44445555:6666:7777:8888', + '1111:2222:3333:4444:55556666:7777:8888', + '1111:2222:3333:4444:5555:66667777:8888', + '1111:2222:3333:4444:5555:6666:77778888', + + # Missing : intended for :: + '1111:2222:3333:4444:5555:6666:7777:8888:', + '1111:2222:3333:4444:5555:6666:7777:', + '1111:2222:3333:4444:5555:6666:', + '1111:2222:3333:4444:5555:', + '1111:2222:3333:4444:', + '1111:2222:3333:', + '1111:2222:', + '1111:', + ':', + ':8888', + ':7777:8888', + ':6666:7777:8888', + ':5555:6666:7777:8888', + ':4444:5555:6666:7777:8888', + ':3333:4444:5555:6666:7777:8888', + ':2222:3333:4444:5555:6666:7777:8888', + ':1111:2222:3333:4444:5555:6666:7777:8888', + + # ::: + ':::2222:3333:4444:5555:6666:7777:8888', + '1111:::3333:4444:5555:6666:7777:8888', + '1111:2222:::4444:5555:6666:7777:8888', + '1111:2222:3333:::5555:6666:7777:8888', + '1111:2222:3333:4444:::6666:7777:8888', + '1111:2222:3333:4444:5555:::7777:8888', + '1111:2222:3333:4444:5555:6666:::8888', + '1111:2222:3333:4444:5555:6666:7777:::', + + # Double ::", + '::2222::4444:5555:6666:7777:8888', + '::2222:3333::5555:6666:7777:8888', + '::2222:3333:4444::6666:7777:8888', + '::2222:3333:4444:5555::7777:8888', + '::2222:3333:4444:5555:7777::8888', + '::2222:3333:4444:5555:7777:8888::', + + '1111::3333::5555:6666:7777:8888', + '1111::3333:4444::6666:7777:8888', + '1111::3333:4444:5555::7777:8888', + '1111::3333:4444:5555:6666::8888', + '1111::3333:4444:5555:6666:7777::', + + '1111:2222::4444::6666:7777:8888', + '1111:2222::4444:5555::7777:8888', + '1111:2222::4444:5555:6666::8888', + '1111:2222::4444:5555:6666:7777::', + + '1111:2222:3333::5555::7777:8888', + '1111:2222:3333::5555:6666::8888', + '1111:2222:3333::5555:6666:7777::', + + '1111:2222:3333:4444::6666::8888', + '1111:2222:3333:4444::6666:7777::', + + '1111:2222:3333:4444:5555::7777::', + + # Too many components" + '1111:2222:3333:4444:5555:6666:7777:8888:1.2.3.4', + '1111:2222:3333:4444:5555:6666:7777:1.2.3.4', + '1111:2222:3333:4444:5555:6666::1.2.3.4', + '::2222:3333:4444:5555:6666:7777:1.2.3.4', + '1111:2222:3333:4444:5555:6666:1.2.3.4.5', + + # Too few components + '1111:2222:3333:4444:5555:1.2.3.4', + '1111:2222:3333:4444:1.2.3.4', + '1111:2222:3333:1.2.3.4', + '1111:2222:1.2.3.4', + '1111:1.2.3.4', + + # Missing : + '11112222:3333:4444:5555:6666:1.2.3.4', + '1111:22223333:4444:5555:6666:1.2.3.4', + '1111:2222:33334444:5555:6666:1.2.3.4', + '1111:2222:3333:44445555:6666:1.2.3.4', + '1111:2222:3333:4444:55556666:1.2.3.4', + '1111:2222:3333:4444:5555:66661.2.3.4', + + # Missing . + '1111:2222:3333:4444:5555:6666:255255.255.255', + '1111:2222:3333:4444:5555:6666:255.255255.255', + '1111:2222:3333:4444:5555:6666:255.255.255255', + + # Missing : intended for :: + ':1.2.3.4', + ':6666:1.2.3.4', + ':5555:6666:1.2.3.4', + ':4444:5555:6666:1.2.3.4', + ':3333:4444:5555:6666:1.2.3.4', + ':2222:3333:4444:5555:6666:1.2.3.4', + ':1111:2222:3333:4444:5555:6666:1.2.3.4', + + # ::: + ':::2222:3333:4444:5555:6666:1.2.3.4', + '1111:::3333:4444:5555:6666:1.2.3.4', + '1111:2222:::4444:5555:6666:1.2.3.4', + '1111:2222:3333:::5555:6666:1.2.3.4', + '1111:2222:3333:4444:::6666:1.2.3.4', + '1111:2222:3333:4444:5555:::1.2.3.4', + + # Double :: + '::2222::4444:5555:6666:1.2.3.4', + '::2222:3333::5555:6666:1.2.3.4', + '::2222:3333:4444::6666:1.2.3.4', + '::2222:3333:4444:5555::1.2.3.4', + + '1111::3333::5555:6666:1.2.3.4', + '1111::3333:4444::6666:1.2.3.4', + '1111::3333:4444:5555::1.2.3.4', + + '1111:2222::4444::6666:1.2.3.4', + '1111:2222::4444:5555::1.2.3.4', + + '1111:2222:3333::5555::1.2.3.4', + + # Missing parts + '::.', + '::..', + '::...', + '::1...', + '::1.2..', + '::1.2.3.', + '::.2..', + '::.2.3.', + '::.2.3.4', + '::..3.', + '::..3.4', + '::...4', + + # Extra : in front + ':1111:2222:3333:4444:5555:6666:7777::', + ':1111:2222:3333:4444:5555:6666::', + ':1111:2222:3333:4444:5555::', + ':1111:2222:3333:4444::', + ':1111:2222:3333::', + ':1111:2222::', + ':1111::', + ':::', + ':1111:2222:3333:4444:5555:6666::8888', + ':1111:2222:3333:4444:5555::8888', + ':1111:2222:3333:4444::8888', + ':1111:2222:3333::8888', + ':1111:2222::8888', + ':1111::8888', + ':::8888', + ':1111:2222:3333:4444:5555::7777:8888', + ':1111:2222:3333:4444::7777:8888', + ':1111:2222:3333::7777:8888', + ':1111:2222::7777:8888', + ':1111::7777:8888', + ':::7777:8888', + ':1111:2222:3333:4444::6666:7777:8888', + ':1111:2222:3333::6666:7777:8888', + ':1111:2222::6666:7777:8888', + ':1111::6666:7777:8888', + ':::6666:7777:8888', + ':1111:2222:3333::5555:6666:7777:8888', + ':1111:2222::5555:6666:7777:8888', + ':1111::5555:6666:7777:8888', + ':::5555:6666:7777:8888', + ':1111:2222::4444:5555:6666:7777:8888', + ':1111::4444:5555:6666:7777:8888', + ':::4444:5555:6666:7777:8888', + ':1111::3333:4444:5555:6666:7777:8888', + ':::3333:4444:5555:6666:7777:8888', + ':::2222:3333:4444:5555:6666:7777:8888', + ':1111:2222:3333:4444:5555:6666:1.2.3.4', + ':1111:2222:3333:4444:5555::1.2.3.4', + ':1111:2222:3333:4444::1.2.3.4', + ':1111:2222:3333::1.2.3.4', + ':1111:2222::1.2.3.4', + ':1111::1.2.3.4', + ':::1.2.3.4', + ':1111:2222:3333:4444::6666:1.2.3.4', + ':1111:2222:3333::6666:1.2.3.4', + ':1111:2222::6666:1.2.3.4', + ':1111::6666:1.2.3.4', + ':::6666:1.2.3.4', + ':1111:2222:3333::5555:6666:1.2.3.4', + ':1111:2222::5555:6666:1.2.3.4', + ':1111::5555:6666:1.2.3.4', + ':::5555:6666:1.2.3.4', + ':1111:2222::4444:5555:6666:1.2.3.4', + ':1111::4444:5555:6666:1.2.3.4', + ':::4444:5555:6666:1.2.3.4', + ':1111::3333:4444:5555:6666:1.2.3.4', + ':::2222:3333:4444:5555:6666:1.2.3.4', + + # Extra : at end + '1111:2222:3333:4444:5555:6666:7777:::', + '1111:2222:3333:4444:5555:6666:::', + '1111:2222:3333:4444:5555:::', + '1111:2222:3333:4444:::', + '1111:2222:3333:::', + '1111:2222:::', + '1111:::', + ':::', + '1111:2222:3333:4444:5555:6666::8888:', + '1111:2222:3333:4444:5555::8888:', + '1111:2222:3333:4444::8888:', + '1111:2222:3333::8888:', + '1111:2222::8888:', + '1111::8888:', + '::8888:', + '1111:2222:3333:4444:5555::7777:8888:', + '1111:2222:3333:4444::7777:8888:', + '1111:2222:3333::7777:8888:', + '1111:2222::7777:8888:', + '1111::7777:8888:', + '::7777:8888:', + '1111:2222:3333:4444::6666:7777:8888:', + '1111:2222:3333::6666:7777:8888:', + '1111:2222::6666:7777:8888:', + '1111::6666:7777:8888:', + '::6666:7777:8888:', + '1111:2222:3333::5555:6666:7777:8888:', + '1111:2222::5555:6666:7777:8888:', + '1111::5555:6666:7777:8888:', + '::5555:6666:7777:8888:', + '1111:2222::4444:5555:6666:7777:8888:', + '1111::4444:5555:6666:7777:8888:', + '::4444:5555:6666:7777:8888:', + '1111::3333:4444:5555:6666:7777:8888:', + '::3333:4444:5555:6666:7777:8888:', + '::2222:3333:4444:5555:6666:7777:8888:'].each do |ip| + it "should reject #{ip.inspect} as an IPv6 address" do + expect { described_class.new(name: 'foo', ip: ip) }.to raise_error(Puppet::ResourceError, %r{Parameter ip failed}) + end + end + + it 'does not accept newlines in resourcename' do + expect { described_class.new(name: "fo\no", ip: '127.0.0.1') }.to raise_error(Puppet::ResourceError, %r{Hostname cannot include newline}) + end + + it 'does not accept newlines in ipaddress' do + expect { described_class.new(name: 'foo', ip: "127.0.0.1\n") }.to raise_error(Puppet::ResourceError, %r{Invalid IP address}) + end + + it 'does not accept newlines in host_aliases' do + expect { described_class.new(name: 'foo', ip: '127.0.0.1', host_aliases: ['well_formed', "thisalias\nhavenewline"]) }.to raise_error(Puppet::ResourceError, %r{Host aliases cannot include whitespace}) + end + + it 'does not accept newlines in comment' do + expect { described_class.new(name: 'foo', ip: '127.0.0.1', comment: "Test of comment blah blah \n test 123") }.to raise_error(Puppet::ResourceError, %r{Comment cannot include newline}) + end + + it 'does not accept spaces in resourcename' do + expect { described_class.new(name: 'foo bar') }.to raise_error(Puppet::ResourceError, %r{Invalid host name}) + end + + it 'does not accept host_aliases with spaces' do + expect { described_class.new(name: 'foo', host_aliases: ['well_formed', 'not wellformed']) }.to raise_error(Puppet::ResourceError, %r{Host aliases cannot include whitespace}) + end + + it 'does not accept empty host_aliases' do + expect { described_class.new(name: 'foo', host_aliases: ['alias1', '']) }.to raise_error(Puppet::ResourceError, %r{Host aliases cannot be an empty string}) + end + end + + describe 'when syncing' do + it 'sends the first value to the provider for ip property' do + ip = described_class.attrclass(:ip).new(resource: resource, should: ['192.168.0.1', '192.168.0.2']) + ip.sync + + expect(provider.ip).to eq('192.168.0.1') + end + + it 'sends the first value to the provider for comment property' do + comment = described_class.attrclass(:comment).new(resource: resource, should: ['Bazinga', 'Notme']) + comment.sync + + expect(provider.comment).to eq('Bazinga') + end + + it 'sends the joined array to the provider for host_alias' do + host_aliases = described_class.attrclass(:host_aliases).new(resource: resource, should: ['foo', 'bar']) + host_aliases.sync + + expect(provider.host_aliases).to eq('foo bar') + end + + it 'alsoes use the specified delimiter for joining' do + host_aliases = described_class.attrclass(:host_aliases).new(resource: resource, should: ['foo', 'bar']) + host_aliases.stubs(:delimiter).returns "\t" + host_aliases.sync + + expect(provider.host_aliases).to eq("foo\tbar") + end + + it 'cares about the order of host_aliases' do + host_aliases = described_class.attrclass(:host_aliases).new(resource: resource, should: ['foo', 'bar']) + expect(host_aliases.insync?(['foo', 'bar'])).to eq(true) + expect(host_aliases.insync?(['bar', 'foo'])).to eq(false) + end + + it 'does not consider aliases to be in sync if should is a subset of current' do + host_aliases = described_class.attrclass(:host_aliases).new(resource: resource, should: ['foo', 'bar']) + expect(host_aliases.insync?(['foo', 'bar', 'anotherone'])).to eq(false) + end + end +end |