# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Gitlab::Cluster::LifecycleEvents do
  using RSpec::Parameterized::TableSyntax

  # we create a new instance to ensure that we do not touch existing hooks
  let(:replica) { Class.new(described_class) }

  before do
    # disable blackout period to speed-up tests
    stub_config(shutdown: { blackout_seconds: 0 })
  end

  context 'outside of clustered environments' do
    where(:hook, :was_executed_immediately) do
      :on_worker_start             | true
      :on_before_fork              | false
      :on_before_graceful_shutdown | false
      :on_before_master_restart    | false
      :on_worker_stop              | false
    end

    with_them do
      it 'executes the given block immediately' do
        was_executed = false
        replica.public_send(hook, &proc { was_executed = true })

        expect(was_executed).to eq(was_executed_immediately)
      end
    end
  end

  context 'in clustered environments' do
    before do
      allow(Gitlab::Runtime).to receive(:puma?).and_return(true)
      replica.set_puma_options(workers: 2)
    end

    where(:hook, :execution_helper) do
      :on_worker_start             | :do_worker_start
      :on_before_fork              | :do_before_fork
      :on_before_graceful_shutdown | :do_before_graceful_shutdown
      :on_before_master_restart    | :do_before_master_restart
      :on_worker_stop              | :do_worker_stop
    end

    with_them do
      it 'requires explicit execution via do_* helper' do
        was_executed = false
        replica.public_send(hook, &proc { was_executed = true })

        expect { replica.public_send(execution_helper) }.to change { was_executed }.from(false).to(true)
      end
    end
  end

  describe '#call' do
    let(:name) { :my_hooks }

    subject { replica.send(:call, name, hooks) }

    context 'when many hooks raise exception' do
      let(:hooks) do
        [
          -> { raise 'Exception A' },
          -> { raise 'Exception B' }
        ]
      end

      context 'USE_FATAL_LIFECYCLE_EVENTS is set to default' do
        it 'only first hook is executed and is fatal' do
          expect(hooks[0]).to receive(:call).and_call_original
          expect(hooks[1]).not_to receive(:call)

          expect(Gitlab::ErrorTracking).to receive(:track_exception).and_call_original
          expect(replica).to receive(:warn).with('ERROR: The hook my_hooks failed with exception (RuntimeError) "Exception A".')

          expect { subject }.to raise_error(described_class::FatalError, 'Exception A')
        end
      end

      context 'when USE_FATAL_LIFECYCLE_EVENTS is disabled' do
        before do
          stub_const('Gitlab::Cluster::LifecycleEvents::USE_FATAL_LIFECYCLE_EVENTS', false)
        end

        it 'many hooks are executed and all exceptions are logged' do
          expect(hooks[0]).to receive(:call).and_call_original
          expect(hooks[1]).to receive(:call).and_call_original

          expect(Gitlab::ErrorTracking).to receive(:track_exception).twice.and_call_original
          expect(replica).to receive(:warn).twice.and_call_original

          expect { subject }.not_to raise_error
        end
      end
    end
  end
end