Back to Repositories

Testing File Processing AbstractAdapter Implementation in Paperclip

This test suite examines the AbstractAdapter class in the Paperclip gem, which handles file processing and content type detection. The tests verify core adapter functionality including content type detection, file operations, and fingerprint generation for file uploads.

Test Coverage Overview

The test suite provides comprehensive coverage of the AbstractAdapter’s core functionality:

  • Content type detection and cleaning
  • File operation delegation to tempfile
  • Filename sanitization and processing
  • Fingerprint generation using different hash algorithms
  • Error handling for file operations

Implementation Analysis

The testing approach utilizes RSpec’s behavior-driven development patterns with extensive use of stubs and mocks. The tests implement context blocks for organized test grouping and leverage RSpec’s expect syntax for assertions.

Key patterns include isolation of file system operations, stubbing of external dependencies, and thorough edge case handling.

Technical Details

Testing tools and configuration:

  • RSpec as the testing framework
  • Mocha for mocking and stubbing
  • FileUtils for file operation testing
  • Custom TestAdapter class for implementation testing
  • Temporary file handling for file operation tests

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolation of external dependencies
  • Comprehensive edge case coverage
  • Clear test organization using contexts
  • Proper cleanup of temporary resources
  • Effective use of before/around blocks for test setup

thoughtbot/paperclip

spec/paperclip/io_adapters/abstract_adapter_spec.rb

            
require 'spec_helper'

describe Paperclip::AbstractAdapter do
  class TestAdapter < Paperclip::AbstractAdapter
    attr_accessor :tempfile

    def content_type
      Paperclip::ContentTypeDetector.new(path).detect
    end
  end

  subject { TestAdapter.new(nil) }

  context "content type from file contents" do
    before do
      subject.stubs(:path).returns("image.png")
      Paperclip.stubs(:run).returns("image/png
")
      Paperclip::ContentTypeDetector.any_instance.stubs(:type_from_mime_magic).returns("image/png")
    end

    it "returns the content type without newline" do
      assert_equal "image/png", subject.content_type
    end
  end

  context "nil?" do
    it "returns false" do
      assert !subject.nil?
    end
  end

  context "delegation" do
    before do
      subject.tempfile = stub("Tempfile")
    end

    [:binmode, :binmode?, :close, :close!, :closed?, :eof?, :path, :readbyte, :rewind, :unlink].each do |method|
      it "delegates #{method} to @tempfile" do
        subject.tempfile.stubs(method)
        subject.public_send(method)
        assert_received subject.tempfile, method
      end
    end
  end

  it 'gets rid of slashes and colons in filenames' do
    subject.original_filename = "awesome/file:name.png"

    assert_equal "awesome_file_name.png", subject.original_filename
  end

  it 'is an assignment' do
    assert subject.assignment?
  end

  it 'is not nil' do
    assert !subject.nil?
  end

  it "generates a destination filename with no original filename" do
    expect(subject.send(:destination).path).to_not be_nil
  end

  it 'uses the original filename to generate the tempfile' do
    subject.original_filename = "file.png"
    expect(subject.send(:destination).path).to end_with(".png")
  end

  context "generates a fingerprint" do
    subject { TestAdapter.new(nil, options) }

    before do
      subject.stubs(:path).returns(fixture_file("50x50.png"))
    end

    context "MD5" do
      let(:options) { { hash_digest: Digest::MD5 } }

      it "returns a fingerprint" do
        expect(subject.fingerprint).to be_a String
        expect(subject.fingerprint).to eq "a790b00c9b5d58a8fd17a1ec5a187129"
      end
    end

    context "SHA256" do
      let(:options) { { hash_digest: Digest::SHA256 } }

      it "returns a fingerprint" do
        expect(subject.fingerprint).to be_a String
        expect(subject.fingerprint).
          to eq "243d7ce1099719df25f600f1c369c629fb979f88d5a01dbe7d0d48c8e6715bb1"
      end
    end
  end

  context "#copy_to_tempfile" do
    around do |example|
      FileUtils.module_eval do
        class << self
          alias paperclip_ln ln

          def ln(*)
            raise Errno::EXDEV
          end
        end
      end

      example.run

      FileUtils.module_eval do
        class << self
          alias ln paperclip_ln
          undef paperclip_ln
        end
      end
    end

    it "should return a readable file even when linking fails" do
      src = open(fixture_file("5k.png"), "rb")
      expect(subject.send(:copy_to_tempfile, src).read).to eq src.read
    end
  end

  context "#original_filename=" do
    it "should not fail with a nil original filename" do
      expect { subject.original_filename = nil }.not_to raise_error
    end
  end

  context "#link_or_copy_file" do
    class TestLinkOrCopyAdapter < Paperclip::AbstractAdapter
      public :copy_to_tempfile, :destination
    end

    subject { TestLinkOrCopyAdapter.new(nil) }
    let(:body) { "body" }

    let(:file) do
      t = Tempfile.new("destination")
      t.print(body)
      t.rewind
      t
    end

    after do
      file.close
      file.unlink
    end

    it "should be able to read the file" do
      expect(subject.copy_to_tempfile(file).read).to eq(body)
    end

    it "should be able to reopen the file after symlink has failed" do
      FileUtils.expects(:ln).raises(Errno::EXDEV)

      expect(subject.copy_to_tempfile(file).read).to eq(body)
    end
  end
end