require "support/shared/integration/integration_helper"

describe "Resource.load_current_value" do
  include IntegrationSupport

  module Namer
    extend self
    attr_accessor :current_index

    def incrementing_value
      @incrementing_value += 1
      @incrementing_value
    end
    attr_writer :incrementing_value
  end

  before(:all) { Namer.current_index = 1 }
  before { Namer.current_index += 1 }
  before { Namer.incrementing_value = 0 }

  let(:resource_name) { :"load_current_value_dsl#{Namer.current_index}" }
  let(:resource_class) do
    result = Class.new(Chef::Resource) do
      def self.to_s; resource_name.to_s; end

      def self.inspect; resource_name.inspect; end
      property :x, default: lazy { "default #{Namer.incrementing_value}" }
      def self.created_x=(value)
        @created = value
      end

      def self.created_x
        @created
      end
      action :create do
        new_resource.class.created_x = new_resource.x
      end
    end
    result.provides resource_name
    result
  end

  # Pull on resource_class to initialize it
  before { resource_class }

  context "with a resource with load_current_value" do
    before :each do
      resource_class.load_current_value do
        x "loaded #{Namer.incrementing_value} (#{self.class.properties.sort_by { |name, p| name }
          .select { |name, p| p.is_set?(self) }
          .map { |name, p| "#{name}=#{p.get(self)}" }
          .join(", ")})"
      end
    end

    context "and a resource with x set to a desired value" do
      let(:resource) do
        e = self
        r = nil
        converge do
          r = public_send(e.resource_name, "blah") do
            x "desired"
          end
        end
        r
      end

      it "current_resource is passed name but not x" do
        expect(resource.current_value.x).to eq "loaded 3 (name=blah)"
      end

      it "resource.current_value returns a different resource" do
        expect(resource.current_value.x).to eq "loaded 3 (name=blah)"
        expect(resource.x).to eq "desired"
      end

      it "resource.current_value constructs the resource anew each time" do
        expect(resource.current_value.x).to eq "loaded 3 (name=blah)"
        expect(resource.current_value.x).to eq "loaded 4 (name=blah)"
      end

      it "the provider accesses the current value of x" do
        expect(resource.class.created_x).to eq "desired"
      end

      context "and identity: :i and :d with desired_state: false" do
        before do
          resource_class.class_eval do
            property :i, identity: true
            property :d, desired_state: false
          end
        end

        before do
          resource.i "desired_i"
          resource.d "desired_d"
        end

        it "i, name and d are passed to load_current_value, but not x" do
          expect(resource.current_value.x).to eq "loaded 3 (d=desired_d, i=desired_i, name=blah)"
        end
      end

      context "and name_property: :i and :d with desired_state: false" do
        before do
          resource_class.class_eval do
            property :i, name_property: true
            property :d, desired_state: false
          end
        end

        before do
          resource.i "desired_i"
          resource.d "desired_d"
        end

        it "i, name and d are passed to load_current_value, but not x" do
          expect(resource.current_value.x).to eq "loaded 3 (d=desired_d, i=desired_i, name=blah)"
        end
      end
    end

    let(:subresource_name) do
      :"load_current_value_subresource_dsl#{Namer.current_index}"
    end
    let(:subresource_class) do
      r = Class.new(resource_class) do
        property :y, default: lazy { "default_y #{Namer.incrementing_value}" }
      end
      r.provides subresource_name
      r
    end

    # Pull on subresource_class to initialize it
    before { subresource_class }

    let(:subresource) do
      e = self
      r = nil
      converge do
        r = public_send(e.subresource_name, "blah") do
          x "desired"
        end
      end
      r
    end

    context "and a child resource class with no load_current_value" do
      it "the parent load_current_value is used" do
        expect(subresource.current_value.x).to eq "loaded 3 (name=blah)"
      end
      it "load_current_value yields a copy of the child class" do
        expect(subresource.current_value).to be_kind_of(subresource_class)
      end
    end

    context "And a child resource class with load_current_value" do
      before do
        subresource_class.load_current_value do
          y "loaded_y #{Namer.incrementing_value} (#{self.class.properties.sort_by { |name, p| name }
            .select { |name, p| p.is_set?(self) }
            .map { |name, p| "#{name}=#{p.get(self)}" }
            .join(", ")})"
        end
      end

      it "the overridden load_current_value is used" do
        current_resource = subresource.current_value
        expect(current_resource.x).to eq "default 4"
        expect(current_resource.y).to eq "loaded_y 3 (name=blah)"
      end
    end

    context "and a child resource class with load_current_value calling super()" do
      before do
        subresource_class.load_current_value do
          super()
          y "loaded_y #{Namer.incrementing_value} (#{self.class.properties.sort_by { |name, p| name }
            .select { |name, p| p.is_set?(self) }
            .map { |name, p| "#{name}=#{p.get(self)}" }
            .join(", ")})"
        end
      end

      it "the original load_current_value is called as well as the child one" do
        current_resource = subresource.current_value
        expect(current_resource.x).to eq "loaded 5 (name=blah)"
        expect(current_resource.y).to eq "loaded_y 6 (name=blah, x=loaded 5 (name=blah))"
      end
    end
  end
