require 'db/mongo/models/stack/stack_aws'
require 'providers/aws'

RSpec.describe Devops::Model::StackAws, type: :model do
  let(:stack) { build(:stack_aws) }

  it 'is valid with factory attrs' do
    expect(build(:stack_aws)).to be_valid
  end

  describe 'validation rules:', stubbed_logger: true do
    include_examples 'field type validation', :id, :not_nil, :non_empty_string
    include_examples 'field type validation', :project, :not_nil, :non_empty_string
    include_examples 'field type validation', :environment, :not_nil, :non_empty_string
    include_examples 'field type validation', :stack_template, :not_nil, :non_empty_string
    include_examples 'field type validation', :name, :maybe_nil, :non_empty_string
    include_examples 'field type validation', :owner, :not_nil, :non_empty_string
    include_examples 'field type validation', :run_list, :maybe_empty_array, :run_list
    include_examples 'field type validation', :tags, :maybe_nil

    it 'validates provider' do
      stack.provider = nil
      expect(stack).not_to be_valid

      stack.provider = ''
      expect(stack).not_to be_valid
    end
  end

  describe '#to_hash' do
    it 'returns hash with several fields' do
      expect(stack.to_hash.keys).to include(*%w(provider project environment stack_template name owner run_list tags))
    end
  end

  describe '#initialize' do
    it 'sets provider to aws even if given attrs don\'t contain it' do
      expect(described_class.new.provider).to eq 'aws'
    end

    it 'sets tags to empty hash if it\'s blank' do
      begin
        expect(create(:stack_aws).tags).to eq({})
      ensure
        Devops::Model::StackAws.delete_all
      end
    end
  end


  describe '#create_stack_in_cloud!' do
    it 'calls create_stack method of provider instance' do
      expect(stack).to receive_message_chain('provider_instance.create_stack')
      stack.create_stack_in_cloud!('')
    end

    it 'raises InvalidRecord if stack with such name already exists in cloud' do
      allow(stack).to receive_message_chain('provider_instance.create_stack').and_raise(ProviderErrors::NameConflict)
      expect { stack.create_stack_in_cloud!('') }.to raise_error(InvalidRecord)
    end
  end

  describe '#delete_stack_in_cloud!' do
    it 'calls delete_stack method of provider instance' do
      expect(stack).to receive_message_chain('provider_instance.delete_stack').with(stack)
      stack.delete_stack_in_cloud!
    end
  end


  describe '#sync_status_and_events!' do
    let(:fresh_events) { double('fresh_events') }
    let(:provider) {
      instance_double(Provider::AwsConnector,
        stack_details: {stack_status: 'CREATE_COMPLETE'},
        stack_events: fresh_events
      )
    }

    before do
      allow(stack).to receive(:provider_instance) {provider}
    end

    it "get fresh stack details and updates stack status" do
      expect(provider).to receive(:stack_details)
      expect {stack.sync_status_and_events!}.to change {stack.stack_status}.from(nil).to('CREATE_COMPLETE')
    end

    it "get fresh stack events and stores it in @events" do
      expect(stack).to receive_message_chain('provider_instance.stack_events')
      expect {stack.sync_status_and_events!}.to change {stack.events}.from(nil).to(fresh_events)
    end
  end

  describe '#update_in_cloud!' do
    before { allow(stack).to receive_message_chain('provider_instance.update_stack') }

    it 'calls to method update_stack of provider instance' do
      expect(stack).to receive_message_chain('provider_instance.update_stack')
      stack.update_in_cloud!({})
    end

    it 'filters params to include only Parameters, TemplateURL and Capabilities' do
      permitted_keys = %w(Parameters TemplateURL Capabilities)
      permitted_hash = permitted_keys.inject({}) {|hash, key| hash[key] = 1; hash}
      unfiltered_hash = permitted_hash.merge('wrong' => 1)
      expect(stack).to receive_message_chain('provider_instance.update_stack').with(stack, permitted_hash)
      stack.update_in_cloud!(unfiltered_hash)
    end
  end

  describe '#change_stack_template!' do
    let(:new_template) { 'brand_new_template' }
    before do
      allow(stack).to receive(:update_in_cloud!)
    end

    it 'updates stack_template' do
      allow(stack).to receive(:stack_template_model) { build(:stack_template_aws) }
      expect(stack).to receive(:save)
      stack.change_stack_template!(new_template)
      expect(stack.stack_template).to eq new_template
    end
  end

  describe '.create' do
    subject { described_class.create({}, '') }

    before do
      allow_any_instance_of(described_class).to receive(:create_stack_in_cloud!)
      allow_any_instance_of(described_class).to receive(:sync_status_and_events!)
    end

    it "returns instance of #{described_class.name}" do
      expect(subject).to be_a(described_class)
    end

    it 'creates stack in AWS' do
      expect_any_instance_of(described_class).to receive(:create_stack_in_cloud!)
      subject
    end

    it 'synchronizes details' do
      expect_any_instance_of(described_class).to receive(:sync_status_and_events!)
      subject
    end
  end

  describe 'lock_persisting!', clean_db_after_example: true do
    it 'sets #persisting_is_locked to true' do
      stack = create(:stack, persisting_is_locked: false)
      expect(stack.persisting_is_locked).to be false
      stack.lock_persisting!
      expect(stack.persisting_is_locked).to be true
      expect(stack.reload.persisting_is_locked).to be true
    end
  end

  describe 'unlock_persisting!', clean_db_after_example: true do
    it 'sets #persisting_is_locked to false' do
      stack = create(:stack, persisting_is_locked: true)
      expect(stack.persisting_is_locked).to be true
      stack.unlock_persisting!
      expect(stack.persisting_is_locked).to be false
      expect(stack.reload.persisting_is_locked).to be false
    end
  end

end

