# frozen_string_literal: true

require 'spec_helper'
require 'image_optim/cache_path'
require 'tempfile'

describe ImageOptim::CachePath do
  before do
    stub_const('Path', ImageOptim::Path)
    stub_const('CachePath', ImageOptim::CachePath)
  end

  describe '#replace' do
    let(:src_dir){ Path.temp_dir }
    let(:src){ CachePath.temp_file_path(nil, src_dir) }
    let(:dst){ Path.temp_file_path }

    shared_examples 'replaces file' do
      it 'moves data to destination' do
        src.write('src')

        src.replace(dst)

        expect(dst.read).to eq('src')
      end

      it 'does not remove original file' do
        src.replace(dst)

        expect(src).to exist
      end

      it 'preserves attributes of destination file', skip: SkipConditions[:any_file_mode_allowed] do
        mode = 0o666

        dst.chmod(mode)

        src.replace(dst)

        got = dst.stat.mode & 0o777
        expect(got).to eq(mode), format('expected %04o, got %04o', mode, got)
      end

      it 'does not preserve mtime of destination file' do
        time = src.mtime - 1000
        dst.utime(time, time)

        time = dst.mtime

        src.replace(dst)

        expect(dst.mtime).to_not eq(time)
      end

      it 'changes inode of destination', skip: SkipConditions[:inodes_support] do
        expect{ src.replace(dst) }.to change{ dst.stat.ino }
      end

      it 'is using temporary file with .tmp extension' do
        expect(src).to receive(:copy).with(having_attributes(extname: '.tmp')).at_least(:once)

        src.replace(dst)
      end
    end

    context 'when src and dst are on same device' do
      before do
        allow_any_instance_of(File::Stat).to receive(:dev).and_return(0)
      end

      include_examples 'replaces file'
    end

    context 'when src and dst are on different devices' do
      before do
        allow_any_instance_of(File::Stat).to receive(:dev, &:__id__)
      end

      include_examples 'replaces file'
    end

    context 'when src and dst are on same device, but rename causes Errno::EXDEV' do
      before do
        allow_any_instance_of(File::Stat).to receive(:dev).and_return(0)
        allow(described_class).to receive(:temp_file_path).and_call_original
        expect(described_class).to receive(:temp_file_path).
          with([dst.basename.to_s, '.tmp'], src.dirname).
          and_wrap_original do |m, *args, &block|
            m.call(*args) do |tmp|
              expect(tmp).to receive(:rename).with(dst.to_s).and_raise(Errno::EXDEV)
              block.call(tmp)
            end
          end
      end

      include_examples 'replaces file'
    end
  end
end
