Back to Repositories

Validating Attachment Content Type Matchers in Paperclip

This test suite validates the content type validation matcher functionality in Paperclip’s attachment system. It ensures proper validation of file attachments based on their MIME types and conditional validation scenarios.

Test Coverage Overview

The test suite provides comprehensive coverage of Paperclip’s ValidateAttachmentContentTypeMatcher.

Key areas tested include:
  • Basic validation presence and absence scenarios
  • Content type matching with regex patterns
  • Allowing specific MIME types
  • Rejecting specific MIME types
  • Conditional validation using :if clauses

Implementation Analysis

The testing approach uses RSpec’s behavior-driven development patterns with extensive use of expect syntax and matcher combinations. The implementation leverages Paperclip’s custom shoulda matchers for attachment validation, demonstrating both positive and negative test cases with clear failure message handling.

Technical Details

Testing components include:
  • RSpec as the testing framework
  • Paperclip::Shoulda::Matchers module
  • Custom table and class reset helpers
  • Dynamic attribute validation setup
  • Mock database schema with avatar attachments

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test scenarios with proper setup and teardown
  • Comprehensive edge case coverage
  • Clear test descriptions and expectations
  • DRY implementation using helper methods
  • Proper error handling verification

thoughtbot/paperclip

spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb

            
require 'spec_helper'
require 'paperclip/matchers'

describe Paperclip::Shoulda::Matchers::ValidateAttachmentContentTypeMatcher do
  extend Paperclip::Shoulda::Matchers

  before do
    reset_table("dummies") do |d|
      d.string :title
      d.string :avatar_file_name
      d.string :avatar_content_type
    end
    reset_class "Dummy"
    Dummy.do_not_validate_attachment_file_type :avatar
    Dummy.has_attached_file :avatar
  end

  it "rejects a class with no validation" do
    expect(matcher).to_not accept(Dummy)
    expect { matcher.failure_message }.to_not raise_error
  end

  it 'rejects a class when the validation fails' do
    Dummy.validates_attachment_content_type :avatar, content_type: %r{audio/.*}
    expect(matcher).to_not accept(Dummy)
    expect { matcher.failure_message }.to_not raise_error
  end

  it "accepts a class with a matching validation" do
    Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}
    expect(matcher).to accept(Dummy)
    expect { matcher.failure_message }.to_not raise_error
  end

  it "accepts a class with other validations but matching types" do
    Dummy.validates_presence_of :title
    Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}
    expect(matcher).to accept(Dummy)
    expect { matcher.failure_message }.to_not raise_error
  end

  it "accepts a class that matches and a matcher that only specifies 'allowing'" do
    Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}
    matcher = plain_matcher.allowing(%w(image/png image/jpeg))

    expect(matcher).to accept(Dummy)
    expect { matcher.failure_message }.to_not raise_error
  end

  it "rejects a class that does not match and a matcher that only specifies 'allowing'" do
    Dummy.validates_attachment_content_type :avatar, content_type: %r{audio/.*}
    matcher = plain_matcher.allowing(%w(image/png image/jpeg))

    expect(matcher).to_not accept(Dummy)
    expect { matcher.failure_message }.to_not raise_error
  end

  it "accepts a class that matches and a matcher that only specifies 'rejecting'" do
    Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}
    matcher = plain_matcher.rejecting(%w(audio/mp3 application/octet-stream))

    expect(matcher).to accept(Dummy)
    expect { matcher.failure_message }.to_not raise_error
  end

  it "rejects a class that does not match and a matcher that only specifies 'rejecting'" do
    Dummy.validates_attachment_content_type :avatar, content_type: %r{audio/.*}
    matcher = plain_matcher.rejecting(%w(audio/mp3 application/octet-stream))

    expect(matcher).to_not accept(Dummy)
    expect { matcher.failure_message }.to_not raise_error
  end

  context "using an :if to control the validation" do
    before do
      Dummy.class_eval do
        validates_attachment_content_type :avatar, content_type: %r{image/*} , if: :go
        attr_accessor :go
      end
    end

    it "runs the validation if the control is true" do
      dummy = Dummy.new
      dummy.go = true
      expect(matcher).to accept(dummy)
      expect { matcher.failure_message }.to_not raise_error
    end

    it "does not run the validation if the control is false" do
      dummy = Dummy.new
      dummy.go = false
      expect(matcher).to_not accept(dummy)
      expect { matcher.failure_message }.to_not raise_error
    end
  end

  private

  def plain_matcher
    self.class.validate_attachment_content_type(:avatar)
  end

  def matcher
    plain_matcher.
      allowing(%w(image/png image/jpeg)).
      rejecting(%w(audio/mp3 application/octet-stream))
  end

end