Back to Repositories

Testing PaperTrail Configuration Management and Version Control Implementation

This test suite validates the configuration functionality of the PaperTrail gem, focusing on version tracking behavior and error handling mechanisms. The tests ensure proper singleton instance management and verify various version limiting scenarios across different model configurations.

Test Coverage Overview

The test suite provides comprehensive coverage of PaperTrail’s configuration features, including version error behaviors and version limiting functionality.

  • Tests singleton instance management and initialization
  • Validates four error handling modes: legacy, exception, log, and silent
  • Verifies version limiting across different model configurations
  • Tests STI class behavior with version limiting

Implementation Analysis

The testing approach employs RSpec’s describe/context/it blocks to organize test scenarios hierarchically. Tests utilize mock objects for logging verification and leverage PaperTrail’s versioning system to track model changes.

Implementation includes:
  • Mock logger implementation for error behavior testing
  • Dynamic version limit configuration
  • Multiple model types for inheritance testing

Technical Details

Testing tools and configuration:
  • RSpec as the testing framework
  • SecureRandom for generating test data
  • ActiveRecord integration testing
  • Custom test models: Widget, LimitedBicycle, UnlimitedBicycle
  • Mock objects for logger validation

Best Practices Demonstrated

The test suite exemplifies several testing best practices for Ruby and RSpec.

  • Proper test isolation using before/after blocks
  • Comprehensive error scenario coverage
  • Clear test case organization
  • Effective use of shared contexts and expectations
  • Thorough edge case handling

paper-trail-gem/paper_trail

spec/paper_trail/config_spec.rb

            
# frozen_string_literal: true

require "securerandom"
require "spec_helper"

