Back to Repositories

Testing URI Adapter File Handling in Paperclip

This test suite validates the UriAdapter functionality in Paperclip, focusing on handling different types of URLs and content dispositions for file attachments. The tests ensure proper file name extraction, content type handling, and data integrity verification.

Test Coverage Overview

The test suite provides comprehensive coverage of the UriAdapter class in Paperclip.

Key areas tested include:
  • File name extraction from various URL formats
  • Content type detection and handling
  • Data size and integrity verification
  • Content disposition header parsing
  • Handling of restricted characters in filenames

Implementation Analysis

The tests utilize RSpec’s context-based structure to organize different scenarios for URI handling.

Testing patterns include:
  • Stubbed HTTP responses using StringIO
  • Mock content type and meta information
  • Multiple test contexts for different URL formats
  • Verification of adapter registration and cleanup

Technical Details

Testing tools and configuration:
  • RSpec for test framework
  • StringIO for content simulation
  • URI parsing for URL handling
  • MD5 digest for fingerprint verification
  • Stubs for HTTP content downloads
  • Custom timeout configuration testing

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Proper test isolation using before/after hooks
  • Comprehensive edge case coverage
  • Clear context separation
  • Consistent assertion patterns
  • Thorough cleanup of registered adapters
  • Explicit handling of various content scenarios

thoughtbot/paperclip

spec/paperclip/io_adapters/uri_adapter_spec.rb

            
require 'spec_helper'

describe Paperclip::UriAdapter do
  let(:content_type) { "image/png" }
  let(:meta) { {} }

  before do
    @open_return = StringIO.new("xxx")
    @open_return.stubs(:content_type).returns(content_type)
    @open_return.stubs(:meta).returns(meta)
    Paperclip::UriAdapter.register
  end

  after do
    Paperclip.io_adapters.unregister(described_class)
  end

  context "a new instance" do
    let(:meta) { { "content-type" => "image/png" } }

    before do
      Paperclip::UriAdapter.any_instance.
        stubs(:download_content).returns(@open_return)

      @uri = URI.parse("http://thoughtbot.com/images/thoughtbot-logo.png")
      @subject = Paperclip.io_adapters.for(@uri, hash_digest: Digest::MD5)
    end

    it "returns a file name" do
      assert_equal "thoughtbot-logo.png", @subject.original_filename
    end

    it 'closes open handle after reading' do
      assert_equal true, @open_return.closed?
    end

    it "returns a content type" do
      assert_equal "image/png", @subject.content_type
    end

    it "returns the size of the data" do
      assert_equal @open_return.size, @subject.size
    end

    it "generates an MD5 hash of the contents" do
      assert_equal Digest::MD5.hexdigest("xxx"), @subject.fingerprint
    end

    it "generates correct fingerprint after read" do
      fingerprint = Digest::MD5.hexdigest(@subject.read)
      assert_equal fingerprint, @subject.fingerprint
    end

    it "generates same fingerprint" do
      assert_equal @subject.fingerprint, @subject.fingerprint
    end

    it "returns the data contained in the StringIO" do
      assert_equal "xxx", @subject.read
    end

    it 'accepts a content_type' do
      @subject.content_type = 'image/png'
      assert_equal 'image/png', @subject.content_type
    end

    it "accepts an original_filename" do
      @subject.original_filename = 'image.png'
      assert_equal 'image.png', @subject.original_filename
    end

  end

  context "a directory index url" do
    let(:content_type) { "text/html" }
    let(:meta) { { "content-type" => "text/html" } }

    before do
      Paperclip::UriAdapter.any_instance.
        stubs(:download_content).returns(@open_return)

      @uri = URI.parse("http://thoughtbot.com")
      @subject = Paperclip.io_adapters.for(@uri)
    end

    it "returns a file name" do
      assert_equal "index.html", @subject.original_filename
    end

    it "returns a content type" do
      assert_equal "text/html", @subject.content_type
    end
  end

  context "a url with query params" do
    before do
      Paperclip::UriAdapter.any_instance.
        stubs(:download_content).returns(@open_return)

      @uri = URI.parse("https://github.com/thoughtbot/paperclip?file=test")
      @subject = Paperclip.io_adapters.for(@uri)
    end

    it "returns a file name" do
      assert_equal "paperclip", @subject.original_filename
    end
  end

  context "a url with content disposition headers" do
    let(:file_name) { "test_document.pdf" }
    let(:filename_from_path) { "paperclip" }

    before do
      Paperclip::UriAdapter.any_instance.
        stubs(:download_content).returns(@open_return)

      @uri = URI.parse(
        "https://github.com/thoughtbot/#{filename_from_path}?file=test")
    end

    it "returns file name from path" do
      meta["content-disposition"] = "inline;"

      @subject = Paperclip.io_adapters.for(@uri)

      assert_equal filename_from_path, @subject.original_filename
    end

    it "returns a file name enclosed in double quotes" do
      file_name = "john's test document.pdf"
      meta["content-disposition"] = "attachment; filename=\"#{file_name}\";"

      @subject = Paperclip.io_adapters.for(@uri)

      assert_equal file_name, @subject.original_filename
    end

    it "returns a file name not enclosed in double quotes" do
      meta["content-disposition"] = "ATTACHMENT; FILENAME=#{file_name};"

      @subject = Paperclip.io_adapters.for(@uri)

      assert_equal file_name, @subject.original_filename
    end

    it "does not crash when an empty filename is given" do
      meta["content-disposition"] = "ATTACHMENT; FILENAME=\"\";"

      @subject = Paperclip.io_adapters.for(@uri)

      assert_equal "", @subject.original_filename
    end

    it "returns a file name ignoring RFC 5987 encoding" do
      meta["content-disposition"] =
        "attachment; filename=#{file_name}; filename* = utf-8''%e2%82%ac%20rates"

      @subject = Paperclip.io_adapters.for(@uri)

      assert_equal file_name, @subject.original_filename
    end

    context "when file name has consecutive periods" do
      let(:file_name) { "test_document..pdf" }

      it "returns a file name" do
        @uri = URI.parse(
          "https://github.com/thoughtbot/#{file_name}?file=test")
        @subject = Paperclip.io_adapters.for(@uri)
        assert_equal file_name, @subject.original_filename
      end
    end
  end

  context "a url with restricted characters in the filename" do
    before do
      Paperclip::UriAdapter.any_instance.
        stubs(:download_content).returns(@open_return)

      @uri = URI.parse("https://github.com/thoughtbot/paper:clip.jpg")
      @subject = Paperclip.io_adapters.for(@uri)
    end

    it "does not generate filenames that include restricted characters" do
      assert_equal "paper_clip.jpg", @subject.original_filename
    end

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

  describe "#download_content" do
    before do
      Paperclip::UriAdapter.any_instance.stubs(:open).returns(@open_return)
      @uri = URI.parse("https://github.com/thoughtbot/paper:clip.jpg")
      @subject = Paperclip.io_adapters.for(@uri)
    end

    after do
      @subject.send(:download_content)
    end

    context "with default read_timeout" do
      it "calls open without options" do
        @subject.expects(:open).with(@uri, {}).at_least_once
      end
    end

    context "with custom read_timeout" do
      before do
        Paperclip.options[:read_timeout] = 120
      end

      it "calls open with read_timeout option" do
        @subject.expects(:open).with(@uri, read_timeout: 120).at_least_once
      end
    end
  end
end