From 2e8babc771fd88d05334a9001d36e40aa86e0bd9 Mon Sep 17 00:00:00 2001
From: Sam Word <sword@gitlab.com>
Date: Thu, 8 Feb 2024 19:24:20 +0000
Subject: [PATCH] Added RefreshImportWorker to Bitbucket Cloud/Server stage
 workers

Adding RefreshImportWorker to Bitbucket Cloud and Server importer
stage workers so that their job IDs are refreshed when a stage begins
preventing StuckImportWroker from mistakenly thinking a slow import
is stuck.

Changelog: fixed
---
 .rubocop_todo/style/arguments_forwarding.yml  |  1 -
 .rubocop_todo/style/guard_clause.yml          |  1 -
 app/workers/all_queues.yml                    |  9 ++
 .../gitlab/bitbucket_import/stage_methods.rb  |  4 +
 .../bitbucket_server_import/stage_methods.rb  |  4 +
 .../gitlab/github_import/stage_methods.rb     |  2 +-
 .../stage/import_issues_notes_worker.rb       |  2 -
 .../stage/import_issues_worker.rb             |  2 -
 .../stage/import_lfs_objects_worker.rb        |  2 -
 .../import_pull_requests_notes_worker.rb      |  2 -
 .../stage/import_pull_requests_worker.rb      |  2 -
 .../stage/import_lfs_objects_worker.rb        |  2 -
 .../stage/import_notes_worker.rb              |  2 -
 .../stage/import_pull_requests_worker.rb      |  2 -
 .../refresh_import_jid_worker.rb              | 37 +------
 .../import/refresh_import_jid_worker.rb       | 41 ++++++++
 config/sidekiq_queues.yml                     |  2 +
 lib/gitlab/github_import/parallel_importer.rb |  2 +-
 spec/support/rspec_order_todo.yml             |  1 -
 .../stage_methods_shared_examples.rb          | 13 ++-
 .../stage_methods_shared_examples.rb          | 22 +++++
 .../stage_methods_shared_examples.rb          |  2 +-
 spec/workers/every_sidekiq_worker_spec.rb     |  2 +-
 .../refresh_import_jid_worker_spec.rb         | 98 +------------------
 .../import/refresh_import_jid_worker_spec.rb  | 87 ++++++++++++++++
 25 files changed, 192 insertions(+), 152 deletions(-)
 create mode 100644 app/workers/gitlab/import/refresh_import_jid_worker.rb
 create mode 100644 spec/workers/gitlab/import/refresh_import_jid_worker_spec.rb

diff --git a/.rubocop_todo/style/arguments_forwarding.yml b/.rubocop_todo/style/arguments_forwarding.yml
index b72751ed27946..53ae5e4a96c71 100644
--- a/.rubocop_todo/style/arguments_forwarding.yml
+++ b/.rubocop_todo/style/arguments_forwarding.yml
@@ -47,7 +47,6 @@ Style/ArgumentsForwarding:
     - 'app/workers/concerns/limited_capacity/worker.rb'
     - 'app/workers/concerns/reactive_cacheable_worker.rb'
     - 'app/workers/concerns/reenqueuer.rb'
-    - 'app/workers/gitlab/github_import/refresh_import_jid_worker.rb'
     - 'app/workers/pages_worker.rb'
     - 'config/initializers/6_labkit_middleware.rb'
     - 'config/initializers/active_record_table_definition.rb'
diff --git a/.rubocop_todo/style/guard_clause.yml b/.rubocop_todo/style/guard_clause.yml
index 9896356b1f603..3ff511a25b76c 100644
--- a/.rubocop_todo/style/guard_clause.yml
+++ b/.rubocop_todo/style/guard_clause.yml
@@ -234,7 +234,6 @@ Style/GuardClause:
     - 'app/workers/container_registry/migration/guard_worker.rb'
     - 'app/workers/deployments/hooks_worker.rb'
     - 'app/workers/deployments/link_merge_request_worker.rb'
-    - 'app/workers/gitlab/github_import/refresh_import_jid_worker.rb'
     - 'app/workers/google_cloud/create_cloudsql_instance_worker.rb'
     - 'app/workers/packages/cleanup/execute_policy_worker.rb'
     - 'app/workers/packages/maven/metadata/sync_worker.rb'
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 269fac4151dd4..1413638e903db 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -3198,6 +3198,15 @@
   :weight: 2
   :idempotent: true
   :tags: []
