# encoding: UTF-8

require File.expand_path('../../test_helper', __FILE__)

# Store.new(backend_class, options) #=> Store
# Store#revision(revision_number)   #=> Revision
# Store#revisions #=> [Fixnum]
# Revision#static_template(output) #=> (String or nil)
# Revision#dynamic_template(output, request_method = "GET") #=> (String or nil)
# Revision#transaction #=> Transaction
# Revision#delete
# Transaction#write(output, template)
# Transaction#commit
# Transaction#rollback
# Backend#store_static(revision, key, template)
# Backend#store_protected(revision, key, template)
# Backend#store_dynamic(revision, key, template)
# Backend#load_static(revision, key) #=> String
# Backend#load_protected(revision, key) #=> String
# Backend#load_dynamic(revision, key) #=> String
# Backend#delete_revision(revision)
# Backend#revisions #=> [Fixnum]

describe "Output store" do
  start do
    site = setup_site
    let(:site) { site  }

    class CustomPage < Page
      add_output :xml, dynamic: true, language: 'php'
      add_output :json
      box :stuff
    end

    home = CustomPage.create
    page = CustomPage.new(slug: 'one')
    home.stuff << page
    page.save
    home.save
    let(:home) { home }
    let(:page) { page }
  end

  finish do
    Content.delete
    Object.send(:remove_const, :CustomPage) rescue nil
    teardown_site
  end

  describe "File" do
    let(:root)  { Dir.mktmpdir }
    let(:store) { Spontaneous::Output::Store::File.new(root) }
    let(:revision) { 100 }
    let(:revision_path) { ::File.join(root, "00100") }


    it "puts static files under 'static'" do
      store.store_static(revision, "/one.html", "*template*")
      ::File.read(::File.join(revision_path, 'static', 'one.html')).must_equal "*template*"
    end

    it "puts protected files under 'protected'" do
      store.store_protected(revision, "/one.html", "*template*")
      ::File.read(::File.join(revision_path, 'protected', 'one.html')).must_equal "*template*"
    end

    it "puts dynamic files under 'dynamic'" do
      store.store_dynamic(revision, "/one.html", "*template*")
      ::File.read(::File.join(revision_path, 'dynamic', 'one.html')).must_equal "*template*"
    end

    it "enables the retrieval of available revisions" do
      store.store_static(1, "/one.html", "*template*")
      store.store_protected(2, "/one.html", "*template*")
      store.store_dynamic(3, "/one.html", "*template*")
      store.revisions.must_equal [1, 2, 3]
    end

    it "enables the retrieval of templates" do
      store.store_static(revision, "/one.html", "*template*")
      store.load_static(revision, "/one.html").read.must_equal "*template*"
    end

    it "returns a File object for static templates" do
      store.store_static(revision, "/one.html", "*template*")
      result = store.load_static(revision, "/one.html")
      result.must_be_instance_of ::File
      result.to_path.must_equal ::File.join(revision_path, 'static', 'one.html')
      result.read.must_equal "*template*"
    end

    it "returns a File object for dynamic templates" do
      store.store_dynamic(revision, "/one.html", "*template*")
      result = store.load_dynamic(revision, "/one.html")
      result.must_be_instance_of ::File
      result.to_path.must_equal ::File.join(revision_path, 'dynamic', 'one.html')
      result.read.must_equal "*template*"
    end

    it "sets the returned template's encoding as UTF-8" do
      store.store_static(revision, "/one.html", "«küßî»")
      result = store.load_static(revision, "/one.html")
      result.external_encoding.must_equal Encoding::UTF_8
      result.read.must_equal "«küßî»"
    end

    it "returns nil when attempting to retrieve a non-existant template" do
      store.load_static(revision, "/faile.html").must_equal nil
    end

    it "puts all written keys into a transaction if given" do
      transaction = []
      store.store_static(revision, "/one.html", "*template*", transaction)
      store.store_protected(revision, "/one.html", "*template*", transaction)
      store.store_dynamic(revision, "/one.html", "*template*", transaction)
      transaction.length.must_equal 3
      transaction.must_equal ["00100/static/one.html", "00100/protected/one.html", "00100/dynamic/one.html"]
    end

    it "enables registration of a revision" do
      store.add_revision(100, ["100/one.html", "100/two.html", "100/three.html"])
      store.add_revision(101, ["101/one.html", "101/two.html", "101/three.html"])
      store.revisions.must_equal [100, 101]
    end

    it "allows for the deletion of a revision" do
      store.store_static(100, "/one.html", "*template*")
      store.store_static(100, "/another/two.html", "*template*")
      ::File.exist?(::File.join(revision_path, 'static', 'one.html')).must_equal true
      ::File.exist?(::File.join(revision_path, 'static', 'another/two.html')).must_equal true
      store.delete_revision(100)
      ::File.exist?(revision_path).must_equal false
    end
  end

  describe "Moneta" do
    let(:store) { Spontaneous::Output::Store::Moneta.new(:Memory) }
    let(:r) { 100 }
    let(:revision) { Spontaneous::Output::Store::Revision.new(r, store) }
    let(:transaction) { revision.transaction }


    it "allows the storage & retrieval of static templates" do
      store.store_static(r, "/one.html", "*template*")
      result = store.load_static(r, "/one.html")
      result.respond_to?(:read).must_equal true
      result.read.must_equal "*template*"
      result.respond_to?(:path).must_equal true
      result.respond_to?(:to_path).must_equal false
      result.path.must_equal "/#{r}/static/one.html"
    end

    it "allows the storage & retrieval of protected templates" do
      store.store_protected(r, "/one.html", "*template*")
      result = store.load_protected(r, "/one.html")
      result.respond_to?(:read).must_equal true
      result.read.must_equal "*template*"
      result.respond_to?(:path).must_equal true
      result.respond_to?(:to_path).must_equal false
      result.path.must_equal "/#{r}/protected/one.html"
    end

    it "allows the storage & retrieval of dynamic templates" do
      store.store_dynamic(r, "/one.html", "*template*")
      result = store.load_dynamic(r, "/one.html")
      result.respond_to?(:read).must_equal true
      result.read.must_equal "*template*"
      result.respond_to?(:path).must_equal true
      result.respond_to?(:to_path).must_equal false
      result.path.must_equal "/#{r}/dynamic/one.html"
    end

    it "puts all written keys into a transaction if given" do
      transaction = []
      store.store_static(r, "/one.html", "*template*", transaction)
      store.store_protected(r, "/two.html", "*template*", transaction)
      store.store_dynamic(r, "/three.html", "*template*", transaction)
      transaction.length.must_equal 3
      transaction.must_equal ["100:static:/one.html", "100:protected:/two.html", "100:dynamic:/three.html"]
    end

    it "enables registration of a revision" do
      store.add_revision(100, ["100:static:/one.html", "100:static:/two.html", "100:static:/three.html"])
      store.add_revision(101, ["101:static:/one.html", "101:static:/two.html", "101:static:/three.html"])
      store.revisions.must_equal [100, 101]
    end

    it "enables removal of a revision" do
      keys = []
      store.store_static(r, "/one.html", "*template*", keys)
      store.store_protected(r, "/two.html", "*template*", keys)
      store.store_dynamic(r, "/three.html", "*template*", keys)
      store.add_revision(100, keys)
      keys.each do |key|
        store.backend.key?(key).must_equal true
      end
      store.add_revision(101, ["101:static:/one.html", "101:protected:/two.html", "101:dynamic:/three.html"])
      store.revisions.must_equal [100, 101]
      store.delete_revision(100)
      store.revisions.must_equal [101]
      hash = store.backend.backend
      keys.each do |key|
        hash.key?(key).must_equal false
      end
      hash.key?(store.revision_key(100)).must_equal false
    end


  end

  describe "Transaction" do
    let(:store) { Spontaneous::Output::Store::Moneta.new(:Memory) }
    let(:r) { 100 }
    let(:transaction) { Spontaneous::Output::Store::Transaction.new(r, store) }
    let(:output_html) { page.output(:html) }
    let(:output_xml) { page.output(:xml) }
    let(:output_json) { page.output(:json) }

    it "tracks all keys written to it" do
      transaction.store(output_html, true, "*template*")
      transaction.store(output_xml, false, "*template*")
      transaction.store(output_json, false, "*template*")
      transaction.commit
      store.revisions.must_equal [ 100 ]
    end

    it "clears up the store after a rollback" do
      keys = ["100:static:/one.html"]
      transaction.store(output_html, false, "*template*")
      keys.each do |key|
        store.backend.key?(key).must_equal true
      end
      transaction.rollback
      store.revisions.must_equal []
      keys.each do |key|
        store.backend.key?(key).must_equal false
      end
    end

    it "ignores a rollback after a commit" do
      transaction.store(output_html, true, "*template*")
      transaction.store(output_xml, false, "*template*")
      transaction.store(output_json, false, "*template*")
      transaction.commit
      transaction.rollback
      store.revisions.must_equal [ 100 ]
    end

    it "writes to index.xxx when given the site root" do
      store.expects(:store_static).with(r, "/index.html", "*template*", transaction)
      transaction.store(home.output(:html), false, "*template*")
      store.expects(:store_dynamic).with(r, "/index.xml.php", "*template*", transaction)
      transaction.store(home.output(:xml), false, "*template*")
    end

    describe "template partitioning" do
      it "writes dynamic templates to dynamic partition" do
        store.expects(:store_dynamic).with(r, "/one.html.cut", "*template*", transaction)
        transaction.store(output_html, true, "*template*")
      end

      it "writes dynamic outputs to dynamic partition" do
        store.expects(:store_dynamic).with(r, "/one.xml.php", "*template*", transaction)
        transaction.store(output_xml, false, "*template*")
      end

      it "writes protected templates to protected partition" do
        page.expects(:dynamic?).returns(true)
        store.expects(:store_protected).with(r, "/one.html", "*template*", transaction)
        transaction.store(output_html, false, "*template*")
      end

      it "writes static templates to the static partition" do
        store.expects(:store_static).with(r, "/one.html", "*template*", transaction)
        transaction.store(output_html, false, "*template*")
      end
    end
  end

  describe "Revision" do
    let(:r) { 100 }
    let(:store) { Spontaneous::Output::Store::Moneta.new(:Memory) }
    let(:revision) { Spontaneous::Output::Store::Revision.new(r, store) }
    let(:output_html) { page.output(:html) }
    let(:output_xml) { page.output(:xml) }
    let(:output_json) { page.output(:json) }

    before do
      transaction = revision.transaction
      transaction.store(output_html, false, "HTML")
      transaction.store(output_xml, false, "XML")
      transaction.commit
    end

    it "provides a transaction for writing" do
      revision.transaction.must_be_instance_of Spontaneous::Output::Store::Transaction
      revision.transaction.revision.must_equal r
    end

    it "allows for reading a static template" do
      template = revision.static_template(output_html)
      template.read.must_equal "HTML"
    end

    it "allows for reading a dynamic template" do
      template = revision.dynamic_template(output_xml)
      template.read.must_equal "XML"
    end

    it "doesn't return a dynamic template as a static one" do
      template = revision.static_template(output_xml)
      template.must_equal nil
    end

    it "allows for deletion of the revision" do
      store.expects(:delete_revision).with(r)
      revision.delete
    end
  end

  describe "Store" do
    let(:store) { Spontaneous::Output::Store.new(:Memory) }
    let(:output_html) { page.output(:html) }
    let(:output_xml) { page.output(:xml) }

    before do
      transaction = store.revision(20).transaction
      transaction.store(output_html, false, "HTML")
      transaction.store(output_xml, false, "XML")
      transaction.commit
    end

    it "will provide a revision instance" do
      store.revision(20).must_be_instance_of Spontaneous::Output::Store::Revision
      store.revision(20).revision.must_equal 20
    end

    it "can provide a list of revisions" do
      store.revisions.must_equal [20]
    end
  end
end