diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-03 15:12:41 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-03 15:12:41 +0000 |
commit | 67daaf4021a180166ad063e3a75ea777e96586a6 (patch) | |
tree | 9ba7459c9c149e151fd31fa1fa7f31a186602eff /spec | |
parent | 584ccdaf68710dec2c717a010cbab2610c0155ed (diff) | |
download | gitlab-ce-67daaf4021a180166ad063e3a75ea777e96586a6.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
18 files changed, 363 insertions, 190 deletions
diff --git a/spec/channels/application_cable/connection_spec.rb b/spec/channels/application_cable/connection_spec.rb index c10e0c0cab4..affde0095cf 100644 --- a/spec/channels/application_cable/connection_spec.rb +++ b/spec/channels/application_cable/connection_spec.rb @@ -3,15 +3,11 @@ require 'spec_helper' RSpec.describe ApplicationCable::Connection, :clean_gitlab_redis_sessions do - let(:session_id) { Rack::Session::SessionId.new('6919a6f1bb119dd7396fadc38fd18d0d') } + include SessionHelpers context 'when session cookie is set' do before do - Gitlab::Redis::Sessions.with do |redis| - redis.set("session:gitlab:#{session_id.private_id}", Marshal.dump(session_hash)) - end - - cookies[Gitlab::Application.config.session_options[:key]] = session_id.public_id + stub_session(session_hash) end context 'when user is logged in' do diff --git a/spec/factories/gitlab/database/background_migration/batched_jobs.rb b/spec/factories/gitlab/database/background_migration/batched_jobs.rb index cec20616f7f..3c7dcd03701 100644 --- a/spec/factories/gitlab/database/background_migration/batched_jobs.rb +++ b/spec/factories/gitlab/database/background_migration/batched_jobs.rb @@ -9,5 +9,21 @@ FactoryBot.define do batch_size { 5 } sub_batch_size { 1 } pause_ms { 100 } + + trait(:pending) do + status { 0 } + end + + trait(:running) do + status { 1 } + end + + trait(:failed) do + status { 2 } + end + + trait(:succeeded) do + status { 3 } + end end end diff --git a/spec/features/admin/admin_sees_background_migrations_spec.rb b/spec/features/admin/admin_sees_background_migrations_spec.rb index e71cb11b413..a3d0c7bdd4d 100644 --- a/spec/features/admin/admin_sees_background_migrations_spec.rb +++ b/spec/features/admin/admin_sees_background_migrations_spec.rb @@ -10,7 +10,7 @@ RSpec.describe "Admin > Admin sees background migrations" do let_it_be(:finished_migration) { create(:batched_background_migration, table_name: 'finished', status: :finished) } before_all do - create(:batched_background_migration_job, batched_migration: failed_migration, batch_size: 10, min_value: 6, max_value: 15, status: :failed, attempts: 3) + create(:batched_background_migration_job, :failed, batched_migration: failed_migration, batch_size: 10, min_value: 6, max_value: 15, attempts: 3) end before do diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index 19f60ce55d3..925bbc47cf6 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -52,7 +52,7 @@ RSpec.describe 'Group' do click_button 'Create group' expect(current_path).to eq(new_group_path) - expect(page).to have_text('Please choose a group URL with no special characters or spaces.') + expect(page).to have_text('Choose a group path that does not start with a dash or end with a period. It can also contain alphanumeric characters and underscores.') end end @@ -90,7 +90,7 @@ RSpec.describe 'Group' do fill_in 'group_path', with: user.username wait_for_requests - expect(page).to have_content("Group path is already taken. We've suggested one that is available.") + expect(page).to have_content("Group path is unavailable. Path has been replaced with a suggested available path.") end it 'does not break after an invalid form submit' do @@ -279,7 +279,7 @@ RSpec.describe 'Group' do fill_in 'Group URL', with: subgroup.path wait_for_requests - expect(page).to have_content("Group path is already taken. We've suggested one that is available.") + expect(page).to have_content("Group path is unavailable. Path has been replaced with a suggested available path.") end end end diff --git a/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js b/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js index 794deca42ac..ddabb7194cb 100644 --- a/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js +++ b/spec/frontend/reports/accessibility_report/components/accessibility_issue_body_spec.js @@ -1,3 +1,4 @@ +import { GlBadge } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import AccessibilityIssueBody from '~/reports/accessibility_report/components/accessibility_issue_body.vue'; @@ -29,7 +30,7 @@ describe('CustomMetricsForm', () => { }); }; - const findIsNewBadge = () => wrapper.find({ ref: 'accessibility-issue-is-new-badge' }); + const findIsNewBadge = () => wrapper.findComponent(GlBadge); beforeEach(() => { mountComponent(issue); @@ -37,7 +38,6 @@ describe('CustomMetricsForm', () => { afterEach(() => { wrapper.destroy(); - wrapper = null; }); it('Displays the issue message', () => { @@ -52,7 +52,7 @@ describe('CustomMetricsForm', () => { .find({ ref: 'accessibility-issue-learn-more' }) .attributes('href'); - expect(learnMoreUrl).toEqual(issue.learnMoreUrl); + expect(learnMoreUrl).toBe(issue.learnMoreUrl); }); }); @@ -69,7 +69,7 @@ describe('CustomMetricsForm', () => { .find({ ref: 'accessibility-issue-learn-more' }) .attributes('href'); - expect(learnMoreUrl).toEqual('https://www.w3.org/TR/WCAG20-TECHS/Overview.html'); + expect(learnMoreUrl).toBe('https://www.w3.org/TR/WCAG20-TECHS/Overview.html'); }); }); @@ -86,7 +86,7 @@ describe('CustomMetricsForm', () => { .find({ ref: 'accessibility-issue-learn-more' }) .attributes('href'); - expect(learnMoreUrl).toEqual('https://www.w3.org/TR/WCAG20-TECHS/Overview.html'); + expect(learnMoreUrl).toBe('https://www.w3.org/TR/WCAG20-TECHS/Overview.html'); }); }); @@ -96,7 +96,7 @@ describe('CustomMetricsForm', () => { }); it('Renders the new badge', () => { - expect(findIsNewBadge().exists()).toEqual(true); + expect(findIsNewBadge().exists()).toBe(true); }); }); @@ -106,7 +106,7 @@ describe('CustomMetricsForm', () => { }); it('Does not render the new badge', () => { - expect(findIsNewBadge().exists()).toEqual(false); + expect(findIsNewBadge().exists()).toBe(false); }); }); }); diff --git a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb index d810cd2b570..7338ea657b9 100644 --- a/spec/lib/gitlab/database/background_migration/batched_job_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_job_spec.rb @@ -10,16 +10,82 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d it { is_expected.to have_many(:batched_job_transition_logs).with_foreign_key(:batched_background_migration_job_id) } end + describe 'state machine' do + let_it_be(:job) { create(:batched_background_migration_job, :failed) } + + context 'when a job is running' do + it 'logs the transition' do + expect(Gitlab::AppLogger).to receive(:info).with( { batched_job_id: job.id, message: 'BatchedJob transition', new_state: :running, previous_state: :failed } ) + + expect { job.run! }.to change(job, :started_at) + end + end + + context 'when a job succeed' do + let(:job) { create(:batched_background_migration_job, :running) } + + it 'logs the transition' do + expect(Gitlab::AppLogger).to receive(:info).with( { batched_job_id: job.id, message: 'BatchedJob transition', new_state: :succeeded, previous_state: :running } ) + + job.succeed! + end + + it 'updates the finished_at' do + expect { job.succeed! }.to change(job, :finished_at).from(nil).to(Time) + end + + it 'creates a new transition log' do + job.succeed! + + transition_log = job.batched_job_transition_logs.first + + expect(transition_log.next_status).to eq('succeeded') + expect(transition_log.exception_class).to be_nil + expect(transition_log.exception_message).to be_nil + end + end + + context 'when a job fails' do + let(:job) { create(:batched_background_migration_job, :running) } + + it 'logs the transition' do + expect(Gitlab::AppLogger).to receive(:info).with( { batched_job_id: job.id, message: 'BatchedJob transition', new_state: :failed, previous_state: :running } ) + + job.failure! + end + + it 'tracks the exception' do + expect(Gitlab::ErrorTracking).to receive(:track_exception).with(RuntimeError, { batched_job_id: job.id } ) + + job.failure!(error: RuntimeError.new) + end + + it 'updates the finished_at' do + expect { job.failure! }.to change(job, :finished_at).from(nil).to(Time) + end + + it 'creates a new transition log' do + job.failure!(error: RuntimeError.new) + + transition_log = job.batched_job_transition_logs.first + + expect(transition_log.next_status).to eq('failed') + expect(transition_log.exception_class).to eq('RuntimeError') + expect(transition_log.exception_message).to eq('RuntimeError') + end + end + end + describe 'scopes' do let_it_be(:fixed_time) { Time.new(2021, 04, 27, 10, 00, 00, 00) } - let_it_be(:pending_job) { create(:batched_background_migration_job, status: :pending, updated_at: fixed_time) } - let_it_be(:running_job) { create(:batched_background_migration_job, status: :running, updated_at: fixed_time) } - let_it_be(:stuck_job) { create(:batched_background_migration_job, status: :pending, updated_at: fixed_time - described_class::STUCK_JOBS_TIMEOUT) } - let_it_be(:failed_job) { create(:batched_background_migration_job, status: :failed, attempts: 1) } + let_it_be(:pending_job) { create(:batched_background_migration_job, :pending, updated_at: fixed_time) } + let_it_be(:running_job) { create(:batched_background_migration_job, :running, updated_at: fixed_time) } + let_it_be(:stuck_job) { create(:batched_background_migration_job, :pending, updated_at: fixed_time - described_class::STUCK_JOBS_TIMEOUT) } + let_it_be(:failed_job) { create(:batched_background_migration_job, :failed, attempts: 1) } - let!(:max_attempts_failed_job) { create(:batched_background_migration_job, status: :failed, attempts: described_class::MAX_ATTEMPTS) } - let!(:succeeded_job) { create(:batched_background_migration_job, status: :succeeded) } + let!(:max_attempts_failed_job) { create(:batched_background_migration_job, :failed, attempts: described_class::MAX_ATTEMPTS) } + let!(:succeeded_job) { create(:batched_background_migration_job, :succeeded) } before do travel_to fixed_time @@ -83,10 +149,10 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d subject { job.time_efficiency } let(:migration) { build(:batched_background_migration, interval: 120.seconds) } - let(:job) { build(:batched_background_migration_job, status: :succeeded, batched_migration: migration) } + let(:job) { build(:batched_background_migration_job, :succeeded, batched_migration: migration) } context 'when job has not yet succeeded' do - let(:job) { build(:batched_background_migration_job, status: :running) } + let(:job) { build(:batched_background_migration_job, :running) } it 'returns nil' do expect(subject).to be_nil @@ -131,7 +197,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d end describe '#split_and_retry!' do - let!(:job) { create(:batched_background_migration_job, batch_size: 10, min_value: 6, max_value: 15, status: :failed, attempts: 3) } + let!(:job) { create(:batched_background_migration_job, :failed, batch_size: 10, min_value: 6, max_value: 15, attempts: 3) } context 'when job can be split' do before do @@ -147,7 +213,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d min_value: 6, max_value: 10, batch_size: 5, - status: 'failed', + status_name: :failed, attempts: 0, started_at: nil, finished_at: nil, @@ -161,7 +227,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d min_value: 11, max_value: 15, batch_size: 5, - status: 'failed', + status_name: :failed, attempts: 0, started_at: nil, finished_at: nil, @@ -178,7 +244,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d end context 'when job is not failed' do - let!(:job) { create(:batched_background_migration_job, status: :succeeded) } + let!(:job) { create(:batched_background_migration_job, :succeeded) } it 'raises an exception' do expect { job.split_and_retry! }.to raise_error 'Only failed jobs can be split' @@ -186,7 +252,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d end context 'when batch size is already 1' do - let!(:job) { create(:batched_background_migration_job, batch_size: 1, status: :failed) } + let!(:job) { create(:batched_background_migration_job, :failed, batch_size: 1) } it 'raises an exception' do expect { job.split_and_retry! }.to raise_error 'Job cannot be split further' @@ -205,7 +271,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedJob, type: :model d expect(job.batch_size).to eq(5) expect(job.attempts).to eq(0) - expect(job.status).to eq('failed') + expect(job.status_name).to eq(:failed) end end end diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb index 04c18a98ee6..bb2c6b9a3ae 100644 --- a/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_migration_runner_spec.rb @@ -96,13 +96,12 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do end let!(:previous_job) do - create(:batched_background_migration_job, + create(:batched_background_migration_job, :succeeded, batched_migration: migration, min_value: event1.id, max_value: event2.id, batch_size: 2, - sub_batch_size: 1, - status: :succeeded + sub_batch_size: 1 ) end @@ -144,7 +143,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do context 'when migration has failed jobs' do before do - previous_job.update!(status: :failed) + previous_job.failure! end it 'retries the failed job' do @@ -172,7 +171,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do context 'when migration has stuck jobs' do before do - previous_job.update!(status: :running, updated_at: 1.hour.ago - Gitlab::Database::BackgroundMigration::BatchedJob::STUCK_JOBS_TIMEOUT) + previous_job.update!(status_event: 'run', updated_at: 1.hour.ago - Gitlab::Database::BackgroundMigration::BatchedJob::STUCK_JOBS_TIMEOUT) end it 'retries the stuck job' do @@ -186,7 +185,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do context 'when migration has possible stuck jobs' do before do - previous_job.update!(status: :running, updated_at: 1.hour.from_now - Gitlab::Database::BackgroundMigration::BatchedJob::STUCK_JOBS_TIMEOUT) + previous_job.update!(status_event: 'run', updated_at: 1.hour.from_now - Gitlab::Database::BackgroundMigration::BatchedJob::STUCK_JOBS_TIMEOUT) end it 'keeps the migration active' do @@ -201,13 +200,13 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do context 'when the migration has batches to process and failed jobs' do before do migration.update!(max_value: event3.id) - previous_job.update!(status: :failed) + previous_job.failure! end it 'runs next batch then retries the failed job' do expect(migration_wrapper).to receive(:perform) do |job_record| expect(job_record).to eq(job_relation.last) - job_record.update!(status: :succeeded) + job_record.succeed! end expect { runner.run_migration_job(migration) }.to change { job_relation.count }.by(1) @@ -264,12 +263,12 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do it 'runs all jobs inline until finishing the migration' do expect(migration_wrapper).to receive(:perform) do |job_record| expect(job_record).to eq(job_relation.first) - job_record.update!(status: :succeeded) + job_record.succeed! end expect(migration_wrapper).to receive(:perform) do |job_record| expect(job_record).to eq(job_relation.last) - job_record.update!(status: :succeeded) + job_record.succeed! end expect { runner.run_entire_migration(migration) }.to change { job_relation.count }.by(2) @@ -330,9 +329,9 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do pause_ms: 0 } - create(:batched_background_migration_job, common_attributes.merge(status: :succeeded, min_value: 1, max_value: 2)) - create(:batched_background_migration_job, common_attributes.merge(status: :pending, min_value: 3, max_value: 4)) - create(:batched_background_migration_job, common_attributes.merge(status: :failed, min_value: 5, max_value: 6, attempts: 1)) + create(:batched_background_migration_job, :succeeded, common_attributes.merge(min_value: 1, max_value: 2)) + create(:batched_background_migration_job, :pending, common_attributes.merge(min_value: 3, max_value: 4)) + create(:batched_background_migration_job, :failed, common_attributes.merge(min_value: 5, max_value: 6, attempts: 1)) end it 'completes the migration' do @@ -359,7 +358,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationRunner do context 'when migration fails to complete' do it 'raises an error' do - batched_migration.batched_jobs.failed.update_all(attempts: Gitlab::Database::BackgroundMigration::BatchedJob::MAX_ATTEMPTS) + batched_migration.batched_jobs.with_status(:failed).update_all(attempts: Gitlab::Database::BackgroundMigration::BatchedJob::MAX_ATTEMPTS) expect do runner.finalize( diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb index 01d61a525e6..ea4ba4dd137 100644 --- a/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_migration_spec.rb @@ -26,7 +26,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m context 'when there are failed jobs' do let(:batched_migration) { create(:batched_background_migration, status: :active, total_tuple_count: 100) } - let!(:batched_job) { create(:batched_background_migration_job, batched_migration: batched_migration, status: :failed) } + let!(:batched_job) { create(:batched_background_migration_job, :failed, batched_migration: batched_migration) } it 'raises an exception' do expect { batched_migration.finished! }.to raise_error(ActiveRecord::RecordInvalid) @@ -37,7 +37,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m context 'when the jobs are completed' do let(:batched_migration) { create(:batched_background_migration, status: :active, total_tuple_count: 100) } - let!(:batched_job) { create(:batched_background_migration_job, batched_migration: batched_migration, status: :succeeded) } + let!(:batched_job) { create(:batched_background_migration_job, :succeeded, batched_migration: batched_migration) } it 'finishes the migration' do batched_migration.finished! @@ -64,7 +64,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m it 'returns the first active migration according to queue order' do expect(described_class.active_migration).to eq(migration2) - create(:batched_background_migration_job, batched_migration: migration1, batch_size: 1000, status: :succeeded) + create(:batched_background_migration_job, :succeeded, batched_migration: migration1, batch_size: 1000) end end @@ -84,10 +84,10 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m let!(:migration_without_jobs) { create(:batched_background_migration) } before do - create(:batched_background_migration_job, batched_migration: migration1, batch_size: 1000, status: :succeeded) - create(:batched_background_migration_job, batched_migration: migration1, batch_size: 200, status: :failed) - create(:batched_background_migration_job, batched_migration: migration2, batch_size: 500, status: :succeeded) - create(:batched_background_migration_job, batched_migration: migration2, batch_size: 200, status: :running) + create(:batched_background_migration_job, :succeeded, batched_migration: migration1, batch_size: 1000) + create(:batched_background_migration_job, :failed, batched_migration: migration1, batch_size: 200) + create(:batched_background_migration_job, :succeeded, batched_migration: migration2, batch_size: 500) + create(:batched_background_migration_job, :running, batched_migration: migration2, batch_size: 200) end it 'returns totals from successful jobs' do @@ -268,7 +268,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m subject(:retry_failed_jobs) { batched_migration.retry_failed_jobs! } context 'when there are failed migration jobs' do - let!(:batched_background_migration_job) { create(:batched_background_migration_job, batched_migration: batched_migration, batch_size: 10, min_value: 6, max_value: 15, status: :failed, attempts: 3) } + let!(:batched_background_migration_job) { create(:batched_background_migration_job, :failed, batched_migration: batched_migration, batch_size: 10, min_value: 6, max_value: 15, attempts: 3) } before do allow_next_instance_of(Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchingStrategy) do |batch_class| @@ -312,9 +312,9 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m let(:batched_migration) { create(:batched_background_migration) } before do - create_list(:batched_background_migration_job, 5, status: :succeeded, batch_size: 1_000, batched_migration: batched_migration) - create_list(:batched_background_migration_job, 1, status: :running, batch_size: 1_000, batched_migration: batched_migration) - create_list(:batched_background_migration_job, 1, status: :failed, batch_size: 1_000, batched_migration: batched_migration) + create_list(:batched_background_migration_job, 5, :succeeded, batch_size: 1_000, batched_migration: batched_migration) + create_list(:batched_background_migration_job, 1, :running, batch_size: 1_000, batched_migration: batched_migration) + create_list(:batched_background_migration_job, 1, :failed, batch_size: 1_000, batched_migration: batched_migration) end it 'sums the batch_size of succeeded jobs' do @@ -347,7 +347,6 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m let_it_be(:common_attrs) do { - status: :succeeded, batched_migration: migration, finished_at: end_time } @@ -357,7 +356,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m subject { migration.smoothed_time_efficiency(number_of_jobs: 10) } it 'returns nil' do - create_list(:batched_background_migration_job, 9, **common_attrs) + create_list(:batched_background_migration_job, 9, :succeeded, **common_attrs) expect(subject).to be_nil end @@ -369,6 +368,8 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m subject { migration.smoothed_time_efficiency(number_of_jobs: number_of_jobs) } + let!(:jobs) { create_list(:batched_background_migration_job, number_of_jobs, :succeeded, **common_attrs.merge(batched_migration: migration)) } + before do expect(migration).to receive_message_chain(:batched_jobs, :successful_in_execution_order, :reverse_order, :limit, :with_preloads) .and_return(jobs) diff --git a/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb b/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb index c1183a15e37..b3c4522a2a1 100644 --- a/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb +++ b/spec/lib/gitlab/database/background_migration/batched_migration_wrapper_spec.rb @@ -35,8 +35,6 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, ' expect(job_instance).to receive(:perform) expect(job_instance).to receive(:batch_metrics).and_return(test_metrics) - expect(job_record).to receive(:update!).with(hash_including(attempts: 1, status: :running)).and_call_original - freeze_time do subject @@ -51,11 +49,10 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, ' context 'when running a job that failed previously' do let!(:job_record) do - create(:batched_background_migration_job, + create(:batched_background_migration_job, :failed, batched_migration: active_migration, pause_ms: pause_ms, attempts: 1, - status: :failed, finished_at: 1.hour.ago, metrics: { 'my_metrics' => 'some_value' } ) @@ -67,10 +64,6 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, ' expect(job_instance).to receive(:perform) expect(job_instance).to receive(:batch_metrics).and_return(updated_metrics) - expect(job_record).to receive(:update!).with( - hash_including(attempts: 2, status: :running, finished_at: nil, metrics: {}) - ).and_call_original - freeze_time do subject diff --git a/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb b/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb index a0e78186caa..c8e744ab262 100644 --- a/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb +++ b/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb @@ -119,123 +119,80 @@ RSpec.describe Gitlab::GithubImport::Importer::DiffNoteImporter, :aggregate_fail .and_return(discussion_id) end - context 'when github_importer_use_diff_note_with_suggestions is disabled' do - before do - stub_feature_flags(github_importer_use_diff_note_with_suggestions: false) + it_behaves_like 'diff notes without suggestion' + + context 'when the note has suggestions' do + let(:note_body) do + <<~EOB + Suggestion: + ```suggestion + what do you think to do it like this + ``` + EOB end - it_behaves_like 'diff notes without suggestion' + before do + stub_user_finder(user.id, true) + end - context 'when the note has suggestions' do - let(:note_body) do - <<~EOB + it 'imports the note as diff note' do + expect { subject.execute } + .to change(DiffNote, :count) + .by(1) + + note = project.notes.diff_notes.take + expect(note).to be_valid + expect(note.noteable_type).to eq('MergeRequest') + expect(note.noteable_id).to eq(merge_request.id) + expect(note.project_id).to eq(project.id) + expect(note.author_id).to eq(user.id) + expect(note.system).to eq(false) + expect(note.discussion_id).to eq(discussion_id) + expect(note.commit_id).to eq('original123abc') + expect(note.line_code).to eq(note_representation.line_code) + expect(note.type).to eq('DiffNote') + expect(note.created_at).to eq(created_at) + expect(note.updated_at).to eq(updated_at) + expect(note.position.to_h).to eq({ + base_sha: merge_request.diffs.diff_refs.base_sha, + head_sha: merge_request.diffs.diff_refs.head_sha, + start_sha: merge_request.diffs.diff_refs.start_sha, + new_line: 15, + old_line: nil, + new_path: file_path, + old_path: file_path, + position_type: 'text', + line_range: nil + }) + expect(note.note) + .to eq <<~NOTE Suggestion: - ```suggestion + ```suggestion:-0+0 what do you think to do it like this ``` - EOB - end - - it 'imports the note' do - stub_user_finder(user.id, true) - - expect { subject.execute } - .to change(LegacyDiffNote, :count) - .and not_change(DiffNote, :count) - - note = project.notes.diff_notes.take - expect(note).to be_valid - expect(note.note) - .to eq <<~NOTE - Suggestion: - ```suggestion:-0+0 - what do you think to do it like this - ``` - NOTE - end - end - end - - context 'when github_importer_use_diff_note_with_suggestions is enabled' do - before do - stub_feature_flags(github_importer_use_diff_note_with_suggestions: true) + NOTE end - it_behaves_like 'diff notes without suggestion' + context 'when the note diff file creation fails' do + it 'falls back to the LegacyDiffNote' do + exception = ::DiffNote::NoteDiffFileCreationError.new('Failed to create diff note file') - context 'when the note has suggestions' do - let(:note_body) do - <<~EOB - Suggestion: - ```suggestion - what do you think to do it like this - ``` - EOB - end + expect_next_instance_of(::Import::Github::Notes::CreateService) do |service| + expect(service) + .to receive(:execute) + .and_raise(exception) + end - before do - stub_user_finder(user.id, true) - end + expect(Gitlab::GithubImport::Logger) + .to receive(:warn) + .with( + message: 'Failed to create diff note file', + 'error.class': 'DiffNote::NoteDiffFileCreationError' + ) - it 'imports the note as diff note' do expect { subject.execute } - .to change(DiffNote, :count) - .by(1) - - note = project.notes.diff_notes.take - expect(note).to be_valid - expect(note.noteable_type).to eq('MergeRequest') - expect(note.noteable_id).to eq(merge_request.id) - expect(note.project_id).to eq(project.id) - expect(note.author_id).to eq(user.id) - expect(note.system).to eq(false) - expect(note.discussion_id).to eq(discussion_id) - expect(note.commit_id).to eq('original123abc') - expect(note.line_code).to eq(note_representation.line_code) - expect(note.type).to eq('DiffNote') - expect(note.created_at).to eq(created_at) - expect(note.updated_at).to eq(updated_at) - expect(note.position.to_h).to eq({ - base_sha: merge_request.diffs.diff_refs.base_sha, - head_sha: merge_request.diffs.diff_refs.head_sha, - start_sha: merge_request.diffs.diff_refs.start_sha, - new_line: 15, - old_line: nil, - new_path: file_path, - old_path: file_path, - position_type: 'text', - line_range: nil - }) - expect(note.note) - .to eq <<~NOTE - Suggestion: - ```suggestion:-0+0 - what do you think to do it like this - ``` - NOTE - end - - context 'when the note diff file creation fails' do - it 'falls back to the LegacyDiffNote' do - exception = ::DiffNote::NoteDiffFileCreationError.new('Failed to create diff note file') - - expect_next_instance_of(::Import::Github::Notes::CreateService) do |service| - expect(service) - .to receive(:execute) - .and_raise(exception) - end - - expect(Gitlab::GithubImport::Logger) - .to receive(:warn) - .with( - message: 'Failed to create diff note file', - 'error.class': 'DiffNote::NoteDiffFileCreationError' - ) - - expect { subject.execute } - .to change(LegacyDiffNote, :count) - .and not_change(DiffNote, :count) - end + .to change(LegacyDiffNote, :count) + .and not_change(DiffNote, :count) end end end diff --git a/spec/lib/gitlab/rack_attack/request_spec.rb b/spec/lib/gitlab/rack_attack/request_spec.rb index 37db588ce16..9b882f26480 100644 --- a/spec/lib/gitlab/rack_attack/request_spec.rb +++ b/spec/lib/gitlab/rack_attack/request_spec.rb @@ -192,6 +192,44 @@ RSpec.describe Gitlab::RackAttack::Request do end end + describe '#frontend_request?', :allow_forgery_protection do + subject { request.send(:frontend_request?) } + + let(:path) { '/' } + + # Define these as local variables so we can use them in the `where` block. + valid_token = SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH) + other_token = SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH) + + where(:session, :env, :expected) do + {} | {} | false # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands + {} | { 'HTTP_X_CSRF_TOKEN' => valid_token } | false + { _csrf_token: valid_token } | { 'HTTP_X_CSRF_TOKEN' => other_token } | false + { _csrf_token: valid_token } | { 'HTTP_X_CSRF_TOKEN' => valid_token } | true + end + + with_them do + it { is_expected.to eq(expected) } + end + + context 'when the feature flag is disabled' do + before do + stub_feature_flags(rate_limit_frontend_requests: false) + end + + where(:session, :env) do + {} | {} # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands + {} | { 'HTTP_X_CSRF_TOKEN' => valid_token } + { _csrf_token: valid_token } | { 'HTTP_X_CSRF_TOKEN' => other_token } + { _csrf_token: valid_token } | { 'HTTP_X_CSRF_TOKEN' => valid_token } + end + + with_them do + it { is_expected.to be(false) } + end + end + end + describe '#deprecated_api_request?' do subject { request.send(:deprecated_api_request?) } diff --git a/spec/requests/admin/background_migrations_controller_spec.rb b/spec/requests/admin/background_migrations_controller_spec.rb index c7d5d5cae08..67c9c4df827 100644 --- a/spec/requests/admin/background_migrations_controller_spec.rb +++ b/spec/requests/admin/background_migrations_controller_spec.rb @@ -13,7 +13,7 @@ RSpec.describe Admin::BackgroundMigrationsController, :enable_admin_mode do let(:migration) { create(:batched_background_migration, status: 'failed') } before do - create(:batched_background_migration_job, batched_migration: migration, batch_size: 10, min_value: 6, max_value: 15, status: :failed, attempts: 3) + create(:batched_background_migration_job, :failed, batched_migration: migration, batch_size: 10, min_value: 6, max_value: 15, attempts: 3) allow_next_instance_of(Gitlab::BackgroundMigration::BatchingStrategies::PrimaryKeyBatchingStrategy) do |batch_class| allow(batch_class).to receive(:next_batch).with(anything, anything, batch_min_value: 6, batch_size: 5).and_return([6, 10]) diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 27572b38451..156a4cf5ff3 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -5,6 +5,7 @@ require 'mime/types' RSpec.describe API::Commits do include ProjectForksHelper + include SessionHelpers let(:user) { create(:user) } let(:guest) { create(:user).tap { |u| project.add_guest(u) } } @@ -384,14 +385,7 @@ RSpec.describe API::Commits do context 'when using warden' do it 'increments usage counters', :clean_gitlab_redis_sessions do - session_id = Rack::Session::SessionId.new('6919a6f1bb119dd7396fadc38fd18d0d') - session_hash = { 'warden.user.user.key' => [[user.id], user.encrypted_password[0, 29]] } - - Gitlab::Redis::Sessions.with do |redis| - redis.set("session:gitlab:#{session_id.private_id}", Marshal.dump(session_hash)) - end - - cookies[Gitlab::Application.config.session_options[:key]] = session_id.public_id + stub_session('warden.user.user.key' => [[user.id], user.encrypted_password[0, 29]]) expect(::Gitlab::UsageDataCounters::WebIdeCounter).to receive(:increment_commits_count) expect(::Gitlab::UsageDataCounters::EditorUniqueCounter).to receive(:track_web_ide_edit_action) diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb index 25eae57a134..f2126e3cf9c 100644 --- a/spec/requests/rack_attack_global_spec.rb +++ b/spec/requests/rack_attack_global_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_caching do include RackAttackSpecHelpers + include SessionHelpers let(:settings) { Gitlab::CurrentSettings.current_application_settings } @@ -63,6 +64,22 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac end end + describe 'API requests from the frontend', :api, :clean_gitlab_redis_sessions do + context 'when unauthenticated' do + it_behaves_like 'rate-limited frontend API requests' do + let(:throttle_setting_prefix) { 'throttle_unauthenticated' } + end + end + + context 'when authenticated' do + it_behaves_like 'rate-limited frontend API requests' do + let_it_be(:personal_access_token) { create(:personal_access_token) } + + let(:throttle_setting_prefix) { 'throttle_authenticated' } + end + end + end + describe 'API requests authenticated with personal access token', :api do let_it_be(:user) { create(:user) } let_it_be(:token) { create(:personal_access_token, user: user) } diff --git a/spec/support/helpers/rack_attack_spec_helpers.rb b/spec/support/helpers/rack_attack_spec_helpers.rb index d50a6382a40..c82a87dc58e 100644 --- a/spec/support/helpers/rack_attack_spec_helpers.rb +++ b/spec/support/helpers/rack_attack_spec_helpers.rb @@ -26,14 +26,14 @@ module RackAttackSpecHelpers { 'AUTHORIZATION' => "Basic #{encoded_login}" } end - def expect_rejection(&block) + def expect_rejection(name = nil, &block) yield expect(response).to have_gitlab_http_status(:too_many_requests) expect(response.headers.to_h).to include( 'RateLimit-Limit' => a_string_matching(/^\d+$/), - 'RateLimit-Name' => a_string_matching(/^throttle_.*$/), + 'RateLimit-Name' => name || a_string_matching(/^throttle_.*$/), 'RateLimit-Observed' => a_string_matching(/^\d+$/), 'RateLimit-Remaining' => a_string_matching(/^\d+$/), 'Retry-After' => a_string_matching(/^\d+$/) diff --git a/spec/support/helpers/session_helpers.rb b/spec/support/helpers/session_helpers.rb index 236585296e5..394a401afca 100644 --- a/spec/support/helpers/session_helpers.rb +++ b/spec/support/helpers/session_helpers.rb @@ -1,6 +1,22 @@ # frozen_string_literal: true module SessionHelpers + # Stub a session in Redis, for use in request specs where we can't mock the session directly. + # This also needs the :clean_gitlab_redis_sessions tag on the spec. + def stub_session(session_hash) + unless RSpec.current_example.metadata[:clean_gitlab_redis_sessions] + raise 'Add :clean_gitlab_redis_sessions to your spec!' + end + + session_id = Rack::Session::SessionId.new(SecureRandom.hex) + + Gitlab::Redis::Sessions.with do |redis| + redis.set("session:gitlab:#{session_id.private_id}", Marshal.dump(session_hash)) + end + + cookies[Gitlab::Application.config.session_options[:key]] = session_id.public_id + end + def expect_single_session_with_authenticated_ttl expect_single_session_with_expiration(Settings.gitlab['session_expire_delay'] * 60) end diff --git a/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb index 8bffd1f71e9..a42a1fda62e 100644 --- a/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/graphql/mutations/snippets_shared_examples.rb @@ -10,6 +10,8 @@ RSpec.shared_examples 'when the snippet is not found' do end RSpec.shared_examples 'snippet edit usage data counters' do + include SessionHelpers + context 'when user is sessionless' do it 'does not track usage data actions' do expect(::Gitlab::UsageDataCounters::EditorUniqueCounter).not_to receive(:track_snippet_editor_edit_action) @@ -20,14 +22,7 @@ RSpec.shared_examples 'snippet edit usage data counters' do context 'when user is not sessionless', :clean_gitlab_redis_sessions do before do - session_id = Rack::Session::SessionId.new('6919a6f1bb119dd7396fadc38fd18d0d') - session_hash = { 'warden.user.user.key' => [[current_user.id], current_user.encrypted_password[0, 29]] } - - Gitlab::Redis::Sessions.with do |redis| - redis.set("session:gitlab:#{session_id.private_id}", Marshal.dump(session_hash)) - end - - cookies[Gitlab::Application.config.session_options[:key]] = session_id.public_id + stub_session('warden.user.user.key' => [[current_user.id], current_user.encrypted_password[0, 29]]) end it 'tracks usage data actions', :clean_gitlab_redis_sessions do diff --git a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb index b294467d482..c6c6c44dce8 100644 --- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb +++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb @@ -580,3 +580,88 @@ RSpec.shared_examples 'rate-limited unauthenticated requests' do end end end + +# Requires let variables: +# * throttle_setting_prefix: "throttle_authenticated", "throttle_unauthenticated" +RSpec.shared_examples 'rate-limited frontend API requests' do + let(:requests_per_period) { 1 } + let(:csrf_token) { SecureRandom.base64(ActionController::RequestForgeryProtection::AUTHENTICITY_TOKEN_LENGTH) } + let(:csrf_session) { { _csrf_token: csrf_token } } + let(:personal_access_token) { nil } + + let(:api_path) { '/projects' } + + # These don't actually exist, so a 404 is the expected response. + let(:files_api_path) { '/projects/1/repository/files/ref/path' } + let(:packages_api_path) { '/projects/1/packages/foo' } + let(:deprecated_api_path) { '/groups/1?with_projects=true' } + + def get_api(path: api_path, csrf: false) + headers = csrf ? { 'X-CSRF-Token' => csrf_token } : nil + get api(path, personal_access_token: personal_access_token), headers: headers + end + + def expect_not_found(&block) + yield + + expect(response).to have_gitlab_http_status(:not_found) + end + + before do + stub_application_setting( + "#{throttle_setting_prefix}_enabled" => true, + "#{throttle_setting_prefix}_requests_per_period" => requests_per_period, + "#{throttle_setting_prefix}_api_enabled" => true, + "#{throttle_setting_prefix}_api_requests_per_period" => requests_per_period, + "#{throttle_setting_prefix}_web_enabled" => true, + "#{throttle_setting_prefix}_web_requests_per_period" => requests_per_period, + "#{throttle_setting_prefix}_files_api_enabled" => true, + "#{throttle_setting_prefix}_packages_api_enabled" => true, + "#{throttle_setting_prefix}_deprecated_api_enabled" => true + ) + + stub_session(csrf_session) + end + + context 'with a CSRF token' do + it 'uses the rate limit for web requests' do + requests_per_period.times { get_api csrf: true } + + expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true } + expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true, path: files_api_path } + expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true, path: packages_api_path } + expect_rejection("#{throttle_setting_prefix}_web") { get_api csrf: true, path: deprecated_api_path } + + # API rate limit is not triggered yet + expect_ok { get_api } + expect_not_found { get_api path: files_api_path } + expect_not_found { get_api path: packages_api_path } + expect_not_found { get_api path: deprecated_api_path } + end + + context 'without a CSRF session' do + let(:csrf_session) { nil } + + it 'always uses the rate limit for API requests' do + requests_per_period.times { get_api csrf: true } + + expect_rejection("#{throttle_setting_prefix}_api") { get_api csrf: true } + expect_rejection("#{throttle_setting_prefix}_api") { get_api } + end + end + end + + context 'without a CSRF token' do + it 'uses the rate limit for API requests' do + requests_per_period.times { get_api } + + expect_rejection("#{throttle_setting_prefix}_api") { get_api } + + # Web and custom API rate limits are not triggered yet + expect_ok { get_api csrf: true } + expect_not_found { get_api path: files_api_path } + expect_not_found { get_api path: packages_api_path } + expect_not_found { get_api path: deprecated_api_path } + end + end +end |