Back to Repositories

Testing PaperTrail Version Tracking and Metadata Management in paper_trail

This RSpec test suite validates the PaperTrail versioning functionality in a Rails controller context, focusing on tracking metadata like IP addresses and user agents during CRUD operations. The tests ensure proper version creation and metadata storage for widget operations while also verifying the ability to disable tracking when needed.

Test Coverage Overview

The test suite provides comprehensive coverage of widget controller actions including create, update, and destroy operations.

Key areas tested include:
  • Version creation with metadata storage
  • IP address and user agent tracking
  • Whodunnit attribution
  • PaperTrail enable/disable functionality
  • Controller metadata evaluation

Implementation Analysis

The testing approach utilizes RSpec controller specs with careful setup of request environment variables to simulate real-world conditions.

Technical patterns include:
  • Before/after hooks for environment setup
  • Context-based test organization
  • Controller state verification
  • Version metadata validation
  • PaperTrail configuration testing

Technical Details

Testing infrastructure includes:
  • RSpec testing framework
  • PaperTrail gem configuration
  • RequestStore for request-specific data
  • Rails controller testing helpers
  • Mock HTTP headers and environment variables

Best Practices Demonstrated

The test suite exemplifies several testing best practices including isolation of test scenarios, comprehensive state verification, and proper setup/teardown patterns.

Notable practices include:
  • Clear test case organization
  • Explicit context separation
  • Thorough metadata verification
  • Clean environment management
  • Effective use of RSpec expectations

paper-trail-gem/paper_trail

spec/controllers/widgets_controller_spec.rb

            
# frozen_string_literal: true

require "spec_helper"

RSpec.describe WidgetsController, type: :controller, versioning: true do
  before { request.env["REMOTE_ADDR"] = "127.0.0.1" }

  after { RequestStore.store[:paper_trail] = nil }

  describe "#create" do
    context "with PT enabled" do
      it "stores information like IP address in version" do
        post(:create, params: { widget: { name: "Flugel" } })
        widget = assigns(:widget)
        expect(widget.versions.length).to(eq(1))
        expect(widget.versions.last.whodunnit.to_i).to(eq(153))
        expect(widget.versions.last.ip).to(eq("127.0.0.1"))
        expect(widget.versions.last.user_agent).to(eq("Rails Testing"))
      end

      it "controller metadata methods should get evaluated" do
        request.env["HTTP_USER_AGENT"] = "User-Agent"
        post :create, params: { widget: { name: "Flugel" } }
        expect(PaperTrail.request.enabled?).to eq(true)
        expect(PaperTrail.request.whodunnit).to(eq(153))
        expect(PaperTrail.request.controller_info.present?).to(eq(true))
        expect(PaperTrail.request.controller_info.key?(:ip)).to(eq(true))
        expect(PaperTrail.request.controller_info.key?(:user_agent)).to(eq(true))
      end
    end

    context "with PT disabled" do
      it "does not save a version, and metadata is not set" do
        request.env["HTTP_USER_AGENT"] = "Disable User-Agent"
        post :create, params: { widget: { name: "Flugel" } }
        expect(assigns(:widget).versions.length).to(eq(0))
        expect(PaperTrail.request.enabled?).to eq(false)
        expect(PaperTrail.request.whodunnit).to be_nil
        expect(PaperTrail.request.controller_info).to eq({})
      end
    end
  end

  describe "#destroy" do
    it "can be disabled" do
      request.env["HTTP_USER_AGENT"] = "Disable User-Agent"
      post(:create, params: { widget: { name: "Flugel" } })
      w = assigns(:widget)
      expect(w.versions.length).to(eq(0))
      delete(:destroy, params: { id: w.id })
      expect(PaperTrail::Version.with_item_keys("Widget", w.id).size).to(eq(0))
    end

    it "stores information like IP address in version" do
      w = Widget.create(name: "Roundel")
      expect(w.versions.length).to(eq(1))
      delete(:destroy, params: { id: w.id })
      widget = assigns(:widget)
      expect(widget.versions.length).to(eq(2))
      expect(widget.versions.last.ip).to(eq("127.0.0.1"))
      expect(widget.versions.last.user_agent).to(eq("Rails Testing"))
      expect(widget.versions.last.whodunnit.to_i).to(eq(153))
    end
  end

  describe "#update" do
    it "stores information like IP address in version" do
      w = Widget.create(name: "Duvel")
      expect(w.versions.length).to(eq(1))
      put(:update, params: { id: w.id, widget: { name: "Bugle" } })
      widget = assigns(:widget)
      expect(widget.versions.length).to(eq(2))
      expect(widget.versions.last.whodunnit.to_i).to(eq(153))
      expect(widget.versions.last.ip).to(eq("127.0.0.1"))
      expect(widget.versions.last.user_agent).to(eq("Rails Testing"))
    end

    it "can be disabled" do
      request.env["HTTP_USER_AGENT"] = "Disable User-Agent"
      post(:create, params: { widget: { name: "Flugel" } })
      w = assigns(:widget)
      expect(w.versions.length).to(eq(0))
      put(:update, params: { id: w.id, widget: { name: "Bugle" } })
      widget = assigns(:widget)
      expect(widget.versions.length).to(eq(0))
    end
  end
end