+- :name: import_refresh_import_jid
+  :worker_name: Gitlab::Import::RefreshImportJidWorker
+  :feature_category: :importers
+  :has_external_dependencies: false
+  :urgency: :low
+  :resource_boundary: :unknown
+  :weight: 1
+  :idempotent: true
+  :tags: []
 - :name: incident_management_close_incident
   :worker_name: IncidentManagement::CloseIncidentWorker
   :feature_category: :incident_management
diff --git a/app/workers/concerns/gitlab/bitbucket_import/stage_methods.rb b/app/workers/concerns/gitlab/bitbucket_import/stage_methods.rb
index 51e2f5cff227d..3af4f24438aff 100644
--- a/app/workers/concerns/gitlab/bitbucket_import/stage_methods.rb
+++ b/app/workers/concerns/gitlab/bitbucket_import/stage_methods.rb
@@ -16,6 +16,8 @@ module StageMethods
 
         sidekiq_options dead: false, retry: 6
 
+        sidekiq_options status_expiration: Gitlab::Import::StuckImportJob::IMPORT_JOBS_EXPIRATION
+
         sidekiq_retries_exhausted do |msg, e|
           Gitlab::Import::ImportFailureService.track(
             project_id: msg['args'][0],
@@ -33,6 +35,8 @@ def perform(project_id)
 
         return unless project
 
+        Import::RefreshImportJidWorker.perform_in_the_future(project_id, jid)
+
         import(project)
 
         info(project_id, message: 'stage finished')
diff --git a/app/workers/concerns/gitlab/bitbucket_server_import/stage_methods.rb b/app/workers/concerns/gitlab/bitbucket_server_import/stage_methods.rb
index cf5710e6108c3..304547ac1cc35 100644
--- a/app/workers/concerns/gitlab/bitbucket_server_import/stage_methods.rb
+++ b/app/workers/concerns/gitlab/bitbucket_server_import/stage_methods.rb
@@ -16,6 +16,8 @@ module StageMethods
 
         sidekiq_options dead: false, retry: 6
 
+        sidekiq_options status_expiration: Gitlab::Import::StuckImportJob::IMPORT_JOBS_EXPIRATION
+
         sidekiq_retries_exhausted do |msg, e|
           Gitlab::Import::ImportFailureService.track(
             project_id: msg['args'][0],
@@ -31,6 +33,8 @@ def perform(project_id)
 
         return unless (project = find_project(project_id))
 
+        Import::RefreshImportJidWorker.perform_in_the_future(project_id, jid)
+
         import(project)
 
         info(project_id, message: 'stage finished')
diff --git a/app/workers/concerns/gitlab/github_import/stage_methods.rb b/app/workers/concerns/gitlab/github_import/stage_methods.rb
index 69cf6f424af02..f6a46f50ca388 100644
--- a/app/workers/concerns/gitlab/github_import/stage_methods.rb
+++ b/app/workers/concerns/gitlab/github_import/stage_methods.rb
@@ -62,7 +62,7 @@ def perform(project_id)
           return
         end
 
-        RefreshImportJidWorker.perform_in_the_future(project.id, jid)
+        Import::RefreshImportJidWorker.perform_in_the_future(project.id, jid)
 
         client = GithubImport.new_client_for(project)
 
diff --git a/app/workers/gitlab/bitbucket_import/stage/import_issues_notes_worker.rb b/app/workers/gitlab/bitbucket_import/stage/import_issues_notes_worker.rb
index cbd67099086a8..af7927b80a819 100644
--- a/app/workers/gitlab/bitbucket_import/stage/import_issues_notes_worker.rb
+++ b/app/workers/gitlab/bitbucket_import/stage/import_issues_notes_worker.rb
@@ -12,8 +12,6 @@ class ImportIssuesNotesWorker # rubocop:disable Scalability/IdempotentWorker
         def import(project)
           waiter = importer_class.new(project).execute
 
-          project.import_state.refresh_jid_expiration
-
           AdvanceStageWorker.perform_async(
             project.id,
             { waiter.key => waiter.jobs_remaining },
diff --git a/app/workers/gitlab/bitbucket_import/stage/import_issues_worker.rb b/app/workers/gitlab/bitbucket_import/stage/import_issues_worker.rb
index 31a11d802c737..d2dac824e8139 100644
--- a/app/workers/gitlab/bitbucket_import/stage/import_issues_worker.rb
+++ b/app/workers/gitlab/bitbucket_import/stage/import_issues_worker.rb
@@ -12,8 +12,6 @@ class ImportIssuesWorker # rubocop:disable Scalability/IdempotentWorker
         def import(project)
           waiter = importer_class.new(project).execute
 
-          project.import_state.refresh_jid_expiration
-
           AdvanceStageWorker.perform_async(
             project.id,
             { waiter.key => waiter.jobs_remaining },
diff --git a/app/workers/gitlab/bitbucket_import/stage/import_lfs_objects_worker.rb b/app/workers/gitlab/bitbucket_import/stage/import_lfs_objects_worker.rb
index c88a1be34466b..328a66dcdca9e 100644
--- a/app/workers/gitlab/bitbucket_import/stage/import_lfs_objects_worker.rb
+++ b/app/workers/gitlab/bitbucket_import/stage/import_lfs_objects_worker.rb
@@ -12,8 +12,6 @@ class ImportLfsObjectsWorker # rubocop:disable Scalability/IdempotentWorker
         def import(project)
           waiter = importer_class.new(project).execute
 
-          project.import_state.refresh_jid_expiration
-
           AdvanceStageWorker.perform_async(
             project.id,
             { waiter.key => waiter.jobs_remaining },
diff --git a/app/workers/gitlab/bitbucket_import/stage/import_pull_requests_notes_worker.rb b/app/workers/gitlab/bitbucket_import/stage/import_pull_requests_notes_worker.rb
index 36d60c7246c58..fcabe80abc49d 100644
--- a/app/workers/gitlab/bitbucket_import/stage/import_pull_requests_notes_worker.rb
+++ b/app/workers/gitlab/bitbucket_import/stage/import_pull_requests_notes_worker.rb
@@ -12,8 +12,6 @@ class ImportPullRequestsNotesWorker # rubocop:disable Scalability/IdempotentWork
         def import(project)
           waiter = importer_class.new(project).execute
 
-          project.import_state.refresh_jid_expiration
-
           AdvanceStageWorker.perform_async(
             project.id,
             { waiter.key => waiter.jobs_remaining },
diff --git a/app/workers/gitlab/bitbucket_import/stage/import_pull_requests_worker.rb b/app/workers/gitlab/bitbucket_import/stage/import_pull_requests_worker.rb
index 3f85c832d5098..373dfcdb1f787 100644
--- a/app/workers/gitlab/bitbucket_import/stage/import_pull_requests_worker.rb
+++ b/app/workers/gitlab/bitbucket_import/stage/import_pull_requests_worker.rb
@@ -12,8 +12,6 @@ class ImportPullRequestsWorker # rubocop:disable Scalability/IdempotentWorker
         def import(project)
           waiter = importer_class.new(project).execute
 
-          project.import_state.refresh_jid_expiration
-
           AdvanceStageWorker.perform_async(
             project.id,
             { waiter.key => waiter.jobs_remaining },
diff --git a/app/workers/gitlab/bitbucket_server_import/stage/import_lfs_objects_worker.rb b/app/workers/gitlab/bitbucket_server_import/stage/import_lfs_objects_worker.rb
index 1002047225c93..020205dcddbe9 100644
--- a/app/workers/gitlab/bitbucket_server_import/stage/import_lfs_objects_worker.rb
+++ b/app/workers/gitlab/bitbucket_server_import/stage/import_lfs_objects_worker.rb
@@ -12,8 +12,6 @@ class ImportLfsObjectsWorker # rubocop:disable Scalability/IdempotentWorker
         def import(project)
           waiter = importer_class.new(project).execute
 
-          project.import_state.refresh_jid_expiration
-
           AdvanceStageWorker.perform_async(
             project.id,
             { waiter.key => waiter.jobs_remaining },
diff --git a/app/workers/gitlab/bitbucket_server_import/stage/import_notes_worker.rb b/app/workers/gitlab/bitbucket_server_import/stage/import_notes_worker.rb
index b30f93058298a..8020dc7ab3af9 100644
--- a/app/workers/gitlab/bitbucket_server_import/stage/import_notes_worker.rb
+++ b/app/workers/gitlab/bitbucket_server_import/stage/import_notes_worker.rb
@@ -12,8 +12,6 @@ class ImportNotesWorker # rubocop:disable Scalability/IdempotentWorker
         def import(project)
           waiter = importer_class.new(project).execute
 
-          project.import_state.refresh_jid_expiration
-
           AdvanceStageWorker.perform_async(
             project.id,
             { waiter.key => waiter.jobs_remaining },
diff --git a/app/workers/gitlab/bitbucket_server_import/stage/import_pull_requests_worker.rb b/app/workers/gitlab/bitbucket_server_import/stage/import_pull_requests_worker.rb
index 9e3d570e20dfb..c39665b682965 100644
--- a/app/workers/gitlab/bitbucket_server_import/stage/import_pull_requests_worker.rb
+++ b/app/workers/gitlab/bitbucket_server_import/stage/import_pull_requests_worker.rb
@@ -12,8 +12,6 @@ class ImportPullRequestsWorker # rubocop:disable Scalability/IdempotentWorker
         def import(project)
           waiter = importer_class.new(project).execute
 
-          project.import_state.refresh_jid_expiration
-
           AdvanceStageWorker.perform_async(
             project.id,
             { waiter.key => waiter.jobs_remaining },
diff --git a/app/workers/gitlab/github_import/refresh_import_jid_worker.rb b/app/workers/gitlab/github_import/refresh_import_jid_worker.rb
index dfc581f201b69..ee7e45889bd84 100644
--- a/app/workers/gitlab/github_import/refresh_import_jid_worker.rb
+++ b/app/workers/gitlab/github_import/refresh_import_jid_worker.rb
@@ -9,42 +9,11 @@ class RefreshImportJidWorker # rubocop:disable Scalability/IdempotentWorker
 
       include GithubImport::Queue
 
-      sidekiq_options retry: 5
-
-      # The interval to schedule new instances of this job at.
-      INTERVAL = 5.minutes.to_i
-
       def self.perform_in_the_future(*args)
-        perform_in(INTERVAL, *args)
-      end
-
-      # project_id - The ID of the project that is being imported.
-      # check_job_id - The ID of the job for which to check the status.
-      def perform(project_id, check_job_id)
-        import_state = find_import_state(project_id)
-        return unless import_state
-
-        if SidekiqStatus.running?(check_job_id)
-          # As long as the worker is running we want to keep refreshing
-          # the worker's JID as well as the import's JID.
-          Gitlab::SidekiqStatus.expire(check_job_id, Gitlab::Import::StuckImportJob::IMPORT_JOBS_EXPIRATION)
-          Gitlab::SidekiqStatus.set(import_state.jid, Gitlab::Import::StuckImportJob::IMPORT_JOBS_EXPIRATION)
-
-          self.class.perform_in_the_future(project_id, check_job_id)
-        end
-
-        # If the job is no longer running there's nothing else we need to do. If
-        # the clone job completed successfully it will have scheduled the next
-        # stage, if it died there's nothing we can do anyway.
-      end
-
-      # rubocop: disable CodeReuse/ActiveRecord
-      def find_import_state(project_id)
-        ProjectImportState.select(:jid)
-          .with_status(:started)
-          .find_by(project_id: project_id)
+        # Delegate to new version of this job so stale sidekiq nodes can still
+        # run instead of no-op
+        Gitlab::Import::RefreshImportJidWorker.perform_in_the_future(*args)
       end
-      # rubocop: enable CodeReuse/ActiveRecord
     end
   end
 end
diff --git a/app/workers/gitlab/import/refresh_import_jid_worker.rb b/app/workers/gitlab/import/refresh_import_jid_worker.rb
new file mode 100644
index 0000000000000..f9a7bf870afcb
--- /dev/null
+++ b/app/workers/gitlab/import/refresh_import_jid_worker.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Gitlab
+  module Import
+    class RefreshImportJidWorker
+      include ApplicationWorker
+
+      data_consistency :delayed
+      idempotent!
+
+      feature_category :importers
+      sidekiq_options dead: false
+
+      sidekiq_options retry: 5
+
+      # The interval to schedule new instances of this job at.
+      INTERVAL = 5.minutes.to_i
+
+      def self.perform_in_the_future(*args)
+        perform_in(INTERVAL, *args)
+      end
+
+      # project_id - The ID of the project that is being imported.
+      # check_job_id - The ID of the job for which to check the status.
+      # params - to avoid multiple releases if parameters change
+      def perform(project_id, check_job_id, _params = {})
+        return unless SidekiqStatus.running?(check_job_id)
+
+        import_state_jid = ProjectImportState.jid_by(project_id: project_id, status: :started)&.jid
+        return unless import_state_jid
+
+        # As long as the worker is running we want to keep refreshing
+        # the worker's JID as well as the import's JID.
+        Gitlab::SidekiqStatus.expire(check_job_id, Gitlab::Import::StuckImportJob::IMPORT_JOBS_EXPIRATION)
+        Gitlab::SidekiqStatus.set(import_state_jid, Gitlab::Import::StuckImportJob::IMPORT_JOBS_EXPIRATION)
+
+        self.class.perform_in_the_future(project_id, check_job_id)
+      end
+    end
+  end
+end
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 994cdb7d67aee..a7182e334180c 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -373,6 +373,8 @@
   - 1
 - - import_issues_csv
   - 2
+- - import_refresh_import_jid
+  - 1
 - - incident_management
   - 2
 - - incident_management_apply_incident_sla_exceeded_label
diff --git a/lib/gitlab/github_import/parallel_importer.rb b/lib/gitlab/github_import/parallel_importer.rb
index a71590c02f82e..e045c424fe1bb 100644
--- a/lib/gitlab/github_import/parallel_importer.rb
+++ b/lib/gitlab/github_import/parallel_importer.rb
@@ -35,7 +35,7 @@ def initialize(project)
       def execute
         Gitlab::Import::SetAsyncJid.set_jid(project.import_state)
 
-        # We need to track this job's status for use by Gitlab::GithubImport::RefreshImportJidWorker.
+        # We need to track this job's status for use by Gitlab::Import::RefreshImportJidWorker.
         Stage::ImportRepositoryWorker
           .with_status
           .perform_async(project.id)
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index 22d8871b71877..9e1038b4d4edd 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -9489,7 +9489,6 @@
 - './spec/workers/gitlab/github_import/import_issue_worker_spec.rb'
 - './spec/workers/gitlab/github_import/import_note_worker_spec.rb'
 - './spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb'
-- './spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb'
 - './spec/workers/gitlab/github_import/stage/finish_import_worker_spec.rb'
 - './spec/workers/gitlab/github_import/stage/import_base_data_worker_spec.rb'
 - './spec/workers/gitlab/github_import/stage/import_issue_events_worker_spec.rb'
diff --git a/spec/support/shared_examples/lib/gitlab/bitbucket_import/stage_methods_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/bitbucket_import/stage_methods_shared_examples.rb
index f128aa92a5317..46771afaa3422 100644
--- a/spec/support/shared_examples/lib/gitlab/bitbucket_import/stage_methods_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/bitbucket_import/stage_methods_shared_examples.rb
@@ -16,7 +16,7 @@
     end
   end
 
-  describe '.perform' do
+  describe '#perform' do
     let(:worker) { described_class.new }
 
     it 'executes the import' do
@@ -25,5 +25,16 @@
 
       worker.perform(project.id)
     end
+
+    it 'queues RefreshImportJidWorker' do
+      allow(worker).to receive(:import)
+      allow(worker).to receive(:jid).and_return('mock_jid')
+
+      expect(Gitlab::Import::RefreshImportJidWorker)
+        .to receive(:perform_in_the_future)
+        .with(project.id, 'mock_jid')
+
+      worker.perform(project.id)
+    end
   end
 end
diff --git a/spec/support/shared_examples/lib/gitlab/bitbucket_server_import/stage_methods_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/bitbucket_server_import/stage_methods_shared_examples.rb
index 1246dd2979b9c..c73c9f6028228 100644
--- a/spec/support/shared_examples/lib/gitlab/bitbucket_server_import/stage_methods_shared_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/bitbucket_server_import/stage_methods_shared_examples.rb
@@ -15,4 +15,26 @@
       described_class.sidekiq_retries_exhausted_block.call(job, StandardError.new)
     end
   end
+
+  describe '#perform' do
+    let(:worker) { described_class.new }
+
+    it 'executes the import' do
+      expect(worker).to receive(:import).with(project).once
+      expect(Gitlab::BitbucketServerImport::Logger).to receive(:info).twice
+
+      worker.perform(project.id)
+    end
+
+    it 'queues RefreshImportJidWorker' do
+      allow(worker).to receive(:import)
+      allow(worker).to receive(:jid).and_return('mock_jid')
+
+      expect(Gitlab::Import::RefreshImportJidWorker)
+        .to receive(:perform_in_the_future)
+        .with(project.id, 'mock_jid')
+
+      worker.perform(project.id)
+    end
+  end
 end
diff --git a/spec/support/shared_examples/workers/gitlab/github_import/stage_methods_shared_examples.rb b/spec/support/shared_examples/workers/gitlab/github_import/stage_methods_shared_examples.rb
index b5e3589d86c50..6d064e0c999d3 100644
--- a/spec/support/shared_examples/workers/gitlab/github_import/stage_methods_shared_examples.rb
+++ b/spec/support/shared_examples/workers/gitlab/github_import/stage_methods_shared_examples.rb
@@ -100,7 +100,7 @@
       allow(worker).to receive(:import)
       allow(worker).to receive(:jid).and_return('mock_jid')
 
-      expect(Gitlab::GithubImport::RefreshImportJidWorker)
+      expect(Gitlab::Import::RefreshImportJidWorker)
         .to receive(:perform_in_the_future)
         .with(project.id, 'mock_jid')
 
diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb
index 75101c1a24e48..dc2851bd562eb 100644
--- a/spec/workers/every_sidekiq_worker_spec.rb
+++ b/spec/workers/every_sidekiq_worker_spec.rb
@@ -279,7 +279,6 @@
         'Gitlab::GithubImport::PullRequests::ImportReviewWorker' => 5,
         'Gitlab::GithubImport::PullRequests::ImportMergedByWorker' => 5,
         'Gitlab::GithubImport::ImportPullRequestWorker' => 5,
-        'Gitlab::GithubImport::RefreshImportJidWorker' => 5,
         'Gitlab::GithubImport::ReplayEventsWorker' => 5,
         'Gitlab::GithubImport::Stage::FinishImportWorker' => 6,
         'Gitlab::GithubImport::Stage::ImportBaseDataWorker' => 6,
@@ -298,6 +297,7 @@
         'Gitlab::GithubGistsImport::ImportGistWorker' => 5,
         'Gitlab::GithubGistsImport::StartImportWorker' => 5,
         'Gitlab::GithubGistsImport::FinishImportWorker' => 5,
+        'Gitlab::Import::RefreshImportJidWorker' => 5,
         'Gitlab::JiraImport::AdvanceStageWorker' => 6,
         'Gitlab::JiraImport::ImportIssueWorker' => 5,
         'Gitlab::JiraImport::Stage::FinishImportWorker' => 6,
diff --git a/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb b/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb
index 5d0cb05c8d5ed..70f958b60f6a1 100644
--- a/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/refresh_import_jid_worker_spec.rb
@@ -6,102 +6,12 @@
   let(:worker) { described_class.new }
 
   describe '.perform_in_the_future' do
-    it 'schedules a job in the future' do
-      expect(described_class)
-        .to receive(:perform_in)
-        .with(5.minutes.to_i, 10, '123')
+    it 'calls Gitlab::Import::RefreshImportJidWorker#perform_in_the_future' do
+      expect(Gitlab::Import::RefreshImportJidWorker)
+        .to receive(:perform_in_the_future)
+        .with(10, '123')
 
       described_class.perform_in_the_future(10, '123')
     end
   end
-
-  describe '#perform' do
-    let(:project) { create(:project) }
-    let(:import_state) { create(:import_state, project: project, jid: '123abc') }
-
-    context 'when the project does not exist' do
-      it 'does nothing' do
-        expect(Gitlab::SidekiqStatus)
-          .not_to receive(:running?)
-
-        worker.perform(-1, '123')
-      end
-    end
-
-    context 'when the job is running' do
-      it 'refreshes the import JID and reschedules itself' do
-        allow(worker)
-          .to receive(:find_import_state)
-          .with(project.id)
-          .and_return(import_state)
-
-        expect(Gitlab::SidekiqStatus)
-          .to receive(:running?)
-          .with('123')
-          .and_return(true)
-
-        expect(Gitlab::SidekiqStatus)
-          .to receive(:expire)
-          .with('123', Gitlab::Import::StuckImportJob::IMPORT_JOBS_EXPIRATION)
-
-        expect(Gitlab::SidekiqStatus)
-          .to receive(:set)
-          .with(import_state.jid, Gitlab::Import::StuckImportJob::IMPORT_JOBS_EXPIRATION)
-
-        expect(worker.class)
-          .to receive(:perform_in_the_future)
-          .with(project.id, '123')
-
-        worker.perform(project.id, '123')
-      end
-    end
-
-    context 'when the job is no longer running' do
-      it 'returns' do
-        allow(worker)
-          .to receive(:find_import_state)
-          .with(project.id)
-          .and_return(project)
-
-        expect(Gitlab::SidekiqStatus)
-          .to receive(:running?)
-          .with('123')
-          .and_return(false)
-
-        expect(Gitlab::SidekiqStatus)
-          .not_to receive(:expire)
-
-        expect(Gitlab::SidekiqStatus)
-          .not_to receive(:set)
-
-        worker.perform(project.id, '123')
-      end
-    end
-  end
-
-  describe '#find_import_state' do
-    it 'returns a ProjectImportState' do
-      project = create(:project, :import_started)
-
-      expect(worker.find_import_state(project.id)).to be_an_instance_of(ProjectImportState)
-    end
-
-    # it 'only selects the import JID field' do
-    #   project = create(:project, :import_started)
-    #   project.import_state.update_attributes(jid: '123abc')
-    #
-    #   expect(worker.find_project(project.id).attributes)
-    #     .to eq({ 'id' => nil, 'import_jid' => '123abc' })
-    # end
-
-    it 'returns nil for a import state for which the import process failed' do
-      project = create(:project, :import_failed)
-
-      expect(worker.find_import_state(project.id)).to be_nil
-    end
-
-    it 'returns nil for a non-existing find_import_state' do
-      expect(worker.find_import_state(-1)).to be_nil
-    end
-  end
 end
diff --git a/spec/workers/gitlab/import/refresh_import_jid_worker_spec.rb b/spec/workers/gitlab/import/refresh_import_jid_worker_spec.rb
new file mode 100644
index 0000000000000..be4a585d60ed6
--- /dev/null
+++ b/spec/workers/gitlab/import/refresh_import_jid_worker_spec.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Import::RefreshImportJidWorker, feature_category: :importers do
+  let(:worker) { described_class.new }
+
+  describe '.perform_in_the_future' do
+    it 'schedules a job in the future' do
+      expect(described_class)
+        .to receive(:perform_in)
+        .with(5.minutes.to_i, 10, '123')
+
+      described_class.perform_in_the_future(10, '123')
+    end
+  end
+
+  describe '#perform' do
+    let_it_be(:project) { create(:project) }
+    let(:import_state) { create(:import_state, project: project, jid: '123abc', status: :started) }
+
+    context 'when the project does not exist' do
+      let(:job_args) { [-1, '123'] }
+
+      it_behaves_like 'an idempotent worker'
+
+      it 'does nothing' do
+        expect(Gitlab::SidekiqStatus)
+          .not_to receive(:expire)
+
+        worker.perform(*job_args)
+      end
+    end
+
+    context 'when the job is running' do
+      let(:job_args) { [project.id, '123'] }
+
+      before do
+        allow(Gitlab::SidekiqStatus)
+          .to receive(:running?)
+          .with('123')
+          .and_return(true)
+      end
+
+      it_behaves_like 'an idempotent worker'
+
+      it 'refreshes the import JID and reschedules itself' do
+        expect(Gitlab::SidekiqStatus)
+          .to receive(:expire)
+          .with('123', Gitlab::Import::StuckImportJob::IMPORT_JOBS_EXPIRATION)
+
+        expect(Gitlab::SidekiqStatus)
+          .to receive(:set)
+          .with(import_state.jid, Gitlab::Import::StuckImportJob::IMPORT_JOBS_EXPIRATION)
+
+        expect(worker.class)
+          .to receive(:perform_in_the_future)
+          .with(project.id, '123')
+
+        worker.perform(*job_args)
+      end
+    end
+
+    context 'when the job is no longer running' do
+      let(:job_args) { [project.id, '123'] }
+
+      before do
+        allow(Gitlab::SidekiqStatus)
+          .to receive(:running?)
+          .with('123')
+          .and_return(false)
+      end
+
+      it_behaves_like 'an idempotent worker'
+
+      it 'returns' do
+        expect(Gitlab::SidekiqStatus)
+          .not_to receive(:expire)
+
+        expect(Gitlab::SidekiqStatus)
+          .not_to receive(:set)
+
+        worker.perform(*job_args)
+      end
+    end
+  end
+end
-- 
GitLab