require 'spec_helper'

describe Puppet::Type.type(:service).provider(:smf), :if => Puppet.features.posix? do
  before(:each) do
    # Create a mock resource
    @resource = Puppet::Type.type(:service).new(
      :name => "/system/myservice", :ensure => :running, :enable => :true)
    @provider = described_class.new(@resource)

    allow(FileTest).to receive(:file?).with('/usr/sbin/svcadm').and_return(true)
    allow(FileTest).to receive(:executable?).with('/usr/sbin/svcadm').and_return(true)
    allow(FileTest).to receive(:file?).with('/usr/bin/svcs').and_return(true)
    allow(FileTest).to receive(:executable?).with('/usr/bin/svcs').and_return(true)
    allow(Facter).to receive(:value).with(:operatingsystem).and_return('Solaris')
    allow(Facter).to receive(:value).with(:osfamily).and_return('Solaris')
    allow(Facter).to receive(:value).with(:operatingsystemrelease).and_return('11.2')
  end

  context ".instances" do
    it "should have an instances method" do
      expect(described_class).to respond_to :instances
    end

    it "should get a list of services (excluding legacy)" do
      expect(described_class).to receive(:svcs).with('-H', '-o', 'state,fmri').and_return(File.read(my_fixture('svcs.out')))
      instances = described_class.instances.map { |p| {:name => p.get(:name), :ensure => p.get(:ensure)} }
      # we dont manage legacy
      expect(instances.size).to eq(3)
      expect(instances[0]).to eq({:name => 'svc:/system/svc/restarter:default', :ensure => :running })
      expect(instances[1]).to eq({:name => 'svc:/network/cswrsyncd:default', :ensure => :maintenance })
      expect(instances[2]).to eq({:name => 'svc:/network/dns/client:default', :ensure => :degraded })
    end
  end

  it "should have a restart method" do
    expect(@provider).to respond_to(:restart)
  end

  it "should have a restartcmd method" do
    expect(@provider).to respond_to(:restartcmd)
  end

  it "should have a start method" do
    expect(@provider).to respond_to(:start)
  end

  it "should have a stop method" do
    expect(@provider).to respond_to(:stop)
  end

  it "should have an enabled? method" do
    expect(@provider).to respond_to(:enabled?)
  end

  it "should have an enable method" do
    expect(@provider).to respond_to(:enable)
  end

  it "should have a disable method" do
    expect(@provider).to respond_to(:disable)
  end

  describe "when checking status" do
    before(:each) do
      allow(@provider).to receive(:complete_service?).and_return(true)
    end

    it "should call the external command 'svcs /system/myservice' once" do
      expect(@provider).to receive(:svcs).with('-H', '-o', 'state,nstate', "/system/myservice").and_return("online\t-")
      @provider.status
    end

    it "should return stopped if svcs can't find the service" do
      allow(@provider).to receive(:svcs).and_raise(Puppet::ExecutionFailure.new("no svc found"))
      expect(@provider.status).to eq(:stopped)
    end

    it "should return stopped for an incomplete service on Solaris 11" do
      allow(Facter).to receive(:value).with(:operatingsystemrelease).and_return('11.3')
      allow(@provider).to receive(:complete_service?).and_return(false)
      expect(@provider.status).to eq(:stopped)
    end

    it "should return running if online in svcs output" do
      allow(@provider).to receive(:svcs).and_return("online\t-")
      expect(@provider.status).to eq(:running)
    end

    it "should return stopped if disabled in svcs output" do
      allow(@provider).to receive(:svcs).and_return("disabled\t-")
      expect(@provider.status).to eq(:stopped)
    end

    it "should return maintenance if in maintenance in svcs output" do
      allow(@provider).to receive(:svcs).and_return("maintenance\t-")
      expect(@provider.status).to eq(:maintenance)
    end

    it "should return degraded if in degraded in svcs output" do
      allow(@provider).to receive(:svcs).and_return("degraded\t-")
      expect(@provider.status).to eq(:degraded)
    end

    it "should return target state if transitioning in svcs output" do
      allow(@provider).to receive(:svcs).and_return("online\tdisabled")
      expect(@provider.status).to eq(:stopped)
    end

    it "should throw error if it's a legacy service in svcs output" do
      allow(@provider).to receive(:svcs).and_return("legacy_run\t-")
      expect { @provider.status }.to raise_error(Puppet::Error, "Cannot manage legacy services through SMF")
    end
  end

  context "when starting" do
    it "should enable the service if it is not enabled" do
      expect(@provider).to receive(:status).and_return(:stopped)
      expect(@provider).to receive(:texecute).with(:start, ['/usr/sbin/svcadm', :enable, '-rs', '/system/myservice'], true)
      expect(@provider).to receive(:wait).with('online')
      @provider.start
    end

    it "should always execute external command 'svcadm enable /system/myservice'" do
      expect(@provider).to receive(:status).and_return(:running)
      expect(@provider).to receive(:texecute).with(:start, ['/usr/sbin/svcadm', :enable, '-rs', '/system/myservice'], true)
      expect(@provider).to receive(:wait).with('online')
      @provider.start
    end

    it "should execute external command 'svcadm clear /system/myservice' if in maintenance" do
      allow(@provider).to receive(:status).and_return(:maintenance)
      expect(@provider).to receive(:texecute).with(:start, ["/usr/sbin/svcadm", :clear, "/system/myservice"], true)
      expect(@provider).to receive(:wait).with('online')
      @provider.start
    end

    it "should execute external command 'svcadm clear /system/myservice' if in degraded" do
      allow(@provider).to receive(:status).and_return(:degraded)
      expect(@provider).to receive(:texecute).with(:start, ["/usr/sbin/svcadm", :clear, "/system/myservice"], true)
      expect(@provider).to receive(:wait).with('online')
      @provider.start
    end

    it "should error if timeout occurs while starting the service" do
      expect(@provider).to receive(:status).and_return(:stopped)
      expect(@provider).to receive(:texecute).with(:start, ["/usr/sbin/svcadm", :enable, '-rs', "/system/myservice"], true)
      expect(Timeout).to receive(:timeout).with(60).and_raise(Timeout::Error)
      expect { @provider.start }.to raise_error Puppet::Error, ('Timed out waiting for /system/myservice to transition states')
    end
  end

  context "when starting a service with a manifest" do
    before(:each) do
      @resource = Puppet::Type.type(:service).new(:name => "/system/myservice", :ensure => :running, :enable => :true, :manifest => "/tmp/myservice.xml")
      @provider = described_class.new(@resource)
      allow($CHILD_STATUS).to receive(:exitstatus).and_return(1)
    end

    it "should import the manifest if service is missing" do
      allow(@provider).to receive(:complete_service?).and_return(true)
      expect(@provider).to receive(:svcs).with('-l', '/system/myservice').and_raise(Puppet::ExecutionFailure, "Exited 1")
      expect(@provider).to receive(:svccfg).with(:import, "/tmp/myservice.xml")
      expect(@provider).to receive(:texecute).with(:start, ["/usr/sbin/svcadm", :enable, '-rs', "/system/myservice"], true)
      expect(@provider).to receive(:wait).with('online')
      expect(@provider).to receive(:svcs).with('-H', '-o', 'state,nstate', "/system/myservice").and_return("online\t-")
      @provider.start
    end

    it "should handle failures if importing a manifest" do
      expect(@provider).to receive(:svcs).with('-l', '/system/myservice').and_raise(Puppet::ExecutionFailure, "Exited 1")
      expect(@provider).to receive(:svccfg).and_raise(Puppet::ExecutionFailure.new("can't svccfg import"))
      expect { @provider.start }.to raise_error(Puppet::Error, "Cannot config /system/myservice to enable it: can't svccfg import")
    end
  end

  context "when stopping" do
    it "should execute external command 'svcadm disable /system/myservice'" do
      expect(@provider).to receive(:texecute).with(:stop, ["/usr/sbin/svcadm", :disable, '-s', "/system/myservice"], true)
      expect(@provider).to receive(:wait).with('offline', 'disabled', 'uninitialized')
      @provider.stop
    end

    it "should error if timeout occurs while stopping the service" do
      expect(@provider).to receive(:texecute).with(:stop, ["/usr/sbin/svcadm", :disable, '-s', "/system/myservice"], true)
      expect(Timeout).to receive(:timeout).with(60).and_raise(Timeout::Error)
      expect { @provider.stop }.to raise_error Puppet::Error, ('Timed out waiting for /system/myservice to transition states')
    end
  end

  context "when restarting" do
    it "should error if timeout occurs while restarting the service" do
      expect(@provider).to receive(:texecute).with(:restart, ["/usr/sbin/svcadm", :restart, '-s', "/system/myservice"], true)
      expect(Timeout).to receive(:timeout).with(60).and_raise(Timeout::Error)
      expect { @provider.restart }.to raise_error Puppet::Error, ('Timed out waiting for /system/myservice to transition states')
    end

    context 'with :operatingsystemrelease == 10_u10' do
      it "should call 'svcadm restart /system/myservice'" do
        allow(Facter).to receive(:value).with(:operatingsystemrelease).and_return('10_u10')
        expect(@provider).to receive(:texecute).with(:restart, ["/usr/sbin/svcadm", :restart, "/system/myservice"], true)
        expect(@provider).to receive(:wait).with('online')
        @provider.restart
      end
    end

    context 'with :operatingsystemrelease == 11.2' do
      it "should call 'svcadm restart -s /system/myservice'" do
        allow(Facter).to receive(:value).with(:operatingsystemrelease).and_return('11.2')
        expect(@provider).to receive(:texecute).with(:restart, ["/usr/sbin/svcadm", :restart, '-s', "/system/myservice"], true)
        expect(@provider).to receive(:wait).with('online')
        @provider.restart
      end
    end

    context 'with :operatingsystemrelease > 11.2' do
      it "should call 'svcadm restart -s /system/myservice'" do
        allow(Facter).to receive(:value).with(:operatingsystemrelease).and_return('11.3')
        expect(@provider).to receive(:texecute).with(:restart, ["/usr/sbin/svcadm", :restart, '-s', "/system/myservice"], true)
        expect(@provider).to receive(:wait).with('online')
        @provider.restart
      end
    end
  end

  describe '#service_fmri' do
    it 'raises a Puppet::Error if the service resource matches multiple FMRIs' do
      allow(@provider).to receive(:svcs).with('-l', @provider.resource[:name]).and_return(File.read(my_fixture('svcs_multiple_fmris.out')))

      expect { @provider.service_fmri }.to raise_error do |error|
        expect(error).to be_a(Puppet::Error)
        expect(error.message).to match(@provider.resource[:name])
        expect(error.message).to match('multiple')

        matched_fmris = ["svc:/application/tstapp:one", "svc:/application/tstapp:two"]
        expect(error.message).to match(matched_fmris.join(', '))
      end
    end

    it 'raises a Puppet:ExecutionFailure if svcs fails' do
      allow(@provider).to receive(:svcs).with('-l', @provider.resource[:name]).and_raise(
        Puppet::ExecutionFailure, 'svcs failed!'
      )

      expect { @provider.service_fmri }.to raise_error do |error|
        expect(error).to be_a(Puppet::ExecutionFailure)
        expect(error.message).to match('svcs failed!')
      end
    end

    it "returns the service resource's fmri and memoizes it" do
      allow(@provider).to receive(:svcs).with('-l', @provider.resource[:name]).and_return(File.read(my_fixture('svcs_fmri.out')))

      expected_fmri = 'svc:/application/tstapp:default'

      expect(@provider.service_fmri).to eql(expected_fmri)
      expect(@provider.instance_variable_get(:@fmri)).to eql(expected_fmri)
    end
  end

  describe '#complete_service?' do
    let(:fmri) { 'service_fmri' }

    before(:each) do
      allow(@provider).to receive(:service_fmri).and_return(fmri)
    end

    it 'should raise a Puppet::Error if it is called on an older Solaris machine' do
      allow(Facter).to receive(:value).with(:operatingsystemrelease).and_return('10.0')

      expect { @provider.complete_service? }.to raise_error(Puppet::Error)
    end

    it 'should return false for an incomplete service' do
      allow(@provider).to receive(:svccfg).with('-s', fmri, 'listprop', 'general/complete').and_return("")
      expect(@provider.complete_service?).to be false
    end

    it 'should return true for a complete service' do
      allow(@provider).to receive(:svccfg)
        .with('-s', fmri, 'listprop', 'general/complete')
        .and_return("general/complete astring")

      expect(@provider.complete_service?).to be true
    end
  end
end
