# frozen_string_literal: true

require 'spec_helper'

describe Delayed::Heartbeat do
  let(:current_version) { 'current version' }
  let!(:active_worker) { create_worker_model(name: 'active_worker', version: current_version) }

  let!(:active_job) do
    Delayed::Job.create!(locked_by: active_worker.name,
                         locked_at: active_worker.last_heartbeat_at,
                         payload_object: TestJob.new)
  end

  let!(:orphaned_job) do
    Delayed::Job.create!(locked_by: dead_worker.name,
                         locked_at: dead_worker.last_heartbeat_at,
                         payload_object: TestJob.new)
  end

  shared_examples "it destroys a worker and unlocks its jobs" do |expected_orphaned_job_attempts: nil|
    specify { expect(active_worker).not_to have_been_destroyed }
    specify { expect(active_job.reload.locked_by).not_to be_nil }
    specify { expect(active_job.reload.locked_at).not_to be_nil }
    specify { expect(active_job.reload.attempts).to eq(0) }
    specify { expect(active_job.reload.failed_at).to be_nil }

    specify { expect(dead_worker).to have_been_destroyed }
    specify { expect(orphaned_job.reload.locked_by).to be_nil }
    specify { expect(orphaned_job.reload.locked_at).to be_nil }
    specify { expect(orphaned_job.reload.attempts).to eq(expected_orphaned_job_attempts) }
    specify { expect(orphaned_job.reload.failed_at).to be_nil }
  end

  describe ".delete_timed_out_workers" do
    let!(:dead_worker) do
      create_worker_model(name: 'dead_worker',
                          last_heartbeat_at: Time.now - Delayed::Heartbeat.configuration.heartbeat_timeout_seconds - 1)
    end

    let(:max_attempts) { 5 }

    let!(:failed_orphaned_job) do
      Delayed::Job.create!(locked_by: dead_worker.name, locked_at: dead_worker.last_heartbeat_at,
          payload_object: TestJobWithCallbacks.new) do |job|
        job.attempts = max_attempts - 1
      end
    end

    before do
      TestJobWithCallbacks.clear
      @old_attempts = Delayed::Worker.max_attempts
      Delayed::Worker.max_attempts = max_attempts

      Delayed::Heartbeat.delete_timed_out_workers
    end

    after do
      Delayed::Worker.max_attempts = @old_attempts
    end

    specify { expect(failed_orphaned_job.reload.failed_at).to be_present }
    specify { expect(TestJobWithCallbacks.called_callbacks).to eq [:failure] }

    it_behaves_like "it destroys a worker and unlocks its jobs", expected_orphaned_job_attempts: 1
  end

  describe ".delete_workers_with_different_version" do
    let!(:dead_worker) { create_worker_model(name: 'old_worker', version: 'old version') }

    before do
      Delayed::Heartbeat.delete_workers_with_different_version(current_version)
    end

    it_behaves_like "it destroys a worker and unlocks its jobs", expected_orphaned_job_attempts: 0
  end

  def create_worker_model(attributes)
    Delayed::Heartbeat::Worker.create!(attributes)
  end

end
