require 'spec_helper'
require 'puppet/file_bucket/dipper'
require 'puppet_spec/compiler'

describe Puppet::Type.type(:cron).provider(:crontab), '(integration)', :unless => Puppet.features.microsoft_windows? do
  include PuppetSpec::Files
  include PuppetSpec::Compiler

  before :each do
    allow(Puppet::Type.type(:cron)).to receive(:defaultprovider).and_return(described_class)
    allow(described_class).to receive(:suitable?).and_return(true)
    allow_any_instance_of(Puppet::FileBucket::Dipper).to receive(:backup) # Don't backup to filebucket

    # I don't want to execute anything
    allow(described_class).to receive(:filetype).and_return(Puppet::Util::FileType::FileTypeFlat)
    allow(described_class).to receive(:default_target).and_return(crontab_user1)

    # I don't want to stub Time.now to get a static header because I don't know
    # where Time.now is used elsewhere, so just go with a very simple header
    allow(described_class).to receive(:header).and_return("# HEADER: some simple\n# HEADER: header\n")
    FileUtils.cp(my_fixture('crontab_user1'), crontab_user1)
    FileUtils.cp(my_fixture('crontab_user2'), crontab_user2)
  end

  after :each do
    described_class.clear
  end

  let :crontab_user1 do
    tmpfile('cron_integration_specs')
  end

  let :crontab_user2 do
    tmpfile('cron_integration_specs')
  end

  def expect_output(fixture_name)
    expect(File.read(crontab_user1)).to eq(File.read(my_fixture(fixture_name)))
  end

  describe "when managing a cron entry" do

    it "should be able to purge unmanaged entries" do
      apply_with_error_check(<<-MANIFEST)
      cron {
        'only managed entry':
          ensure      => 'present',
          command     => '/bin/true',
          target      => '#{crontab_user1}',
      }
      resources { 'cron': purge => 'true' }
      MANIFEST
      expect_output('purged')
    end

    describe "with ensure absent" do
      it "should do nothing if entry already absent" do
        apply_with_error_check(<<-MANIFEST)
        cron {
          'no_such_entry':
            ensure => 'absent',
            target => '#{crontab_user1}',
        }
        MANIFEST
        expect_output('crontab_user1')
      end

      it "should remove the resource from crontab if present" do
        apply_with_error_check(<<-MANIFEST)
        cron {
          'My daily failure':
            ensure => 'absent',
            target => '#{crontab_user1}',
        }
        MANIFEST
        expect_output('remove_named_resource')
      end

      it "should remove a matching cronentry if present" do
        apply_with_error_check(<<-MANIFEST)
        cron {
          'no_such_named_resource_in_crontab':
            ensure   => absent,
            minute   => [ '17-19', '22' ],
            hour     => [ '0-23/2' ],
            weekday  => 'Tue',
            command  => '/bin/unnamed_regular_command',
            target   => '#{crontab_user1}',
        }
        MANIFEST
        expect_output('remove_unnamed_resource')
      end
    end

    describe "with ensure present" do

      context "and no command specified" do
        it "should work if the resource is already present" do
          apply_with_error_check(<<-MANIFEST)
          cron {
            'My daily failure':
              special => 'daily',
              target  => '#{crontab_user1}',
          }
          MANIFEST
          expect_output('crontab_user1')
        end
        it "should fail if the resource needs creating" do
          manifest = <<-MANIFEST
          cron {
            'Entirely new resource':
              special => 'daily',
              target  => '#{crontab_user1}',
          }
          MANIFEST
          apply_compiled_manifest(manifest) do |res|
            if res.ref == 'Cron[Entirely new resource]'
              expect(res).to receive(:err).with(/no command/)
            else
              expect(res).not_to receive(:err)
            end
          end
        end
      end

      it "should do nothing if entry already present" do
        apply_with_error_check(<<-MANIFEST)
        cron {
          'My daily failure':
            special => 'daily',
            command => '/bin/false',
            target  => '#{crontab_user1}',
        }
        MANIFEST
        expect_output('crontab_user1')
      end

      it "should work correctly when managing 'target' but not 'user'" do
        apply_with_error_check(<<-MANIFEST)
        cron {
          'My daily failure':
            special => 'daily',
            command => '/bin/false',
            target  => '#{crontab_user1}',
        }
        MANIFEST
        expect_output('crontab_user1')
      end

      it "should do nothing if a matching entry already present" do
        apply_with_error_check(<<-MANIFEST)
        cron {
          'no_such_named_resource_in_crontab':
            ensure   => present,
            minute   => [ '17-19', '22' ],
            hour     => [ '0-23/2' ],
            command  => '/bin/unnamed_regular_command',
            target   => '#{crontab_user1}',
        }
        MANIFEST
        expect_output('crontab_user1')
      end

      it "should add a new normal entry if currently absent" do
        apply_with_error_check(<<-MANIFEST)
        cron {
          'new entry':
            ensure      => present,
            minute      => '12',
            weekday     => 'Tue',
            command     => '/bin/new',
            environment => [
              'MAILTO=""',
              'SHELL=/bin/bash'
            ],
            target      => '#{crontab_user1}',
        }
        MANIFEST
        expect_output('create_normal_entry')
      end

      it "should add a new special entry if currently absent" do
        apply_with_error_check(<<-MANIFEST)
        cron {
          'new special entry':
            ensure      => present,
            special     => 'reboot',
            command     => 'echo "Booted" 1>&2',
            environment => 'MAILTO=bob@company.com',
            target      => '#{crontab_user1}',
        }
        MANIFEST
        expect_output('create_special_entry')
      end

      it "should change existing entry if out of sync" do
        apply_with_error_check(<<-MANIFEST)
        cron {
          'Monthly job':
            ensure      => present,
            special     => 'monthly',
            #minute => ['22'],
            command     => '/usr/bin/monthly',
            environment => [],
            target      => '#{crontab_user1}',
        }
        MANIFEST
        expect_output('modify_entry')
      end
      it "should change a special schedule to numeric if requested" do
        apply_with_error_check(<<-MANIFEST)
        cron {
          'My daily failure':
            special     => 'absent',
            command     => '/bin/false',
            target      => '#{crontab_user1}',
        }
        MANIFEST
        expect_output('unspecialized')
      end
      it "should not try to move an entry from one file to another" do
        # force the parsedfile provider to also parse user1's crontab
        apply_with_error_check(<<-MANIFEST)
        cron {
          'foo':
            ensure => absent,
            target => '#{crontab_user1}';
          'My daily failure':
            special      => 'daily',
            command      => "/bin/false",
            target       => '#{crontab_user2}',
        }
        MANIFEST
        expect(File.read(crontab_user1)).to eq(File.read(my_fixture('moved_cronjob_input1')))
        expect(File.read(crontab_user2)).to eq(File.read(my_fixture('moved_cronjob_input2')))
      end
    end
  end

end
