# encoding: UTF-8 require File.expand_path('../../test_helper', __FILE__) describe "Revisions" do Revision = Spontaneous::Publishing::Revision start do site = setup_site let(:site) { site } Content.delete class Page field :title, :string, :default => "New Page" box :things end class Piece box :things end root = Page.create(:uid => "root") count = 0 2.times do |i| c = Page.new(:uid => i) root.things << c count += 1 2.times do |j| d = Piece.new(:uid => "#{i}.#{j}") c.things << d count += 1 2.times do |k| d.things << Page.new(:uid => "#{i}.#{j}.#{k}") d.save count += 1 end end c.save end root.save Revision.history_dataset(Content).delete Revision.archive_dataset(Content).delete Revision.delete_all(Content) let(:root) { root } end finish do Object.send(:remove_const, :Page) rescue nil Object.send(:remove_const, :Piece) rescue nil Content.delete teardown_site end before do @now = Time.now stub_time(@now) end after do Revision.delete_all(Content) end describe "data sources" do it "have the right names" do Content.revision_table(23).must_equal :'__r00023_content' Content.revision_table(nil).must_equal :'content' end it "be recognisable" do refute Content.revision_table?('content') assert Content.revision_table?('__r00023_content') refute Content.revision_table?('__r00023_not') refute Content.revision_table?('subscribers') end it "be switchable within blocks" do Content.with_revision(23) do Content.revision.must_equal 23 Content.mapper.current_revision.must_equal 23 end Content.mapper.current_revision.must_be_nil end it "know which revision is active" do Content.with_revision(23) do Content.revision.must_equal 23 end end it "understand with_editable" do Content.with_revision(23) do Content.mapper.current_revision.must_equal 23 Content.with_editable do Content.mapper.current_revision.must_be_nil end end end it "understand with_published" do site.stubs(:published_revision).returns(99) Content.with_published(site) do Content.mapper.current_revision.must_equal 99 end end it "be stackable" do Content.with_revision(23) do Content.mapper.current_revision.must_equal 23 Content.with_revision(24) do Content.mapper.current_revision.must_equal 24 end end end it "reset datasource after an exception" do begin Content.with_revision(23) do Content.mapper.current_revision.must_equal 23 raise "Fail" end rescue Exception end Content.mapper.current_revision.must_be_nil end it "read revision from the environment if present" do ENV["SPOT_REVISION"] = '1001' Content.with_published(site) do Content.mapper.current_revision.must_equal 1001 end ENV.delete("SPOT_REVISION") end describe "subclasses" do before do class ::Subclass < Page; end end after do Object.send(:remove_const, :Subclass) end it "set all subclasses to use the same dataset" do Content.with_revision(23) do Subclass.revision.must_equal 23 Subclass.mapper.current_revision.must_equal 23 # piece wasn't loaded until this point Piece.mapper.current_revision.must_equal 23 end end end end describe "content revisions" do before do @revision = 1 end after do # Revision.delete_all(Content) end it "be testable for existance" do refute Content.revision_exists?(@revision) Revision.create(Content, @revision) assert Revision.exists?(Content, @revision) end it "not be deletable if their revision is nil" do Revision.delete(Content, nil) assert Content.db.table_exists?(:content) end it "be deletable en masse" do revisions = (1..10).to_a tables = revisions.map { |i| Content.revision_table(i).to_sym } revisions.each do |r| Content.history_dataset.insert(:revision => r, :uid => "revision-#{r}") Content.archive_dataset.insert(:revision => r, :uid => "archive-#{r}") end Content.history_dataset.count.must_equal 10 Content.archive_dataset.count.must_equal 10 tables.each do |t| DB.create_table(t){Integer :id} rescue nil end tables.each do |t| assert DB.tables.include?(t) end Revision.delete_all(Content) tables.each do |t| refute DB.tables.include?(t) end Content.history_dataset.count.must_equal 0 Content.archive_dataset.count.must_equal 0 end it "be creatable from current content" do refute DB.tables.include?(Content.revision_table(@revision).to_sym) Revision.create(Content, @revision) assert DB.tables.include?(Content.revision_table(@revision).to_sym) count = Content.count Content.with_revision(@revision) do Content.count.must_equal count Content.all.each do |published| published.revision.must_equal @revision Content.with_editable do e = Content[published.id] assert_content_equal(e, published, :revision) e.revision.must_be_nil end end end Content.history_dataset(@revision).count.must_equal count Content.history_dataset.select(:revision).group(:revision).all.must_equal [{:revision => 1}] Content.archive_dataset(@revision).count.must_equal count Content.archive_dataset.select(:revision).group(:revision).all.must_equal [{:revision => 1}] end it "have the correct indexes" do Revision.create(Content, @revision) content_indexes = DB.indexes(:content) published_indexes = DB.indexes(Content.revision_table(@revision)) # made slightly complex by the fact that the index names depend on the table names # (which are different) assert_has_elements published_indexes.values, content_indexes.values end it "only be kept until a new revision is available" do (0..2).each do |r| Revision.create(Content, @revision+r) Content.history_dataset(@revision+r).count.must_equal 15 Content.archive_dataset(@revision+r).count.must_equal 15 end Content.revision_tables.must_equal [:__r00001_content, :__r00002_content, :__r00003_content] Revision.cleanup(Content, @revision+2, 2) Content.revision_tables.must_equal [:__r00003_content] Content.history_dataset(@revision).count.must_equal 0 Content.archive_dataset(@revision).count.must_equal 15 Content.history_dataset(@revision+2).count.must_equal 15 end describe "incremental publishing" do before do @initial_revision = 1 @final_revision = 2 Revision.create(Content, @initial_revision) end it "duplicate changes to only a single item" do editable1 = Content.first(:uid => '1.0') editable1.label.must_be_nil editable1.label = "published" editable1.save editable2 = Content.first(:uid => '1.1') editable2.label = "unpublished" editable2.save editable2.reload Revision.patch(Content, @final_revision, [editable1.id]) editable1.reload Content.with_revision(@final_revision) do published = Content.first :id => editable1.id unpublished = Content.first :id => editable2.id assert_content_equal(published, editable1, :revision) assert_content_unequal(unpublished, editable2, :revision) end end it "publish additions to contents of a page" do editable1 = Content.first(:uid => '0') new_content = Piece.new(:uid => "new") editable1.things << new_content editable1.save Revision.patch(Content, @final_revision, [editable1.id]) new_content.reload editable1.reload Content.with_revision(@final_revision) do published1 = Content[editable1.id] published2 = Content[new_content.id] assert_content_equal(published2, new_content, :revision) assert_content_equal(published1, editable1, :revision) end end it "publish deletions to contents of page" do editable1 = Content.first(:uid => '0') deleted = editable1.contents.first editable1.contents.first.destroy Revision.patch(Content, @final_revision, [editable1.id]) editable1.reload Content.with_revision(@final_revision) do published1 = Content[editable1.id] assert_content_equal(published1, editable1, :revision) Content[deleted.id].must_be_nil end end it "not publish page additions" do editable1 = Content.first(:uid => '0') new_page = Page.new(:uid => "new") editable1.things << new_page editable1.save new_page.save Revision.patch(Content, @final_revision, [editable1.id]) new_page.reload editable1.reload Content.with_revision(@final_revision) do published1 = Content[editable1.id] published2 = Content[new_page.id] published2.must_be_nil assert_content_equal(published1, editable1, :revision) end end it "not publish changes to existing pages unless explicitly asked" do editable1 = Content.first(:uid => '0') editable1.things << Piece.new(:uid => "added") editable1.save editable2 = Content.first(:uid => '0.0.0') new_content = Piece.new(:uid => "new") editable2.things << new_content editable2.save Revision.patch(Content, @final_revision, [editable1.id]) editable1.reload editable2.reload new_content.reload Content.with_revision(@final_revision) do published1 = Content.first :id => editable1.id Content.first(:uid => "added").wont_be_nil published3 = Content.first :id => editable2.id assert_content_equal(published1, editable1, :revision) assert_content_unequal(published3, editable2, :revision) published3.uid.wont_equal "new" end Revision.patch(Content, @final_revision+1, [editable2.id]) editable1.reload editable2.reload new_content.reload Content.with_revision(@final_revision+1) do published1 = Content.first :id => editable1.id assert_content_equal(published1, editable1, :revision) published2 = Content.first :id => editable2.id assert_content_equal(published2, editable2, :revision) published3 = Content.first :id => editable2.contents.first.id # published3.must_equal editable2.contents.first assert_content_equal(published3, editable2.contents.first, :revision) end end it "insert an entry value into the parent of a newly added page when that page is published aaa" do editable1 = Content.first(:uid => '0') new_page = Page.new(:uid => "new") editable1.things << new_page editable1.save new_page.save Revision.patch(Content, @final_revision, [new_page.id]) new_page.reload editable1.reload Content.with_revision(@final_revision) do published1 = Content[editable1.id] published2 = Content[new_page.id] assert_content_equal(published2, new_page, :revision) editable1.entry_store.must_equal published1.entry_store end end it "choose a sensible position for entry into the parent of a newly added page" do editable1 = Content.first(:uid => '0') new_page1 = Page.new(:uid => "new1") new_page2 = Page.new(:uid => "new2") editable1.things << new_page1 editable1.things << new_page2 editable1.save new_page1.save new_page2.save Revision.patch(Content, @final_revision, [new_page2.id]) new_page1.reload new_page2.reload editable1.reload Content.with_revision(@final_revision) do published1 = Content[editable1.id] published2 = Content[new_page1.id] published3 = Content[new_page2.id] published2.must_be_nil assert_content_equal(published3, new_page2, :revision) editable1.entry_store.reject { |e| e[0] == new_page1.id }.must_equal published1.entry_store end end it "not duplicate entries when publishing pages for the first time" do editable1 = Content.first(:uid => '0') new_page1 = Page.new(:uid => "new1") new_page2 = Page.new(:uid => "new2") editable1.things << new_page1 editable1.things << new_page2 editable1.save new_page1.save new_page2.save Revision.patch(Content, @final_revision, [editable1.id, new_page2.id]) new_page1.reload new_page2.reload editable1.reload Content.with_revision(@final_revision) do published1 = Content[editable1.id] published2 = Content[new_page1.id] published3 = Content[new_page2.id] published2.must_be_nil assert_content_equal(published3, new_page2, :revision) assert_content_equal(published1, editable1, :revision) end end it "remove deleted pages from the published content" do page = Page.first :uid => "0" piece = page.things.first child = piece.things.first page.things.first.destroy Revision.patch(Content, @final_revision, [page.id]) Content.with_revision(@final_revision) do published_parent = Content[page.id] published_piece = Content[piece.id] published_page = Content[child.id] published_parent.must_equal page.reload published_piece.must_be_nil published_page.must_be_nil end end end end describe "publication timestamps" do before do @revision = 1 Revision.delete(Content, @revision+1) end it "set correct timestamps on first publish" do first = Content.first first.reload.first_published_at.must_be_nil first.reload.last_published_at.must_be_nil Revision.create(Content, @revision) first.reload.first_published_at.to_i.must_equal @now.to_i first.reload.last_published_at.to_i.must_equal @now.to_i first.reload.first_published_revision.must_equal @revision Content.with_editable do first.reload.first_published_at.to_i.must_equal @now.to_i first.reload.last_published_at.to_i.must_equal @now.to_i first.reload.first_published_revision.must_equal @revision end Content.with_revision(@revision) do first.reload.first_published_at.to_i.must_equal @now.to_i first.reload.last_published_at.to_i.must_equal @now.to_i first.reload.first_published_revision.must_equal @revision end end it "set correct timestamps on later publishes" do first = Content.first first.first_published_at.must_be_nil Revision.create(Content, @revision) first.reload.first_published_at.to_i.must_equal @now.to_i c = Page.create c.first_published_at.must_be_nil stub_time(@now + 100) Revision.create(Content, @revision+1) first.reload.first_published_at.to_i.must_equal @now.to_i first.reload.last_published_at.to_i.must_equal @now.to_i + 100 Content.with_editable do c = Content.first :id => c.id c.first_published_at.to_i.must_equal @now.to_i + 100 end Content.with_revision(@revision+1) do c = Content.first :id => c.id c.first_published_at.to_i.must_equal @now.to_i + 100 end end it "not set publishing date for items not published" do Revision.create(Content, @revision) page = Content.first page.uid = "fish" page.save added = Content.create added.first_published_at.must_be_nil Revision.patch(Content, @revision+1, [page]) page.first_published_at.to_i.must_equal @now.to_i added.first_published_at.must_be_nil added.last_published_at.must_be_nil end it "not set publishing dates if exception raised in passed block" do Content.first.first_published_at.must_be_nil begin Revision.create(Content, @revision) do raise "Fail" end rescue Exception; end Content.first.first_published_at.must_be_nil end it "delete revision tables if exception raised in passed block" do refute Revision.exists?(Content, @revision) begin Revision.create(Content, @revision) do assert Revision.exists?(Content, @revision) Content.revision.must_equal @revision raise "Fail" end rescue Exception; end refute Revision.exists?(Content, @revision) end it "always publish all if no previous revisions exist" do page = Content.first Content.filter(:first_published_at => nil).count.must_equal Content.count Revision.patch(Content, @revision, [page]) Content.filter(:first_published_at => nil).count.must_equal 0 end end end