#
# Author:: Bryan McLellan (btm@loftninjas.org)
# Author:: Scott Bonds (scott@ggr.com)
# Copyright:: Copyright (c) 2009 Bryan McLellan
# Copyright:: Copyright (c) 2014 Scott Bonds
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require 'spec_helper'

class Chef::Provider::Service::Openbsd
  public :builtin_service_enable_variable_name
  public :determine_enabled_status!
  public :determine_current_status!
  public :is_enabled?
  attr_accessor :rc_conf, :rc_conf_local
end

describe Chef::Provider::Service::Openbsd do
  let(:node) do
    node = Chef::Node.new
    node.automatic_attrs[:command] = {:ps => "ps -ax"}
    node
  end

  let(:supports) { {:status => false} }

  let(:new_resource) do
    new_resource = Chef::Resource::Service.new("sndiod")
    new_resource.pattern("sndiod")
    new_resource.supports(supports)
    new_resource
  end

  let(:current_resource) do
    current_resource = Chef::Resource::Service.new("sndiod")
    current_resource
  end

  let(:provider) do
    events = Chef::EventDispatch::Dispatcher.new
    run_context = Chef::RunContext.new(node, {}, events)
    allow(::File).to receive(:read).with('/etc/rc.conf').and_return('')
    allow(::File).to receive(:read).with('/etc/rc.conf.local').and_return('')
    provider = Chef::Provider::Service::Openbsd.new(new_resource,run_context)
    provider.action = :start
    provider
  end

  before do
    allow(Chef::Resource::Service).to receive(:new).and_return(current_resource)
  end

  def stub_etc_rcd_script
    allow(::File).to receive(:exist?).and_return(false)
    expect(::File).to receive(:exist?).with("/etc/rc.d/#{new_resource.service_name}").and_return(true)
  end

  def run_load_current_resource
    stub_etc_rcd_script
    provider.load_current_resource
  end

  describe Chef::Provider::Service::Openbsd, "initialize" do
    it "should find /etc/rc.d init scripts" do
      stub_etc_rcd_script
      expect(provider.init_command).to eql "/etc/rc.d/sndiod"
    end

    it "should set init_command to nil if it can't find anything" do
      expect(::File).to receive(:exist?).with('/etc/rc.d/sndiod').and_return(false)
      expect(provider.init_command).to be nil
    end
  end

  describe Chef::Provider::Service::Openbsd, "determine_current_status!" do
    before do
      stub_etc_rcd_script
      provider.current_resource = current_resource
      current_resource.service_name(new_resource.service_name)
    end

    context "when a status command has been specified" do
      let(:status) { double(:stdout => "", :exitstatus => 0) }

      before do
        new_resource.status_command("/bin/chefhasmonkeypants status")
      end

      it "should run the services status command if one has been specified" do
        expect(provider).to receive(:shell_out).with("/bin/chefhasmonkeypants status").and_return(status)
        provider.determine_current_status!
      end
    end

    context "when the service supports status" do
      let(:status) { double(:stdout => "", :exitstatus => 0) }

      let(:supports) { { :status => true } }

      it "should run '/etc/rc.d/service_name status'" do
        expect(provider).to receive(:shell_out).with("/etc/rc.d/#{new_resource.service_name} check").and_return(status)
        provider.determine_current_status!
      end

      it "should set running to true if the status command returns 0" do
        expect(provider).to receive(:shell_out).with("/etc/rc.d/#{new_resource.service_name} check").and_return(status)
        provider.determine_current_status!
        expect(current_resource.running).to be true
      end

      it "should set running to false if the status command returns anything except 0" do
        expect(provider).to receive(:shell_out).with("/etc/rc.d/#{new_resource.service_name} check").and_raise(Mixlib::ShellOut::ShellCommandFailed)
        provider.determine_current_status!
        expect(current_resource.running).to be false
      end
    end
  end

  describe Chef::Provider::Service::Openbsd, "determine_enabled_status!" do
    before do
      stub_etc_rcd_script
      provider.current_resource = current_resource
      current_resource.service_name(new_resource.service_name)

      allow(provider).to receive(:service_enable_variable_name).and_return("#{new_resource.service_name}_enable")
    end

    context "when the service is builtin" do
      before do
        expect(::File).to receive(:open).with("/etc/rc.d/#{new_resource.service_name}")
        provider.rc_conf = "#{provider.builtin_service_enable_variable_name}=NO"
        provider.rc_conf_local = lines.join("\n")
      end

      %w{YES Yes yes yEs YeS}.each do |setting|
        context "when the enable variable is set to #{setting}" do
          let(:lines) { [ %Q{#{provider.builtin_service_enable_variable_name}="#{setting}"} ] }
          it "sets enabled to true" do
            provider.determine_enabled_status!
            expect(current_resource.enabled).to be true
          end
        end
      end

      %w{No NO no nO None NONE none nOnE}.each do |setting|
        context "when the enable variable is set to #{setting}" do
          let(:lines) { [ %Q{#{provider.builtin_service_enable_variable_name}="#{setting}"} ] }
          it "sets enabled to false" do
            provider.determine_enabled_status!
            expect(current_resource.enabled).to be false
          end
        end
      end

      context "when the enable variable is garbage" do
        let(:lines) { [ %Q{#{provider.builtin_service_enable_variable_name}_enable="alskdjflasdkjflakdfj"} ] }
        it "sets enabled to false" do
          provider.determine_enabled_status!
          expect(current_resource.enabled).to be false
        end
      end

      context "when the enable variable partial matches (left) some other service and we are disabled" do
        let(:lines) { [
          %Q{thing_#{provider.builtin_service_enable_variable_name}="YES"},
          %Q{#{provider.builtin_service_enable_variable_name}="NO"},
        ] }
        it "sets enabled based on the exact match (false)" do
          provider.determine_enabled_status!
          expect(current_resource.enabled).to be false
        end
      end

      context "when the enable variable partial matches (right) some other service and we are disabled" do
        let(:lines) { [
          %Q{#{provider.builtin_service_enable_variable_name}_thing="YES"},
          %Q{#{provider.builtin_service_enable_variable_name}},
        ] }
        it "sets enabled based on the exact match (false)" do
          provider.determine_enabled_status!
          expect(current_resource.enabled).to be false
        end
      end

      context "when the enable variable partial matches (left) some other disabled service and we are enabled" do
        let(:lines) { [
          %Q{thing_#{provider.builtin_service_enable_variable_name}="NO"},
          %Q{#{provider.builtin_service_enable_variable_name}="YES"},
        ] }
        it "sets enabled based on the exact match (true)" do
          provider.determine_enabled_status!
          expect(current_resource.enabled).to be true
        end
      end

      context "when the enable variable partial matches (right) some other disabled service and we are enabled" do
        let(:lines) { [
          %Q{#{provider.builtin_service_enable_variable_name}_thing="NO"},
          %Q{#{provider.builtin_service_enable_variable_name}="YES"},
        ] }
        it "sets enabled based on the exact match (true)" do
          provider.determine_enabled_status!
          expect(current_resource.enabled).to be true
        end
      end

      context "when the enable variable only partial matches (left) some other enabled service" do
        let(:lines) { [ %Q{thing_#{provider.builtin_service_enable_variable_name}_enable="YES"} ] }
        it "sets enabled to false" do
          provider.determine_enabled_status!
          expect(current_resource.enabled).to be false
        end
      end

      context "when the enable variable only partial matches (right) some other enabled service" do
        let(:lines) { [ %Q{#{provider.builtin_service_enable_variable_name}_thing_enable="YES"} ] }
        it "sets enabled to false" do
          provider.determine_enabled_status!
          expect(current_resource.enabled).to be false
        end
      end

      context "when nothing matches" do
        let(:lines) { [] }
        it "sets enabled to true" do
          provider.determine_enabled_status!
          expect(current_resource.enabled).to be false
        end
      end
    end
  end

  describe Chef::Provider::Service::Openbsd, "load_current_resource" do
    before(:each) do
      stub_etc_rcd_script
      expect(provider).to receive(:determine_current_status!)
      current_resource.running(false)
      allow(provider).to receive(:service_enable_variable_name).and_return "#{new_resource.service_name}_enable"
      expect(::File).to receive(:open).with("/etc/rc.d/#{new_resource.service_name}")
     end

    it "should create a current resource with the name of the new resource" do
      expect(Chef::Resource::Service).to receive(:new).and_return(current_resource)
      provider.load_current_resource
    end

    it "should set the current resources service name to the new resources service name" do
      provider.load_current_resource
      expect(current_resource.service_name).to eq(new_resource.service_name)
    end

    it "should return the current resource" do
      expect(provider.load_current_resource).to eql(current_resource)
    end

  end

  context "when testing actions" do
    before(:each) do
      stub_etc_rcd_script
      expect(provider).to receive(:determine_current_status!)
      current_resource.running(false)
      expect(provider).to receive(:determine_enabled_status!)
      current_resource.enabled(false)
      provider.load_current_resource
    end

    describe Chef::Provider::Service::Openbsd, "start_service" do
      it "should call the start command if one is specified" do
        new_resource.start_command("/etc/rc.d/chef startyousillysally")
        expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/chef startyousillysally")
        provider.start_service()
      end

      it "should call '/usr/local/etc/rc.d/service_name start' if no start command is specified" do
        expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{new_resource.service_name} start")
        provider.start_service()
      end
    end

    describe Chef::Provider::Service::Openbsd, "stop_service" do
      it "should call the stop command if one is specified" do
        new_resource.stop_command("/etc/init.d/chef itoldyoutostop")
        expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef itoldyoutostop")
        provider.stop_service()
      end

      it "should call '/usr/local/etc/rc.d/service_name stop' if no stop command is specified" do
        expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{new_resource.service_name} stop")
        provider.stop_service()
      end
    end

    describe Chef::Provider::Service::Openbsd, "restart_service" do
      context "when the new_resource supports restart" do
        let(:supports) { { restart: true } }
        it "should call 'restart' on the service_name if the resource supports it" do
          expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{new_resource.service_name} restart")
          provider.restart_service()
        end
      end

      it "should call the restart_command if one has been specified" do
        new_resource.restart_command("/etc/init.d/chef restartinafire")
        expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef restartinafire")
        provider.restart_service()
      end

      it "otherwise it should call stop and start" do
        expect(provider).to receive(:stop_service)
        expect(provider).to receive(:start_service)
        provider.restart_service()
      end
    end
  end

  describe Chef::Provider::Service::Openbsd, "define_resource_requirements" do
    before do
      provider.current_resource = current_resource
    end

    context "when the init script is not found" do
      before do
        provider.init_command = nil
        allow(provider).to receive(:builtin_service_enable_variable_name).and_return("#{new_resource.service_name}_enable")
      end

      [ "start", "reload", "restart", "enable" ].each do |action|
        it "should raise an exception when the action is #{action}" do
          provider.define_resource_requirements
          provider.action = action
          expect { provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service)
        end
      end

      [ "stop", "disable" ].each do |action|
        it "should not raise an error when the action is #{action}" do
          provider.define_resource_requirements
          provider.action = action
          expect { provider.process_resource_requirements }.not_to raise_error
        end
      end
    end

    context "when the init script is found, but the service_enable_variable_name is nil" do
      before do
        allow(provider).to receive(:builtin_service_enable_variable_name).and_return(nil)
      end

      [ "start", "reload", "restart", "enable" ].each do |action|
        it "should raise an exception when the action is #{action}" do
          provider.action = action
          provider.define_resource_requirements
          expect { provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service)
        end
      end

      [ "stop", "disable" ].each do |action|
        it "should not raise an error when the action is #{action}" do
          provider.action = action
          provider.define_resource_requirements
          expect { provider.process_resource_requirements }.not_to raise_error
        end
      end
    end
  end

  describe Chef::Provider::Service::Openbsd, "enable_service" do
    before do
      provider.current_resource = current_resource
      allow(FileUtils).to receive(:touch).with('/etc/rc.conf.local')
    end
    context "is builtin and disabled by default" do
      before do
        provider.rc_conf = "#{provider.builtin_service_enable_variable_name}=NO"
      end
      context "is enabled" do
        before do
          provider.rc_conf_local = "#{provider.builtin_service_enable_variable_name}=\"\""
        end
        it "should not change rc.conf.local since it is already enabled" do
          expect(::File).not_to receive(:write)
          provider.enable_service
        end
      end
      context "is disabled" do
        before do
          provider.rc_conf_local = ''
        end
        it "should enable the service by adding a line to rc.conf.local" do
          expect(::File).to receive(:write).with('/etc/rc.conf.local', include("#{provider.builtin_service_enable_variable_name}=\"\""))
          expect(provider.is_enabled?).to be false
          provider.enable_service
          expect(provider.is_enabled?).to be true
        end
      end
    end
    context "is builtin and enabled by default" do
      before do
        provider.rc_conf = "#{provider.builtin_service_enable_variable_name}=\"\""
      end
      context "is enabled" do
        before do
          provider.rc_conf_local = ''
        end
        it "should not change rc.conf.local since it is already enabled" do
          expect(::File).not_to receive(:write)
          provider.enable_service
        end
      end
      context "is disabled" do
        before do
          provider.rc_conf_local = "#{provider.builtin_service_enable_variable_name}=NO"
        end
        it "should enable the service by removing a line from rc.conf.local" do
          expect(::File).to receive(:write).with('/etc/rc.conf.local', /^(?!#{provider.builtin_service_enable_variable_name})$/)
          expect(provider.is_enabled?).to be false
          provider.enable_service
          expect(provider.is_enabled?).to be true
        end
      end
    end
    context "is not builtin" do
      before do
        provider.rc_conf = ''
      end
      context "is enabled" do
        before do
          provider.rc_conf_local = "pkg_scripts=\"#{new_resource.service_name}\"\n"
        end
        it "should not change rc.conf.local since it is already enabled" do
          expect(::File).not_to receive(:write)
          provider.enable_service
        end
      end
      context "is disabled" do
        before do
          provider.rc_conf_local = ''
        end
        it "should enable the service by adding it to the pkg_scripts list" do
          expect(::File).to receive(:write).with('/etc/rc.conf.local', "\npkg_scripts=\"#{new_resource.service_name}\"\n")
          expect(provider.is_enabled?).to be false
          provider.enable_service
          expect(provider.is_enabled?).to be true
        end
      end
    end
  end

  describe Chef::Provider::Service::Openbsd, "disable_service" do
    before do
      provider.current_resource = current_resource
      allow(FileUtils).to receive(:touch).with('/etc/rc.conf.local')
    end
    context "is builtin and disabled by default" do
      before do
        provider.rc_conf = "#{provider.builtin_service_enable_variable_name}=NO"
      end
      context "is enabled" do
        before do
          provider.rc_conf_local = "#{provider.builtin_service_enable_variable_name}=\"\""
        end
        it "should disable the service by removing its line from rc.conf.local" do
          expect(::File).to receive(:write).with('/etc/rc.conf.local', /^(?!#{provider.builtin_service_enable_variable_name})$/)
          expect(provider.is_enabled?).to be true
          provider.disable_service
          expect(provider.is_enabled?).to be false
        end
      end
      context "is disabled" do
        before do
          provider.rc_conf_local = ''
        end
        it "should not change rc.conf.local since it is already disabled" do
          expect(::File).not_to receive(:write)
          provider.disable_service
        end
      end
    end
    context "is builtin and enabled by default" do
      before do
        provider.rc_conf = "#{provider.builtin_service_enable_variable_name}=\"\""
      end
      context "is enabled" do
        before do
          provider.rc_conf_local = ''
        end
        it "should disable the service by adding a line to rc.conf.local" do
          expect(::File).to receive(:write).with('/etc/rc.conf.local', include("#{provider.builtin_service_enable_variable_name}=\"NO\""))
          expect(provider.is_enabled?).to be true
          provider.disable_service
          expect(provider.is_enabled?).to be false
        end
      end
      context "is disabled" do
        before do
          provider.rc_conf_local = "#{provider.builtin_service_enable_variable_name}=NO"
        end
        it "should not change rc.conf.local since it is already disabled" do
          expect(::File).not_to receive(:write)
          provider.disable_service
        end
      end
    end
    context "is not builtin" do
      before do
        provider.rc_conf = ''
      end
      context "is enabled" do
        before do
          provider.rc_conf_local = "pkg_scripts=\"#{new_resource.service_name}\"\n"
        end
        it "should disable the service by removing it from the pkg_scripts list" do
          expect(::File).to receive(:write).with('/etc/rc.conf.local', /^(?!#{new_resource.service_name})$/)
          expect(provider.is_enabled?).to be true
          provider.disable_service
          expect(provider.is_enabled?).to be false
        end
      end
      context "is disabled" do
        before do
          provider.rc_conf_local = ''
        end
        it "should not change rc.conf.local since it is already disabled" do
          expect(::File).not_to receive(:write)
          provider.disable_service
        end
      end
    end
  end

end