require 'spec_helper'

class MigrateModel < CouchRest::Model::Base
  use_database :migrations
  proxy_database_method :id
  proxy_for :migrate_proxy_models
  property :name
  property :value
  design { view :by_name }
end

class MigrateProxyModel < CouchRest::Model::Base
  proxied_by :migrate_model
  proxy_database_method :id
  proxy_for :migrate_proxy_nested_models
  property :name
  property :value
  design { view :by_name }
end

class MigrateProxyNestedModel < CouchRest::Model::Base
  proxied_by :migrate_proxy_model
  property :name
  property :value
  design { view :by_name }
end

RSpec::Matchers.define :database_matching do |database|
  match do |actual|
    actual.server == database.server && actual.name == database.name
  end
end

describe CouchRest::Model::Utils::Migrate do

  before :each do
    @module = CouchRest::Model::Utils::Migrate
  end

  describe "#load_all_models" do
    it "should not do anything if Rails is not available" do
      @module.load_all_models
    end

    context "when detect Rails" do
      before do
        Rails = double()
      end

      it "should reload models but should not load anything inside models/concerns folder" do
        allow(Rails).to receive(:root).and_return("")
        expect(Dir).to receive(:[]).with("app/models/**/*.rb").and_return(Dir[ File.join(MODEL_PATH, "**/*.rb") ])

        @module.load_all_models

        expect(defined?(Cat)).to eq("constant") # as an example
        expect(defined?(Attachable)).to eq(nil)
      end

      it "should raise a LoadError for invalid model paths" do
        allow(Rails).to receive(:root).and_return("")
        expect(Dir).to receive(:[]).with("app/models/**/*.rb").and_return(['failed_require'])

        expect {
          @module.load_all_models
        }.to raise_error(LoadError)
      end
    end
  end

  describe "migrations" do
    let!(:stdout) { $stdout }
    before :each do
      allow(CouchRest::Model::Base).to receive(:subclasses).and_return([MigrateModel, MigrateProxyModel, MigrateProxyNestedModel])
      $stdout = StringIO.new
    end

    after :each do
      $stdout = stdout
    end

    describe "#all_models" do
      it "should migrate root subclasses of CouchRest::Model::Base" do
        expect(MigrateModel.design_docs.first).to receive(:migrate)
        @module.all_models
      end

      it "shouldn't migrate proxied subclasses with of CouchRest::Model::Base" do
        expect(MigrateProxyModel.design_docs.first).not_to receive(:migrate)
        expect(MigrateProxyNestedModel.design_docs.first).not_to receive(:migrate)
        @module.all_models
      end

      context "migration design docs" do
        before :each do
          @module.all_models
          @design_doc = MigrateModel.design_doc
        end

        it "shouldn't modify the original design doc if activate is false" do
          @design_doc.create_view(:by_name_and_id)
          @module.all_models(activate: false)

          fetched_ddoc = MigrateModel.get(@design_doc.id)
          expect(fetched_ddoc['views']).not_to have_key('by_name_and_id')
        end

        it "should remove a leftover migration doc" do
          @design_doc.create_view(:by_name_and_value)
          @module.all_models(activate: false)

          expect(MigrateModel.get("#{@design_doc.id}_migration")).not_to be_nil
          @module.all_models
          expect(MigrateModel.get("#{@design_doc.id}_migration")).to be_nil
        end
      end
    end

    describe "#all_models_and_proxies" do
      before :each do
        # clear data from previous test runs
        MigrateModel.all.each do |mm|
          next if mm.nil?
          mm.migrate_proxy_models.all.each do |mpm|
            mpm.migrate_proxy_nested_models.database.delete! rescue nil
          end rescue nil
          mm.migrate_proxy_models.database.delete!
          mm.destroy rescue nil
        end
        MigrateModel.database.recreate!
      end

      it "should migrate first level proxied subclasses of CouchRest::Model::Base" do
        mm = MigrateModel.new(name: "Migration").save
        expect(MigrateProxyModel.design_docs.first).to receive(:migrate).with(database_matching(mm.migrate_proxy_models.database)).and_call_original
        @module.all_models_and_proxies
      end

      it "should migrate the second level proxied subclasses of CouchRest::Model::Base" do
        mm = MigrateModel.new(name: "Migration").save
        mpm = mm.migrate_proxy_models.new(name: "Migration Proxy").save
        expect(MigrateProxyNestedModel.design_docs.first).to receive(:migrate).with(database_matching(mpm.migrate_proxy_nested_models.database))
        @module.all_models_and_proxies
      end

      context "migration design docs" do
        before :each do
          @mm_instance = MigrateModel.new(name: "Migration").save
          mpm = @mm_instance.migrate_proxy_models.new(name: "Migration Proxy")

          @module.all_models_and_proxies

          @design_doc = MigrateProxyModel.design_doc
        end

        it "shouldn't modify the original design doc if activate is false" do
          @design_doc.create_view(:by_name_and_id)
          @module.all_models_and_proxies(activate: false)

          fetched_ddoc = @mm_instance.migrate_proxy_models.get(@design_doc.id)
          expect(fetched_ddoc['views']).not_to have_key('by_name_and_id')
        end

        it "should remove a leftover migration doc" do
          @design_doc.create_view(:by_name_and_value)
          @module.all_models_and_proxies(activate: false)

          expect(@mm_instance.migrate_proxy_models.get("#{@design_doc.id}_migration")).not_to be_nil
          @module.all_models_and_proxies
          expect(@mm_instance.migrate_proxy_models.get("#{@design_doc.id}_migration")).to be_nil
        end
      end
    end
  end
end
