require 'spec_helper'

describe Puppet::Type.type(:file).attrclass(:mode) do
  include PuppetSpec::Files

  let(:path) { tmpfile('mode_spec') }
  let(:resource) { Puppet::Type.type(:file).new :path => path, :mode => '0644' }
  let(:mode) { resource.property(:mode) }

  describe "#validate" do
    it "should reject non-string values" do
      expect {
        mode.value = 0755
      }.to raise_error(Puppet::Error, /The file mode specification must be a string, not '(?:Fixnum|Integer)'/)
    end

    it "should accept values specified as octal numbers in strings" do
      expect { mode.value = '0755' }.not_to raise_error
    end

    it "should accept valid symbolic strings" do
      expect { mode.value = 'g+w,u-x' }.not_to raise_error
    end

    it "should not accept strings other than octal numbers" do
      expect do
        mode.value = 'readable please!'
      end.to raise_error(Puppet::Error, /The file mode specification is invalid/)
    end
  end

  describe "#munge" do
    # This is sort of a redundant test, but its spec is important.
    it "should return the value as a string" do
      expect(mode.munge('0644')).to be_a(String)
    end

    it "should accept strings as arguments" do
      expect(mode.munge('0644')).to eq('644')
    end

    it "should accept symbolic strings as arguments and return them intact" do
      expect(mode.munge('u=rw,go=r')).to eq('u=rw,go=r')
    end

    it "should accept integers are arguments" do
      expect(mode.munge(0644)).to eq('644')
    end
  end

  describe "#dirmask" do
    before :each do
      Dir.mkdir(path)
    end

    it "should add execute bits corresponding to read bits for directories" do
      expect(mode.dirmask('0644')).to eq('755')
    end

    it "should not add an execute bit when there is no read bit" do
      expect(mode.dirmask('0600')).to eq('700')
    end

    it "should not add execute bits for files that aren't directories" do
      resource[:path] = tmpfile('other_file')
      expect(mode.dirmask('0644')).to eq('0644')
    end
  end

  describe "#insync?" do
    it "should return true if the mode is correct" do
      FileUtils.touch(path)

      expect(mode).to be_insync('644')
    end

    it "should return false if the mode is incorrect" do
      FileUtils.touch(path)

      expect(mode).to_not be_insync('755')
    end

    it "should return true if the file is a link and we are managing links", :if => Puppet.features.manages_symlinks? do
      Puppet::FileSystem.symlink('anything', path)

      expect(mode).to be_insync('644')
    end

    describe "with a symbolic mode" do
      let(:resource_sym) { Puppet::Type.type(:file).new :path => path, :mode => 'u+w,g-w' }
      let(:mode_sym) { resource_sym.property(:mode) }

      it "should return true if the mode matches, regardless of other bits" do
        FileUtils.touch(path)

        expect(mode_sym).to be_insync('644')
      end

      it "should return false if the mode requires 0's where there are 1's" do
        FileUtils.touch(path)

        expect(mode_sym).to_not be_insync('624')
      end

      it "should return false if the mode requires 1's where there are 0's" do
        FileUtils.touch(path)

        expect(mode_sym).to_not be_insync('044')
      end
    end
  end

  describe "#retrieve" do
    it "should return absent if the resource doesn't exist" do
      resource[:path] = File.expand_path("/does/not/exist")
      expect(mode.retrieve).to eq(:absent)
    end

    it "should retrieve the directory mode from the provider" do
      Dir.mkdir(path)

      expect(mode).to receive(:dirmask).with('644').and_return('755')
      expect(resource.provider).to receive(:mode).and_return('755')

      expect(mode.retrieve).to eq('755')
    end

    it "should retrieve the file mode from the provider" do
      FileUtils.touch(path)

      expect(mode).to receive(:dirmask).with('644').and_return('644')
      expect(resource.provider).to receive(:mode).and_return('644')

      expect(mode.retrieve).to eq('644')
    end
  end

  describe '#should_to_s' do
    describe 'with a 3-digit mode' do
      it 'returns a 4-digit mode with a leading zero' do
        expect(mode.should_to_s('755')).to eq("'0755'")
      end
    end

    describe 'with a 4-digit mode' do
      it 'returns the 4-digit mode when the first digit is a zero' do
        expect(mode.should_to_s('0755')).to eq("'0755'")
      end

      it 'returns the 4-digit mode when the first digit is not a zero' do
        expect(mode.should_to_s('1755')).to eq("'1755'")
      end
    end
  end

  describe '#is_to_s' do
    describe 'with a 3-digit mode' do
      it 'returns a 4-digit mode with a leading zero' do
        expect(mode.is_to_s('755')).to eq("'0755'")
      end
    end

    describe 'with a 4-digit mode' do
      it 'returns the 4-digit mode when the first digit is a zero' do
        expect(mode.is_to_s('0755')).to eq("'0755'")
      end

      it 'returns the 4-digit mode when the first digit is not a zero' do
        expect(mode.is_to_s('1755')).to eq("'1755'")
      end
    end

    describe 'when passed :absent' do
      it "returns 'absent'" do
        expect(mode.is_to_s(:absent)).to eq("'absent'")
      end
    end
  end

  describe "#sync with a symbolic mode" do
    let(:resource_sym) { Puppet::Type.type(:file).new :path => path, :mode => 'u+w,g-w' }
    let(:mode_sym) { resource_sym.property(:mode) }

    before { FileUtils.touch(path) }

    it "changes only the requested bits" do
      # lower nibble must be set to 4 for the sake of passing on Windows
      Puppet::FileSystem.chmod(0464, path)

      mode_sym.sync
      stat = Puppet::FileSystem.stat(path)
      expect((stat.mode & 0777).to_s(8)).to eq("644")
    end
  end

  describe '#sync with a symbolic mode of +X for a file' do
    let(:resource_sym) { Puppet::Type.type(:file).new :path => path, :mode => 'g+wX' }
    let(:mode_sym) { resource_sym.property(:mode) }

    before { FileUtils.touch(path) }

    it 'does not change executable bit if no executable bit is set' do
      Puppet::FileSystem.chmod(0644, path)

      mode_sym.sync

      stat = Puppet::FileSystem.stat(path)
      expect((stat.mode & 0777).to_s(8)).to eq('664')
    end

    it 'does change executable bit if an executable bit is set' do
      Puppet::FileSystem.chmod(0744, path)

      mode_sym.sync

      stat = Puppet::FileSystem.stat(path)
      expect((stat.mode & 0777).to_s(8)).to eq('774')
    end
  end
end