end

describe "simple load_current_value tests" do
  let(:resource_class) do
    Class.new(Chef::Resource) do
      attr_writer :index # this is our hacky global state

      def index; @index ||= 1; end

      property :myindex, Integer

      load_current_value do |new_resource|
        myindex new_resource.index
      end

      action :run do
        new_resource.index += 1
      end
    end
  end

  let(:node) { Chef::Node.new }
  let(:events) { Chef::EventDispatch::Dispatcher.new }
  let(:run_context) { Chef::RunContext.new(node, {}, events) }
  let(:new_resource) { resource_class.new("test", run_context) }
  let(:provider) { new_resource.provider_for_action(:run) }

  it "calling the action on the provider sets the current_resource" do
    expect(events).to receive(:resource_current_state_loaded).with(new_resource, :run, anything)
    provider.run_action(:run)
    expect(provider.current_resource.myindex).to eql(1)
  end

  it "calling the action on the provider sets the after_resource" do
    expect(events).to receive(:resource_after_state_loaded).with(new_resource, :run, anything)
    provider.run_action(:run)
    expect(provider.after_resource.myindex).to eql(2)
  end
end

describe "simple load_current_resource tests" do
  let(:provider_class) do
    Class.new(Chef::Provider) do
      provides :no_load_current_value
      def load_current_resource
        @current_resource = new_resource.dup
        @current_resource.myindex = 1
      end
      action :run do
      end
    end
  end

  let(:resource_class) do
    provider_class # vivify the provider_class
    Class.new(Chef::Resource) do
      provides :no_load_current_value
      property :myindex, Integer
    end
  end

  let(:node) { Chef::Node.new }
  let(:events) { Chef::EventDispatch::Dispatcher.new }
  let(:run_context) { Chef::RunContext.new(node, {}, events) }
  let(:new_resource) { resource_class.new("test", run_context) }
  let(:provider) { new_resource.provider_for_action(:run) }

  it "calling the action on the provider sets the current_resource" do
    expect(events).to receive(:resource_current_state_loaded).with(new_resource, :run, anything)
    provider.run_action(:run)
    expect(provider.current_resource.myindex).to eql(1)
  end

  it "calling the action on the provider sets the after_resource" do
    expect(events).to receive(:resource_after_state_loaded).with(new_resource, :run, new_resource)
    provider.run_action(:run)
    expect(provider.after_resource.myindex).to eql(nil)
  end
end

describe "simple load_current_resource and load_after_resource tests" do
  let(:provider_class) do
    Class.new(Chef::Provider) do
      provides :load_after
      def load_current_resource
        @current_resource = new_resource.dup
        @current_resource.myindex = 1
      end

      def load_after_resource
        @after_resource = new_resource.dup
        @after_resource.myindex = 2
      end
      action :run do
      end
    end
  end

  let(:resource_class) do
    provider_class # autovivify provider class
    Class.new(Chef::Resource) do
      provides :load_after
      property :myindex, Integer
    end
  end

  let(:node) { Chef::Node.new }
  let(:events) { Chef::EventDispatch::Dispatcher.new }
  let(:run_context) { Chef::RunContext.new(node, {}, events) }
  let(:new_resource) { resource_class.new("test", run_context) }
  let(:provider) { new_resource.provider_for_action(:run) }

  it "calling the action on the provider sets the current_resource" do
    expect(events).to receive(:resource_current_state_loaded).with(new_resource, :run, anything)
    provider.run_action(:run)
    expect(provider.current_resource.myindex).to eql(1)
  end

  it "calling the action on the provider sets the after_resource" do
    expect(events).to receive(:resource_after_state_loaded).with(new_resource, :run, anything)
    provider.run_action(:run)
    expect(provider.after_resource.myindex).to eql(2)
  end
end
