Back to Repositories

Validating File Extension Allowlist Functionality in CarrierWave

This test suite thoroughly validates the file extension allowlist functionality in CarrierWave, ensuring proper handling of allowed and restricted file uploads. It covers both allowlist and legacy whitelist implementations, with comprehensive case sensitivity and regular expression support testing.

Test Coverage Overview

The test suite provides extensive coverage of CarrierWave’s extension validation system:
  • No allowlist scenarios
  • Array-based allowlist validation
  • Single value allowlist handling
  • Case sensitivity testing
  • Regular expression pattern matching
  • Legacy whitelist support with deprecation warnings
  • Internationalization (I18n) integration

Implementation Analysis

The testing approach employs RSpec’s context-driven structure to organize test scenarios logically. It utilizes mocking with allow/receive patterns to isolate extension validation behavior, and implements expect blocks to verify both successful operations and error conditions.

The implementation specifically tests file extension validation through the #cache! method, covering various extension formats and matching strategies.

Technical Details

Testing infrastructure includes:
  • RSpec as the testing framework
  • File system operations with FileUtils
  • Mock objects for isolation testing
  • Dynamic class creation for uploader testing
  • I18n integration for localized error messages
  • Custom matcher support for extension validation

Best Practices Demonstrated

The test suite exemplifies several testing best practices:
  • Proper test isolation with before/after hooks
  • Comprehensive edge case coverage
  • Clear context separation
  • Explicit expectation setting
  • Proper error handling validation
  • Deprecation warning verification
  • Internationalization testing

carrierwaveuploader/carrierwave

spec/uploader/extension_allowlist_spec.rb

            
require 'spec_helper'

describe CarrierWave::Uploader do
  before do
    @uploader_class = Class.new(CarrierWave::Uploader::Base)
    @uploader = @uploader_class.new
  end

  after do
    FileUtils.rm_rf(public_path)
  end

  describe '#cache!' do
    before do
      allow(CarrierWave).to receive(:generate_cache_id).and_return('1369894322-345-1234-2255')
    end

    context "when there is no allowlist" do
      it "does not raise an integrity error" do
        allow(@uploader).to receive(:extension_allowlist).and_return(nil)

        expect {
          @uploader.cache!(File.open(file_path('test.jpg')))
        }.not_to raise_error
      end
    end

    context "when there is an allowlist" do
      context "when the allowlist is an array of values" do
        it "does not raise an integrity error when the file has an allowlisted extension" do
          allow(@uploader).to receive(:extension_allowlist).and_return(%w(jpg gif png))

          expect {
            @uploader.cache!(File.open(file_path('test.jpg')))
          }.not_to raise_error
        end

        it "raises an integrity error if the file has not an allowlisted extension" do
          allow(@uploader).to receive(:extension_allowlist).and_return(%w(txt doc xls))

          expect {
            @uploader.cache!(File.open(file_path('test.jpg')))
          }.to raise_error(CarrierWave::IntegrityError, 'You are not allowed to upload "jpg" files, allowed types: txt, doc, xls')
        end

        it "raises an integrity error if the file has not an allowlisted extension" do
          allow(@uploader).to receive(:extension_allowlist).and_return(%w(txt doc xls))

          expect {
            @uploader.cache!(File.open(file_path('test.jpg')))
          }.to raise_error(CarrierWave::IntegrityError)
        end

        it "raises an integrity error if the file has not an allowlisted extension, using start of string matcher" do
          allow(@uploader).to receive(:extension_allowlist).and_return(%w(txt))

          expect {
            @uploader.cache!(File.open(file_path('bork.ttxt')))
          }.to raise_error(CarrierWave::IntegrityError)
        end

        it "raises an integrity error if the file has not an allowlisted extension, using end of string matcher" do
          allow(@uploader).to receive(:extension_allowlist).and_return(%w(txt))

          expect {
            @uploader.cache!(File.open(file_path('bork.txtt')))
          }.to raise_error(CarrierWave::IntegrityError)
        end

        it "compares extensions in a case insensitive manner when capitalized extension provided" do
          allow(@uploader).to receive(:extension_allowlist).and_return(%w(jpg gif png))

          expect {
            @uploader.cache!(File.open(file_path('case.JPG')))
          }.not_to raise_error
        end

        it "compares extensions in a case insensitive manner when lowercase extension provided" do
          allow(@uploader).to receive(:extension_allowlist).and_return(%w(JPG GIF PNG))

          expect {
            @uploader.cache!(File.open(file_path('test.jpg')))
          }.not_to raise_error
        end

        it "accepts extensions as regular expressions" do
          allow(@uploader).to receive(:extension_allowlist).and_return([/jpe?g/, 'gif', 'png'])

          expect {
            @uploader.cache!(File.open(file_path('test.jpeg')))
          }.not_to raise_error
        end

        it "accepts extensions as regular expressions in a case insensitive manner" do

          allow(@uploader).to receive(:extension_allowlist).and_return([/jpe?g/, 'gif', 'png'])
          expect {
            @uploader.cache!(File.open(file_path('case.JPG')))
          }.not_to raise_error
        end
      end

      context "when the allowlist is a single value" do
        it "accepts a single extension string value" do
          allow(@uploader).to receive(:extension_allowlist).and_return('jpeg')

          expect { @uploader.cache!(File.open(file_path('test.jpg'))) }.to raise_error(CarrierWave::IntegrityError)
        end

        it "accepts a single extension regular expression value" do
          allow(@uploader).to receive(:extension_allowlist).and_return(/jpe?g/)

          expect { @uploader.cache!(File.open(file_path('bork.txt')))}.to raise_error(CarrierWave::IntegrityError)

        end
      end
    end

    context "when there is a whitelist" do
      it "uses the whitelist but shows deprecation" do
        allow(@uploader).to receive(:extension_whitelist).and_return(%w(txt doc xls))

        expect(CarrierWave.deprecator).to receive(:warn).with('#extension_whitelist is deprecated, use #extension_allowlist instead.')
        expect {
          @uploader.cache!(File.open(file_path('test.jpg')))
        }.to raise_error(CarrierWave::IntegrityError)
      end

      it "looks for extension_allowlist first for I18n translation" do
        allow(@uploader).to receive(:extension_allowlist).and_return(%w(txt doc xls))

        change_locale_and_store_translations(:nl, :errors => {
          :messages => {
            :extension_whitelist_error => "this will not be used",
            :extension_allowlist_error => "Het is niet toegestaan om %{extension} bestanden te uploaden; toegestane bestandstypes: %{allowed_types}"
          }
        }) do
          expect {
            @uploader.cache!(File.open(file_path('test.jpg')))
          }.to raise_error(CarrierWave::IntegrityError, 'Het is niet toegestaan om "jpg" bestanden te uploaden; toegestane bestandstypes: txt, doc, xls')
        end
      end
    end
  end
end