#
# Author:: Adam Jacob (<adam@opscode.com>)
# Copyright:: Copyright (c) 2008 Opscode, Inc.
# 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'

describe "Chef::Platform supports" do
  [
    :freebsd,
    :ubuntu,
    :debian,
    :centos,
    :fedora,
    :suse,
    :opensuse,
    :redhat,
    :oracle,
    :gentoo,
    :arch,
    :solaris,
    :gcel,
    :ibm_powerkvm
  ].each do |platform|
    it "#{platform}" do
      expect(Chef::Platform.platforms).to have_key(platform)
    end
  end
end

describe Chef::Platform do

  context "while testing with fake data" do

    before :all do
      @original_platform_map = Chef::Platform.platforms
    end

    after :all do ||
      Chef::Platform.platforms = @original_platform_map
    end

    before(:each) do
      Chef::Platform.platforms = {
        :darwin => {
          ">= 10.11" => {
            :file => "new_darwinian"
          },
          "9.2.2" => {
            :file => "darwinian",
            :else => "thing"
          },
          :default => {
            :file => "old school",
            :snicker => "snack"
          }
        },
        :mars_volta => {
        },
        :default => {
          :file => Chef::Provider::File,
          :pax => "brittania",
          :cat => "nice"
        }
      }
      @events = Chef::EventDispatch::Dispatcher.new
    end

    it "should allow you to look up a platform by name and version, returning the provider map for it" do
      pmap = Chef::Platform.find("Darwin", "9.2.2")
      expect(pmap).to be_a_kind_of(Hash)
      expect(pmap[:file]).to eql("darwinian")
    end

    it "should allow you to look up a platform by name and version using \"greater than\" style operators" do
      pmap = Chef::Platform.find("Darwin", "11.1.0")
      expect(pmap).to be_a_kind_of(Hash)
      expect(pmap[:file]).to eql("new_darwinian")
    end

    it "should use the default providers for an os if the specific version does not exist" do
      pmap = Chef::Platform.find("Darwin", "1")
      expect(pmap).to be_a_kind_of(Hash)
      expect(pmap[:file]).to eql("old school")
    end

    it "should use the default providers if the os doesn't give me a default, but does exist" do
      pmap = Chef::Platform.find("mars_volta", "1")
      expect(pmap).to be_a_kind_of(Hash)
      expect(pmap[:file]).to eql(Chef::Provider::File)
    end

    it "should use the default provider if the os does not exist" do
      pmap = Chef::Platform.find("AIX", "1")
      expect(pmap).to be_a_kind_of(Hash)
      expect(pmap[:file]).to eql(Chef::Provider::File)
    end

    it "should merge the defaults for an os with the specific version" do
      pmap = Chef::Platform.find("Darwin", "9.2.2")
      expect(pmap[:file]).to eql("darwinian")
      expect(pmap[:snicker]).to eql("snack")
    end

    it "should merge the defaults for an os with the universal defaults" do
      pmap = Chef::Platform.find("Darwin", "9.2.2")
      expect(pmap[:file]).to eql("darwinian")
      expect(pmap[:pax]).to eql("brittania")
    end

    it "should allow you to look up a provider for a platform directly by symbol" do
      expect(Chef::Platform.find_provider("Darwin", "9.2.2", :file)).to eql("darwinian")
    end

    it "should raise an exception if a provider cannot be found for a resource type" do
      expect { Chef::Platform.find_provider("Darwin", "9.2.2", :coffee) }.to raise_error(ArgumentError)
    end

    it "should look up a provider for a resource with a Chef::Resource object" do
      kitty = Chef::Resource::Cat.new("loulou")
      expect(Chef::Platform.find_provider("Darwin", "9.2.2", kitty)).to eql("nice")
    end

    it "should look up a provider with a node and a Chef::Resource object" do
      kitty = Chef::Resource::Cat.new("loulou")
      node = Chef::Node.new
      node.name("Intel")
      node.automatic_attrs[:platform] = "mac_os_x"
      node.automatic_attrs[:platform_version] = "9.2.2"
      expect(Chef::Platform.find_provider_for_node(node, kitty)).to eql("nice")
    end

    it "should not throw an exception when the platform version has an unknown format" do
      expect(Chef::Platform.find_provider(:darwin, "bad-version", :file)).to eql("old school")
    end

    it "should prefer an explicit provider" do
      kitty = Chef::Resource::Cat.new("loulou")
      allow(kitty).to receive(:provider).and_return(Chef::Provider::File)
      node = Chef::Node.new
      node.name("Intel")
      node.automatic_attrs[:platform] = "mac_os_x"
      node.automatic_attrs[:platform_version] = "9.2.2"
      expect(Chef::Platform.find_provider_for_node(node, kitty)).to eql(Chef::Provider::File)
    end

    it "should look up a provider based on the resource name if nothing else matches" do
      kitty = Chef::Resource::Cat.new("loulou")
      class Chef::Provider::Cat < Chef::Provider; end
      Chef::Platform.platforms[:default].delete(:cat)
      node = Chef::Node.new
      node.name("Intel")
      node.automatic_attrs[:platform] = "mac_os_x"
      node.automatic_attrs[:platform_version] = "8.5"
      expect(Chef::Platform.find_provider_for_node(node, kitty)).to eql(Chef::Provider::Cat)
    end

    def setup_file_resource
      node = Chef::Node.new
      node.automatic_attrs[:platform] = "mac_os_x"
      node.automatic_attrs[:platform_version] = "9.2.2"
      run_context = Chef::RunContext.new(node, {}, @events)
      [ Chef::Resource::File.new("whateva", run_context), run_context ]
    end

    it "returns a provider object given a Chef::Resource object which has a valid run context and an action" do
      file, run_context = setup_file_resource
      provider = Chef::Platform.provider_for_resource(file, :foo)
      expect(provider).to be_an_instance_of(Chef::Provider::File)
      expect(provider.new_resource).to equal(file)
      expect(provider.run_context).to equal(run_context)
    end

    it "returns a provider object given a Chef::Resource object which has a valid run context without an action" do
      file, run_context = setup_file_resource
      provider = Chef::Platform.provider_for_resource(file)
      expect(provider).to be_an_instance_of(Chef::Provider::File)
      expect(provider.new_resource).to equal(file)
      expect(provider.run_context).to equal(run_context)
    end

    it "raises an error when trying to find the provider for a resource with no run context" do
      file = Chef::Resource::File.new("whateva")
      expect {Chef::Platform.provider_for_resource(file)}.to raise_error(ArgumentError)
    end

    it "does not support finding a provider by resource and node -- a run context is required" do
      expect {Chef::Platform.provider_for_node('node', 'resource')}.to raise_error(NotImplementedError)
    end

    it "should update the provider map with map" do
      Chef::Platform.set(
           :platform => :darwin,
           :version => "9.2.2",
           :resource => :file,
           :provider => "masterful"
      )
      expect(Chef::Platform.platforms[:darwin]["9.2.2"][:file]).to eql("masterful")
      Chef::Platform.set(
           :platform => :darwin,
           :resource => :file,
           :provider => "masterful"
      )
      expect(Chef::Platform.platforms[:darwin][:default][:file]).to eql("masterful")
      Chef::Platform.set(
           :resource => :file,
           :provider => "masterful"
      )
      expect(Chef::Platform.platforms[:default][:file]).to eql("masterful")

      Chef::Platform.set(
           :platform => :hero,
           :version => "9.2.2",
           :resource => :file,
           :provider => "masterful"
      )
      expect(Chef::Platform.platforms[:hero]["9.2.2"][:file]).to eql("masterful")

      Chef::Platform.set(
           :resource => :file,
           :provider => "masterful"
      )
      expect(Chef::Platform.platforms[:default][:file]).to eql("masterful")

      Chef::Platform.platforms = {}

      Chef::Platform.set(
           :resource => :file,
           :provider => "masterful"
      )
      expect(Chef::Platform.platforms[:default][:file]).to eql("masterful")

      Chef::Platform.platforms = { :neurosis => {} }
      Chef::Platform.set(:platform => :neurosis, :resource => :package, :provider => "masterful")
      expect(Chef::Platform.platforms[:neurosis][:default][:package]).to eql("masterful")

    end

    it "does not overwrite the platform map when using :default platform" do
      Chef::Platform.set(
        :resource => :file,
        :platform => :default,
        :provider => "new school"
      )
      expect(Chef::Platform.platforms[:default][:file]).to eql("new school")
      expect(Chef::Platform.platforms[:default][:cat]).to eql("nice")
    end

  end

  context "while testing the configured platform data" do

    it "should use the solaris package provider on Solaris <11" do
      pmap = Chef::Platform.find("Solaris2", "5.9")
      expect(pmap[:package]).to eql(Chef::Provider::Package::Solaris)
    end

    it "should use the IPS package provider on Solaris 11" do
      pmap = Chef::Platform.find("Solaris2", "5.11")
      expect(pmap[:package]).to eql(Chef::Provider::Package::Ips)
    end

    it "should use the Redhat service provider on SLES11" do
      1.upto(3) do |sp|
        pmap = Chef::Platform.find("SUSE", "11.#{sp}")
        expect(pmap[:service]).to eql(Chef::Provider::Service::Redhat)
      end
    end

    it "should use the Systemd service provider on SLES12" do
      pmap = Chef::Platform.find("SUSE", "12.0")
      expect(pmap[:service]).to eql(Chef::Provider::Service::Systemd)
    end

    it "should use the SUSE group provider on SLES11" do
      1.upto(3) do |sp|
        pmap = Chef::Platform.find("SUSE", "11.#{sp}")
        expect(pmap[:group]).to eql(Chef::Provider::Group::Suse)
      end
    end

    it "should use the Gpasswd group provider on SLES12" do
      pmap = Chef::Platform.find("SUSE", "12.0")
      expect(pmap[:group]).to eql(Chef::Provider::Group::Gpasswd)
    end
  end

end
