require 'spec_helper'
require 'puppet/face'
require 'puppet_spec/puppetserver'

describe "puppet plugin", unless: Puppet::Util::Platform.jruby? do
  include_context "https client"

  let(:server) { PuppetSpec::Puppetserver.new }
  let(:plugin) { Puppet::Application[:plugin] }
  let(:response_body) { "[{\"path\":\"/etc/puppetlabs/code/environments/production/modules\",\"relative_path\":\".\",\"links\":\"follow\",\"owner\":0,\"group\":0,\"mode\":493,\"checksum\":{\"type\":\"ctime\",\"value\":\"{ctime}2020-03-06 20:14:25 UTC\"},\"type\":\"directory\",\"destination\":null}]" }

  it "downloads from plugins, pluginsfacts and locales mounts when i18n is enabled" do
    Puppet[:disable_i18n] = false
    current_version_handler = -> (req, res) {
      res['X-Puppet-Version'] = Puppet.version
      res['Content-Type'] = 'application/json'
      res.body = response_body
    }

    server.start_server(mounts: {file_metadatas: current_version_handler}) do |port|
      Puppet[:serverport] = port
      expect {
        plugin.command_line.args << 'download'
        plugin.run
      }.to exit_with(0)
       .and output(matching(
         "Downloaded these plugins: #{Regexp.escape(Puppet[:pluginfactdest])}, #{Regexp.escape(Puppet[:plugindest])}, #{Regexp.escape(Puppet[:localedest])}"
       )).to_stdout
    end
  end

  it "downloads from plugins, pluginsfacts but no locales mounts when i18n is disabled" do
    Puppet[:disable_i18n] = true

    current_version_handler = -> (req, res) {
      res['X-Puppet-Version'] = Puppet.version
      res['Content-Type'] = 'application/json'
      res.body = response_body
    }

    server.start_server(mounts: {file_metadatas: current_version_handler}) do |port|
      Puppet[:serverport] = port
      expect {
        plugin.command_line.args << 'download'
        plugin.run
      }.to exit_with(0)
       .and output(matching(
         "Downloaded these plugins: #{Regexp.escape(Puppet[:pluginfactdest])}, #{Regexp.escape(Puppet[:plugindest])}"
       )).to_stdout
    end
  end

  it "downloads from plugins and pluginsfacts from older puppetservers" do
    no_locales_handler = -> (req, res) {
      res['X-Puppet-Version'] = '5.3.3' # locales mount was added in 5.3.4
      res['Content-Type'] = 'application/json'
      res.body = response_body
    }

    server.start_server(mounts: {file_metadatas: no_locales_handler}) do |port|
      Puppet[:serverport] = port
      expect {
        plugin.command_line.args << 'download'
        plugin.run
      }.to exit_with(0)
       .and output(matching(
         "Downloaded these plugins: #{Regexp.escape(Puppet[:pluginfactdest])}, #{Regexp.escape(Puppet[:plugindest])}"
       )).to_stdout
    end
  end

  it "downloads from an environment that doesn't exist locally" do
    requested_environment = nil

    current_version_handler = -> (req, res) {
      res['X-Puppet-Version'] = Puppet.version
      res['Content-Type'] = 'application/json'
      res.body = response_body
      requested_environment = req.query['environment']
    }

    server.start_server(mounts: {file_metadatas: current_version_handler}) do |port|
      Puppet[:environment] = 'doesnotexistontheagent'
      Puppet[:serverport] = port
      expect {
        plugin.command_line.args << 'download'
        plugin.run
      }.to exit_with(0)
       .and output(matching("Downloaded these plugins")).to_stdout

      expect(requested_environment).to eq('doesnotexistontheagent')
    end
  end

  context "pluginsync for external facts uses source permissions to preserve fact executable-ness" do
    before :all do
      WebMock.enable!
    end

    after :all do
      WebMock.disable!
    end

    before :each do
      metadata = "[{\"path\":\"/etc/puppetlabs/code\",\"relative_path\":\".\",\"links\":\"follow\",\"owner\":0,\"group\":0,\"mode\":420,\"checksum\":{\"type\":\"ctime\",\"value\":\"{ctime}2020-07-10 14:00:00 -0700\"},\"type\":\"directory\",\"destination\":null}]"
      stub_request(:get, %r{/puppet/v3/file_metadatas/(plugins|locales)}).to_return(status: 200, body: metadata, headers: {'Content-Type' => 'application/json'})

      # response retains owner/group/mode due to source_permissions => use
      facts_metadata = "[{\"path\":\"/etc/puppetlabs/code\",\"relative_path\":\".\",\"links\":\"follow\",\"owner\":500,\"group\":500,\"mode\":493,\"checksum\":{\"type\":\"ctime\",\"value\":\"{ctime}2020-07-10 14:00:00 -0700\"},\"type\":\"directory\",\"destination\":null}]"
      stub_request(:get, %r{/puppet/v3/file_metadatas/pluginfacts}).to_return(status: 200, body: facts_metadata, headers: {'Content-Type' => 'application/json'})
    end

    it "processes a download request resulting in no changes" do
      # Create these so there are no changes
      Puppet::FileSystem.mkpath(Puppet[:plugindest])
      Puppet::FileSystem.mkpath(Puppet[:localedest])

      # /opt/puppetlabs/puppet/cache/facts.d will be created based on our umask.
      # If the mode on disk is not 0755, then the mode from the metadata response
      # (493 => 0755) will be applied, resulting in "plugins were downloaded"
      # message. Enforce a umask so the results are consistent.
      Puppet::FileSystem.mkpath(Puppet[:pluginfactdest])
      Puppet::FileSystem.chmod(0755, Puppet[:pluginfactdest])

      app = Puppet::Application[:plugin]
      app.command_line.args << 'download'
      expect {
        app.run
      }.to exit_with(0)
       .and output(/No plugins downloaded/).to_stdout
    end

    it "updates the facts.d mode", unless: Puppet::Util::Platform.windows? do
      Puppet::FileSystem.mkpath(Puppet[:pluginfactdest])
      Puppet::FileSystem.chmod(0775, Puppet[:pluginfactdest])

      app = Puppet::Application[:plugin]
      app.command_line.args << 'download'
      expect {
        app.run
      }.to exit_with(0)
       .and output(/Downloaded these plugins: .*facts\.d/).to_stdout
    end
  end
end
