Back to Repositories

Validating Attachment Content Type Validation in Paperclip

This test suite validates the content type validation functionality in Paperclip’s attachment system. It ensures proper handling of file type restrictions, both through whitelist and blacklist approaches, while verifying error message generation and nil/blank value handling.

Test Coverage Overview

The test suite provides comprehensive coverage of the AttachmentContentTypeValidator class, examining both positive and negative validation scenarios.

Key areas tested include:
  • Nil content type handling
  • Allow nil/blank options
  • Whitelist and blacklist validation formats
  • Custom error message handling
  • Helper method integration

Implementation Analysis

The testing approach utilizes RSpec’s context-based structure to organize related test cases logically. It employs stub methods for content type simulation and implements before blocks for test setup. The implementation demonstrates thorough validation logic testing through both string and regex-based content type matching.

Technical Details

Testing tools and configuration:
  • RSpec as the testing framework
  • Stub methods for mock behavior
  • Custom validator builder method
  • Model rebuilding for clean test states
  • Assert and expect syntax combinations

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test cases with proper setup and teardown
  • Clear context organization
  • Comprehensive edge case coverage
  • DRY principle through helper methods
  • Consistent error message validation

thoughtbot/paperclip

spec/paperclip/validators/attachment_content_type_validator_spec.rb

            
require 'spec_helper'

