Back to Repositories

Testing File Upload Adapter Implementation in Paperclip

This test suite validates the UploadedFileAdapter functionality in Paperclip, focusing on file handling, content type detection, and metadata processing. The tests ensure proper handling of uploaded files with various characteristics and configurations.

Test Coverage Overview

The test suite provides comprehensive coverage of the UploadedFileAdapter class, examining file processing and metadata extraction.

Key areas tested include:
  • Filename handling and sanitization
  • Content type detection and validation
  • File size calculation
  • Binary mode operations
  • MD5 hash generation
  • File content reading
  • Handling of restricted characters in filenames

Implementation Analysis

The testing approach utilizes RSpec’s context-based structure to organize different scenarios for uploaded file handling.

Key implementation patterns include:
  • Mock UploadedFile class using OpenStruct
  • Multiple context blocks for different file configurations
  • Before blocks for test setup and configuration
  • Explicit content type detector configuration
  • Binary file handling verification

Technical Details

Testing tools and configuration:
  • RSpec as the testing framework
  • Fixture files for test data (5k.png)
  • Custom UploadedFile class implementation
  • Paperclip::FileCommandContentTypeDetector integration
  • MD5 digest configuration for fingerprinting
  • Tempfile and File I/O operations

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolation of different test scenarios using contexts
  • Comprehensive edge case coverage
  • Proper test setup and teardown
  • Clear test descriptions using RSpec’s descriptive syntax
  • Verification of both happy path and error conditions
  • Security considerations in filename handling

thoughtbot/paperclip

spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb

            
require 'spec_helper'

describe Paperclip::UploadedFileAdapter do
  context "a new instance" do
    context "with UploadedFile responding to #tempfile" do
      before do
        Paperclip::UploadedFileAdapter.content_type_detector = nil

        class UploadedFile < OpenStruct; end
        tempfile = File.new(fixture_file("5k.png"))
        tempfile.binmode

        @file = UploadedFile.new(
          original_filename: "5k.png",
          content_type: "image/x-png-by-browser\r",
          head: "",
          tempfile: tempfile,
          path: tempfile.path
        )
        @subject = Paperclip.io_adapters.for(@file, hash_digest: Digest::MD5)
      end

      it "gets the right filename" do
        assert_equal "5k.png", @subject.original_filename
      end

      it "forces binmode on tempfile" do
        assert @subject.instance_variable_get("@tempfile").binmode?
      end

      it "gets the content type" do
        assert_equal "image/png", @subject.content_type
      end

      it "gets the file's size" do
        assert_equal 4456, @subject.size
      end

      it "returns false for a call to nil?" do
        assert ! @subject.nil?
      end

      it "generates a MD5 hash of the contents" do
        expected = Digest::MD5.file(@file.tempfile.path).to_s
        assert_equal expected, @subject.fingerprint
      end

      it "reads the contents of the file" do
        expected = @file.tempfile.read
        assert expected.length > 0
        assert_equal expected, @subject.read
      end
    end

    context "with UploadedFile that has restricted characters" do
      before do
        Paperclip::UploadedFileAdapter.content_type_detector = nil

        class UploadedFile < OpenStruct; end
        @file = UploadedFile.new(
          original_filename: "image:restricted.gif",
          content_type: "image/x-png-by-browser",
          head: "",
          path: fixture_file("5k.png")
        )
        @subject = Paperclip.io_adapters.for(@file, hash_digest: Digest::MD5)
      end

      it "does not generate paths that include restricted characters" do
        expect(@subject.path).to_not match(/:/)
      end

      it "does not generate filenames that include restricted characters" do
        assert_equal 'image_restricted.gif', @subject.original_filename
      end
    end

    context "with UploadFile responding to #path" do
      before do
        Paperclip::UploadedFileAdapter.content_type_detector = nil

        class UploadedFile < OpenStruct; end
        @file = UploadedFile.new(
          original_filename: "5k.png",
          content_type: "image/x-png-by-browser",
          head: "",
          path: fixture_file("5k.png")
        )
        @subject = Paperclip.io_adapters.for(@file, hash_digest: Digest::MD5)
      end

      it "gets the right filename" do
        assert_equal "5k.png", @subject.original_filename
      end

      it "forces binmode on tempfile" do
        assert @subject.instance_variable_get("@tempfile").binmode?
      end

      it "gets the content type" do
        assert_equal "image/png", @subject.content_type
      end

      it "gets the file's size" do
        assert_equal 4456, @subject.size
      end

      it "returns false for a call to nil?" do
        assert ! @subject.nil?
      end

      it "generates a MD5 hash of the contents" do
        expected = Digest::MD5.file(@file.path).to_s
        assert_equal expected, @subject.fingerprint
      end

      it "reads the contents of the file" do
        expected_file = File.new(@file.path)
        expected_file.binmode
        expected = expected_file.read
        assert expected.length > 0
        assert_equal expected, @subject.read
      end

      context "don't trust client-given MIME type" do
        before do
          Paperclip::UploadedFileAdapter.content_type_detector =
            Paperclip::FileCommandContentTypeDetector

          class UploadedFile < OpenStruct; end
          @file = UploadedFile.new(
            original_filename: "5k.png",
            content_type: "image/x-png-by-browser",
            head: "",
            path: fixture_file("5k.png")
          )
          @subject = Paperclip.io_adapters.for(@file)
        end

        it "gets the content type" do
          assert_equal "image/png", @subject.content_type
        end
      end
    end
  end
end