# encoding: UTF-8 require File.expand_path('../../test_helper', __FILE__) class PublishingTest < MiniTest::Spec def self.site_root @site_root end def self.startup @site_root = Dir.mktmpdir template_source = File.expand_path(File.dirname(__FILE__) / "../fixtures/templates/publishing/templates") FileUtils.cp_r(template_source, @site_root) end def self.shutdown S::Content.delete_all_revisions! rescue nil end @@now = Time.now def setup @site = setup_site(self.class.site_root) @now = @@now Site.publishing_method = :immediate end context "publishing" do setup do stub_time(@@now) # DB.logger = Logger.new($stdout) Content.delete class Page < Spontaneous::Page field :title, :string, :default => "New Page" box :things end class Piece < Spontaneous::Piece box :things end count = 0 2.times do |i| c = Page.new(:uid => i) 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 end teardown do PublishingTest.send(:remove_const, :Page) rescue nil PublishingTest.send(:remove_const, :Piece) rescue nil Content.delete DB.logger = nil end context "site publishing" do setup do Content.delete @revision = 3 State.delete State.create(:revision => @revision, :published_revision => 2) State.revision.should == @revision end teardown do Content.delete_revision(@revision) Revision.delete end should "delete any conflicting revision tables" do S::Content.create_revision(3) Site.publish_all end should "issue a publish_all if passed page id list including all pages (in any order)" do skip "Implement after scheduled publishes" end should "publish all" do Content.expects(:publish).with(@revision, nil) Site.publish_all end should "record date and time of publish" do Content.expects(:publish).with(@revision, nil) Revision.expects(:create).with(:revision => @revision, :published_at => @now) Site.publish_all end should "bump revision after a publish" do Site.publish_all Site.revision.should == @revision + 1 Site.published_revision.should == @revision end should "not delete scheduled changes after an exception during publish" do skip "Implement after scheduled publishes" end should "set Site.pending_revision before publishing" do Content.expects(:publish).with(@revision, nil) { Site.pending_revision == @revision } Site.publish_all end should "reset Site.pending_revision after publishing" do Site.publish_all Site.pending_revision.should be_nil end should "not update first_published or last_published if rendering fails" do c = Content.create c.first_published_at.should be_nil Spontaneous::Page.expects(:order).returns([c]) # c.expects(:render).raises(Exception) begin silence_logger { Site.publish_all } # Site.publish_all rescue Exception; end c.reload Content.with_editable do c.first_published_at.should be_nil end end should "clean up state on publishing failure" do Site.pending_revision.should be_nil Content.revision_exists?(@revision).should be_false # don't like peeking into implementation here but don't know how else # to simulate the right error root = Page.create() Spontaneous::Page.expects(:order).returns([root]) output = root.output(:html) output.expects(:render_using).raises(Exception) root.expects(:outputs).at_least_once.returns([output]) begin silence_logger { Site.publish_all } rescue Exception; end Site.pending_revision.should be_nil Content.revision_exists?(@revision).should be_false begin silence_logger { Site.publish_pages([change1]) } rescue Exception; end Site.pending_revision.should be_nil Content.revision_exists?(@revision).should be_false end end context "rendering" do setup do @revision = 2 Content.delete_revision(@revision) Content.delete State.delete State.create(:revision => @revision, :published_revision => 2) Site.revision.should == @revision class ::PublishablePage < Page; end class ::DynamicPublishablePage < Page; end PublishablePage.box :box1 DynamicPublishablePage.box :box1 PublishablePage.layout :"static" PublishablePage.layout :"dynamic" DynamicPublishablePage.layout :"static" DynamicPublishablePage.layout :"dynamic" DynamicPublishablePage.request { show "/about" } @home = PublishablePage.create(:title => 'Home') @home.layout = :"dynamic" @about = PublishablePage.create(:title => "About", :slug => "about") @blog = PublishablePage.create(:title => "Blog", :slug => "blog") @post1 = PublishablePage.create(:title => "Post 1", :slug => "post-1") @post2 = PublishablePage.create(:title => "Post 2", :slug => "post-2") @post3 = PublishablePage.create(:title => "Post 3", :slug => "post-3") @news = DynamicPublishablePage.create(:title => "News", :slug => "news") @contact = DynamicPublishablePage.create(:title => "Contact", :slug => "contact") @contact.layout = :dynamic @home.box1 << @about @home.box1 << @blog @home.box1 << @news @home.box1 << @contact @blog.box1 << @post1 @blog.box1 << @post2 @blog.box1 << @post3 @pages = [@home, @about, @blog, @news, @post1, @post2, @post3] @pages.each { |p| p.save } Site.publish_all end teardown do Content.delete_revision(@revision) Content.delete_revision(@revision+1) Content.delete State.delete Object.send(:remove_const, :PublishablePage) rescue nil Object.send(:remove_const, :DynamicPublishablePage) rescue nil end should "put its files into a numbered revision directory" do Spontaneous.revision_dir(2).should == Pathname.new(@site.root / 'cache/revisions' / "00002").realpath.to_s end should "symlink the latest revision to 'current'" do revision_dir = @site.revision_root / "00002" current_dir = @site.revision_root / "current" File.exists?(current_dir).should be_true File.symlink?(current_dir).should be_true File.readlink(current_dir).should == revision_dir end should "have access to the current revision within the templates" do revision_dir = @site.revision_root / "00002" File.read(revision_dir / "static/about.html").should == "Page: 'About' 2\n" end should "produce rendered versions of each page" do revision_dir = @site.revision_root / "00002" file = result = nil @pages.each do |page| case page.slug when "" # root is a dynamic page with no request handler file = revision_dir / "dynamic/index.html.cut" result = "Page: '#{page.title}' {{Time.now.to_i}}\n" when "news" # news produces a static template but has a request handler file = revision_dir / "protected/news.html" result = "Page: 'News' 2\n" when "contact" # contact is both dynamic and has a request handler file = revision_dir / "dynamic/contact.html.cut" result = "Page: 'Contact' {{Time.now.to_i}}\n" else # the other pages are static file = revision_dir / "static#{page.path}.html" result = "Page: '#{page.title}' 2\n" end assert File.exists?(file), "File '#{file}' should exist" File.read(file).should == result end revision_dir = @site.revision_root / "00002" Dir[S.root / "public/**/*"].each do |public_file| site_file = public_file.gsub(%r(^#{S.root}/), '') publish_file = revision_dir / site_file File.exists?(publish_file).should be_true end end should "generate a config.ru file pointing to the current root" do config_file = @site.revision_root / "00002/config.ru" File.exists?(config_file).should be_true File.read(config_file).should =~ %r(#{Spontaneous.root}) end should "transparently support previously unknown formats by assuming a simple HTML like rendering model" do PublishablePage.add_output :rtf Content.delete_revision(@revision+1) Site.publish_all File.read("#{@site.revision_root}/00003/static/index.rtf").should == "RICH!\n" end should "respect a format's #dynamic? setting when deciding a rendered templates location xxx" do PublishablePage.add_output :rtf, :dynamic => true Content.delete_revision(@revision+1) Site.publish_all File.read("#{@site.revision_root}/00003/dynamic/index.rtf.cut").should == "RICH!\n" end context "hooks & triggers" do setup do end should "after_publish hook should be fired after publish is complete" do publish1 = mock publish1.expects(:finished).with(@revision+1) publish2 = mock publish2.expects(:finished) Site.after_publish do |revision| publish1.finished(revision) end Site.after_publish do publish2.finished end Content.delete_revision(@revision+1) Site.publish_all Site.published_revision.should == @revision+1 end should "abort publish if hook raises error" do published_revision = @revision+1 Site.pending_revision.should be_nil Content.revision_exists?(published_revision).should be_false Site.after_publish do |revision| raise "Boom" end begin silence_logger { Site.publish_all } rescue Exception; end Site.pending_revision.should be_nil Site.published_revision.should == @revision Content.revision_exists?(published_revision).should be_false S::Revision.first(:revision => published_revision).should be_nil previous_root = Spontaneous.revision_dir(@revision) published_root = Spontaneous.revision_dir(published_revision) symlink = Pathname.new Spontaneous.revision_dir symlink.realpath.to_s.should == Pathname.new(previous_root).realpath.to_s end end end end end