require 'spec_helper'

describe Ridley::NodeResource do
  let(:host) { "33.33.33.10" }
  let(:worker) { double('worker', alive?: true, terminate: nil) }
  let(:host_commander) { double('host-commander') }
  let(:options) do
    {
      server_url: double('server_url'),
      validator_path: double('validator_path'),
      validator_client: double('validator_client'),
      encrypted_data_bag_secret: double('encrypted_data_bag_secret'),
      ssh: double('ssh'),
      winrm: double('winrm'),
      chef_version: double('chef_version')
    }
  end
  let(:instance) do
    inst = described_class.new(double, options)
    inst.stub(connection: chef_zero_connection, host_commander: host_commander)
    inst
  end

  describe "#bootstrap" do
    it "sends the message #bootstrap to the instance's host_commander" do
      host_commander.should_receive(:bootstrap).with(host, options)
      instance.bootstrap(host)
    end

    it "passes pre-configured options to #bootstrap" do
      host_commander.should_receive(:bootstrap).with(host, options)
      instance.bootstrap(host)
    end
  end

  describe "#chef_run" do
    it "sends the message #chef_client to the instance's host_commander" do
      host_commander.should_receive(:chef_client).with(host, ssh: instance.ssh, winrm: instance.winrm)
      instance.chef_run(host)
    end
  end

  describe "#put_secret" do
    let(:secret) { options[:encrypted_data_bag_secret] }

    it "sends the message #put_secret to the instance's host_commander" do
      host_commander.should_receive(:put_secret).with(host, secret, options.slice(:ssh, :winrm))
      instance.put_secret(host)
    end
  end

  describe "#ruby_script" do
    let(:command_lines) { ["puts 'hello'", "puts 'there'"] }

    it "sends the message #ruby_script to the instance's host_commander" do
      host_commander.should_receive(:ruby_script).with(host, command_lines, ssh: instance.ssh, winrm: instance.winrm)
      instance.ruby_script(host, command_lines)
    end
  end

  describe "#run" do
    let(:command) { "echo 'hello world'" }

    it "sends the message #run to the instance's host_commander" do
      host_commander.should_receive(:run).with(host, command, ssh: instance.ssh, winrm: instance.winrm)
      instance.run(host, command)
    end
  end

  describe "#platform_specific_run" do
    let(:ssh_command) { "hostname -f" }
    let(:winrm_command) { "echo %COMPUTERNAME%" }
    let(:ssh_connector) { Ridley::HostConnector::SSH.new }
    let(:winrm_connector) { Ridley::HostConnector::WinRM.new }
    let(:unsupported_connector) { Object.new }

    describe "expecting the ssh connector" do
      before do
        host_commander.stub(:connector_for).and_return ssh_connector
      end
      it "sends the ssh command" do
        instance.should_receive(:run).with(host, ssh_command)
        instance.platform_specific_run(host, ssh: ssh_command, winrm: winrm_command)
      end

      it "raises an error if no command is provided for the ssh connector when the ssh connector is used" do
        expect {
          instance.platform_specific_run(host, winrm: winrm_command)
        }.to raise_error(Ridley::Errors::CommandNotProvided)
      end

      it "raises an error if an empty command is provided for the ssh connector when the ssh connector is used" do
        expect {
          instance.platform_specific_run(host, ssh: "", winrm: winrm_command)
        }.to raise_error(Ridley::Errors::CommandNotProvided)
      end

      it "raises an error if a nil command is provided for the ssh connector when the ssh connector is used" do
        expect {
          instance.platform_specific_run(host, ssh: nil, winrm: winrm_command)
        }.to raise_error(Ridley::Errors::CommandNotProvided)
      end
    end

    describe "expecting the winrm connector" do
      before do
        host_commander.stub(:connector_for).and_return winrm_connector
      end
      it "sends the ssh command if the connector is winrm" do
        instance.should_receive(:run).with(host, winrm_command)
        instance.platform_specific_run(host, ssh: ssh_command, winrm: winrm_command)
      end

      it "raises an error if no command is provided for the winrm connector when the winrm connector is used" do
        expect {
          instance.platform_specific_run(host, ssh: ssh_command)
        }.to raise_error(Ridley::Errors::CommandNotProvided)
      end

      it "raises an error if an empty is provided for the winrm connector when the winrm connector is used" do
        expect {
          instance.platform_specific_run(host, ssh: ssh_command, winrm: "")
        }.to raise_error(Ridley::Errors::CommandNotProvided)
      end

      it "raises a nil command is provided for the winrm connector when the winrm connector is used" do
        expect {
          instance.platform_specific_run(host, ssh: ssh_command, winrm: nil)
        }.to raise_error(Ridley::Errors::CommandNotProvided)
      end
    end

    it "raises a RuntimeError if an unsupported connector is used" do
      host_commander.stub(:connector_for).and_return unsupported_connector
      expect {
        instance.platform_specific_run(host, ssh: ssh_command, winrm: winrm_command)
      }.to raise_error(RuntimeError)
    end
  end

  describe "#merge_data" do
    let(:node_name) { "rspec-test" }
    let(:run_list) { [ "recipe[one]", "recipe[two]" ] }
    let(:attributes) { { deep: { two: "val" } } }

    subject(:result) { instance.merge_data(node_name, run_list: run_list, attributes: attributes) }

    context "when a node of the given name exists" do
      before do
        chef_node(node_name,
          run_list: [ "recipe[one]", "recipe[three]" ],
          normal: { deep: { one: "val" } }
        )
      end

      it "returns a Ridley::NodeObject" do
        expect(result).to be_a(Ridley::NodeObject)
      end

      it "has a union between the run list of the original node and the new run list" do
        expect(result.run_list).to eql(["recipe[one]","recipe[three]","recipe[two]"])
      end

      it "has a deep merge between the attributes of the original node and the new attributes" do
        expect(result.normal.to_hash).to eql("deep" => { "one" => "val", "two" => "val" })
      end
    end

    context "when a node with the given name does not exist" do
      let(:node_name) { "does_not_exist" }

      it "raises a ResourceNotFound error" do
        expect { result }.to raise_error(Ridley::Errors::ResourceNotFound)
      end
    end
  end
end