Back to Repositories

Testing Version Limit Management in Paper Trail

This test suite validates PaperTrail’s version limiting functionality, ensuring proper cleanup of historical version records while maintaining data integrity. The tests verify both global and model-specific version limits, along with proper handling of chronological version management.

Test Coverage Overview

The test suite provides comprehensive coverage of PaperTrail’s version limiting capabilities.

Key areas tested include:
  • Model-specific version limits overriding global settings
  • Inheritance-based version limit handling
  • Global version limit enforcement
  • Chronological version cleanup with database ordering variations

Implementation Analysis

The testing approach utilizes RSpec to validate version limit functionality across different scenarios. The implementation employs systematic version creation and verification patterns, using both direct model updates and manual version record creation to test edge cases.

Framework-specific features include:
  • RSpec shared contexts for versioning
  • Dynamic version creation loops
  • Custom version cleanup triggers

Technical Details

Testing tools and configuration:
  • RSpec as the testing framework
  • PaperTrail configuration management
  • Version limit settings at multiple levels
  • Custom model inheritance structures
  • YAML serialization for version objects

Best Practices Demonstrated

The test suite exemplifies several testing best practices for version management systems.

Notable practices include:
  • Proper test isolation with configuration resets
  • Comprehensive edge case coverage
  • Clear test organization and naming
  • Explicit version count validation
  • Thorough cleanup scenario testing

paper-trail-gem/paper_trail

spec/paper_trail/version_limit_spec.rb

            
# frozen_string_literal: true

require "spec_helper"

module PaperTrail
  ::RSpec.describe Cleaner, versioning: true do
    after do
      PaperTrail.config.version_limit = nil
    end

    it "cleans up old versions with limit specified in model" do
      PaperTrail.config.version_limit = 10

      # LimitedBicycle overrides the global version_limit
      bike = LimitedBicycle.create(name: "Bike") # has_paper_trail limit: 3

      15.times do |i|
        bike.update(name: "Name #{i}")
      end
      expect(LimitedBicycle.find(bike.id).versions.count).to eq(4)
      # 4 versions = 3 updates + 1 create.
    end

    it "cleans up old versions with limit specified on base class" do
      PaperTrail.config.version_limit = 10

      Animal.paper_trail_options[:limit] = 5
      Dog.paper_trail_options = Animal.paper_trail_options.without(:limit)

      dog = Dog.create(name: "Fluffy") # Dog specified has_paper_trail with no limit option

      15.times do |i|
        dog.update(name: "Name #{i}")
      end
      expect(Dog.find(dog.id).versions.count).to eq(6) # Dog uses limit option on base class, Animal
      # 6 versions = 5 updates + 1 create.
    end

    it "cleans up old versions" do
      PaperTrail.config.version_limit = 10
      widget = Widget.create

      100.times do |i|
        widget.update(name: "Name #{i}")
        expect(Widget.find(widget.id).versions.count).to be <= 11
        # 11 versions = 10 updates + 1 create.
      end
    end

    it "deletes oldest versions, when the database returns them in a different order" do
      epoch = Date.new(2017, 1, 1)
      widget = Widget.create(created_at: epoch)

      # Sometimes a database will returns records in a different order than
      # they were inserted. That's hard to get the database to do, so we'll
      # just create them out-of-order:
      (1..5).to_a.shuffle.each do |n|
        PaperTrail::Version.create!(
          created_at: epoch + n.hours,
          item: widget,
          event: "update",
          object: { "id" => widget.id, "name" => "Name #{n}" }.to_yaml
        )
      end
      expect(Widget.find(widget.id).versions.count).to eq(6) # 1 create + 5 updates

      # Now, we've recreated the scenario where we can accidentally clean up
      # the wrong versions. Re-enable the version_limit, and trigger the
      # clean-up:
      PaperTrail.config.version_limit = 3
      widget.versions.last.send(:enforce_version_limit!)

      # Verify that we have fewer versions:
      expect(widget.reload.versions.count).to eq(4) # 1 create + 4 updates

      # Exclude the create, because the create will return nil for `#reify`.
      last_names = widget.versions.not_creates.map(&:reify).map(&:name)
      expect(last_names).to eq(["Name 3", "Name 4", "Name 5"])
      # No matter what order the version records are returned it, we should
      # always keep the most-recent changes.
    end
  end
end