describe Paperclip::Validators::AttachmentContentTypeValidator do
  before do
    rebuild_model
    @dummy = Dummy.new
  end

  def build_validator(options)
    @validator = Paperclip::Validators::AttachmentContentTypeValidator.new(options.merge(
      attributes: :avatar
    ))
  end

  context "with a nil content type" do
    before do
      build_validator content_type: "image/jpg"
      @dummy.stubs(avatar_content_type: nil)
      @validator.validate(@dummy)
    end

    it "does not set an error message" do
      assert @dummy.errors[:avatar_content_type].blank?
    end
  end

  context "with :allow_nil option" do
    context "as true" do
      before do
        build_validator content_type: "image/png", allow_nil: true
        @dummy.stubs(avatar_content_type: nil)
        @validator.validate(@dummy)
      end

      it "allows avatar_content_type as nil" do
        assert @dummy.errors[:avatar_content_type].blank?
      end
    end

    context "as false" do
      before do
        build_validator content_type: "image/png", allow_nil: false
        @dummy.stubs(avatar_content_type: nil)
        @validator.validate(@dummy)
      end

      it "does not allow avatar_content_type as nil" do
        assert @dummy.errors[:avatar_content_type].present?
      end
    end
  end

  context "with a failing validation" do
    before do
      build_validator content_type: "image/png", allow_nil: false
      @dummy.stubs(avatar_content_type: nil)
      @validator.validate(@dummy)
    end

    it "adds error to the base object" do
      assert @dummy.errors[:avatar].present?,
        "Error not added to base attribute"
    end

    it "adds error to base object as a string" do
      expect(@dummy.errors[:avatar].first).to be_a String
    end
  end

  context "with a successful validation" do
    before do
      build_validator content_type: "image/png", allow_nil: false
      @dummy.stubs(avatar_content_type: "image/png")
      @validator.validate(@dummy)
    end

    it "does not add error to the base object" do
      assert @dummy.errors[:avatar].blank?,
        "Error was added to base attribute"
    end
  end

  context "with :allow_blank option" do
    context "as true" do
      before do
        build_validator content_type: "image/png", allow_blank: true
        @dummy.stubs(avatar_content_type: "")
        @validator.validate(@dummy)
      end

      it "allows avatar_content_type as blank" do
        assert @dummy.errors[:avatar_content_type].blank?
      end
    end

    context "as false" do
      before do
        build_validator content_type: "image/png", allow_blank: false
        @dummy.stubs(avatar_content_type: "")
        @validator.validate(@dummy)
      end

      it "does not allow avatar_content_type as blank" do
        assert @dummy.errors[:avatar_content_type].present?
      end
    end
  end

  context "whitelist format" do
    context "with an allowed type" do
      context "as a string" do
        before do
          build_validator content_type: "image/jpg"
          @dummy.stubs(avatar_content_type: "image/jpg")
          @validator.validate(@dummy)
        end

        it "does not set an error message" do
          assert @dummy.errors[:avatar_content_type].blank?
        end
      end

      context "as an regexp" do
        before do
          build_validator content_type: /^image\/.*/
          @dummy.stubs(avatar_content_type: "image/jpg")
          @validator.validate(@dummy)
        end

        it "does not set an error message" do
          assert @dummy.errors[:avatar_content_type].blank?
        end
      end

      context "as a list" do
        before do
          build_validator content_type: ["image/png", "image/jpg", "image/jpeg"]
          @dummy.stubs(avatar_content_type: "image/jpg")
          @validator.validate(@dummy)
        end

        it "does not set an error message" do
          assert @dummy.errors[:avatar_content_type].blank?
        end
      end
    end

    context "with a disallowed type" do
      context "as a string" do
        before do
          build_validator content_type: "image/png"
          @dummy.stubs(avatar_content_type: "image/jpg")
          @validator.validate(@dummy)
        end

        it "sets a correct default error message" do
          assert @dummy.errors[:avatar_content_type].present?
          expect(@dummy.errors[:avatar_content_type]).to include "is invalid"
        end
      end

      context "as a regexp" do
        before do
          build_validator content_type: /^text\/.*/
          @dummy.stubs(avatar_content_type: "image/jpg")
          @validator.validate(@dummy)
        end

        it "sets a correct default error message" do
          assert @dummy.errors[:avatar_content_type].present?
          expect(@dummy.errors[:avatar_content_type]).to include "is invalid"
        end
      end

      context "with :message option" do
        context "without interpolation" do
          before do
            build_validator content_type: "image/png", message: "should be a PNG image"
            @dummy.stubs(avatar_content_type: "image/jpg")
            @validator.validate(@dummy)
          end

          it "sets a correct error message" do
            expect(@dummy.errors[:avatar_content_type]).to include "should be a PNG image"
          end
        end

        context "with interpolation" do
          before do
            build_validator content_type: "image/png", message: "should have content type %{types}"
            @dummy.stubs(avatar_content_type: "image/jpg")
            @validator.validate(@dummy)
          end

          it "sets a correct error message" do
            expect(@dummy.errors[:avatar_content_type]).to include "should have content type image/png"
          end
        end
      end
    end
  end

  context "blacklist format" do
    context "with an allowed type" do
      context "as a string" do
        before do
          build_validator not: "image/gif"
          @dummy.stubs(avatar_content_type: "image/jpg")
          @validator.validate(@dummy)
        end

        it "does not set an error message" do
          assert @dummy.errors[:avatar_content_type].blank?
        end
      end

      context "as an regexp" do
        before do
          build_validator not: /^text\/.*/
          @dummy.stubs(avatar_content_type: "image/jpg")
          @validator.validate(@dummy)
        end

        it "does not set an error message" do
          assert @dummy.errors[:avatar_content_type].blank?
        end
      end

      context "as a list" do
        before do
          build_validator not: ["image/png", "image/jpg", "image/jpeg"]
          @dummy.stubs(avatar_content_type: "image/gif")
          @validator.validate(@dummy)
        end

        it "does not set an error message" do
          assert @dummy.errors[:avatar_content_type].blank?
        end
      end
    end

    context "with a disallowed type" do
      context "as a string" do
        before do
          build_validator not: "image/png"
          @dummy.stubs(avatar_content_type: "image/png")
          @validator.validate(@dummy)
        end

        it "sets a correct default error message" do
          assert @dummy.errors[:avatar_content_type].present?
          expect(@dummy.errors[:avatar_content_type]).to include "is invalid"
        end
      end

      context "as a regexp" do
        before do
          build_validator not: /^text\/.*/
          @dummy.stubs(avatar_content_type: "text/plain")
          @validator.validate(@dummy)
        end

        it "sets a correct default error message" do
          assert @dummy.errors[:avatar_content_type].present?
          expect(@dummy.errors[:avatar_content_type]).to include "is invalid"
        end
      end

      context "with :message option" do
        context "without interpolation" do
          before do
            build_validator not: "image/png", message: "should not be a PNG image"
            @dummy.stubs(avatar_content_type: "image/png")
            @validator.validate(@dummy)
          end

          it "sets a correct error message" do
            expect(@dummy.errors[:avatar_content_type]).to include "should not be a PNG image"
          end
        end

        context "with interpolation" do
          before do
            build_validator not: "image/png", message: "should not have content type %{types}"
            @dummy.stubs(avatar_content_type: "image/png")
            @validator.validate(@dummy)
          end

          it "sets a correct error message" do
            expect(@dummy.errors[:avatar_content_type]).to include "should not have content type image/png"
          end
        end
      end
    end
  end

  context "using the helper" do
    before do
      Dummy.validates_attachment_content_type :avatar, content_type: "image/jpg"
    end

    it "adds the validator to the class" do
      assert Dummy.validators_on(:avatar).any?{ |validator| validator.kind == :attachment_content_type }
    end
  end

  context "given options" do
    it "raises argument error if no required argument was given" do
      assert_raises(ArgumentError) do
        build_validator message: "Some message"
      end
    end

    it "does not raise argument error if :content_type was given" do
      build_validator content_type: "image/jpg"
    end

    it "does not raise argument error if :not was given" do
      build_validator not: "image/jpg"
    end
  end
end