summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-12 09:09:55 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-12 09:09:55 +0000
commit18f7828977b74bf6e5153594a098ef90e773b3b7 (patch)
tree49cb1e16d5341d773807ee583357ae6eb167d61f /spec
parent8191b1571c017378eac33b3ed296ad5216d0a410 (diff)
downloadgitlab-ce-18f7828977b74bf6e5153594a098ef90e773b3b7.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/projects_controller_spec.rb6
-rw-r--r--spec/factories/project_export_jobs.rb8
-rw-r--r--spec/features/markdown/mermaid_spec.rb27
-rw-r--r--spec/finders/projects/export_job_finder_spec.rb51
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/project/export_status.json3
-rw-r--r--spec/frontend/blob/blob_file_dropzone_spec.js50
-rw-r--r--spec/javascripts/blob/blob_file_dropzone_spec.js39
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb6
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/quick_actions/extractor_spec.rb27
-rw-r--r--spec/models/project_export_job_spec.rb19
-rw-r--r--spec/models/project_spec.rb86
-rw-r--r--spec/requests/api/project_export_spec.rb53
-rw-r--r--spec/services/ci/create_cross_project_pipeline_service_spec.rb20
-rw-r--r--spec/services/ci/pipeline_bridge_status_service_spec.rb18
-rw-r--r--spec/workers/concerns/project_export_options_spec.rb41
-rw-r--r--spec/workers/project_export_worker_spec.rb46
-rw-r--r--spec/workers/stuck_export_jobs_worker_spec.rb75
19 files changed, 512 insertions, 70 deletions
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 67e24841dee..53a57937e9b 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -1140,7 +1140,7 @@ describe ProjectsController do
end
it 'prevents requesting project export' do
- get action, params: { namespace_id: project.namespace, id: project }
+ post action, params: { namespace_id: project.namespace, id: project }
expect(flash[:alert]).to eq('This endpoint has been requested too many times. Try again later.')
expect(response).to have_gitlab_http_status(:found)
@@ -1152,7 +1152,7 @@ describe ProjectsController do
context 'when project export is enabled' do
it 'returns 302' do
- get action, params: { namespace_id: project.namespace, id: project }
+ post action, params: { namespace_id: project.namespace, id: project }
expect(response).to have_gitlab_http_status(:found)
end
@@ -1164,7 +1164,7 @@ describe ProjectsController do
end
it 'returns 404' do
- get action, params: { namespace_id: project.namespace, id: project }
+ post action, params: { namespace_id: project.namespace, id: project }
expect(response).to have_gitlab_http_status(:not_found)
end
diff --git a/spec/factories/project_export_jobs.rb b/spec/factories/project_export_jobs.rb
new file mode 100644
index 00000000000..b2666555ea8
--- /dev/null
+++ b/spec/factories/project_export_jobs.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :project_export_job do
+ project
+ jid { SecureRandom.hex(8) }
+ end
+end
diff --git a/spec/features/markdown/mermaid_spec.rb b/spec/features/markdown/mermaid_spec.rb
index 542caccb18d..1cd5760c30e 100644
--- a/spec/features/markdown/mermaid_spec.rb
+++ b/spec/features/markdown/mermaid_spec.rb
@@ -94,8 +94,31 @@ describe 'Mermaid rendering', :js do
page.find('summary').click
svg = page.find('svg.mermaid')
- expect(svg[:width].to_i).to be_within(5).of(120)
- expect(svg[:height].to_i).to be_within(5).of(220)
+ expect(svg[:style]).to match(/max-width/)
+ expect(svg[:width].to_i).to eq(100)
+ expect(svg[:height].to_i).to eq(0)
end
end
+
+ it 'correctly sizes mermaid diagram block', :js do
+ description = <<~MERMAID
+ ```mermaid
+ graph TD;
+ A-->B;
+ A-->C;
+ B-->D;
+ C-->D;
+ ```
+ MERMAID
+
+ project = create(:project, :public)
+ issue = create(:issue, project: project, description: description)
+
+ visit project_issue_path(project, issue)
+
+ svg = page.find('svg.mermaid')
+ expect(svg[:style]).to match(/max-width/)
+ expect(svg[:width].to_i).to eq(100)
+ expect(svg[:height].to_i).to eq(0)
+ end
end
diff --git a/spec/finders/projects/export_job_finder_spec.rb b/spec/finders/projects/export_job_finder_spec.rb
new file mode 100644
index 00000000000..31b68717d13
--- /dev/null
+++ b/spec/finders/projects/export_job_finder_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::ExportJobFinder do
+ let(:project) { create(:project) }
+ let(:project_export_job1) { create(:project_export_job, project: project) }
+ let(:project_export_job2) { create(:project_export_job, project: project) }
+
+ describe '#execute' do
+ subject { described_class.new(project, params).execute }
+
+ context 'when queried for a project' do
+ let(:params) { {} }
+
+ it 'scopes to the project' do
+ expect(subject).to contain_exactly(
+ project_export_job1, project_export_job2
+ )
+ end
+ end
+
+ context 'when queried by job id' do
+ let(:params) { { jid: project_export_job1.jid } }
+
+ it 'filters records' do
+ expect(subject).to contain_exactly(project_export_job1)
+ end
+ end
+
+ context 'when queried by status' do
+ let(:params) { { status: :started } }
+
+ before do
+ project_export_job2.start!
+ end
+
+ it 'filters records' do
+ expect(subject).to contain_exactly(project_export_job2)
+ end
+ end
+
+ context 'when queried by invalid status' do
+ let(:params) { { status: '1234ad' } }
+
+ it 'raises exception' do
+ expect { subject }.to raise_error(described_class::InvalidExportJobStatusError, 'Invalid export job status')
+ end
+ end
+ end
+end
diff --git a/spec/fixtures/api/schemas/public_api/v4/project/export_status.json b/spec/fixtures/api/schemas/public_api/v4/project/export_status.json
index 81c8815caf6..fd35ba34b49 100644
--- a/spec/fixtures/api/schemas/public_api/v4/project/export_status.json
+++ b/spec/fixtures/api/schemas/public_api/v4/project/export_status.json
@@ -13,9 +13,10 @@
"type": "string",
"enum": [
"none",
+ "queued",
"started",
"finished",
- "after_export_action"
+ "regeneration_in_progress"
]
}
}
diff --git a/spec/frontend/blob/blob_file_dropzone_spec.js b/spec/frontend/blob/blob_file_dropzone_spec.js
new file mode 100644
index 00000000000..4e9a05418df
--- /dev/null
+++ b/spec/frontend/blob/blob_file_dropzone_spec.js
@@ -0,0 +1,50 @@
+import $ from 'jquery';
+import BlobFileDropzone from '~/blob/blob_file_dropzone';
+
+describe('BlobFileDropzone', () => {
+ preloadFixtures('blob/show.html');
+ let dropzone;
+ let replaceFileButton;
+ const jQueryMock = {
+ enable: jest.fn(),
+ disable: jest.fn(),
+ };
+
+ beforeEach(() => {
+ loadFixtures('blob/show.html');
+ const form = $('.js-upload-blob-form');
+ // eslint-disable-next-line no-new
+ new BlobFileDropzone(form, 'POST');
+ dropzone = $('.js-upload-blob-form .dropzone').get(0).dropzone;
+ dropzone.processQueue = jest.fn();
+ replaceFileButton = $('#submit-all');
+ $.fn.extend(jQueryMock);
+ });
+
+ describe('submit button', () => {
+ it('requires file', () => {
+ jest.spyOn(window, 'alert').mockImplementation(() => {});
+
+ replaceFileButton.click();
+
+ expect(window.alert).toHaveBeenCalled();
+ });
+
+ it('is disabled while uploading', () => {
+ jest.spyOn(window, 'alert').mockImplementation(() => {});
+
+ const file = new File([], 'some-file.jpg');
+ const fakeEvent = $.Event('drop', {
+ dataTransfer: { files: [file] },
+ });
+
+ dropzone.listeners[0].events.drop(fakeEvent);
+
+ replaceFileButton.click();
+
+ expect(window.alert).not.toHaveBeenCalled();
+ expect(jQueryMock.enable).toHaveBeenCalled();
+ expect(dropzone.processQueue).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/javascripts/blob/blob_file_dropzone_spec.js b/spec/javascripts/blob/blob_file_dropzone_spec.js
deleted file mode 100644
index fe03775ec4d..00000000000
--- a/spec/javascripts/blob/blob_file_dropzone_spec.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import $ from 'jquery';
-import BlobFileDropzone from '~/blob/blob_file_dropzone';
-
-describe('BlobFileDropzone', function() {
- preloadFixtures('blob/show.html');
-
- beforeEach(() => {
- loadFixtures('blob/show.html');
- const form = $('.js-upload-blob-form');
- this.blobFileDropzone = new BlobFileDropzone(form, 'POST');
- this.dropzone = $('.js-upload-blob-form .dropzone').get(0).dropzone;
- this.replaceFileButton = $('#submit-all');
- });
-
- describe('submit button', () => {
- it('requires file', () => {
- spyOn(window, 'alert');
-
- this.replaceFileButton.click();
-
- expect(window.alert).toHaveBeenCalled();
- });
-
- it('is disabled while uploading', () => {
- spyOn(window, 'alert');
-
- const file = new File([], 'some-file.jpg');
- const fakeEvent = $.Event('drop', {
- dataTransfer: { files: [file] },
- });
-
- this.dropzone.listeners[0].events.drop(fakeEvent);
- this.replaceFileButton.click();
-
- expect(window.alert).not.toHaveBeenCalled();
- expect(this.replaceFileButton.is(':disabled')).toEqual(true);
- });
- });
-});
diff --git a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
index 86ceb97b250..1631de393b5 100644
--- a/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
+++ b/spec/lib/gitlab/import_export/after_export_strategies/base_after_export_strategy_spec.rb
@@ -3,6 +3,12 @@
require 'spec_helper'
describe Gitlab::ImportExport::AfterExportStrategies::BaseAfterExportStrategy do
+ before do
+ allow_next_instance_of(ProjectExportWorker) do |job|
+ allow(job).to receive(:jid).and_return(SecureRandom.hex(8))
+ end
+ end
+
let!(:service) { described_class.new }
let!(:project) { create(:project, :with_export) }
let(:shared) { project.import_export_shared }
diff --git a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
index 95c47d15f8f..7792daed99c 100644
--- a/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
+++ b/spec/lib/gitlab/import_export/after_export_strategies/web_upload_strategy_spec.rb
@@ -5,6 +5,12 @@ require 'spec_helper'
describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
include StubRequests
+ before do
+ allow_next_instance_of(ProjectExportWorker) do |job|
+ allow(job).to receive(:jid).and_return(SecureRandom.hex(8))
+ end
+ end
+
let(:example_url) { 'http://www.example.com' }
let(:strategy) { subject.new(url: example_url, http_method: 'post') }
let!(:project) { create(:project, :with_export) }
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index e579c8474b7..37b3e4a4a22 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -469,6 +469,7 @@ project:
- autoclose_referenced_issues
- status_page_setting
- requirements
+- export_jobs
award_emoji:
- awardable
- user
diff --git a/spec/lib/gitlab/quick_actions/extractor_spec.rb b/spec/lib/gitlab/quick_actions/extractor_spec.rb
index 1843acb0cc0..6ea597bf01e 100644
--- a/spec/lib/gitlab/quick_actions/extractor_spec.rb
+++ b/spec/lib/gitlab/quick_actions/extractor_spec.rb
@@ -291,6 +291,33 @@ describe Gitlab::QuickActions::Extractor do
expect(msg).to eq expected
end
+ it 'does not extract commands in multiline inline code on seperated rows' do
+ msg = "Hello\r\n`\r\nThis is some text\r\n/close\r\n/assign @user\r\n`\r\n\r\nWorld"
+ expected = msg.delete("\r")
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to be_empty
+ expect(msg).to eq expected
+ end
+
+ it 'does not extract commands in multiline inline code starting from text' do
+ msg = "Hello `This is some text\r\n/close\r\n/assign @user\r\n`\r\n\r\nWorld"
+ expected = msg.delete("\r")
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to be_empty
+ expect(msg).to eq expected
+ end
+
+ it 'does not extract commands in inline code' do
+ msg = "`This is some text\r\n/close\r\n/assign @user\r\n`\r\n\r\nWorld"
+ expected = msg.delete("\r")
+ msg, commands = extractor.extract_commands(msg)
+
+ expect(commands).to be_empty
+ expect(msg).to eq expected
+ end
+
it 'limits to passed commands when they are passed' do
msg = <<~MSG.strip
Hello, we should only extract the commands passed
diff --git a/spec/models/project_export_job_spec.rb b/spec/models/project_export_job_spec.rb
new file mode 100644
index 00000000000..dc39d0e401d
--- /dev/null
+++ b/spec/models/project_export_job_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ProjectExportJob, type: :model do
+ let(:project) { create(:project) }
+ let!(:job1) { create(:project_export_job, project: project, status: 0) }
+ let!(:job2) { create(:project_export_job, project: project, status: 2) }
+
+ describe 'associations' do
+ it { expect(job1).to belong_to(:project) }
+ end
+
+ describe 'validations' do
+ it { expect(job1).to validate_presence_of(:project) }
+ it { expect(job1).to validate_presence_of(:jid) }
+ it { expect(job1).to validate_presence_of(:status) }
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 782e526b69d..6d9b46c9941 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -3957,6 +3957,12 @@ describe Project do
describe '#remove_export' do
let(:project) { create(:project, :with_export) }
+ before do
+ allow_next_instance_of(ProjectExportWorker) do |job|
+ allow(job).to receive(:jid).and_return(SecureRandom.hex(8))
+ end
+ end
+
it 'removes the export' do
project.remove_exports
@@ -5813,6 +5819,86 @@ describe Project do
end
end
+ describe '#add_export_job' do
+ context 'if not already present' do
+ it 'starts project export job' do
+ user = create(:user)
+ project = build(:project)
+
+ expect(ProjectExportWorker).to receive(:perform_async).with(user.id, project.id, nil, {})
+
+ project.add_export_job(current_user: user)
+ end
+ end
+ end
+
+ describe '#export_in_progress?' do
+ let(:project) { build(:project) }
+ let!(:project_export_job ) { create(:project_export_job, project: project) }
+
+ context 'when project export is enqueued' do
+ it { expect(project.export_in_progress?).to be false }
+ end
+
+ context 'when project export is in progress' do
+ before do
+ project_export_job.start!
+ end
+
+ it { expect(project.export_in_progress?).to be true }
+ end
+
+ context 'when project export is completed' do
+ before do
+ finish_job(project_export_job)
+ end
+
+ it { expect(project.export_in_progress?).to be false }
+ end
+ end
+
+ describe '#export_status' do
+ let(:project) { build(:project) }
+ let!(:project_export_job ) { create(:project_export_job, project: project) }
+
+ context 'when project export is enqueued' do
+ it { expect(project.export_status).to eq :queued }
+ end
+
+ context 'when project export is in progress' do
+ before do
+ project_export_job.start!
+ end
+
+ it { expect(project.export_status).to eq :started }
+ end
+
+ context 'when project export is completed' do
+ before do
+ finish_job(project_export_job)
+ allow(project).to receive(:export_file).and_return(double(ImportExportUploader, file: 'exists.zip'))
+ end
+
+ it { expect(project.export_status).to eq :finished }
+ end
+
+ context 'when project export is being regenerated' do
+ let!(:new_project_export_job ) { create(:project_export_job, project: project) }
+
+ before do
+ finish_job(project_export_job)
+ allow(project).to receive(:export_file).and_return(double(ImportExportUploader, file: 'exists.zip'))
+ end
+
+ it { expect(project.export_status).to eq :regeneration_in_progress }
+ end
+ end
+
+ def finish_job(export_job)
+ export_job.start
+ export_job.finish
+ end
+
def rugged_config
rugged_repo(project.repository).config
end
diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb
index d5c822385da..859a3cca44f 100644
--- a/spec/requests/api/project_export_spec.rb
+++ b/spec/requests/api/project_export_spec.rb
@@ -27,12 +27,9 @@ describe API::ProjectExport, :clean_gitlab_redis_cache do
before do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
-
- # simulate exporting work directory
- FileUtils.mkdir_p File.join(project_started.export_path, 'securerandom-hex')
-
- # simulate in after export action
- FileUtils.touch File.join(project_after_export.import_export_shared.lock_files_path, SecureRandom.hex)
+ allow_next_instance_of(ProjectExportWorker) do |job|
+ allow(job).to receive(:jid).and_return(SecureRandom.hex(8))
+ end
end
after do
@@ -82,28 +79,42 @@ describe API::ProjectExport, :clean_gitlab_redis_cache do
expect(json_response['export_status']).to eq('none')
end
- it 'is started' do
- get api(path_started, user)
+ context 'when project export has started' do
+ before do
+ create(:project_export_job, project: project_started, status: 1)
+ end
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('public_api/v4/project/export_status')
- expect(json_response['export_status']).to eq('started')
+ it 'returns status started' do
+ get api(path_started, user)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/project/export_status')
+ expect(json_response['export_status']).to eq('started')
+ end
end
- it 'is after_export' do
- get api(path_after_export, user)
+ context 'when project export has finished' do
+ it 'returns status finished' do
+ get api(path_finished, user)
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('public_api/v4/project/export_status')
- expect(json_response['export_status']).to eq('after_export_action')
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/project/export_status')
+ expect(json_response['export_status']).to eq('finished')
+ end
end
- it 'is finished' do
- get api(path_finished, user)
+ context 'when project export is being regenerated' do
+ before do
+ create(:project_export_job, project: project_finished, status: 1)
+ end
+
+ it 'returns status regeneration_in_progress' do
+ get api(path_finished, user)
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('public_api/v4/project/export_status')
- expect(json_response['export_status']).to eq('finished')
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/project/export_status')
+ expect(json_response['export_status']).to eq('regeneration_in_progress')
+ end
end
end
diff --git a/spec/services/ci/create_cross_project_pipeline_service_spec.rb b/spec/services/ci/create_cross_project_pipeline_service_spec.rb
index 667ad532fb0..99c44c3aa17 100644
--- a/spec/services/ci/create_cross_project_pipeline_service_spec.rb
+++ b/spec/services/ci/create_cross_project_pipeline_service_spec.rb
@@ -362,6 +362,26 @@ describe Ci::CreateCrossProjectPipelineService, '#execute' do
end
end
+ context 'when bridge job status update raises state machine errors' do
+ let(:stub_config) { false }
+
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(invalid: { yaml: 'error' }))
+ bridge.drop!
+ end
+
+ it 'tracks the exception' do
+ expect(Gitlab::ErrorTracking)
+ .to receive(:track_exception)
+ .with(
+ instance_of(Ci::Bridge::InvalidTransitionError),
+ bridge_id: bridge.id,
+ downstream_pipeline_id: kind_of(Numeric))
+
+ service.execute(bridge)
+ end
+ end
+
context 'when bridge job has YAML variables defined' do
before do
bridge.yaml_variables = [{ key: 'BRIDGE', value: 'var', public: true }]
diff --git a/spec/services/ci/pipeline_bridge_status_service_spec.rb b/spec/services/ci/pipeline_bridge_status_service_spec.rb
index 95f16af3af9..0b6ae976d97 100644
--- a/spec/services/ci/pipeline_bridge_status_service_spec.rb
+++ b/spec/services/ci/pipeline_bridge_status_service_spec.rb
@@ -22,6 +22,24 @@ describe Ci::PipelineBridgeStatusService do
subject
end
+
+ context 'when bridge job status raises state machine errors' do
+ before do
+ pipeline.drop!
+ bridge.drop!
+ end
+
+ it 'tracks the exception' do
+ expect(Gitlab::ErrorTracking)
+ .to receive(:track_exception)
+ .with(
+ instance_of(Ci::Bridge::InvalidTransitionError),
+ bridge_id: bridge.id,
+ downstream_pipeline_id: pipeline.id)
+
+ subject
+ end
+ end
end
end
end
diff --git a/spec/workers/concerns/project_export_options_spec.rb b/spec/workers/concerns/project_export_options_spec.rb
new file mode 100644
index 00000000000..985afaaf11e
--- /dev/null
+++ b/spec/workers/concerns/project_export_options_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ProjectExportOptions do
+ let(:project) { create(:project) }
+ let(:project_export_job) { create(:project_export_job, project: project, jid: '123', status: 1) }
+ let(:job) { { 'args' => [project.owner.id, project.id, nil, nil], 'jid' => '123' } }
+ let(:worker_class) do
+ Class.new do
+ include Sidekiq::Worker
+ include ProjectExportOptions
+ end
+ end
+
+ it 'sets default retry limit' do
+ expect(worker_class.sidekiq_options['retry']).to eq(ProjectExportOptions::EXPORT_RETRY_COUNT)
+ end
+
+ it 'sets default status expiration' do
+ expect(worker_class.sidekiq_options['status_expiration']).to eq(StuckExportJobsWorker::EXPORT_JOBS_EXPIRATION)
+ end
+
+ describe '.sidekiq_retries_exhausted' do
+ it 'marks status as failed' do
+ expect { worker_class.sidekiq_retries_exhausted_block.call(job) }.to change { project_export_job.reload.status }.from(1).to(3)
+ end
+
+ context 'when status update fails' do
+ before do
+ project_export_job.update(status: 2)
+ end
+
+ it 'logs an error' do
+ expect(Sidekiq.logger).to receive(:error).with("Failed to set Job #{job['jid']} for project #{project.id} to failed state")
+
+ worker_class.sidekiq_retries_exhausted_block.call(job)
+ end
+ end
+ end
+end
diff --git a/spec/workers/project_export_worker_spec.rb b/spec/workers/project_export_worker_spec.rb
index 8065087796c..d0d52e0df2d 100644
--- a/spec/workers/project_export_worker_spec.rb
+++ b/spec/workers/project_export_worker_spec.rb
@@ -9,21 +9,59 @@ describe ProjectExportWorker do
subject { described_class.new }
describe '#perform' do
+ before do
+ allow_next_instance_of(described_class) do |job|
+ allow(job).to receive(:jid).and_return(SecureRandom.hex(8))
+ end
+ end
+
context 'when it succeeds' do
it 'calls the ExportService' do
expect_any_instance_of(::Projects::ImportExport::ExportService).to receive(:execute)
subject.perform(user.id, project.id, { 'klass' => 'Gitlab::ImportExport::AfterExportStrategies::DownloadNotificationStrategy' })
end
+
+ context 'export job' do
+ before do
+ allow_any_instance_of(::Projects::ImportExport::ExportService).to receive(:execute)
+ end
+
+ it 'creates an export job record for the project' do
+ expect { subject.perform(user.id, project.id, {}) }.to change { project.export_jobs.count }.from(0).to(1)
+ end
+
+ it 'sets the export job status to started' do
+ expect_next_instance_of(ProjectExportJob) do |job|
+ expect(job).to receive(:start)
+ end
+
+ subject.perform(user.id, project.id, {})
+ end
+
+ it 'sets the export job status to finished' do
+ expect_next_instance_of(ProjectExportJob) do |job|
+ expect(job).to receive(:finish)
+ end
+
+ subject.perform(user.id, project.id, {})
+ end
+ end
end
context 'when it fails' do
- it 'raises an exception when params are invalid' do
+ it 'does not raise an exception when strategy is invalid' do
expect_any_instance_of(::Projects::ImportExport::ExportService).not_to receive(:execute)
- expect { subject.perform(1234, project.id, {}) }.to raise_exception(ActiveRecord::RecordNotFound)
- expect { subject.perform(user.id, 1234, {}) }.to raise_exception(ActiveRecord::RecordNotFound)
- expect { subject.perform(user.id, project.id, { 'klass' => 'Whatever' }) }.to raise_exception(Gitlab::ImportExport::AfterExportStrategyBuilder::StrategyNotFoundError)
+ expect { subject.perform(user.id, project.id, { 'klass' => 'Whatever' }) }.not_to raise_error
+ end
+
+ it 'does not raise error when project cannot be found' do
+ expect { subject.perform(user.id, -234, {}) }.not_to raise_error
+ end
+
+ it 'does not raise error when user cannot be found' do
+ expect { subject.perform(-863, project.id, {}) }.not_to raise_error
end
end
end
diff --git a/spec/workers/stuck_export_jobs_worker_spec.rb b/spec/workers/stuck_export_jobs_worker_spec.rb
new file mode 100644
index 00000000000..fc5758fdadf
--- /dev/null
+++ b/spec/workers/stuck_export_jobs_worker_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe StuckExportJobsWorker do
+ let(:worker) { described_class.new }
+
+ shared_examples 'project export job detection' do
+ context 'when the job has completed' do
+ context 'when the export status was already updated' do
+ before do
+ allow(Gitlab::SidekiqStatus).to receive(:completed_jids) do
+ project_export_job.start
+ project_export_job.finish
+
+ [project_export_job.jid]
+ end
+ end
+
+ it 'does not mark the export as failed' do
+ worker.perform
+
+ expect(project_export_job.reload.finished?).to be true
+ end
+ end
+
+ context 'when the export status was not updated' do
+ before do
+ allow(Gitlab::SidekiqStatus).to receive(:completed_jids) do
+ project_export_job.start
+
+ [project_export_job.jid]
+ end
+ end
+
+ it 'marks the project as failed' do
+ worker.perform
+
+ expect(project_export_job.reload.failed?).to be true
+ end
+ end
+
+ context 'when the job is not in queue and db record in queued state' do
+ before do
+ allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return([project_export_job.jid])
+ end
+
+ it 'marks the project as failed' do
+ expect(project_export_job.queued?).to be true
+
+ worker.perform
+
+ expect(project_export_job.reload.failed?).to be true
+ end
+ end
+ end
+
+ context 'when the job is running in Sidekiq' do
+ before do
+ allow(Gitlab::SidekiqStatus).to receive(:completed_jids).and_return([])
+ end
+
+ it 'does not mark the project export as failed' do
+ expect { worker.perform }.not_to change { project_export_job.reload.status }
+ end
+ end
+ end
+
+ describe 'with started export status' do
+ it_behaves_like 'project export job detection' do
+ let(:project) { create(:project) }
+ let!(:project_export_job) { create(:project_export_job, project: project, jid: '123') }
+ end
+ end
+end