Back to Repositories

Testing File Sanitization Implementation in CarrierWave

This test suite comprehensively validates the SanitizedFile class in CarrierWave, which handles file sanitization and manipulation. It ensures proper handling of file names, content types, and file operations across different input types including Files, Tempfiles, StringIO, and Pathnames.

Test Coverage Overview

The test suite provides extensive coverage of file handling functionality including:

  • File attribute validation (name, extension, content type)
  • File operations (move, copy, delete)
  • Content type detection and verification
  • Edge cases with empty files and invalid inputs
  • Integration with different file input types (File, Tempfile, StringIO, Pathname)

Implementation Analysis

The testing approach uses RSpec shared examples to validate common functionality across different file types. It employs extensive stubbing and mocking to isolate file system operations and implements before/after hooks for proper test setup and cleanup. The tests verify both happy paths and error conditions for file operations.

Technical Details

Key technical components include:

  • RSpec for test framework
  • FileUtils for file system operations
  • StringIO and Tempfile for file handling
  • Mime type detection
  • File permission validation
  • Directory structure management

Best Practices Demonstrated

The test suite exemplifies testing best practices including:

  • Comprehensive shared examples for DRY test code
  • Proper test isolation and cleanup
  • Edge case coverage
  • Consistent validation patterns
  • Clear test organization and naming

carrierwaveuploader/carrierwave

spec/sanitized_file_spec.rb

            
require 'spec_helper'

