Back to Repositories

Testing Version History Cleanup Operations in Paper Trail

This test suite validates the version cleaning functionality in PaperTrail, focusing on the Cleaner module’s ability to manage and prune version history records. The tests ensure proper version management across different models with various cleaning configurations and options.

Test Coverage Overview

The test suite provides comprehensive coverage of PaperTrail’s version cleaning capabilities:

  • Basic version cleaning without options
  • Version retention with keeping parameter
  • Date-based version cleaning
  • Item-specific version cleaning
  • Combined option scenarios

Implementation Analysis

The testing approach uses RSpec to verify version cleaning behavior through multiple contexts and scenarios. It employs before hooks for test setup and leverages FFaker for generating test data. The implementation follows a structured pattern of establishing baseline states and verifying post-cleaning conditions.

Technical Details

Key technical components include:

  • RSpec testing framework
  • PaperTrail versioning system
  • FFaker for test data generation
  • Multiple model types (Animal, Dog, Cat)
  • Version manipulation through created_at timestamps

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Isolated test contexts
  • Comprehensive edge case coverage
  • Clear test setup and teardown
  • Consistent assertion patterns
  • Modular test organization

paper-trail-gem/paper_trail

spec/paper_trail/cleaner_spec.rb

            
# frozen_string_literal: true

require "spec_helper"

module PaperTrail
  ::RSpec.describe Cleaner, versioning: true do
    describe "clean_versions!" do
      let(:animal) { ::Animal.new }
      let(:dog) { ::Dog.new }
      let(:cat) { ::Cat.new }
      let(:animals) { [animal, dog, cat] }

      before do
        animals.each do |animal|
          3.times do
            animal.update_attribute(:name, FFaker::Name.name)
          end
        end
      end

      it "baseline test setup" do
        expect(PaperTrail::Version.count).to(eq(9))
        animals.each { |animal| expect(animal.versions.size).to(eq(3)) }
      end

      context "with no options provided" do
        it "removes extra versions for each item" do
          PaperTrail.clean_versions!
          expect(PaperTrail::Version.count).to(eq(3))
          animals.each { |animal| expect(animal.versions.size).to(eq(1)) }
        end

        it "removes the earliest version(s)" do
          before = animals.map { |animal| animal.versions.last.reify.name }
          PaperTrail.clean_versions!
          after = animals.map { |animal| animal.versions.last.reify.name }
          expect(after).to(eq(before))
        end
      end

      context "when keeping 2" do
        it "keeps two records, instead of the usual one" do
          PaperTrail.clean_versions!(keeping: 2)
          expect(PaperTrail::Version.all.count).to(eq(6))
          animals.each { |animal| expect(animal.versions.size).to(eq(2)) }
        end
      end

      context "with the :date option" do
        it "only deletes versions created on the given date" do
          animal.versions.each do |ver|
            ver.update_attribute(:created_at, (ver.created_at - 1.day))
          end
          date = animal.versions.first.created_at.to_date
          animal.update_attribute(:name, FFaker::Name.name)
          expect(PaperTrail::Version.count).to(eq(10))
          expect(animal.versions.size).to(eq(4))
          expect(animal.paper_trail.versions_between(date, (date + 1.day)).size).to(eq(3))
          PaperTrail.clean_versions!(date: date)
          expect(PaperTrail::Version.count).to(eq(8))
          expect(animal.versions.reload.size).to(eq(2))
          expect(animal.versions.first.created_at.to_date).to(eq(date))
          # Why use `equal?` here instead of something less strict?
          # Doesn't `to_date` always produce a new date object?
          expect(date.equal?(animal.versions.last.created_at.to_date)).to eq(false)
        end
      end

      context "with the :item_id option" do
        context "when a single ID is received" do
          it "only deletes the versions for the Item with that ID" do
            PaperTrail.clean_versions!(item_id: animal.id)
            expect(animal.versions.size).to(eq(1))
            expect(PaperTrail::Version.count).to(eq(7))
          end
        end

        context "when a collection of IDs is received" do
          it "only deletes versions for the Item(s) with those IDs" do
            PaperTrail.clean_versions!(item_id: [animal.id, dog.id])
            expect(animal.versions.size).to(eq(1))
            expect(dog.versions.size).to(eq(1))
            expect(PaperTrail::Version.count).to(eq(5))
          end
        end
      end

      context "with options combinations" do
        context "with :date" do
          before do
            [animal, dog].each do |animal|
              animal.versions.each do |ver|
                ver.update_attribute(:created_at, (ver.created_at - 1.day))
              end
              animal.update_attribute(:name, FFaker::Name.name)
            end
          end

          it "baseline test setup" do
            date = animal.versions.first.created_at.to_date
            expect(PaperTrail::Version.count).to(eq(11))
            [animal, dog].each do |animal|
              expect(animal.versions.size).to(eq(4))
              expect(animal.versions.between(date, (date + 1.day)).size).to(eq(3))
            end
          end

          context "with :keeping" do
            it "restrict cleaning properly" do
              date = animal.versions.first.created_at.to_date
              PaperTrail.clean_versions!(date: date, keeping: 2)
              [animal, dog].each do |animal|
                animal.versions.reload
                expect(animal.versions.size).to(eq(3))
                expect(animal.versions.between(date, (date + 1.day)).size).to(eq(2))
              end
              expect(PaperTrail::Version.count).to(eq(9))
            end
          end

          context "with :item_id" do
            it "restrict cleaning properly" do
              date = animal.versions.first.created_at.to_date
              PaperTrail.clean_versions!(date: date, item_id: dog.id)
              dog.versions.reload
              expect(dog.versions.size).to(eq(2))
              expect(dog.versions.between(date, (date + 1.day)).size).to(eq(1))
              expect(PaperTrail::Version.count).to(eq(9))
            end
          end

          context "with :item_id, and :keeping" do
            it "restrict cleaning properly" do
              date = animal.versions.first.created_at.to_date
              PaperTrail.clean_versions!(date: date, item_id: dog.id, keeping: 2)
              dog.versions.reload
              expect(dog.versions.size).to(eq(3))
              expect(dog.versions.between(date, (date + 1.day)).size).to(eq(2))
              expect(PaperTrail::Version.count).to(eq(10))
            end
          end
        end

        context "with :keeping and :item_id" do
          it "restrict cleaning properly" do
            PaperTrail.clean_versions!(keeping: 2, item_id: animal.id)
            expect(animal.versions.size).to(eq(2))
            expect(PaperTrail::Version.count).to(eq(8))
          end
        end
      end
    end
  end
end