require "support/shared/integration/integration_helper" describe "Chef::Resource#identity and #state" do include IntegrationSupport class NewResourceNamer @i = 0 def self.next "chef_resource_property_spec_#{@i += 1}" end end def self.new_resource_name NewResourceNamer.next end let(:resource_class) do new_resource_name = self.class.new_resource_name Class.new(Chef::Resource) do resource_name new_resource_name end end let(:resource) do resource_class.new("blah") end def self.english_join(values) return "" if values.size == 0 return values[0].inspect if values.size == 1 "#{values[0..-2].map(&:inspect).join(", ")} and #{values[-1].inspect}" end def self.with_property(*properties, &block) tags_index = properties.find_index { |p| !p.is_a?(String) } if tags_index properties, tags = properties[0..tags_index - 1], properties[tags_index..-1] else tags = [] end properties = properties.map { |property| "property #{property}" } context "With properties #{english_join(properties)}", *tags do before do properties.each do |property_str| resource_class.class_eval(property_str, __FILE__, __LINE__) end end instance_eval(&block) end end # identity context "Chef::Resource#identity_properties" do with_property ":x" do it "name is the default identity" do expect(resource_class.identity_properties).to eq [ Chef::Resource.properties[:name] ] expect(Chef::Resource.properties[:name].identity?).to be_falsey expect(resource.name).to eq "blah" expect(resource.identity).to eq "blah" end it "identity_properties :x changes the identity" do expect(resource_class.identity_properties :x).to eq [ resource_class.properties[:x] ] expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ] expect(Chef::Resource.properties[:name].identity?).to be_falsey expect(resource_class.properties[:x].identity?).to be_truthy expect(resource.x "woo").to eq "woo" expect(resource.x).to eq "woo" expect(resource.name).to eq "blah" expect(resource.identity).to eq "woo" end with_property ":y, identity: true" do context "and identity_properties :x" do before do resource_class.class_eval do identity_properties :x end end it "only returns :x as identity" do resource.x "foo" resource.y "bar" expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ] expect(resource.identity).to eq "foo" end it "does not flip y.desired_state off" do resource.x "foo" resource.y "bar" expect(resource_class.state_properties).to eq [ resource_class.properties[:x], resource_class.properties[:y], ] expect(resource.state_for_resource_reporter).to eq(x: "foo", y: "bar") end end end context "With a subclass" do let(:subresource_class) do new_resource_name = self.class.new_resource_name Class.new(resource_class) do resource_name new_resource_name end end let(:subresource) do subresource_class.new("sub") end it "name is the default identity on the subclass" do expect(subresource_class.identity_properties).to eq [ Chef::Resource.properties[:name] ] expect(Chef::Resource.properties[:name].identity?).to be_falsey expect(subresource.name).to eq "sub" expect(subresource.identity).to eq "sub" end context "With identity_properties :x on the superclass" do before do resource_class.class_eval do identity_properties :x end end it "The subclass inherits :x as identity" do expect(subresource_class.identity_properties).to eq [ subresource_class.properties[:x] ] expect(Chef::Resource.properties[:name].identity?).to be_falsey expect(subresource_class.properties[:x].identity?).to be_truthy subresource.x "foo" expect(subresource.identity).to eq "foo" end context "With property :y, identity: true on the subclass" do before do subresource_class.class_eval do property :y, identity: true end end it "The subclass's identity includes both x and y" do expect(subresource_class.identity_properties).to eq [ subresource_class.properties[:x], subresource_class.properties[:y], ] subresource.x "foo" subresource.y "bar" expect(subresource.identity).to eq(x: "foo", y: "bar") end end with_property ":y, String" do context "With identity_properties :y on the subclass" do before do subresource_class.class_eval do identity_properties :y end end it "y is part of state" do subresource.x "foo" subresource.y "bar" expect(subresource.state_for_resource_reporter).to eq(x: "foo", y: "bar") expect(subresource_class.state_properties).to eq [ subresource_class.properties[:x], subresource_class.properties[:y], ] end it "y is the identity" do expect(subresource_class.identity_properties).to eq [ subresource_class.properties[:y] ] subresource.x "foo" subresource.y "bar" expect(subresource.identity).to eq "bar" end it "y still has validation" do expect { subresource.y 12 }.to raise_error Chef::Exceptions::ValidationFailed end end end end end end with_property ":string_only, String, identity: true", ":string_only2, String" do it "identity_properties does not change validation" do resource_class.identity_properties :string_only expect { resource.string_only 12 }.to raise_error Chef::Exceptions::ValidationFailed expect { resource.string_only2 12 }.to raise_error Chef::Exceptions::ValidationFailed end end with_property ":x, desired_state: false" do it "identity_properties does not change desired_state" do resource_class.identity_properties :x resource.x "hi" expect(resource.identity).to eq "hi" expect(resource_class.properties[:x].desired_state?).to be_falsey expect(resource_class.state_properties).to eq [] expect(resource.state_for_resource_reporter).to eq({}) end end context "With custom property custom_property defined only as methods, using different variables for storage" do before do resource_class.class_eval do def custom_property @blarghle ? @blarghle * 3 : nil end def custom_property=(x) @blarghle = x * 2 end end end context "And identity_properties :custom_property" do before do resource_class.class_eval do identity_properties :custom_property end end it "identity_properties comes back as :custom_property" do expect(resource_class.properties[:custom_property].identity?).to be_truthy expect(resource_class.identity_properties).to eq [ resource_class.properties[:custom_property] ] end it "custom_property becomes part of desired_state" do resource.custom_property = 1 expect(resource.state_for_resource_reporter).to eq(custom_property: 6) expect(resource_class.properties[:custom_property].desired_state?).to be_truthy expect(resource_class.state_properties).to eq [ resource_class.properties[:custom_property], ] end it "identity_properties does not change custom_property's getter or setter" do resource.custom_property = 1 expect(resource.custom_property).to eq 6 end it "custom_property is returned as the identity" do expect(resource.identity).to be_nil resource.custom_property = 1 expect(resource.identity).to eq 6 end end end end context "Property#identity" do with_property ":x, identity: true" do it "name is only part of the identity if an identity attribute is defined" do expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ] resource.x "woo" expect(resource.identity).to eq "woo" end end with_property ":x, identity: true, default: 'xxx'", ":y, identity: true, default: 'yyy'", ":z, identity: true, default: 'zzz'" do it "identity_property raises an error if multiple identity values are defined" do expect { resource_class.identity_property }.to raise_error Chef::Exceptions::MultipleIdentityError end it "identity_attr raises an error if multiple identity values are defined" do expect { resource_class.identity_attr }.to raise_error Chef::Exceptions::MultipleIdentityError end it "identity returns all identity values in a hash if multiple are defined" do resource.x "foo" resource.y "bar" resource.z "baz" expect(resource.identity).to eq(x: "foo", y: "bar", z: "baz") end it "identity returns all values whether any value is set or not" do expect(resource.identity).to eq(x: "xxx", y: "yyy", z: "zzz") end it "identity_properties wipes out any other identity attributes if multiple are defined" do resource_class.identity_properties :y resource.x "foo" resource.y "bar" resource.z "baz" expect(resource.identity).to eq "bar" end end with_property ":x, identity: true, name_property: true" do it "identity when x is not defined returns the value of x" do expect(resource.identity).to eq "blah" end it "the name property is not included in the desired state" do expect(resource.state_for_resource_reporter).to eq({}) end end end # state_properties context "Chef::Resource#state_properties" do it "state_properties is empty by default" do expect(Chef::Resource.state_properties).to eq [] expect(resource.state_for_resource_reporter).to eq({}) end with_property ":x", ":y", ":z" do it "x, y and z are state attributes" do resource.x 1 resource.y 2 resource.z 3 expect(resource_class.state_properties).to eq [ resource_class.properties[:x], resource_class.properties[:y], resource_class.properties[:z], ] expect(resource.state_for_resource_reporter).to eq(x: 1, y: 2, z: 3) end it "values that are not set are not included in state" do resource.x 1 expect(resource.state_for_resource_reporter).to eq(x: 1) end it "when no values are set, nothing is included in state" do end end with_property ":x", ":y, desired_state: false", ":z, desired_state: true" do it "x and z are state attributes, and y is not" do resource.x 1 resource.y 2 resource.z 3 expect(resource_class.state_properties).to eq [ resource_class.properties[:x], resource_class.properties[:z], ] expect(resource.state_for_resource_reporter).to eq(x: 1, z: 3) end end with_property ":x, name_property: true" do it "Is by default the identity property if no other identity property is included" do expect(resource.identity).to eq("blah") end it "Unset values with name_property are not included in state" do expect(resource.state_for_resource_reporter).to eq({}) end it "Set values with name_property are not included in state" do resource.x 1 expect(resource.state_for_resource_reporter).to eq({}) end end with_property ":x, default: 1" do it "Unset values with defaults are not included in state" do expect(resource.state_for_resource_reporter).to eq({}) end it "Set values with defaults are included in state" do resource.x 1 expect(resource.state_for_resource_reporter).to eq(x: 1) end end context "With a class with a normal getter and setter" do before do resource_class.class_eval do def x @blah * 3 end def x=(value) @blah = value * 2 end end end it "state_properties(:x) causes the value to be included in properties" do resource_class.state_properties(:x) resource.x = 1 expect(resource.x).to eq 6 expect(resource.state_for_resource_reporter).to eq(x: 6) end end context "When state_properties happens before properties are declared" do before do resource_class.class_eval do state_properties :x property :x end end it "the property works and is in state_properties" do expect(resource_class.state_properties).to include(resource_class.properties[:x]) resource.x = 1 expect(resource.x).to eq 1 expect(resource.state_for_resource_reporter).to eq(x: 1) end end with_property ":x, Integer, identity: true" do it "state_properties(:x) leaves the property in desired_state" do resource_class.state_properties(:x) resource.x 10 expect(resource_class.properties[:x].desired_state?).to be_truthy expect(resource_class.state_properties).to eq [ resource_class.properties[:x], ] expect(resource.state_for_resource_reporter).to eq(x: 10) end it "state_properties(:x) does not turn off validation" do resource_class.state_properties(:x) expect { resource.x "ouch" }.to raise_error Chef::Exceptions::ValidationFailed end it "state_properties(:x) does not turn off identity" do resource_class.state_properties(:x) resource.x 10 expect(resource_class.identity_properties).to eq [ resource_class.properties[:x] ] expect(resource_class.properties[:x].identity?).to be_truthy expect(resource.identity).to eq 10 end end with_property ":x, Integer, identity: true, desired_state: false" do before do resource_class.class_eval do def y 20 end end end it "state_properties(:x) leaves x identical" do old_value = resource_class.properties[:y] resource_class.state_properties(:x) resource.x 10 expect(resource_class.properties[:y].object_id).to eq old_value.object_id expect(resource_class.properties[:x].desired_state?).to be_truthy expect(resource_class.properties[:x].identity?).to be_truthy expect(resource_class.identity_properties).to eq [ resource_class.properties[:x], ] expect(resource.identity).to eq(10) expect(resource_class.state_properties).to eq [ resource_class.properties[:x], ] expect(resource.state_for_resource_reporter).to eq(x: 10) end it "state_properties(:y) adds y to desired state" do old_value = resource_class.properties[:x] resource_class.state_properties(:y) resource.x 10 expect(resource_class.properties[:x].object_id).to eq old_value.object_id expect(resource_class.properties[:x].desired_state?).to be_falsey expect(resource_class.properties[:y].desired_state?).to be_truthy expect(resource_class.state_properties).to eq [ resource_class.properties[:y], ] expect(resource.state_for_resource_reporter).to eq(y: 20) end context "With a subclassed resource" do let(:subresource_class) do new_resource_name = self.class.new_resource_name Class.new(resource_class) do resource_name new_resource_name end end let(:subresource) do subresource_class.new("blah") end it "state_properties(:x) adds x to desired state" do old_value = resource_class.properties[:y] subresource_class.state_properties(:x) subresource.x 10 expect(subresource_class.properties[:y].object_id).to eq old_value.object_id expect(subresource_class.properties[:x].desired_state?).to be_truthy expect(subresource_class.properties[:x].identity?).to be_truthy expect(subresource_class.identity_properties).to eq [ subresource_class.properties[:x], ] expect(subresource.identity).to eq(10) expect(subresource_class.state_properties).to eq [ subresource_class.properties[:x], ] expect(subresource.state_for_resource_reporter).to eq(x: 10) end it "state_properties(:y) adds y to desired state" do old_value = resource_class.properties[:x] subresource_class.state_properties(:y) subresource.x 10 expect(subresource_class.properties[:x].object_id).to eq old_value.object_id expect(subresource_class.properties[:y].desired_state?).to be_truthy expect(subresource_class.state_properties).to eq [ subresource_class.properties[:y], ] expect(subresource.state_for_resource_reporter).to eq(y: 20) expect(subresource_class.properties[:x].identity?).to be_truthy expect(subresource_class.identity_properties).to eq [ subresource_class.properties[:x], ] expect(subresource.identity).to eq(10) end end end end end