describe CarrierWave::SanitizedFile do
  before do
    FileUtils.cp(file_path('test.jpg'), file_path('llama.jpg'))
  end

  after do
    FileUtils.rm_rf(file_path("new_dir"))
  end

  after(:all) do
    if File.exist?(file_path('llama.jpg'))
      FileUtils.rm(file_path('llama.jpg'))
    end
    FileUtils.rm_rf(public_path)
  end

  describe "#empty?" do
    it "should be empty for nil" do
      sanitized_file = CarrierWave::SanitizedFile.new(nil)

      expect(sanitized_file).to be_empty
    end

    it "should be empty for an empty string" do
      sanitized_file = CarrierWave::SanitizedFile.new("")

      expect(sanitized_file).to be_empty
    end

    it "should be empty for an empty StringIO" do
      sanitized_file = CarrierWave::SanitizedFile.new(StringIO.new(""))

      expect(sanitized_file).to be_empty
    end

  end

  describe '#original_filename' do
    it "should default to the original_filename" do
      file = double('file', :original_filename => 'llama.jpg')
      sanitized_file = CarrierWave::SanitizedFile.new(file)
      expect(sanitized_file.original_filename).to eq("llama.jpg")
    end

    it "should defer to the base name of the path if original_filename is unavailable" do
      file = double('file', :path => '/path/to/test.jpg')
      sanitized_file = CarrierWave::SanitizedFile.new(file)
      expect(sanitized_file.original_filename).to eq("test.jpg")
    end

    it "should be nil otherwise" do
      file = double('file')
      sanitized_file = CarrierWave::SanitizedFile.new(file)
      expect(sanitized_file.original_filename).to be_nil
    end
  end

  describe "#basename" do
    it "should return the basename for complicated extensions" do
      sanitized_file = CarrierWave::SanitizedFile.new(file_path("complex.filename.tar.gz"))

      expect(sanitized_file.basename).to eq("complex.filename")
    end

    it "should be the filename if the file has no extension" do
      sanitized_file = CarrierWave::SanitizedFile.new(file_path("complex"))

      expect(sanitized_file.basename).to eq("complex")
    end
  end

  describe "#extension" do
    %w[gz bz2 z lz xz].each do |ext|
      it "should return the extension for complicated extensions (tar.#{ext})" do
        sanitized_file = CarrierWave::SanitizedFile.new(file_path("complex.filename.tar.#{ext}"))

        expect(sanitized_file.extension).to eq("tar.#{ext}")
      end
    end

    it "should return the extension for real-world user file names" do
      sanitized_file = CarrierWave::SanitizedFile.new(file_path("Photo on 2009-12-01 at 11.12.jpg"))

      expect(sanitized_file.extension).to eq("jpg")
    end

    it "should return the extension for basic filenames" do
      sanitized_file = CarrierWave::SanitizedFile.new(file_path("something.png"))

      expect(sanitized_file.extension).to eq("png")
    end

    it "should be an empty string if the file has no extension" do
      sanitized_file = CarrierWave::SanitizedFile.new(file_path("complex"))

      expect(sanitized_file.extension).to eq("")
    end
  end

  describe "#filename" do
    let(:sanitized_file) { CarrierWave::SanitizedFile.new(nil) }

    it "should default to the original filename if it is valid" do
      expect(sanitized_file).to receive(:original_filename).at_least(:once).and_return("llama.jpg")
      expect(sanitized_file.filename).to eq("llama.jpg")
    end

    it "should remove illegal characters from a filename" do
      expect(sanitized_file).to receive(:original_filename).at_least(:once).and_return("test-s,%&m#st?.jpg")
      expect(sanitized_file.filename).to eq("test-s___m_st_.jpg")
    end

    it "should remove slashes from the filename" do
      expect(sanitized_file).to receive(:original_filename).at_least(:once).and_return("../../very_tricky/foo.bar")
      expect(sanitized_file.filename).not_to match(/[\\\/]/)
    end

    it "should remove illegal characters if there is no extension" do
      expect(sanitized_file).to receive(:original_filename).at_least(:once).and_return("`*foo")
      expect(sanitized_file.filename).to eq("__foo")
    end

    it "should remove the path prefix on Windows" do
      expect(sanitized_file).to receive(:original_filename).at_least(:once).and_return('c:\temp\foo.txt')
      expect(sanitized_file.filename).to eq("foo.txt")
    end

    it "should make sure the *nix directory thingies can't be used as filenames" do
      expect(sanitized_file).to receive(:original_filename).at_least(:once).and_return(".")
      expect(sanitized_file.filename).to eq("_.")
    end

    it "should maintain uppercase filenames" do
      expect(sanitized_file).to receive(:original_filename).at_least(:once).and_return("DSC4056.JPG")
      expect(sanitized_file.filename).to eq("DSC4056.JPG")
    end

    it "should remove illegal characters from a non-ASCII filename" do
      expect(sanitized_file).to receive(:original_filename).at_least(:once).and_return("⟲«Du côté des chars lourds»_123.doc")
      expect(sanitized_file.filename).to eq("__Du_côté_des_chars_lourds__123.doc")
    end

    it "should default to the original non-ASCII filename if it is valid" do
      expect(sanitized_file).to receive(:original_filename).at_least(:once).and_return("тестовый.jpg")
      expect(sanitized_file.filename).to eq("тестовый.jpg")
    end

    it "should downcase non-ASCII characters properly" do
      expect(sanitized_file).to receive(:original_filename).at_least(:once).and_return("ТестоВый Ёжик.jpg")
      expect(sanitized_file.filename).to eq("ТестоВый_Ёжик.jpg")
    end

    it "should remove invalid byte sequences from the filename" do
      expect(sanitized_file).to receive(:original_filename).at_least(:once).and_return("test\xDD.jpg")
      expect(sanitized_file.filename).to eq("test_.jpg")
    end
  end

  describe "#filename with an overridden sanitize_regexp" do
    let(:sanitized_file) { CarrierWave::SanitizedFile.new(nil) }

    before do
      allow(sanitized_file).to receive(:sanitize_regexp).and_return(/[^a-zA-Z\.\-\+_]/)
    end

    it "should default to the original filename if it is valid" do
      expect(sanitized_file).to receive(:original_filename).at_least(:once).and_return("llama.jpg")
      expect(sanitized_file.filename).to eq("llama.jpg")
    end

    it "should remove illegal characters from a filename" do
      expect(sanitized_file).to receive(:original_filename).at_least(:once).and_return("123.jpg")
      expect(sanitized_file.filename).to eq("___.jpg")
    end
  end

  describe "#content_type" do
    it "preserves file's content_type" do
      sanitized_file = CarrierWave::SanitizedFile.new(content_type: "image/png")

      expect(sanitized_file.content_type).to eq("image/png")
    end

    it "preserves file's content_type when passed as type (Rack)" do
      sanitized_file = CarrierWave::SanitizedFile.new(type: "image/png")

      expect(sanitized_file.content_type).to eq("image/png")
    end

    it "handles Mime::Type object" do
      file = File.open(file_path('sponsored.doc'))
      allow(file).to receive(:content_type) { 'application/msword' }

      sanitized_file = CarrierWave::SanitizedFile.new(file)
      allow(sanitized_file).to receive(:file).and_return(file)

      expect { sanitized_file.content_type }.not_to raise_error
      expect(sanitized_file.content_type).to eq("application/msword")
    end

    it "handles Mime::Type of docx" do
      file = File.open(file_path('resume.docx'))

      sanitized_file = CarrierWave::SanitizedFile.new(file)
      allow(sanitized_file).to receive(:file).and_return(file)

      expect { sanitized_file.content_type }.not_to raise_error
      expect(sanitized_file.content_type).to eq("application/vnd.openxmlformats-officedocument.wordprocessingml.document")
    end

    it "handles Mime::Type of pptx" do
      file = File.open(file_path('slidedeck.pptx'))

      sanitized_file = CarrierWave::SanitizedFile.new(file)
      allow(sanitized_file).to receive(:file).and_return(file)

      expect { sanitized_file.content_type }.not_to raise_error
      expect(sanitized_file.content_type).to eq("application/vnd.openxmlformats-officedocument.presentationml.presentation")
    end

    it "does not raise an error if missing" do
      sanitized_file = CarrierWave::SanitizedFile.new("llama.jpg")

      expect(sanitized_file.content_type).to eq("application/octet-stream")
    end

    it "reads content type of a CSV linked to a file" do
      file = File.open(file_path('addresses.csv'))
      csv_file = CSV.new(file)
      sanitized_file = CarrierWave::SanitizedFile.new(csv_file)

      expect(sanitized_file.content_type).to eq("text/csv")
    end

    it "does not allow spoofing of the mime type passed by Hash" do
      file = File.open(file_path("landscape.jpg"))

      sanitized_file = CarrierWave::SanitizedFile.new(tempfile: file, content_type: 'image/png')

      expect(sanitized_file.content_type).to eq("image/jpeg")
    end

    it "does not allow spoofing of the mime type by setting file's content type" do
      file = File.open(file_path("landscape.jpg"))
      allow(file).to receive(:content_type) { 'image/png' }

      sanitized_file = CarrierWave::SanitizedFile.new(file)

      expect(sanitized_file.content_type).to eq("image/jpeg")
    end

    it "does not allow spoofing of the mime type by file extension" do
      file = File.open(file_path("zip.png"))

      sanitized_file = CarrierWave::SanitizedFile.new(file)
      expect { sanitized_file.content_type }.not_to raise_error

      expect(sanitized_file.content_type).to eq("application/zip")
    end

    it "does not allow spoofing of the mime type if the mime type is not detectable" do
      file = File.open(file_path('spoof.png'))

      sanitized_file = CarrierWave::SanitizedFile.new(file)

      expect { sanitized_file.content_type }.not_to raise_error

      expect(sanitized_file.content_type).to_not eq 'image/png'
      expect(sanitized_file.content_type).to eq 'application/octet-stream'
    end

    it "returns valid content type on text file" do
      file = File.open(file_path('bork.txt'))

      sanitized_file = CarrierWave::SanitizedFile.new(file)

      expect { sanitized_file.content_type }.not_to raise_error

      expect(sanitized_file.content_type).to eq 'text/plain'
    end

    it "returns valid content type on JSON file (which is a text file in disguise)" do
      file = File.open(file_path('bork.json'))

      sanitized_file = CarrierWave::SanitizedFile.new(file)

      expect { sanitized_file.content_type }.not_to raise_error

      expect(sanitized_file.content_type).to eq 'application/json'
    end

    it "returns valid content type on DOTX file" do
      file = File.open(file_path('sample.dotx'))

      sanitized_file = CarrierWave::SanitizedFile.new(file)

      expect { sanitized_file.content_type }.not_to raise_error

      expect(sanitized_file.content_type).to eq 'application/vnd.openxmlformats-officedocument.wordprocessingml.template'
    end

    it "returns valid content type on AI file" do
      file = File.open(file_path('sample.ai'))

      sanitized_file = CarrierWave::SanitizedFile.new(file)

      expect { sanitized_file.content_type }.not_to raise_error

      expect(sanitized_file.content_type).to eq 'application/illustrator'
    end

    it "returns binary content type with unknown extension" do
      file = File.open(file_path('bork.ABCDE'))

      sanitized_file = CarrierWave::SanitizedFile.new(file)

      expect { sanitized_file.content_type }.not_to raise_error

      expect(sanitized_file.content_type).to eq "application/octet-stream"
    end

    it "does not raise an error if the path is not present" do
      sanitized_file = CarrierWave::SanitizedFile.new(nil)

      expect { sanitized_file.content_type }.not_to raise_error
    end

    it "uses the first one when multiple mime types are given using a semicolon" do
      file = File.open(file_path("bork.txt"))
      allow(file).to receive(:content_type) { 'image/png; text/html' }

      sanitized_file = CarrierWave::SanitizedFile.new(file)

      expect(sanitized_file.content_type).to eq("image/png")
    end

    it "uses the first one when multiple mime types are given using a comma" do
      file = File.open(file_path("bork.txt"))
      allow(file).to receive(:content_type) { 'image/png, text/html' }

      sanitized_file = CarrierWave::SanitizedFile.new(file)

      expect(sanitized_file.content_type).to eq("image/png")
    end

    it "drops content type parameters" do
      file = File.open(file_path("bork.txt"))
      allow(file).to receive(:content_type) { 'text/html; charset=utf-8' }

      sanitized_file = CarrierWave::SanitizedFile.new(file)

      expect(sanitized_file.content_type).to eq("text/html")
    end
  end

  describe "#content_type=" do
    it "sets content_type" do
      sanitized_file = CarrierWave::SanitizedFile.new(content_type: "image/png")
      sanitized_file.content_type = "text/html"

      expect(sanitized_file.content_type).to eq("text/html")
    end
  end

  shared_examples_for "all valid sanitized files" do
    describe '#empty?' do
      it "should not be empty" do
        expect(sanitized_file).not_to be_empty
      end
    end

    describe '#original_filename' do
      it "should return the original filename" do
        expect(sanitized_file.original_filename).to eq("llama.jpg")
      end
    end

    describe "#filename" do
      it "should return the filename" do
        expect(sanitized_file.filename).to eq("llama.jpg")
      end
    end

    describe "#basename" do
      it "should return the basename" do
        expect(sanitized_file.basename).to eq("llama")
      end
    end

    describe "#extension" do
      it "should return the extension" do
        expect(sanitized_file.extension).to eq("jpg")
      end
    end

    describe "#read" do
      it "should return the contents of the file" do
        expect(sanitized_file.read).to eq("this is stuff")
      end

      it "accepts the optional arguments length and outbuf" do
        buf = ""
        sanitized_file.read(4, buf)
        expect(buf).to eq("this")
      end

      it "always reads from the file if arguments are given" do
        sanitized_file.read(0)
        expect(sanitized_file.file).to receive(:read).with(4).and_call_original
        expect(sanitized_file.read(4)).to eq("this")
      end

      it "properly emulates the behavior of IO#read" do
        expect(sanitized_file.read(4)).to eq("this")
        expect(sanitized_file.read(10)).to eq(" is stuff")
        expect(sanitized_file.read(1)).to be_nil
      end
    end

    describe "#rewind" do
      it "rewinds the underlying file" do
        sanitized_file.read(5)
        sanitized_file.rewind
        expect(sanitized_file.read(4)).to eq("this")
      end
    end

    describe "#size" do
      it "should return the size of the file" do
        expect(sanitized_file.size).to eq(13)
      end
    end

    describe "#move_to" do
      after do
        FileUtils.rm_f(file_path("gurr.png"))
      end

      it "should be moved to the correct location" do
        sanitized_file.move_to(file_path("gurr.png"))

        expect(File.exist?( file_path("gurr.png") )).to be_truthy
      end

      it "should have changed its path when moved" do
        sanitized_file.move_to(file_path("gurr.png"))

        expect(sanitized_file.path).to eq(file_path("gurr.png"))
      end

      it "should have changed its filename when moved" do
        sanitized_file.move_to(file_path("gurr.png"))

        expect(sanitized_file.filename).to eq("gurr.png")
      end

      it "should have changed its basename when moved" do
        sanitized_file.move_to(file_path("gurr.png"))

        expect(sanitized_file.basename).to eq("gurr")
      end

      it "should have changed its extension when moved" do
        sanitized_file.move_to(file_path("gurr.png"))

        expect(sanitized_file.extension).to eq("png")
      end

      it "should set the right permissions" do
        sanitized_file.move_to(file_path("gurr.png"), 0o755)

        expect(sanitized_file).to have_permissions(0o755)
      end

      it "should set the right directory permissions" do
        sanitized_file.move_to(file_path("new_dir", "gurr.png"), nil, 0o775)

        expect(sanitized_file).to have_directory_permissions(0o775)
      end

      it "should return itself" do
        expect(sanitized_file.move_to(file_path("gurr.png"))).to eq(sanitized_file)
      end

      it "should preserve the file's content type" do
        sanitized_file.content_type = 'application/x-something'
        sanitized_file.move_to(file_path("new_dir", "gurr.png"))

        expect(sanitized_file.content_type).to eq("application/x-something")
      end

      it "should detect content type using Magic when content_type is reset" do
        sanitized_file.content_type = nil
        sanitized_file.move_to(file_path("new_dir", "gurr.png"))

        expect(sanitized_file).to receive(:identified_content_type)
        sanitized_file.content_type
      end

      it "should not try to identify content type if unnecessary" do
        expect(sanitized_file).not_to receive(:identified_content_type)
        sanitized_file.move_to(file_path("new_dir", "gurr.png"))

        expect(sanitized_file.instance_variable_get(:@content_type)).to be nil
      end

      context 'target path only differs by case' do
        let(:upcased_sanitized_file) { CarrierWave::SanitizedFile.new(stub_file("upcase.JPG", "image/jpeg")) }

        before do
          FileUtils.cp(file_path("test.jpg"), file_path("upcase.JPG"))

          expect(upcased_sanitized_file).not_to be_empty
        end

        after do
          FileUtils.rm_f(file_path("upcase.JPG"))
          FileUtils.rm_f(file_path("upcase.jpg"))
        end

        it "should not raise an error when moved" do
          expect { upcased_sanitized_file.move_to(upcased_sanitized_file.path.downcase) }.not_to raise_error
        end
      end
    end

    describe "#copy_to" do
      after do
        FileUtils.rm_f(file_path("gurr.png"))
      end

      it "should be copied to the correct location" do
        sanitized_file.copy_to(file_path("gurr.png"))

        expect(File.exist?( file_path("gurr.png") )).to be_truthy

        expect(file_path("gurr.png")).to be_identical_to(file_path("llama.jpg"))
      end

      it "should not have changed its path when copied" do
        expect { sanitized_file.copy_to(file_path("gurr.png")) }.not_to change(sanitized_file, :path)
      end

      it "should not have changed its filename when copied" do
        expect { sanitized_file.copy_to(file_path("gurr.png")) }.not_to change(sanitized_file, :filename)
      end

      it "should return an object of the same class when copied" do
        new_file = sanitized_file.copy_to(file_path("gurr.png"))

        expect(new_file).to be_an_instance_of(sanitized_file.class)
      end

      it "should adjust the path of the object that is returned when copied" do
        new_file = sanitized_file.copy_to(file_path("gurr.png"))

        expect(new_file.path).to eq(file_path("gurr.png"))
      end

      it "should adjust the filename of the object that is returned when copied" do
        new_file = sanitized_file.copy_to(file_path("gurr.png"))

        expect(new_file.filename).to eq("gurr.png")
      end

      it "should adjust the basename of the object that is returned when copied" do
        new_file = sanitized_file.copy_to(file_path("gurr.png"))

        expect(new_file.basename).to eq("gurr")
      end

      it "should adjust the extension of the object that is returned when copied" do
        new_file = sanitized_file.copy_to(file_path("gurr.png"))

        expect(new_file.extension).to eq("png")
      end

      it "should set the right permissions" do
        new_file = sanitized_file.copy_to(file_path("gurr.png"), 0o755)

        expect(new_file).to have_permissions(0o755)
      end

      it "should set the right directory permissions" do
        new_file = sanitized_file.copy_to(file_path("new_dir", "gurr.png"), nil, 0o755)

        expect(new_file).to have_directory_permissions(0o755)
      end

      it "should preserve the file's content type" do
        new_file = sanitized_file.copy_to(file_path("gurr.png"))

        expect(new_file.content_type).to eq(sanitized_file.content_type)
      end

      it "should not try to identify content type if unnecessary" do
        expect(sanitized_file).not_to receive(:identified_content_type)
        new_file = sanitized_file.copy_to(file_path("new_dir", "gurr.png"))

        expect(new_file.instance_variable_get(:@content_type)).to be nil
      end
    end

    describe "#with_io" do
      it "should yield an IO object" do
        sanitized_file.send(:with_io) do |io|
          expect(io).to respond_to(:read)
        end
      end
    end
  end

  shared_examples_for "all valid sanitized files that are stored on disk" do
    describe "#move_to" do
      it "should not raise an error when moved to its own location" do
        expect { sanitized_file.move_to(sanitized_file.path) }.not_to raise_error
      end

      it "should remove the original file" do
        original_path = sanitized_file.path
        sanitized_file.move_to(public_path("blah.txt"))

        expect(File.exist?(original_path)).to be_falsey
      end
    end

    describe '#copy_to' do
      it "should return a new instance when copied to its own location" do
        expect {
          new_file = sanitized_file.copy_to(sanitized_file.path)
          expect(new_file).to be_an_instance_of(sanitized_file.class)
        }.not_to raise_error
      end

      it "should not remove the original file" do
        new_file = sanitized_file.copy_to(public_path("blah.txt"))

        expect(File.exist?(sanitized_file.path)).to be_truthy
        expect(File.exist?(new_file.path)).to be_truthy
      end
    end

    describe "#exists?" do
      it "should be true" do
        expect(sanitized_file.exists?).to be_truthy
      end
    end

    describe "#delete" do
      it "should remove it from the filesystem" do
        expect(File.exist?(sanitized_file.path)).to be_truthy

        sanitized_file.delete

        expect(File.exist?(sanitized_file.path)).to be_falsey
      end
    end

    describe "#to_file" do
      it "should return a File object" do
        expect(sanitized_file.to_file).to be_a(File)
      end

      it "should have the same path as the SanitizedFile" do
        expect(sanitized_file.to_file.path).to eq(sanitized_file.path)
      end

      it "should have the same contents as the SanitizedFile" do
        expect(sanitized_file.to_file.read).to eq(sanitized_file.read)
      end
    end
  end

  shared_examples_for "all valid sanitized files that are read from an IO object" do
    describe "#read" do
      it "should have an open IO object" do
        expect(sanitized_file.instance_variable_get(:@file).closed?).to be_falsey
      end
    end

    describe "#with_io" do
      it "should try to rewind the IO object on finish" do
        sanitized_file.send(:with_io) do |io|
          io.read
        end
        expect(sanitized_file.file.pos).to eq 0
      end
    end
  end

  shared_examples_for "all valid sanitized files that are read from a non-StringIO object" do
    it_should_behave_like "all valid sanitized files that are read from an IO object"

    describe "#read" do
      it "should close the IO object after reading" do
        sanitized_file.read

        expect(sanitized_file.instance_variable_get(:@file).closed?).to be_truthy
      end
    end
  end

  shared_examples_for "all valid sanitized files that are read from a StringIO object" do
    it_should_behave_like "all valid sanitized files that are read from an IO object"

    describe "#read" do
      it "should not close StringIO inherited object after reading" do
        sanitized_file.read

        expect(sanitized_file.instance_variable_get(:@file).closed?).to be_falsey
      end
    end
  end

  describe "with a valid Hash" do
    let(:hash) {
      {
        "tempfile" => stub_merb_tempfile("llama.jpg"),
        "filename" => "llama.jpg",
        "content_type" => "image/jpeg"
      }
    }
    let(:sanitized_file) { CarrierWave::SanitizedFile.new(hash) }

    it_should_behave_like "all valid sanitized files"

    it_should_behave_like "all valid sanitized files that are stored on disk"

    it_should_behave_like "all valid sanitized files that are read from a non-StringIO object"

    describe "#path" do
      it "should return the path of the tempfile" do
        expect(sanitized_file.path).not_to be_nil
        expect(sanitized_file.path).to eq(hash["tempfile"].path)
      end
    end

    describe "#is_path?" do
      it "should be false" do
        expect(sanitized_file.is_path?).to be_falsey
      end
    end
  end

  describe "with a valid Tempfile" do
    let(:tempfile) { stub_tempfile("llama.jpg", "image/jpeg") }
    let(:sanitized_file) { CarrierWave::SanitizedFile.new(tempfile) }

    it_should_behave_like "all valid sanitized files"

    it_should_behave_like "all valid sanitized files that are stored on disk"

    it_should_behave_like "all valid sanitized files that are read from a non-StringIO object"

    describe "#is_path?" do
      it "should be false" do
        expect(sanitized_file.is_path?).to be_falsey
      end
    end

    describe "#path" do
      it "should return the path of the tempfile" do
        expect(sanitized_file.path).not_to be_nil
        expect(sanitized_file.path).to eq(tempfile.path)
      end
    end
  end

  describe "with a valid StringIO" do
    let(:sanitized_file) { CarrierWave::SanitizedFile.new(stub_stringio("llama.jpg", "image/jpeg")) }

    it_should_behave_like "all valid sanitized files"

    it_should_behave_like "all valid sanitized files that are read from a StringIO object"

    describe "#exists?" do
      it "should be false" do
        expect(sanitized_file.exists?).to be_falsey
      end
    end

    describe "#is_path?" do
      it "should be false" do
        expect(sanitized_file.is_path?).to be_falsey
      end
    end

    describe "#path" do
      it "should be nil" do
        expect(sanitized_file.path).to be_nil
      end
    end

    describe "#delete" do
      it "should not raise an error" do
        expect { sanitized_file.delete }.not_to raise_error
      end
    end

    describe "#to_file" do
      it "should be nil" do
        expect(sanitized_file.to_file).to be_nil
      end
    end
  end

  describe "with a valid File object" do
    let(:sanitized_file) { CarrierWave::SanitizedFile.new(stub_file("llama.jpg", "image/jpeg")) }

    before do
      FileUtils.cp(file_path("test.jpg"), file_path("llama.jpg"))

      expect(sanitized_file).not_to be_empty
    end

    it_should_behave_like "all valid sanitized files"

    it_should_behave_like "all valid sanitized files that are stored on disk"

    it_should_behave_like "all valid sanitized files that are read from a non-StringIO object"

    describe "#is_path?" do
      it "should be false" do
        expect(sanitized_file.is_path?).to be_falsey
      end
    end

    describe "#path" do
      it "should return the path of the file" do
        expect(sanitized_file.path).not_to be_nil
        expect(sanitized_file.path).to eq(file_path("llama.jpg"))
      end
    end
  end

  describe "with a valid File object and an empty file" do
    let(:sanitized_file) { CarrierWave::SanitizedFile.new(stub_file("llama.jpg", "image/jpeg")) }

    before do
      FileUtils.cp(file_path("test.jpg"), file_path("llama.jpg"))
      FileUtils.rm file_path("llama.jpg")
      FileUtils.touch file_path("llama.jpg")

      expect(sanitized_file).not_to be_empty
    end

    it_should_behave_like "all valid sanitized files that are stored on disk"

    it_should_behave_like "all valid sanitized files that are read from a non-StringIO object"

    describe "#is_path?" do
      it "should be false" do
        expect(sanitized_file.is_path?).to be_falsey
      end
    end

    describe "#path" do
      it "should return the path of the file" do
        expect(sanitized_file.path).not_to be_nil
        expect(sanitized_file.path).to eq(file_path("llama.jpg"))
      end
    end
  end

  describe "with a valid path" do
    let(:sanitized_file) { CarrierWave::SanitizedFile.new(file_path("llama.jpg")) }

    before do
      FileUtils.cp(file_path("test.jpg"), file_path("llama.jpg"))

      expect(sanitized_file).not_to be_empty
    end

    it_should_behave_like "all valid sanitized files"

    it_should_behave_like "all valid sanitized files that are stored on disk"

    describe "#is_path?" do
      it "should be true" do
        expect(sanitized_file.is_path?).to be_truthy
      end
    end

    describe "#path" do
      it "should return the path of the file" do
        expect(sanitized_file.path).not_to be_nil
        expect(sanitized_file.path).to eq(file_path("llama.jpg"))
      end
    end

  end

  describe "with a valid Pathname" do
    let(:sanitized_file) { CarrierWave::SanitizedFile.new(Pathname.new(file_path("llama.jpg"))) }

    before do
      FileUtils.copy_file(file_path("test.jpg"), file_path("llama.jpg"))

      expect(sanitized_file).not_to be_empty
    end

    it_should_behave_like "all valid sanitized files"

    it_should_behave_like "all valid sanitized files that are stored on disk"

    describe "#is_path?" do
      it "should be true" do
        expect(sanitized_file.is_path?).to be_truthy
      end
    end

    describe "#path" do
      it "should return the path of the file" do
        expect(sanitized_file.path).not_to be_nil
        expect(sanitized_file.path).to eq(file_path("llama.jpg"))
      end
    end

  end

  describe "with an uploader instance" do
    let(:uploader_class) { Class.new(CarrierWave::Uploader::Base) }
    let(:uploader) do
      uploader_class.new.tap do |instance|
        instance.store!(File.open(file_path('llama.jpg')))
      end
    end

    let(:sanitized_file) { CarrierWave::SanitizedFile.new(uploader) }

    it_should_behave_like "all valid sanitized files"

    describe "#is_path?" do
      it "should be false" do
        expect(sanitized_file.is_path?).to be_falsey
      end
    end

    describe "#path" do
      it "should return the path of the stored file" do
        expect(sanitized_file.path).not_to be_nil
        expect(sanitized_file.path).to eq(public_path("uploads/llama.jpg"))
      end
    end
  end

  describe "that is empty" do
    let(:empty) { CarrierWave::SanitizedFile.new(nil) }

    describe "#empty?" do
      it "should be true" do
        expect(empty).to be_empty
      end
    end

    describe "#exists?" do
      it "should be false" do
        expect(empty.exists?).to be_falsey
      end
    end

    describe "#is_path?" do
      it "should be false" do
        expect(empty.is_path?).to be_falsey
      end
    end

    describe "#size" do
      it "should be zero" do
        expect(empty.size).to be_zero
      end
    end

    describe "#path" do
      it "should be nil" do
        expect(empty.path).to be_nil
      end
    end

    describe "#original_filename" do
      it "should be nil" do
        expect(empty.original_filename).to be_nil
      end
    end

    describe "#filename" do
      it "should be nil" do
        expect(empty.filename).to be_nil
      end
    end

    describe "#basename" do
      it "should be nil" do
        expect(empty.basename).to be_nil
      end
    end

    describe "#extension" do
      it "should be nil" do
        expect(empty.extension).to be_nil
      end
    end

    describe "#delete" do
      it "should not raise an error" do
        expect { empty.delete }.not_to raise_error
      end
    end

    describe "#to_file" do
      it "should be nil" do
        expect(empty.to_file).to be_nil
      end
    end
  end

  describe "that is an empty string" do
    let(:empty) { CarrierWave::SanitizedFile.new("") }

    describe "#empty?" do
      it "should be true" do
        expect(empty).to be_empty
      end
    end

    describe "#exists?" do
      it "should be false" do
        expect(empty.exists?).to be_falsey
      end
    end

    describe "#is_path?" do
      it "should be false" do
        expect(empty.is_path?).to be_falsey
      end
    end

    describe "#size" do
      it "should be zero" do
        expect(empty.size).to be_zero
      end
    end

    describe "#path" do
      it "should be nil" do
        expect(empty.path).to be_nil
      end
    end

    describe "#original_filename" do
      it "should be nil" do
        expect(empty.original_filename).to be_nil
      end
    end

    describe "#filename" do
      it "should be nil" do
        expect(empty.filename).to be_nil
      end
    end

    describe "#basename" do
      it "should be nil" do
        expect(empty.basename).to be_nil
      end
    end

    describe "#extension" do
      it "should be nil" do
        expect(empty.extension).to be_nil
      end
    end

    describe "#delete" do
      it "should not raise an error" do
        expect { empty.delete }.not_to raise_error
      end
    end

    describe "#to_file" do
      it "should be nil" do
        expect(empty.to_file).to be_nil
      end
    end
  end
end