module PaperTrail
  ::RSpec.describe Config do
    describe ".instance" do
      it "returns the singleton instance" do
        expect { described_class.instance }.not_to raise_error
      end
    end

    describe ".new" do
      it "raises NoMethodError" do
        expect { described_class.new }.to raise_error(NoMethodError)
      end
    end

    describe ".version_error_behavior", versioning: true do
      let(:logger) { instance_double(Logger) }

      before do
        allow(logger).to receive(:warn)
        allow(logger).to receive(:debug?).and_return(false)

        ActiveRecord::Base.logger = logger
      end

      after do
        ActiveRecord::Base.logger = nil
      end

      context "when :legacy" do
        context "when version cannot be created" do
          before { PaperTrail.config.version_error_behavior = :legacy }

          it "raises an error but does not log on create" do
            expect {
              Comment.create!(content: "Henry")
            }.to raise_error(ActiveRecord::RecordInvalid)

            expect(logger).not_to have_received(:warn)
          end

          it "logs but does not raise error on update" do
            comment = PaperTrail.request(whodunnit: "Foo") do
              Comment.create!(content: "Henry")
            end

            expect {
              comment.update!(content: "Brad")
            }.not_to raise_error

            expect(logger).to have_received(:warn)
          end

          it "does not raise error or log on update_columns" do
            comment = PaperTrail.request(whodunnit: "Foo") do
              Comment.create!(content: "Henry")
            end

            expect {
              comment.update_columns(content: "Brad")
            }.not_to raise_error

            expect(logger).not_to have_received(:warn)
          end

          it "logs but does not raise error on destroy" do
            comment = PaperTrail.request(whodunnit: "Foo") do
              Comment.create!(content: "Henry")
            end

            expect {
              comment.destroy!
            }.not_to raise_error

            expect(logger).to have_received(:warn)
          end
        end
      end

      context "when exception" do
        context "when version cannot be created" do
          before { PaperTrail.config.version_error_behavior = :exception }

          after { PaperTrail.config.version_error_behavior = :legacy }

          it "raises an error but does not log on create" do
            expect {
              Comment.create!(content: "Henry")
            }.to raise_error(ActiveRecord::RecordInvalid)

            expect(logger).not_to have_received(:warn)
          end

          it "raises an error but does not on update" do
            comment = PaperTrail.request(whodunnit: "Foo") do
              Comment.create!(content: "Henry")
            end

            expect {
              comment.update!(content: "Brad")
            }.to raise_error(ActiveRecord::RecordInvalid)

            expect(logger).not_to have_received(:warn)
          end

          it "does not raise an error or log on update_columns" do
            comment = PaperTrail.request(whodunnit: "Foo") do
              Comment.create!(content: "Henry")
            end

            expect {
              comment.update_columns(content: "Brad")
            }.not_to raise_error

            expect(logger).not_to have_received(:warn)
          end

          it "raises an error but does not log on destroy" do
            comment = PaperTrail.request(whodunnit: "Foo") do
              Comment.create!(content: "Henry")
            end

            expect {
              comment.destroy!
            }.to raise_error(ActiveRecord::RecordInvalid)

            expect(logger).not_to have_received(:warn)
          end
        end
      end

      context "when log" do
        context "when version cannot be created" do
          before { PaperTrail.config.version_error_behavior = :log }

          after { PaperTrail.config.version_error_behavior = :legacy }

          it "logs and does not raise an error on create" do
            expect {
              Comment.create!(content: "Henry")
            }.not_to raise_error

            expect(logger).to have_received(:warn)
          end

          it "logs and does not raise an error on update" do
            comment = PaperTrail.request(whodunnit: "Foo") do
              Comment.create!(content: "Henry")
            end

            expect {
              comment.update!(content: "Brad")
            }.not_to raise_error

            expect(logger).to have_received(:warn)
          end

          it "does not raise an error or log on update_columns" do
            comment = PaperTrail.request(whodunnit: "Foo") do
              Comment.create!(content: "Henry")
            end

            expect {
              comment.update_columns(content: "Brad")
            }.not_to raise_error

            expect(logger).not_to have_received(:warn)
          end

          it "logs and does not raise an error on destroy" do
            comment = PaperTrail.request(whodunnit: "Foo") do
              Comment.create!(content: "Henry")
            end

            expect {
              comment.destroy!
            }.not_to raise_error

            expect(logger).to have_received(:warn)
          end
        end
      end

      context "when silent" do
        context "when version cannot be created" do
          before { PaperTrail.config.version_error_behavior = :silent }

          after { PaperTrail.config.version_error_behavior = :legacy }

          it "does not log or raise an error on create" do
            expect {
              Comment.create!(content: "Henry")
            }.not_to raise_error

            expect(logger).not_to have_received(:warn)
          end

          it "does not log or raise an error on update" do
            comment = PaperTrail.request(whodunnit: "Foo") do
              Comment.create!(content: "Henry")
            end

            expect {
              comment.update!(content: "Brad")
            }.not_to raise_error

            expect(logger).not_to have_received(:warn)
          end

          it "does not log or raise an error on update_columns" do
            comment = PaperTrail.request(whodunnit: "Foo") do
              Comment.create!(content: "Henry")
            end

            expect {
              comment.update_columns(content: "Brad")
            }.not_to raise_error

            expect(logger).not_to have_received(:warn)
          end

          it "does not log or raise an error on destroy" do
            comment = PaperTrail.request(whodunnit: "Foo") do
              Comment.create!(content: "Henry")
            end

            expect {
              comment.destroy!
            }.not_to raise_error

            expect(logger).not_to have_received(:warn)
          end
        end
      end
    end

    describe ".version_limit", versioning: true do
      after { PaperTrail.config.version_limit = nil }

      it "limits the number of versions to 3 (2 plus the created at event)" do
        PaperTrail.config.version_limit = 2
        widget = Widget.create!(name: "Henry")
        6.times { widget.update_attribute(:name, SecureRandom.hex(8)) }
        expect(widget.versions.first.event).to(eq("create"))
        expect(widget.versions.size).to(eq(3))
      end

      it "overrides the general limits to 4 (3 plus the created at event)" do
        PaperTrail.config.version_limit = 100
        bike = LimitedBicycle.create!(name: "Limited Bike") # has_paper_trail limit: 3
        10.times { bike.update_attribute(:name, SecureRandom.hex(8)) }
        expect(bike.versions.first.event).to(eq("create"))
        expect(bike.versions.size).to(eq(4))
      end

      it "overrides the general limits with unlimited versions for model" do
        PaperTrail.config.version_limit = 3
        bike = UnlimitedBicycle.create!(name: "Unlimited Bike") # has_paper_trail limit: nil
        6.times { bike.update_attribute(:name, SecureRandom.hex(8)) }
        expect(bike.versions.first.event).to(eq("create"))
        expect(bike.versions.size).to eq(7)
      end

      it "is not enabled on non-papertrail STI base classes, but enabled on subclasses" do
        PaperTrail.config.version_limit = 10
        Vehicle.create!(name: "A Vehicle", type: "Vehicle")
        limited_bike = LimitedBicycle.create!(name: "Limited")
        limited_bike.name = "A new name"
        limited_bike.save
        assert_equal 2, limited_bike.versions.length
      end
    end
  end
end