diff --git a/lib/puppet/resource_api.rb b/lib/puppet/resource_api.rb index 3f692b94..0f9ba788 100644 --- a/lib/puppet/resource_api.rb +++ b/lib/puppet/resource_api.rb @@ -1,8 +1,12 @@ require 'pathname' require 'puppet/resource_api/data_type_handling' require 'puppet/resource_api/glue' +require 'puppet/resource_api/parameter' +require 'puppet/resource_api/property' require 'puppet/resource_api/puppet_context' unless RUBY_PLATFORM == 'java' +require 'puppet/resource_api/read_only_parameter' require 'puppet/resource_api/type_definition' +require 'puppet/resource_api/value_creator' require 'puppet/resource_api/version' require 'puppet/type' require 'puppet/util/network_device' @@ -174,13 +178,23 @@ def to_resource # TODO: using newparam everywhere would suppress change reporting # that would allow more fine-grained reporting through context, # but require more invest in hooking up the infrastructure to emulate existing data - param_or_property = if [:read_only, :parameter, :namevar].include? options[:behaviour] - :newparam - else - :newproperty - end + if [:parameter, :namevar].include? options[:behaviour] + param_or_property = :newparam + parent = Puppet::ResourceApi::Parameter + elsif options[:behaviour] == :read_only + param_or_property = :newparam + parent = Puppet::ResourceApi::ReadOnlyParameter + else + param_or_property = :newproperty + parent = Puppet::ResourceApi::Property + end - send(param_or_property, name.to_sym) do + # This call creates a new parameter or property with all work-arounds or + # customizations required by the Resource API applied. Under the hood, + # this maps to the relevant DSL methods in Puppet::Type. See + # https://puppet.com/docs/puppet/6.0/custom_types.html#reference-5883 + # for details. + send(param_or_property, name.to_sym, parent: parent) do unless options[:type] raise Puppet::DevError, "#{definition[:name]}.#{name} has no type" end @@ -191,144 +205,32 @@ def to_resource warn("#{definition[:name]}.#{name} has no docs") end - if options[:behaviour] == :namevar - isnamevar - end - - # read-only values do not need type checking, but can have default values - if options[:behaviour] != :read_only && options.key?(:default) - if options.key? :default - if options[:default] == false - # work around https://tickets.puppetlabs.com/browse/PUP-2368 - defaultto :false # rubocop:disable Lint/BooleanSymbol - elsif options[:default] == true - # work around https://tickets.puppetlabs.com/browse/PUP-2368 - defaultto :true # rubocop:disable Lint/BooleanSymbol - else - # marshal the default option to decouple that from the actual value. - # we cache the dumped value in `marshalled`, but use a block to unmarshal - # everytime the value is requested. Objects that can't be marshalled - # See https://stackoverflow.com/a/8206537/4918 - marshalled = Marshal.dump(options[:default]) - defaultto { Marshal.load(marshalled) } # rubocop:disable Security/MarshalLoad - end - end + # The initialize method is called when puppet core starts building up + # type objects. The core passes in a hash of shape { resource: + # # }. We use this to pass through the + # required configuration data to the parent (see + # Puppet::ResourceApi::Property, Puppet::ResourceApi::Parameter and + # Puppet::ResourceApi::ReadOnlyParameter). + define_method(:initialize) do |resource_hash| + super(definition[:name], self.class.data_type, name, resource_hash) end - if name == :ensure - def insync?(is) - rs_value.to_s == is.to_s - end + # get pops data type object for this parameter or property + define_singleton_method(:data_type) do + @rsapi_data_type ||= Puppet::ResourceApi::DataTypeHandling.parse_puppet_type( + name, + options[:type], + ) end - type = Puppet::ResourceApi::DataTypeHandling.parse_puppet_type( - name, - options[:type], + # from ValueCreator call create_values which makes alias values and + # default values for properties and params + Puppet::ResourceApi::ValueCreator.create_values( + self, + data_type, + param_or_property, + options, ) - - if param_or_property == :newproperty - define_method(:should) do - if name == :ensure && rs_value.is_a?(String) - rs_value.to_sym - elsif rs_value == false - # work around https://tickets.puppetlabs.com/browse/PUP-2368 - :false # rubocop:disable Lint/BooleanSymbol - elsif rs_value == true - # work around https://tickets.puppetlabs.com/browse/PUP-2368 - :true # rubocop:disable Lint/BooleanSymbol - else - rs_value - end - end - - define_method(:should=) do |value| - @shouldorig = value - - if name == :ensure - value = value.to_s - end - - # Puppet requires the @should value to always be stored as an array. We do not use this - # for anything else - # @see Puppet::Property.should=(value) - @should = [ - Puppet::ResourceApi::DataTypeHandling.mungify( - type, - value, - "#{definition[:name]}.#{name}", - Puppet::ResourceApi.caller_is_resource_app?, - ), - ] - end - - # used internally - # @returns the final mungified value of this property - define_method(:rs_value) do - @should ? @should.first : @should - end - else - define_method(:value) do - @value - end - - define_method(:value=) do |value| - if options[:behaviour] == :read_only - raise Puppet::ResourceError, "Attempting to set `#{name}` read_only attribute value to `#{value}`" - end - - @value = Puppet::ResourceApi::DataTypeHandling.mungify( - type, - value, - "#{definition[:name]}.#{name}", - Puppet::ResourceApi.caller_is_resource_app?, - ) - end - - # used internally - # @returns the final mungified value of this parameter - define_method(:rs_value) do - @value - end - end - - # puppet symbolizes some values through puppet/parameter/value.rb (see .convert()), but (especially) Enums - # are strings. specifying a munge block here skips the value_collection fallback in puppet/parameter.rb's - # default .unsafe_munge() implementation. - munge { |v| v } - - # provide hints to `puppet type generate` for better parsing - if type.instance_of? Puppet::Pops::Types::POptionalType - type = type.type - end - - case type - when Puppet::Pops::Types::PStringType - # require any string value - Puppet::ResourceApi.def_newvalues(self, param_or_property, %r{}) - when Puppet::Pops::Types::PBooleanType - Puppet::ResourceApi.def_newvalues(self, param_or_property, 'true', 'false') - aliasvalue true, 'true' - aliasvalue false, 'false' - aliasvalue :true, 'true' # rubocop:disable Lint/BooleanSymbol - aliasvalue :false, 'false' # rubocop:disable Lint/BooleanSymbol - - when Puppet::Pops::Types::PIntegerType - Puppet::ResourceApi.def_newvalues(self, param_or_property, %r{^-?\d+$}) - when Puppet::Pops::Types::PFloatType, Puppet::Pops::Types::PNumericType - Puppet::ResourceApi.def_newvalues(self, param_or_property, Puppet::Pops::Patterns::NUMERIC) - end - - if param_or_property == :newproperty - # stop puppet from trying to call into the provider when - # no pre-defined values have been specified - # "This is not the provider you are looking for." -- Obi-Wan Kaniesobi. - def call_provider(value); end - end - - case options[:type] - when 'Enum[present, absent]' - Puppet::ResourceApi.def_newvalues(self, param_or_property, 'absent', 'present') - end end end @@ -582,17 +484,6 @@ def self.class_name_from_type_name(type_name) type_name.to_s.split('_').map(&:capitalize).join end - # Add the value to `this` property or param, depending on whether param_or_property is `:newparam`, or `:newproperty` - def self.def_newvalues(this, param_or_property, *values) - if param_or_property == :newparam - this.newvalues(*values) - else - values.each do |v| - this.newvalue(v) {} - end - end - end - def self.caller_is_resource_app? caller.any? { |c| c.match(%r{application/resource.rb:}) } end diff --git a/lib/puppet/resource_api/parameter.rb b/lib/puppet/resource_api/parameter.rb new file mode 100644 index 00000000..ef749405 --- /dev/null +++ b/lib/puppet/resource_api/parameter.rb @@ -0,0 +1,46 @@ +require 'puppet/util' +require 'puppet/parameter' + +module Puppet; module ResourceApi; end; end # predeclare the main module # rubocop:disable Style/Documentation,Style/ClassAndModuleChildren + +# Class containing parameter functionality for ResourceApi. +class Puppet::ResourceApi::Parameter < Puppet::Parameter + attr_reader :value + + # This initialize takes arguments and sets up new parameter. + # @param type_name the name of the Puppet Type + # @param data_type the data type of parameter instance + # @param attribute_name the name of attribue of the parameter + # @param resource_hash the resource hash instance which is passed to the + # parent class. + def initialize(type_name, data_type, attribute_name, resource_hash) + @type_name = type_name + @data_type = data_type + @attribute_name = attribute_name + super(resource_hash) # Pass resource to parent Puppet class. + end + + # This method assigns value to the parameter and cleans value. + # @param value the value to be set and clean + # @return [type] the cleaned value + def value=(value) + @value = Puppet::ResourceApi::DataTypeHandling.mungify( + @data_type, + value, + "#{@type_name}.#{@attribute_name}", + Puppet::ResourceApi.caller_is_resource_app?, + ) + end + + # used internally + # @returns the final mungified value of this parameter + def rs_value + @value + end + + # puppet symbolizes some values through puppet/parameter/value.rb + # (see .convert()), but (especially) Enums are strings. specifying a + # munge block here skips the value_collection fallback in + # puppet/parameter.rb's default .unsafe_munge() implementation. + munge { |v| v } +end diff --git a/lib/puppet/resource_api/property.rb b/lib/puppet/resource_api/property.rb new file mode 100644 index 00000000..34609f8c --- /dev/null +++ b/lib/puppet/resource_api/property.rb @@ -0,0 +1,86 @@ +require 'puppet/util' +require 'puppet/property' + +module Puppet; module ResourceApi; end; end # predeclare the main module # rubocop:disable Style/Documentation,Style/ClassAndModuleChildren + +# Class containing property functionality for ResourceApi. +class Puppet::ResourceApi::Property < Puppet::Property + # This initialize takes arguments and sets up new property. + # @param type_name the name of the Puppet Type + # @param data_type the data type of property instance + # @param attribute_name the name of attribue of the property + # @param resource_hash the resource hash instance which is passed to the + # parent class. + def initialize(type_name, data_type, attribute_name, resource_hash) + @type_name = type_name + @data_type = data_type + @attribute_name = attribute_name + # Define class method insync?(is) if the name is :ensure + def_insync? if @attribute_name == :ensure && self.class != Puppet::ResourceApi::Property + # Pass resource to parent Puppet class. + super(resource_hash) + end + + # This method returns value of the property. + # @return [type] the property value + def should + if @attribute_name == :ensure && rs_value.is_a?(String) + rs_value.to_sym + elsif rs_value == false + # work around https://tickets.puppetlabs.com/browse/PUP-2368 + :false # rubocop:disable Lint/BooleanSymbol + elsif rs_value == true + # work around https://tickets.puppetlabs.com/browse/PUP-2368 + :true # rubocop:disable Lint/BooleanSymbol + else + rs_value + end + end + + # This method sets and returns value of the property and sets @shouldorig. + # @param value the value to be set and clean + # @return [type] the property value + def should=(value) + @shouldorig = value + + if @attribute_name == :ensure + value = value.to_s + end + + # Puppet requires the @should value to always be stored as an array. We do not use this + # for anything else + # @see Puppet::Property.should=(value) + @should = [ + Puppet::ResourceApi::DataTypeHandling.mungify( + @data_type, + value, + "#{@type_name}.#{@attribute_name}", + Puppet::ResourceApi.caller_is_resource_app?, + ), + ] + end + + # used internally + # @returns the final mungified value of this property + def rs_value + @should ? @should.first : @should + end + + # method overloaded only for the :ensure property, add option to check if the + # rs_value matches is. Only if the class is child of + # Puppet::ResourceApi::Property. + def def_insync? + define_singleton_method(:insync?) { |is| rs_value.to_s == is.to_s } + end + + # puppet symbolizes some values through puppet/parameter/value.rb + # (see .convert()), but (especially) Enums are strings. specifying a + # munge block here skips the value_collection fallback in + # puppet/parameter.rb's default .unsafe_munge() implementation. + munge { |v| v } + + # stop puppet from trying to call into the provider when + # no pre-defined values have been specified + # "This is not the provider you are looking for." -- Obi-Wan Kaniesobi. + def call_provider(_value); end +end diff --git a/lib/puppet/resource_api/read_only_parameter.rb b/lib/puppet/resource_api/read_only_parameter.rb new file mode 100644 index 00000000..97458e48 --- /dev/null +++ b/lib/puppet/resource_api/read_only_parameter.rb @@ -0,0 +1,19 @@ +require 'puppet/util' +require 'puppet/resource_api/parameter' + +# Class containing read only parameter functionality for ResourceApi. +class Puppet::ResourceApi::ReadOnlyParameter < Puppet::ResourceApi::Parameter + # This method raises error if the there is attempt to set value in parameter. + # @return [Puppet::ResourceError] the error with information. + def value=(value) + raise Puppet::ResourceError, + "Attempting to set `#{@attribute_name}` read_only attribute value " \ + "to `#{value}`" + end + + # used internally + # @returns the final mungified value of this parameter + def rs_value + @value + end +end diff --git a/lib/puppet/resource_api/value_creator.rb b/lib/puppet/resource_api/value_creator.rb new file mode 100644 index 00000000..cb7c119b --- /dev/null +++ b/lib/puppet/resource_api/value_creator.rb @@ -0,0 +1,75 @@ +module Puppet; module ResourceApi; end; end # predeclare the main module # rubocop:disable Style/Documentation,Style/ClassAndModuleChildren + +# This module is responsible for setting default and alias values for the +# resource class. +module Puppet::ResourceApi::ValueCreator + # This method is responsible for all setup of the value mapping for desired + # resource class which can be Puppet::ResourceApi::Parameter, + # Puppet::ResourceApi::ReadOnlyParameter, Puppet::ResourceApi::Property. + # @param resource_class the class of selected resource to be extended + # @param data_type the type instance + # @param definition the definition of the property + # @param options the ResourceAPI options hash containing setup information for + # selected parameter or property + def self.create_values(attribute_class, data_type, param_or_property, options = {}) + attribute_class.isnamevar if options[:behaviour] == :namevar + + # read-only values do not need type checking, but can have default values + if options[:behaviour] != :read_only && options.key?(:default) + if options[:default] == false + # work around https://tickets.puppetlabs.com/browse/PUP-2368 + attribute_class.defaultto :false # rubocop:disable Lint/BooleanSymbol + elsif options[:default] == true + # work around https://tickets.puppetlabs.com/browse/PUP-2368 + attribute_class.defaultto :true # rubocop:disable Lint/BooleanSymbol + else + # marshal the default option to decouple that from the actual value. + # we cache the dumped value in `marshalled`, but use a block to + # unmarshal everytime the value is requested. Objects that can't be + # marshalled + # See https://stackoverflow.com/a/8206537/4918 + marshalled = Marshal.dump(options[:default]) + attribute_class.defaultto { Marshal.load(marshalled) } # rubocop:disable Security/MarshalLoad + end + end + + # provide hints to `puppet type generate` for better parsing + if data_type.instance_of? Puppet::Pops::Types::POptionalType + data_type = data_type.type + end + + case data_type + when Puppet::Pops::Types::PStringType + # require any string value + def_newvalues(attribute_class, param_or_property, %r{}) + when Puppet::Pops::Types::PBooleanType + def_newvalues(attribute_class, param_or_property, 'true', 'false') + attribute_class.aliasvalue true, 'true' + attribute_class.aliasvalue false, 'false' + attribute_class.aliasvalue :true, 'true' # rubocop:disable Lint/BooleanSymbol + attribute_class.aliasvalue :false, 'false' # rubocop:disable Lint/BooleanSymbol + when Puppet::Pops::Types::PIntegerType + def_newvalues(attribute_class, param_or_property, %r{^-?\d+$}) + when Puppet::Pops::Types::PFloatType, Puppet::Pops::Types::PNumericType + def_newvalues(attribute_class, param_or_property, Puppet::Pops::Patterns::NUMERIC) + end + + case options[:type] + when 'Enum[present, absent]' + def_newvalues(attribute_class, param_or_property, 'absent', 'present') + end + attribute_class + end + + # add the value to `this` property or param, depending on whether + # param_or_property is `:newparam`, or `:newproperty` + def self.def_newvalues(this, param_or_property, *values) + if param_or_property == :newparam + this.newvalues(*values) + else + values.each do |v| + this.newvalue(v) {} + end + end + end +end diff --git a/spec/puppet/resource_api/parameter_spec.rb b/spec/puppet/resource_api/parameter_spec.rb new file mode 100644 index 00000000..2c930f4a --- /dev/null +++ b/spec/puppet/resource_api/parameter_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +RSpec.describe Puppet::ResourceApi::Parameter do + subject(:parameter) do + described_class.new(type_name, data_type, attribute_name, resource_hash) + end + + let(:type_name) { 'test_name' } + let(:attribute_name) { 'some_parameter' } + let(:data_type) { Puppet::Pops::Types::PStringType.new(nil) } + let(:resource_hash) { { resource: {} } } + + describe '#new(type_name, data_type, attribute_name, resource_hash)' do + it { expect { described_class.new(nil) }.to raise_error ArgumentError, %r{wrong number of arguments} } + it { expect { described_class.new(type_name, data_type, attribute_name, resource_hash) }.not_to raise_error } + end + + describe 'value error handling' do + it 'calls mungify and reports its error' do + expect(Puppet::ResourceApi::DataTypeHandling).to receive(:mungify) + .and_raise Exception, 'error' + + expect { parameter.value = 'value' }.to raise_error Exception, 'error' + + expect(parameter.value).to eq nil + end + end + + describe 'value munging and storage' do + before(:each) do + allow(Puppet::ResourceApi::DataTypeHandling).to receive(:mungify) + .with(data_type, value, 'test_name.some_parameter', false) + .and_return(munged_value) + + parameter.value = value + end + + context 'when handling strings' do + let(:value) { 'value' } + let(:munged_value) { 'munged value' } + + it { expect(parameter.rs_value).to eq 'munged value' } + it { expect(parameter.value).to eq 'munged value' } + end + + context 'when handling boolean true' do + let(:value) { true } + let(:munged_value) { true } + let(:data_type) { Puppet::Pops::Types::PBooleanType.new } + + it { expect(parameter.rs_value).to eq true } + it { expect(parameter.value).to eq true } + end + end +end diff --git a/spec/puppet/resource_api/property_spec.rb b/spec/puppet/resource_api/property_spec.rb new file mode 100644 index 00000000..50d6c589 --- /dev/null +++ b/spec/puppet/resource_api/property_spec.rb @@ -0,0 +1,95 @@ +require 'spec_helper' + +RSpec.describe Puppet::ResourceApi::Property do + subject(:property) do + described_class.new(type_name, data_type, attribute_name, resource_hash) + end + + let(:type_name) { 'test_name' } + let(:attribute_name) { 'some_property' } + let(:data_type) { Puppet::Pops::Types::PStringType.new(nil) } + let(:resource_hash) { { resource: {} } } + + describe '#new(type_name, data_type, attribute_name, resource_hash)' do + it { expect { described_class.new(nil) }.to raise_error ArgumentError, %r{wrong number of arguments} } + it { expect { described_class.new(type_name, data_type, attribute_name, resource_hash) }.not_to raise_error } + end + + describe 'the special :ensure behaviour' do + let(:ensure_property_class) do + Class.new(described_class) do + define_method(:initialize) do + super('test_name', + Puppet::Pops::Types::PEnumType.new(%w[absent present]), + :ensure, + { resource: {} }) + end + end + end + let(:ensure_property) { ensure_property_class.new } + + before(:each) do + allow(Puppet::ResourceApi::DataTypeHandling).to receive(:mungify) + .with(Puppet::Pops::Types::PEnumType.new(%w[absent present]), 'present', 'test_name.ensure', false) + .and_return('present') + + ensure_property.should = 'present' + end + + it 'has a #insync? method' do + expect(ensure_property.public_methods(false)).to include(:insync?) + end + + describe '#insync?' do + it 'compares using symbols' do + expect(ensure_property.insync?(:present)).to eq(true) + end + end + + context "when handling 'present' string" do + it { expect(ensure_property.should).to eq :present } + it { expect(ensure_property.rs_value).to eq 'present' } + it { expect(ensure_property.value).to eq :present } + end + end + + describe 'should error handling' do + it 'calls mungify and reports its error' do + expect(Puppet::ResourceApi::DataTypeHandling).to receive(:mungify) + .and_raise Exception, 'error' + + expect { property.should = 'value' }.to raise_error Exception, 'error' + + expect(property.should).to eq nil + end + end + + describe 'value munging and storage' do + before(:each) do + allow(Puppet::ResourceApi::DataTypeHandling).to receive(:mungify) + .with(data_type, value, 'test_name.some_property', false) + .and_return(munged_value) + + property.should = value + end + + context 'when handling strings' do + let(:value) { 'value' } + let(:munged_value) { 'munged value' } + + it { expect(property.should).to eq 'munged value' } + it { expect(property.rs_value).to eq 'munged value' } + it { expect(property.value).to eq 'munged value' } + end + + context 'when handling boolean true' do + let(:value) { true } + let(:munged_value) { true } + let(:data_type) { Puppet::Pops::Types::PBooleanType.new } + + it { expect(property.should).to eq :true } # rubocop:disable Lint/BooleanSymbol + it { expect(property.rs_value).to eq true } + it { expect(property.value).to eq :true } # rubocop:disable Lint/BooleanSymbol + end + end +end diff --git a/spec/puppet/resource_api/read_only_parameter_spec.rb b/spec/puppet/resource_api/read_only_parameter_spec.rb new file mode 100644 index 00000000..226b18e7 --- /dev/null +++ b/spec/puppet/resource_api/read_only_parameter_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +RSpec.describe Puppet::ResourceApi::ReadOnlyParameter do + subject(:read_only_parameter) do + described_class.new(type_name, data_type, attribute_name, resource_hash) + end + + let(:type_name) { 'test_name' } + let(:attribute_name) { 'some_parameter' } + let(:data_type) { Puppet::Pops::Types::PStringType.new(nil) } + let(:resource_hash) { { resource: {} } } + + describe '#new(type_name, data_type, attribute_name, resource_hash)' do + it { expect { described_class.new(nil) }.to raise_error ArgumentError, %r{wrong number of arguments} } + it { expect { described_class.new(type_name, data_type, attribute_name, resource_hash) }.not_to raise_error } + end + + describe 'value munging and storage' do + context 'when the value set attempt is performed' do + it 'value set fails' do + expect { read_only_parameter.value = 'value' }.to raise_error( + Puppet::ResourceError, + %r{Attempting to set `some_parameter` read_only attribute value to `value`}, + ) + end + end + + context 'when value is not set' do + it { expect(read_only_parameter.rs_value).to eq nil } + it { expect(read_only_parameter.value).to eq nil } + end + + context 'when value is already set' do + before(:each) do + read_only_parameter.instance_variable_set(:@value, 'value') + end + + it { expect(read_only_parameter.rs_value).to eq 'value' } + it { expect(read_only_parameter.value).to eq 'value' } + end + end +end diff --git a/spec/puppet/resource_api/value_creator_spec.rb b/spec/puppet/resource_api/value_creator_spec.rb new file mode 100644 index 00000000..feb0e6fb --- /dev/null +++ b/spec/puppet/resource_api/value_creator_spec.rb @@ -0,0 +1,230 @@ +require 'spec_helper' + +RSpec.describe Puppet::ResourceApi::ValueCreator do + subject(:value_creator) do + described_class + end + + let(:attribute_class) { Puppet::ResourceApi::Property } + let(:data_type) { Puppet::Pops::Types::PEnumType.new(['absent', 'present']) } # rubocop:disable Style/WordArray + let(:param_or_property) { :newproperty } + let(:options) do + { + type: 'Enum[present, absent]', + desc: 'Whether this resource should be present or absent on the target system.', + default: 'present', + } + end + + before(:each) do + allow(attribute_class).to receive(:newvalue) + allow(attribute_class).to receive(:newvalues) + allow(attribute_class).to receive(:aliasvalue) + allow(attribute_class).to receive(:defaultto) + allow(attribute_class).to receive(:isnamevar) + end + + it { expect { described_class.create_values(nil) }.to raise_error ArgumentError, %r{wrong number of arguments} } + it { expect { value_creator }.not_to raise_error } + + describe '#create_values' do + before(:each) do + value_creator.create_values(attribute_class, data_type, param_or_property, options) + end + + context 'when attribute class is property' do + context 'when property is :ensure' do + it 'calls #newvalue twice' do + expect(Puppet::ResourceApi::Property).to have_received(:newvalue).with('absent') + expect(Puppet::ResourceApi::Property).to have_received(:newvalue).with('present') + end + end + + context 'when default is set' do + it 'calls #defaultto once' do + expect(attribute_class).to have_received(:defaultto) { |&block| expect(block[0]).to eq('present') } + end + end + + context 'when property has Boolean type' do + let(:data_type) { Puppet::Pops::Types::PBooleanType.new } + let(:options) do + { + type: 'Boolean', + desc: 'Boolean test.', + } + end + + it 'calls #newvalue twice' do + expect(attribute_class).to have_received(:newvalue).with('true') + expect(attribute_class).to have_received(:newvalue).with('false') + end + + it 'calls #aliasvalue four times' do + expect(attribute_class).to have_received(:aliasvalue).with(true, 'true') + expect(attribute_class).to have_received(:aliasvalue).with(false, 'false') + expect(attribute_class).to have_received(:aliasvalue).with(:true, 'true') # rubocop:disable Lint/BooleanSymbol + expect(attribute_class).to have_received(:aliasvalue).with(:false, 'false') # rubocop:disable Lint/BooleanSymbol + end + end + + context 'when property has String type' do + let(:data_type) { Puppet::Pops::Types::PStringType.new('s') } + let(:options) do + { + type: 'String', + desc: 'String test.', + } + end + + it 'calls #newvalue once' do + expect(attribute_class).to have_received(:newvalue).with(%r{}) + end + end + + context 'when property has Integer type' do + let(:data_type) { Puppet::Pops::Types::PIntegerType.new(1) } + let(:options) do + { + type: 'Integer', + desc: 'Integer test.', + } + end + + it 'calls #newvalue once' do + expect(attribute_class).to have_received(:newvalue).with(%r{^-?\d+$}) + end + end + + context 'when property has Float type' do + let(:data_type) { Puppet::Pops::Types::PFloatType.new(1.0) } + let(:options) do + { + type: 'Float', + desc: 'Float test.', + } + end + + it 'calls #newvalue once' do + expect(attribute_class).to have_received(:newvalue) + end + end + end + + context 'when attribute class is parameter' do + let(:attribute_class) { Puppet::ResourceApi::Parameter } + let(:data_type) { Puppet::Pops::Types::PBooleanType.new } + let(:param_or_property) { :newparam } + + it 'attribute_class has no #call_provider method' do + expect(attribute_class.method_defined?(:call_provider)).to eq(false) + end + + context 'when behaviour is set to :namevar' do + let(:options) do + { + type: 'String', + desc: 'Namevar test', + behaviour: :namevar, + } + end + + it 'calls #isnamevar once' do + expect(attribute_class).to have_received(:isnamevar) + end + end + + context 'when default value is set' do + let(:options) do + { + type: 'Boolean', + desc: 'Default value test', + default: true, + } + end + + it 'calls #defaultto once' do + expect(attribute_class).to have_received(:defaultto).with(:true) # rubocop:disable Lint/BooleanSymbol + end + end + + context 'when there is no default value' do + let(:options) do + { + type: 'Boolean', + desc: 'No default value test', + } + end + + it 'does not call #defaultto' do + expect(attribute_class).not_to receive(:defaultto).with(nil) + end + end + + context 'when parameter has Boolean type' do + let(:data_type) { Puppet::Pops::Types::PBooleanType.new } + let(:options) do + { + type: 'Boolean', + desc: 'Boolean test.', + } + end + + it 'calls #newvalues once' do + expect(attribute_class).to have_received(:newvalues).with('true', 'false') + end + + it 'calls #aliasvalue four times' do + expect(attribute_class).to have_received(:aliasvalue).with(true, 'true') + expect(attribute_class).to have_received(:aliasvalue).with(false, 'false') + expect(attribute_class).to have_received(:aliasvalue).with(:true, 'true') # rubocop:disable Lint/BooleanSymbol + expect(attribute_class).to have_received(:aliasvalue).with(:false, 'false') # rubocop:disable Lint/BooleanSymbol + end + end + + context 'when parameter has String type' do + let(:data_type) { Puppet::Pops::Types::PStringType.new('s') } + let(:options) do + { + type: 'String', + desc: 'String test.', + } + end + + it 'calls #newvalues once' do + expect(attribute_class).to have_received(:newvalues).with(%r{}) + end + end + + context 'when parameter has Integer type' do + let(:data_type) { Puppet::Pops::Types::PIntegerType.new(1) } + let(:options) do + { + type: 'Integer', + desc: 'Integer test.', + } + end + + it 'calls #newvalues once' do + expect(attribute_class).to have_received(:newvalues).with(%r{^-?\d+$}) + end + end + + context 'when parameter has Float type' do + let(:data_type) { Puppet::Pops::Types::PFloatType.new(1.0) } + let(:options) do + { + type: 'Float', + desc: 'Float test.', + } + end + + it 'calls #newvalues once' do + expect(attribute_class).to have_received(:newvalues).with( + %r{\A[[:blank:]]*([-+]?)[[:blank:]]*((0[xX][0-9A-Fa-f]+)|(0?\d+)((?:\.\d+)?(?:[eE]-?\d+)?))[[:blank:]]*\z}, + ) + end + end + end + end +end