require 'lib/executors/stack_executor/stack_creation_waiter'

class Devops::Executor::StackExecutor
  RSpec.describe StackCreationWaiter, init_messages: true do
    let(:out) { double(:out, puts: nil, flush: nil) }
    let(:stack) { build(:stack) }
    let(:syncer) { described_class.new(stack, out) }

    before do
      allow(stack).to receive(:sync_status_and_events!)
      allow(stack).to receive(:events).and_return( [{'event_id' => 1}] )
      allow(syncer).to receive(:sleep)
    end

    def setup_statuses(statuses_array)
      statuses = statuses_array.to_enum
      allow(stack).to receive(:sync_status_and_events!) {
        stack.stack_status = statuses.next
      }
    end

    describe '#wait', stubbed_logger: true do
      it 'waits for stack creating to be finished' do
        setup_statuses(['CREATE_IN_PROGRESS'] * 10 + ['CREATE_COMPLETE'])
        expect(syncer).to receive(:sleep).at_least(10).times
        expect(stack).to receive(:sync_status_and_events!).at_least(10).times
        syncer.wait
      end

      it 'prints each message only once' do
        event1 = {'event_id' => 1, 'timestamp' => 't1'}
        event2 = {'event_id' => 2, 'timestamp' => 't2'}
        event3 = {'event_id' => 3, 'timestamp' => 't3'}

        allow(stack).to receive(:events).and_return([event1], [event1, event2], [event1, event2, event3])
        setup_statuses(['CREATE_IN_PROGRESS', 'CREATE_IN_PROGRESS', 'CREATE_IN_PROGRESS', 'CREATE_COMPLETE'])
        expect(syncer).to receive(:sleep).exactly(4).times
        expect(out).to receive(:puts).with(/t1/).once.ordered
        expect(out).to receive(:puts).with(/t2/).once.ordered
        expect(out).to receive(:puts).with(/t3/).once.ordered
        syncer.wait
      end

      it 'updates stack when status is changed' do
        setup_statuses(['CREATE_IN_PROGRESS', 'CREATE_IN_PROGRESS', 'ROLLBACK_IN_PROGRESS', 'ROLLBACK_COMPLETE'])
        expect(stack).to receive(:save).exactly(3).times
        syncer.wait
      end

      context 'when stack creating was successful' do
        it 'updates stack in DB when stack creating is finished and returns 0' do
          setup_statuses(['CREATE_COMPLETE'])
          expect(stack).to receive(:save)
          expect(out).to receive(:puts).with(/CREATE_COMPLETE/)
          expect(syncer.wait).to be_ok
        end
      end

      context 'when stack was rollbacked' do
        it 'returns 1 (:stack_rolled_back)' do
          setup_statuses(['CREATE_IN_PROGRESS', 'ROLLBACK_IN_PROGRESS', 'ROLLBACK_COMPLETE'])
          expect(out).to receive(:puts).with(/ROLLBACK_COMPLETE/)
          expect(syncer.wait).to be_stack_rolled_back
        end
      end

      context 'when unkown stack status was found' do
        it 'returns 2 (:unkown_status)' do
          setup_statuses(['CREATE_IN_PROGRESS', 'unknown'])
          expect(out).to receive(:puts).with(/unknown/)
          expect(syncer.wait).to be_unkown_status
        end
      end

      context "when stack hasn't been synced in an hour" do
        it 'returns 3 (:timeout)' do
          allow(stack).to receive(:stack_status) {'CREATE_IN_PROGRESS'}
          expect(out).to receive(:puts).with(/hasn't been synced/)
          expect(syncer.wait).to be_timeout
        end
      end

      context 'when an error occured during syncing', stubbed_logger: true do
        it 'returns 5 (:error)' do
          setup_statuses(['CREATE_IN_PROGRESS', 'CREATE_COMPLETE'])
          allow(stack).to receive(:save) { raise }
          expect(syncer.wait.code).to eq 5
        end
      end
    end

  end
end