paper-trail-gem--paper_trail/spec/models/widget_spec.rb

339 lines
11 KiB
Ruby

require "spec_helper"
RSpec.describe Widget, type: :model do
describe "`be_versioned` matcher" do
it { is_expected.to be_versioned }
end
let(:widget) { Widget.create! name: "Bob", an_integer: 1 }
describe "`have_a_version_with` matcher", versioning: true do
before do
widget.update_attributes!(name: "Leonard", an_integer: 1)
widget.update_attributes!(name: "Tom")
widget.update_attributes!(name: "Bob")
end
it "is possible to do assertions on version attributes" do
expect(widget).to have_a_version_with name: "Leonard", an_integer: 1
expect(widget).to have_a_version_with an_integer: 1
expect(widget).to have_a_version_with name: "Tom"
end
end
describe "`have_a_version_with_changes` matcher", versioning: true do
before do
widget.update_attributes!(name: "Leonard", an_integer: 2)
widget.update_attributes!(name: "Tom")
widget.update_attributes!(name: "Bob")
end
it "is possible to do assertions on version changes" do
expect(widget).to have_a_version_with_changes name: "Leonard", an_integer: 2
expect(widget).to have_a_version_with_changes an_integer: 2
expect(widget).to have_a_version_with_changes name: "Tom"
expect(widget).to have_a_version_with_changes name: "Bob"
end
end
describe "versioning option" do
context "enabled", versioning: true do
it "enables versioning" do
expect(widget.versions.size).to eq(1)
end
end
context "disabled (default)" do
it "does not enable versioning" do
expect(widget.versions.size).to eq(0)
end
end
end
describe "Callbacks", versioning: true do
describe "before_save" do
before { widget.update_attributes!(name: "Foobar") }
subject { widget.versions.last.reify }
it "resets value for timestamp attrs for update so that value gets updated properly" do
# Travel 1 second because MySQL lacks sub-second resolution
Timecop.travel(1) do
expect { subject.save! }.to change(subject, :updated_at)
end
end
end
describe "after_create" do
let(:widget) { Widget.create!(name: "Foobar", created_at: Time.now - 1.week) }
it "corresponding version uses the widget's `updated_at`" do
expect(widget.versions.last.created_at.to_i).to eq(widget.updated_at.to_i)
end
end
describe "after_update" do
before { widget.update_attributes!(name: "Foobar", updated_at: Time.now + 1.week) }
subject { widget.versions.last.reify }
it { expect(subject.paper_trail).not_to be_live }
it "clears the `versions_association_name` virtual attribute" do
subject.save!
expect(subject.paper_trail).to be_live
end
it "corresponding version uses the widget updated_at" do
expect(widget.versions.last.created_at.to_i).to eq(widget.updated_at.to_i)
end
end
describe "after_destroy" do
it "creates a version for that event" do
expect { widget.destroy }.to change(widget.versions, :count).by(1)
end
it "assigns the version into the `versions_association_name`" do
expect(widget.version).to be_nil
widget.destroy
expect(widget.version).not_to be_nil
expect(widget.version).to eq(widget.versions.last)
end
end
describe "after_rollback" do
let(:rolled_back_name) { "Big Moo" }
before do
begin
widget.transaction do
widget.update_attributes!(name: rolled_back_name)
widget.update_attributes!(name: Widget::EXCLUDED_NAME)
end
rescue ActiveRecord::RecordInvalid
widget.reload
widget.name = nil
widget.save
end
end
it "does not create an event for changes that did not happen" do
widget.versions.map(&:changeset).each do |changeset|
expect(changeset.fetch("name", [])).not_to include(rolled_back_name)
end
end
it "has not yet loaded the assocation" do
expect(widget.versions).not_to be_loaded
end
end
end
describe "Association", versioning: true do
describe "sort order" do
it "sorts by the timestamp order from the `VersionConcern`" do
expect(widget.versions.to_sql).to eq(
widget.versions.reorder(PaperTrail::Version.timestamp_sort_order).to_sql
)
end
end
end
if defined?(ActiveRecord::IdentityMap) && ActiveRecord::IdentityMap.respond_to?(:without)
describe "IdentityMap", versioning: true do
it "does not clobber the IdentityMap when reifying" do
widget.update_attributes name: "Henry", created_at: Time.now - 1.day
widget.update_attributes name: "Harry"
expect(ActiveRecord::IdentityMap).to receive(:without).once
widget.versions.last.reify
end
end
end
describe "#create", versioning: true do
it "creates a version record" do
wordget = Widget.create
assert_equal 1, wordget.versions.length
end
end
describe "#destroy", versioning: true do
it "creates a version record" do
widget = Widget.create
assert_equal 1, widget.versions.length
widget.destroy
versions_for_widget = PaperTrail::Version.with_item_keys("Widget", widget.id)
assert_equal 2, versions_for_widget.length
end
it "can have multiple destruction records" do
versions = lambda { |widget|
# Workaround for AR 3. When we drop AR 3 support, we can simply use
# the `widget.versions` association, instead of `with_item_keys`.
PaperTrail::Version.with_item_keys("Widget", widget.id)
}
widget = Widget.create
assert_equal 1, widget.versions.length
widget.destroy
assert_equal 2, versions.call(widget).length
widget = widget.version.reify
widget.save
assert_equal 3, versions.call(widget).length
widget.destroy
assert_equal 4, versions.call(widget).length
assert_equal 2, versions.call(widget).where(event: "destroy").length
end
end
describe "#paper_trail.originator", versioning: true do
describe "return value" do
let(:orig_name) { FFaker::Name.name }
let(:new_name) { FFaker::Name.name }
before do
PaperTrail.whodunnit = orig_name
end
it "returns the originator for the model at a given state" do
expect(widget.paper_trail).to be_live
expect(widget.paper_trail.originator).to eq(orig_name)
widget.paper_trail.whodunnit(new_name) { |w|
w.update_attributes(name: "Elizabeth")
}
expect(widget.paper_trail.originator).to eq(new_name)
end
it "returns the appropriate originator" do
widget.update_attributes(name: "Andy")
PaperTrail.whodunnit = new_name
widget.update_attributes(name: "Elizabeth")
reified_widget = widget.versions[1].reify
expect(reified_widget.paper_trail.originator).to eq(orig_name)
expect(reified_widget).not_to be_new_record
end
it "can create a new instance with options[:dup]" do
widget.update_attributes(name: "Andy")
PaperTrail.whodunnit = new_name
widget.update_attributes(name: "Elizabeth")
reified_widget = widget.versions[1].reify(dup: true)
expect(reified_widget.paper_trail.originator).to eq(orig_name)
expect(reified_widget).to be_new_record
end
end
end
describe "#version_at", versioning: true do
context "Timestamp argument is AFTER object has been destroyed" do
it "returns nil" do
widget.update_attribute(:name, "foobar")
widget.destroy
expect(widget.paper_trail.version_at(Time.now)).to be_nil
end
end
end
describe "#whodunnit", versioning: true do
context "no block given" do
it "raises an error" do
expect {
widget.paper_trail.whodunnit("Ben")
}.to raise_error(ArgumentError, "expected to receive a block")
end
end
context "block given" do
let(:orig_name) { FFaker::Name.name }
let(:new_name) { FFaker::Name.name }
before do
PaperTrail.whodunnit = orig_name
expect(widget.versions.last.whodunnit).to eq(orig_name) # persist `widget`
end
it "modifies value of `PaperTrail.whodunnit` while executing the block" do
widget.paper_trail.whodunnit(new_name) do
expect(PaperTrail.whodunnit).to eq(new_name)
widget.update_attributes(name: "Elizabeth")
end
expect(widget.versions.last.whodunnit).to eq(new_name)
end
it "reverts value of whodunnit to previous value after executing the block" do
widget.paper_trail.whodunnit(new_name) { |w|
w.update_attributes(name: "Elizabeth")
}
expect(PaperTrail.whodunnit).to eq(orig_name)
end
it "reverts to previous value, even if error within block" do
expect {
widget.paper_trail.whodunnit(new_name) { raise }
}.to raise_error(RuntimeError)
expect(PaperTrail.whodunnit).to eq(orig_name)
end
end
end
describe "#touch_with_version", versioning: true do
it "creates a version" do
count = widget.versions.size
# Travel 1 second because MySQL lacks sub-second resolution
Timecop.travel(1) do
widget.paper_trail.touch_with_version
end
expect(widget.versions.size).to eq(count + 1)
end
it "increments the `:updated_at` timestamp" do
time_was = widget.updated_at
# Travel 1 second because MySQL lacks sub-second resolution
Timecop.travel(1) do
widget.paper_trail.touch_with_version
end
expect(widget.updated_at).to be > time_was
end
end
describe "#update", versioning: true do
it "creates a version record" do
widget = Widget.create
assert_equal 1, widget.versions.length
widget.update_attributes(name: "Bugle")
assert_equal 2, widget.versions.length
end
end
describe ".paper_trail.enabled?" do
it "returns true" do
expect(Widget.paper_trail.enabled?).to eq(true)
end
end
describe ".disable" do
it "sets the `paper_trail.enabled?` to `false`" do
expect(Widget.paper_trail.enabled?).to eq(true)
Widget.paper_trail.disable
expect(Widget.paper_trail.enabled?).to eq(false)
end
after do
Widget.paper_trail.enable
end
end
describe ".enable" do
it "sets the `paper_trail.enabled?` to `true`" do
Widget.paper_trail.disable
expect(Widget.paper_trail.enabled?).to eq(false)
Widget.paper_trail.enable
expect(Widget.paper_trail.enabled?).to eq(true)
end
after do
Widget.paper_trail.enable
end
end
end