diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-19 12:10:08 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-19 12:10:08 +0000 |
commit | 1072f96e340ddad78e5dad6dfedc7c6e85fc53ea (patch) | |
tree | 573525bb3525f79cbb011f39ad11c11c6efea315 /spec | |
parent | 8cc0a0aa965798e74826197d350472d235af2f84 (diff) | |
download | gitlab-ce-1072f96e340ddad78e5dad6dfedc7c6e85fc53ea.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
24 files changed, 411 insertions, 458 deletions
diff --git a/spec/frontend/locale/index_spec.js b/spec/frontend/locale/index_spec.js index a08be502735..220061fc64a 100644 --- a/spec/frontend/locale/index_spec.js +++ b/spec/frontend/locale/index_spec.js @@ -1,5 +1,5 @@ import { setLanguage } from 'helpers/locale_helper'; -import { createDateTimeFormat, formatNumber, languageCode } from '~/locale'; +import { createDateTimeFormat, formatNumber, languageCode, getPreferredLocales } from '~/locale'; describe('locale', () => { afterEach(() => setLanguage(null)); @@ -18,13 +18,91 @@ describe('locale', () => { }); }); + describe('getPreferredLocales', () => { + beforeEach(() => { + // Need to spy on window.navigator.languages as it is read-only + jest + .spyOn(window.navigator, 'languages', 'get') + .mockReturnValueOnce(['en-GB', 'en-US', 'de-AT']); + }); + + it('filters navigator.languages by GitLab language', () => { + setLanguage('en'); + + expect(getPreferredLocales()).toEqual(['en-GB', 'en-US', 'en']); + }); + + it('filters navigator.languages by GitLab language without locale and sets English Fallback', () => { + setLanguage('de'); + + expect(getPreferredLocales()).toEqual(['de-AT', 'de', 'en']); + }); + + it('filters navigator.languages by GitLab language with locale and sets English Fallback', () => { + setLanguage('de-DE'); + + expect(getPreferredLocales()).toEqual(['de-AT', 'de-DE', 'de', 'en']); + }); + + it('adds GitLab language if navigator.languages does not contain it', () => { + setLanguage('es-ES'); + + expect(getPreferredLocales()).toEqual(['es-ES', 'es', 'en']); + }); + }); + describe('createDateTimeFormat', () => { - beforeEach(() => setLanguage('en')); + const date = new Date(2015, 0, 3, 15, 13, 22); + const formatOptions = { dateStyle: 'long', timeStyle: 'medium' }; it('creates an instance of Intl.DateTimeFormat', () => { - const dateFormat = createDateTimeFormat({ year: 'numeric', month: 'long', day: 'numeric' }); + const dateFormat = createDateTimeFormat(formatOptions); + + expect(dateFormat).toBeInstanceOf(Intl.DateTimeFormat); + }); + + it('falls back to `en` and GitLab language is default', () => { + setLanguage(null); + jest.spyOn(window.navigator, 'languages', 'get').mockReturnValueOnce(['de-AT', 'en-GB']); + + const dateFormat = createDateTimeFormat(formatOptions); + expect(dateFormat.format(date)).toBe( + new Intl.DateTimeFormat('en-GB', formatOptions).format(date), + ); + }); + + it('falls back to `en` locale if browser languages are empty', () => { + setLanguage('en'); + jest.spyOn(window.navigator, 'languages', 'get').mockReturnValueOnce([]); + + const dateFormat = createDateTimeFormat(formatOptions); + expect(dateFormat.format(date)).toBe( + new Intl.DateTimeFormat('en', formatOptions).format(date), + ); + }); + + it('prefers `en-GB` if it is the preferred language and GitLab language is `en`', () => { + setLanguage('en'); + jest + .spyOn(window.navigator, 'languages', 'get') + .mockReturnValueOnce(['en-GB', 'en-US', 'en']); + + const dateFormat = createDateTimeFormat(formatOptions); + expect(dateFormat.format(date)).toBe( + new Intl.DateTimeFormat('en-GB', formatOptions).format(date), + ); + }); + + it('prefers `de-AT` if it is GitLab language and not part of the browser languages', () => { + setLanguage('de-AT'); + jest + .spyOn(window.navigator, 'languages', 'get') + .mockReturnValueOnce(['en-GB', 'en-US', 'en']); - expect(dateFormat.format(new Date(2015, 6, 3))).toBe('July 3, 2015'); + const dateFormat = createDateTimeFormat(formatOptions); + expect(dateFormat.format(date)).toBe( + new Intl.DateTimeFormat('de-AT', formatOptions).format(date), + ); }); }); diff --git a/spec/frontend/pages/admin/application_settings/metrics_and_profiling/usage_statistics_spec.js b/spec/frontend/pages/admin/application_settings/metrics_and_profiling/usage_statistics_spec.js index ef649e7697b..858c7b76ac8 100644 --- a/spec/frontend/pages/admin/application_settings/metrics_and_profiling/usage_statistics_spec.js +++ b/spec/frontend/pages/admin/application_settings/metrics_and_profiling/usage_statistics_spec.js @@ -1,6 +1,6 @@ import initSetHelperText, { - HELPER_TEXT_USAGE_PING_DISABLED, - HELPER_TEXT_USAGE_PING_ENABLED, + HELPER_TEXT_SERVICE_PING_DISABLED, + HELPER_TEXT_SERVICE_PING_ENABLED, } from '~/pages/admin/application_settings/metrics_and_profiling/usage_statistics'; describe('UsageStatistics', () => { @@ -17,18 +17,18 @@ describe('UsageStatistics', () => { usagePingFeaturesCheckBox = document.getElementById( 'application_setting_usage_ping_features_enabled', ); - usagePingFeaturesLabel = document.getElementById('usage_ping_features_label'); - usagePingFeaturesHelperText = document.getElementById('usage_ping_features_helper_text'); + usagePingFeaturesLabel = document.getElementById('service_ping_features_label'); + usagePingFeaturesHelperText = document.getElementById('service_ping_features_helper_text'); }); const expectEnabledUsagePingFeaturesCheckBox = () => { expect(usagePingFeaturesCheckBox.classList.contains('gl-cursor-not-allowed')).toBe(false); - expect(usagePingFeaturesHelperText.textContent).toEqual(HELPER_TEXT_USAGE_PING_ENABLED); + expect(usagePingFeaturesHelperText.textContent).toEqual(HELPER_TEXT_SERVICE_PING_ENABLED); }; const expectDisabledUsagePingFeaturesCheckBox = () => { expect(usagePingFeaturesLabel.classList.contains('gl-cursor-not-allowed')).toBe(true); - expect(usagePingFeaturesHelperText.textContent).toEqual(HELPER_TEXT_USAGE_PING_DISABLED); + expect(usagePingFeaturesHelperText.textContent).toEqual(HELPER_TEXT_SERVICE_PING_DISABLED); }; describe('Registration Features checkbox', () => { diff --git a/spec/graphql/types/ci/pipeline_type_spec.rb b/spec/graphql/types/ci/pipeline_type_spec.rb index 35d48229fa4..9ba4252bcd5 100644 --- a/spec/graphql/types/ci/pipeline_type_spec.rb +++ b/spec/graphql/types/ci/pipeline_type_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Types::Ci::PipelineType do coverage created_at updated_at started_at finished_at committed_at stages user retryable cancelable jobs source_job job downstream upstream path project active user_permissions warnings commit_path uses_needs - test_report_summary test_suite + test_report_summary test_suite ref ] if Gitlab.ee? diff --git a/spec/lib/backup/database_spec.rb b/spec/lib/backup/database_spec.rb index 2bce4cab679..f57037d5652 100644 --- a/spec/lib/backup/database_spec.rb +++ b/spec/lib/backup/database_spec.rb @@ -38,7 +38,7 @@ RSpec.describe Backup::Database do context 'when the restore command prints errors' do let(:visible_error) { "This is a test error\n" } - let(:noise) { "Table projects does not exist\nmust be owner of extension pg_trgm\n" } + let(:noise) { "Table projects does not exist\nmust be owner of extension pg_trgm\nWARNING: no privileges could be revoked for public\n" } let(:cmd) { %W[#{Gem.ruby} -e $stderr.write("#{noise}#{visible_error}")] } it 'filters out noise from errors' do diff --git a/spec/lib/gitlab/database/postgres_index_spec.rb b/spec/lib/gitlab/database/postgres_index_spec.rb index da047632985..e1832219ebf 100644 --- a/spec/lib/gitlab/database/postgres_index_spec.rb +++ b/spec/lib/gitlab/database/postgres_index_spec.rb @@ -22,30 +22,6 @@ RSpec.describe Gitlab::Database::PostgresIndex do it_behaves_like 'a postgres model' - describe '.regular' do - it 'only non-unique indexes' do - expect(described_class.regular).to all(have_attributes(unique: false)) - end - - it 'only non partitioned indexes' do - expect(described_class.regular).to all(have_attributes(partitioned: false)) - end - - it 'only indexes that dont serve an exclusion constraint' do - expect(described_class.regular).to all(have_attributes(exclusion: false)) - end - - it 'only non-expression indexes' do - expect(described_class.regular).to all(have_attributes(expression: false)) - end - - it 'only btree and gist indexes' do - types = described_class.regular.map(&:type).uniq - - expect(types & %w(btree gist)).to eq(types) - end - end - describe '.reindexing_support' do it 'only non partitioned indexes' do expect(described_class.reindexing_support).to all(have_attributes(partitioned: false)) diff --git a/spec/lib/gitlab/database/reindexing/concurrent_reindex_spec.rb b/spec/lib/gitlab/database/reindexing/concurrent_reindex_spec.rb deleted file mode 100644 index d9077969003..00000000000 --- a/spec/lib/gitlab/database/reindexing/concurrent_reindex_spec.rb +++ /dev/null @@ -1,303 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::Database::Reindexing::ConcurrentReindex, '#perform' do - subject { described_class.new(index, logger: logger) } - - let(:table_name) { '_test_reindex_table' } - let(:column_name) { '_test_column' } - let(:index_name) { '_test_reindex_index' } - let(:index) { instance_double(Gitlab::Database::PostgresIndex, indexrelid: 42, name: index_name, schema: 'public', tablename: table_name, partitioned?: false, unique?: false, exclusion?: false, expression?: false, definition: 'CREATE INDEX _test_reindex_index ON public._test_reindex_table USING btree (_test_column)') } - let(:logger) { double('logger', debug: nil, info: nil, error: nil ) } - let(:connection) { ActiveRecord::Base.connection } - - before do - connection.execute(<<~SQL) - CREATE TABLE #{table_name} ( - id serial NOT NULL PRIMARY KEY, - #{column_name} integer NOT NULL); - - CREATE INDEX #{index.name} ON #{table_name} (#{column_name}); - SQL - end - - context 'when the index is unique' do - before do - allow(index).to receive(:unique?).and_return(true) - end - - it 'raises an error' do - expect do - subject.perform - end.to raise_error(described_class::ReindexError, /UNIQUE indexes are currently not supported/) - end - end - - context 'when the index is partitioned' do - before do - allow(index).to receive(:partitioned?).and_return(true) - end - - it 'raises an error' do - expect do - subject.perform - end.to raise_error(described_class::ReindexError, /partitioned indexes are currently not supported/) - end - end - - context 'when the index serves an exclusion constraint' do - before do - allow(index).to receive(:exclusion?).and_return(true) - end - - it 'raises an error' do - expect do - subject.perform - end.to raise_error(described_class::ReindexError, /indexes serving an exclusion constraint are currently not supported/) - end - end - - context 'when the index is a lingering temporary index from a previous reindexing run' do - context 'with the temporary index prefix' do - let(:index_name) { 'tmp_reindex_something' } - - it 'raises an error' do - expect do - subject.perform - end.to raise_error(described_class::ReindexError, /left-over temporary index/) - end - end - - context 'with the replaced index prefix' do - let(:index_name) { 'old_reindex_something' } - - it 'raises an error' do - expect do - subject.perform - end.to raise_error(described_class::ReindexError, /left-over temporary index/) - end - end - end - - context 'replacing the original index with a rebuilt copy' do - let(:replacement_name) { 'tmp_reindex_42' } - let(:replaced_name) { 'old_reindex_42' } - - let(:create_index) { "CREATE INDEX CONCURRENTLY #{replacement_name} ON public.#{table_name} USING btree (#{column_name})" } - let(:drop_index) do - <<~SQL - DROP INDEX CONCURRENTLY - IF EXISTS "public"."#{replacement_name}" - SQL - end - - let!(:original_index) { find_index_create_statement } - - it 'integration test: executing full index replacement without mocks' do - allow(connection).to receive(:execute).and_wrap_original do |method, sql| - method.call(sql.sub(/CONCURRENTLY/, '')) - end - - subject.perform - - check_index_exists - end - - context 'mocked specs' do - before do - allow(subject).to receive(:connection).and_return(connection) - allow(connection).to receive(:execute).and_call_original - end - - it 'replaces the existing index with an identical index' do - expect(connection).to receive(:execute).with('SET statement_timeout TO \'32400s\'') - - expect_to_execute_concurrently_in_order(create_index) - - expect_next_instance_of(::Gitlab::Database::WithLockRetries) do |instance| - expect(instance).to receive(:run).with(raise_on_exhaustion: true).and_yield - end - - expect_index_rename(index.name, replaced_name) - expect_index_rename(replacement_name, index.name) - expect_index_rename(replaced_name, replacement_name) - - expect_next_instance_of(::Gitlab::Database::WithLockRetries) do |instance| - expect(instance).to receive(:run).with(raise_on_exhaustion: false).and_yield - end - - expect_to_execute_concurrently_in_order(drop_index) - - subject.perform - - check_index_exists - end - - context 'for expression indexes' do - before do - allow(index).to receive(:expression?).and_return(true) - end - - it 'rebuilds table statistics before dropping the original index' do - expect(connection).to receive(:execute).with('SET statement_timeout TO \'32400s\'') - - expect_to_execute_concurrently_in_order(create_index) - - expect_to_execute_concurrently_in_order(<<~SQL) - ANALYZE "#{index.schema}"."#{index.tablename}" - SQL - - expect_next_instance_of(::Gitlab::Database::WithLockRetries) do |instance| - expect(instance).to receive(:run).with(raise_on_exhaustion: true).and_yield - end - - expect_index_rename(index.name, replaced_name) - expect_index_rename(replacement_name, index.name) - expect_index_rename(replaced_name, replacement_name) - - expect_index_drop(drop_index) - - subject.perform - - check_index_exists - end - end - - context 'when a dangling index is left from a previous run' do - before do - connection.execute("CREATE INDEX #{replacement_name} ON #{table_name} (#{column_name})") - end - - it 'replaces the existing index with an identical index' do - expect_index_drop(drop_index) - expect_to_execute_concurrently_in_order(create_index) - - expect_next_instance_of(::Gitlab::Database::WithLockRetries) do |instance| - expect(instance).to receive(:run).with(raise_on_exhaustion: true).and_yield - end - - expect_index_rename(index.name, replaced_name) - expect_index_rename(replacement_name, index.name) - expect_index_rename(replaced_name, replacement_name) - - expect_index_drop(drop_index) - - subject.perform - - check_index_exists - end - end - - context 'when it fails to create the replacement index' do - it 'safely cleans up and signals the error' do - expect(connection).to receive(:execute).with(create_index).ordered - .and_raise(ActiveRecord::ConnectionTimeoutError, 'connect timeout') - - expect_next_instance_of(::Gitlab::Database::WithLockRetries) do |instance| - expect(instance).to receive(:run).with(raise_on_exhaustion: false).and_yield - end - - expect_to_execute_concurrently_in_order(drop_index) - - expect { subject.perform }.to raise_error(ActiveRecord::ConnectionTimeoutError, /connect timeout/) - - check_index_exists - end - end - - context 'when the replacement index is not valid' do - it 'safely cleans up and signals the error' do - replacement_index = double('replacement index', valid_index?: false) - allow(Gitlab::Database::PostgresIndex).to receive(:find_by).with(schema: 'public', name: replacement_name).and_return(nil, replacement_index) - - expect_to_execute_concurrently_in_order(create_index) - - expect_next_instance_of(::Gitlab::Database::WithLockRetries) do |instance| - expect(instance).to receive(:run).with(raise_on_exhaustion: false).and_yield - end - - expect_to_execute_concurrently_in_order(drop_index) - - expect { subject.perform }.to raise_error(described_class::ReindexError, /replacement index was created as INVALID/) - - check_index_exists - end - end - - context 'when a database error occurs while swapping the indexes' do - it 'safely cleans up and signals the error' do - replacement_index = double('replacement index', valid_index?: true) - allow(Gitlab::Database::PostgresIndex).to receive(:find_by).with(schema: 'public', name: replacement_name).and_return(nil, replacement_index) - - expect_to_execute_concurrently_in_order(create_index) - - expect_next_instance_of(::Gitlab::Database::WithLockRetries) do |instance| - expect(instance).to receive(:run).with(raise_on_exhaustion: true).and_yield - end - - expect_index_rename(index.name, replaced_name).and_raise(ActiveRecord::ConnectionTimeoutError, 'connect timeout') - - expect_index_drop(drop_index) - - expect { subject.perform }.to raise_error(ActiveRecord::ConnectionTimeoutError, /connect timeout/) - - check_index_exists - end - end - - context 'when with_lock_retries fails to acquire the lock' do - it 'safely cleans up and signals the error' do - expect_to_execute_concurrently_in_order(create_index) - - expect_next_instance_of(::Gitlab::Database::WithLockRetries) do |instance| - expect(instance).to receive(:run).with(raise_on_exhaustion: true) - .and_raise(::Gitlab::Database::WithLockRetries::AttemptsExhaustedError, 'exhausted') - end - - expect_index_drop(drop_index) - - expect { subject.perform }.to raise_error(::Gitlab::Database::WithLockRetries::AttemptsExhaustedError, /exhausted/) - - check_index_exists - end - end - end - end - - def expect_to_execute_concurrently_in_order(sql) - # Indexes cannot be created CONCURRENTLY in a transaction. Since the tests are wrapped in transactions, - # verify the original call but pass through the non-concurrent form. - expect(connection).to receive(:execute).with(sql).ordered.and_wrap_original do |method, sql| - method.call(sql.sub(/CONCURRENTLY/, '')) - end - end - - def expect_index_rename(from, to) - expect(connection).to receive(:execute).with(<<~SQL).ordered - ALTER INDEX "public"."#{from}" - RENAME TO "#{to}" - SQL - end - - def expect_index_drop(drop_index) - expect_next_instance_of(::Gitlab::Database::WithLockRetries) do |instance| - expect(instance).to receive(:run).with(raise_on_exhaustion: false).and_yield - end - - expect_to_execute_concurrently_in_order(drop_index) - end - - def find_index_create_statement - ActiveRecord::Base.connection.select_value(<<~SQL) - SELECT indexdef - FROM pg_indexes - WHERE schemaname = 'public' - AND indexname = #{ActiveRecord::Base.connection.quote(index.name)} - SQL - end - - def check_index_exists - expect(find_index_create_statement).to eq(original_index) - end -end diff --git a/spec/lib/gitlab/database/reindexing/coordinator_spec.rb b/spec/lib/gitlab/database/reindexing/coordinator_spec.rb index fd8b177c809..085fd3061ad 100644 --- a/spec/lib/gitlab/database/reindexing/coordinator_spec.rb +++ b/spec/lib/gitlab/database/reindexing/coordinator_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Gitlab::Database::Reindexing::Coordinator do let(:index) { create(:postgres_index) } let(:notifier) { instance_double(Gitlab::Database::Reindexing::GrafanaNotifier, notify_start: nil, notify_end: nil) } - let(:reindexer) { instance_double(Gitlab::Database::Reindexing::ConcurrentReindex, perform: nil) } + let(:reindexer) { instance_double(Gitlab::Database::Reindexing::ReindexConcurrently, perform: nil) } let(:action) { create(:reindex_action, index: index) } let!(:lease) { stub_exclusive_lease(lease_key, uuid, timeout: lease_timeout) } @@ -19,83 +19,64 @@ RSpec.describe Gitlab::Database::Reindexing::Coordinator do let(:lease_timeout) { 1.day } let(:uuid) { 'uuid' } - shared_examples_for 'reindexing coordination' do - context 'locking' do - it 'acquires a lock while reindexing' do - expect(lease).to receive(:try_obtain).ordered.and_return(uuid) + before do + swapout_view_for_table(:postgres_indexes) - expect(reindexer).to receive(:perform).ordered + allow(Gitlab::Database::Reindexing::ReindexConcurrently).to receive(:new).with(index).and_return(reindexer) + allow(Gitlab::Database::Reindexing::ReindexAction).to receive(:create_for).with(index).and_return(action) + end - expect(Gitlab::ExclusiveLease).to receive(:cancel).ordered.with(lease_key, uuid) + context 'locking' do + it 'acquires a lock while reindexing' do + expect(lease).to receive(:try_obtain).ordered.and_return(uuid) - subject - end + expect(reindexer).to receive(:perform).ordered - it 'does not perform reindexing actions if lease is not granted' do - expect(lease).to receive(:try_obtain).ordered.and_return(false) - expect(Gitlab::Database::Reindexing::ConcurrentReindex).not_to receive(:new) + expect(Gitlab::ExclusiveLease).to receive(:cancel).ordered.with(lease_key, uuid) - subject - end + subject end - context 'notifications' do - it 'sends #notify_start before reindexing' do - expect(notifier).to receive(:notify_start).with(action).ordered - expect(reindexer).to receive(:perform).ordered - - subject - end + it 'does not perform reindexing actions if lease is not granted' do + expect(lease).to receive(:try_obtain).ordered.and_return(false) + expect(Gitlab::Database::Reindexing::ReindexConcurrently).not_to receive(:new) - it 'sends #notify_end after reindexing and updating the action is done' do - expect(action).to receive(:finish).ordered - expect(notifier).to receive(:notify_end).with(action).ordered - - subject - end + subject end + end - context 'action tracking' do - it 'calls #finish on the action' do - expect(reindexer).to receive(:perform).ordered - expect(action).to receive(:finish).ordered - - subject - end + context 'notifications' do + it 'sends #notify_start before reindexing' do + expect(notifier).to receive(:notify_start).with(action).ordered + expect(reindexer).to receive(:perform).ordered - it 'upon error, it still calls finish and raises the error' do - expect(reindexer).to receive(:perform).ordered.and_raise('something went wrong') - expect(action).to receive(:finish).ordered + subject + end - expect { subject }.to raise_error(/something went wrong/) + it 'sends #notify_end after reindexing and updating the action is done' do + expect(action).to receive(:finish).ordered + expect(notifier).to receive(:notify_end).with(action).ordered - expect(action).to be_failed - end + subject end end - context 'legacy reindexing method (< PG12) - to be removed' do - before do - stub_feature_flags(database_reindexing_pg12: false) - swapout_view_for_table(:postgres_indexes) + context 'action tracking' do + it 'calls #finish on the action' do + expect(reindexer).to receive(:perform).ordered + expect(action).to receive(:finish).ordered - allow(Gitlab::Database::Reindexing::ConcurrentReindex).to receive(:new).with(index).and_return(reindexer) - allow(Gitlab::Database::Reindexing::ReindexAction).to receive(:create_for).with(index).and_return(action) + subject end - it_behaves_like 'reindexing coordination' - end + it 'upon error, it still calls finish and raises the error' do + expect(reindexer).to receive(:perform).ordered.and_raise('something went wrong') + expect(action).to receive(:finish).ordered - context 'PG12 reindexing method' do - before do - stub_feature_flags(database_reindexing_pg12: true) - swapout_view_for_table(:postgres_indexes) + expect { subject }.to raise_error(/something went wrong/) - allow(Gitlab::Database::Reindexing::ReindexConcurrently).to receive(:new).with(index).and_return(reindexer) - allow(Gitlab::Database::Reindexing::ReindexAction).to receive(:create_for).with(index).and_return(action) + expect(action).to be_failed end - - it_behaves_like 'reindexing coordination' end end end diff --git a/spec/lib/gitlab/database/reindexing_spec.rb b/spec/lib/gitlab/database/reindexing_spec.rb index 0a3cbcef46a..8aff99544ca 100644 --- a/spec/lib/gitlab/database/reindexing_spec.rb +++ b/spec/lib/gitlab/database/reindexing_spec.rb @@ -29,30 +29,11 @@ RSpec.describe Gitlab::Database::Reindexing do describe '.candidate_indexes' do subject { described_class.candidate_indexes } - context 'with deprecated method for < PG12' do - before do - stub_feature_flags(database_reindexing_pg12: false) - end - - it 'retrieves regular indexes that are no left-overs from previous runs' do - result = double - expect(Gitlab::Database::PostgresIndex).to receive_message_chain('not_match.not_match.not_match.regular').with('^tmp_reindex_').with('^old_reindex_').with('\_ccnew[0-9]*$').with(no_args).and_return(result) - - expect(subject).to eq(result) - end - end + it 'retrieves regular indexes that are no left-overs from previous runs' do + result = double + expect(Gitlab::Database::PostgresIndex).to receive_message_chain('not_match.reindexing_support').with('\_ccnew[0-9]*$').with(no_args).and_return(result) - context 'with deprecated method for >= PG12' do - before do - stub_feature_flags(database_reindexing_pg12: true) - end - - it 'retrieves regular indexes that are no left-overs from previous runs' do - result = double - expect(Gitlab::Database::PostgresIndex).to receive_message_chain('not_match.not_match.not_match.reindexing_support').with('^tmp_reindex_').with('^old_reindex_').with('\_ccnew[0-9]*$').with(no_args).and_return(result) - - expect(subject).to eq(result) - end + expect(subject).to eq(result) end end end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 10dfc46c0bb..706bcdea291 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -934,6 +934,63 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do end end + describe '#new_commits' do + let(:repository) { mutable_repository } + let(:new_commit) do + author = { name: 'Test User', email: 'mail@example.com', time: Time.now } + + Rugged::Commit.create(repository_rugged, + author: author, + committer: author, + message: "Message", + parents: [], + tree: "4b825dc642cb6eb9a060e54bf8d69288fbee4904") + end + + let(:expected_commits) { 1 } + let(:revisions) { [new_commit] } + + shared_examples 'an enumeration of new commits' do + it 'enumerates commits' do + commits = repository.new_commits(revisions).to_a + + expect(commits.size).to eq(expected_commits) + commits.each do |commit| + expect(commit.id).to eq(new_commit) + expect(commit.message).to eq("Message") + end + end + end + + context 'with list_commits disabled' do + before do + stub_feature_flags(list_commits: false) + + expect_next_instance_of(Gitlab::GitalyClient::RefService) do |service| + expect(service) + .to receive(:list_new_commits) + .with(new_commit) + .and_call_original + end + end + + it_behaves_like 'an enumeration of new commits' + end + + context 'with list_commits enabled' do + before do + expect_next_instance_of(Gitlab::GitalyClient::CommitService) do |service| + expect(service) + .to receive(:list_commits) + .with([new_commit, '--not', '--all']) + .and_call_original + end + end + + it_behaves_like 'an enumeration of new commits' + end + end + describe '#count_commits_between' do subject { repository.count_commits_between('feature', 'master') } diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index ac4c42d57ee..22c29403255 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -287,6 +287,39 @@ RSpec.describe Gitlab::GitalyClient::CommitService do end end + describe '#list_commits' do + shared_examples 'a ListCommits request' do + before do + ::Gitlab::GitalyClient.clear_stubs! + end + + it 'sends a list_commits message' do + expect_next_instance_of(Gitaly::CommitService::Stub) do |service| + expect(service) + .to receive(:list_commits) + .with(gitaly_request_with_params(expected_params), kind_of(Hash)) + .and_return([]) + end + + client.list_commits(revisions) + end + end + + context 'with a single revision' do + let(:revisions) { 'master' } + let(:expected_params) { %w[master] } + + it_behaves_like 'a ListCommits request' + end + + context 'with multiple revisions' do + let(:revisions) { %w[master --not --all] } + let(:expected_params) { %w[master --not --all] } + + it_behaves_like 'a ListCommits request' + end + end + describe '#commit_stats' do let(:request) do Gitaly::CommitStatsRequest.new( diff --git a/spec/lib/gitlab/sidekiq_middleware/size_limiter/validator_spec.rb b/spec/lib/gitlab/sidekiq_middleware/size_limiter/validator_spec.rb index 4fbe59c3c27..440eca10a88 100644 --- a/spec/lib/gitlab/sidekiq_middleware/size_limiter/validator_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/size_limiter/validator_spec.rb @@ -230,11 +230,11 @@ RSpec.describe Gitlab::SidekiqMiddleware::SizeLimiter::Validator do end context 'in compress mode' do + let(:size_limit) { 50 } + let(:compression_threshold) { 30 } let(:mode) { 'compress' } context 'when job size is less than compression threshold' do - let(:size_limit) { 50 } - let(:compression_threshold) { 30 } let(:job) { job_payload(a: 'a' * 10) } it 'does not raise an exception' do @@ -244,8 +244,6 @@ RSpec.describe Gitlab::SidekiqMiddleware::SizeLimiter::Validator do end context 'when job size is bigger than compression threshold and less than size limit after compressed' do - let(:size_limit) { 50 } - let(:compression_threshold) { 30 } let(:args) { { a: 'a' * 300 } } let(:job) { job_payload(args) } @@ -260,9 +258,20 @@ RSpec.describe Gitlab::SidekiqMiddleware::SizeLimiter::Validator do end end + context 'when the job was already compressed' do + let(:job) do + job_payload({ a: 'a' * 10 }) + .merge(Gitlab::SidekiqMiddleware::SizeLimiter::Compressor::COMPRESSED_KEY => true) + end + + it 'does not compress the arguments again' do + expect(Gitlab::SidekiqMiddleware::SizeLimiter::Compressor).not_to receive(:compress) + + expect { validate.call(TestSizeLimiterWorker, job) }.not_to raise_error + end + end + context 'when job size is bigger than compression threshold and bigger than size limit after compressed' do - let(:size_limit) { 50 } - let(:compression_threshold) { 30 } let(:args) { { a: 'a' * 3000 } } let(:job) { job_payload(args) } diff --git a/spec/lib/gitlab/usage/metric_definition_spec.rb b/spec/lib/gitlab/usage/metric_definition_spec.rb index 1ed639b2f7d..f3c3e5fc550 100644 --- a/spec/lib/gitlab/usage/metric_definition_spec.rb +++ b/spec/lib/gitlab/usage/metric_definition_spec.rb @@ -17,7 +17,8 @@ RSpec.describe Gitlab::Usage::MetricDefinition do data_source: 'database', distribution: %w(ee ce), tier: %w(free starter premium ultimate bronze silver gold), - name: 'count_boards' + name: 'uuid', + data_category: 'Standard' } end @@ -63,6 +64,7 @@ RSpec.describe Gitlab::Usage::MetricDefinition do :value_type | nil :value_type | 'test' :status | nil + :data_category | nil :key_path | nil :product_group | nil :time_frame | nil @@ -196,7 +198,8 @@ RSpec.describe Gitlab::Usage::MetricDefinition do time_frame: 'none', data_source: 'database', distribution: %w(ee ce), - tier: %w(free starter premium ultimate bronze silver gold) + tier: %w(free starter premium ultimate bronze silver gold), + data_category: 'Optional' } end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index e634800320d..efa269cdb5c 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -3156,35 +3156,19 @@ RSpec.describe Project, factory_default: :keep do end end - describe '#change_head' do - let_it_be(:project) { create(:project, :repository) } - - it 'returns error if branch does not exist' do - expect(project.change_head('unexisted-branch')).to be false - expect(project.errors.size).to eq(1) - end - - it 'calls the before_change_head and after_change_head methods' do - expect(project.repository).to receive(:before_change_head) - expect(project.repository).to receive(:after_change_head) - - project.change_head(project.default_branch) - end + describe '#after_repository_change_head' do + let_it_be(:project) { create(:project) } it 'updates commit count' do expect(ProjectCacheWorker).to receive(:perform_async).with(project.id, [], [:commit_count]) - project.change_head(project.default_branch) - end - - it 'copies the gitattributes' do - expect(project.repository).to receive(:copy_gitattributes).with(project.default_branch) - project.change_head(project.default_branch) + project.after_repository_change_head end it 'reloads the default branch' do expect(project).to receive(:reload_default_branch) - project.change_head(project.default_branch) + + project.after_repository_change_head end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 4b0435baa98..452eafe733f 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -2141,6 +2141,12 @@ RSpec.describe Repository do repository.after_change_head end + + it 'calls after_repository_change_head on container' do + expect(repository.container).to receive(:after_repository_change_head) + + repository.after_change_head + end end describe '#expires_caches_for_tags' do @@ -3266,4 +3272,30 @@ RSpec.describe Repository do settings.save! end end + + describe '#change_head' do + let(:branch) { repository.container.default_branch } + + it 'adds an error to container if branch does not exist' do + expect(repository.change_head('unexisted-branch')).to be false + expect(repository.container.errors.size).to eq(1) + end + + it 'calls the before_change_head and after_change_head methods' do + expect(repository).to receive(:before_change_head) + expect(repository).to receive(:after_change_head) + + repository.change_head(branch) + end + + it 'copies the gitattributes' do + expect(repository).to receive(:copy_gitattributes).with(branch) + repository.change_head(branch) + end + + it 'reloads the default branch' do + expect(repository.container).to receive(:reload_default_branch) + repository.change_head(branch) + end + end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 6c09abc7705..59123c3695a 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe ProjectPolicy do include ExternalAuthorizationServiceHelpers + include AdminModeHelper include_context 'ProjectPolicy context' let(:project) { public_project } @@ -1486,7 +1487,12 @@ RSpec.describe ProjectPolicy do describe 'container_image policies' do using RSpec::Parameterized::TableSyntax - let(:guest_operations_permissions) { [:read_container_image] } + # These are permissions that admins should not have when the project is private + # or the container registry is private. + let(:admin_excluded_permissions) { [:build_read_container_image] } + + let(:anonymous_operations_permissions) { [:read_container_image] } + let(:guest_operations_permissions) { anonymous_operations_permissions + [:build_read_container_image] } let(:developer_operations_permissions) do guest_operations_permissions + [ @@ -1500,47 +1506,67 @@ RSpec.describe ProjectPolicy do ] end + let(:all_permissions) { maintainer_operations_permissions } + where(:project_visibility, :access_level, :role, :allowed) do + :public | ProjectFeature::ENABLED | :admin | true + :public | ProjectFeature::ENABLED | :owner | true :public | ProjectFeature::ENABLED | :maintainer | true :public | ProjectFeature::ENABLED | :developer | true :public | ProjectFeature::ENABLED | :reporter | true :public | ProjectFeature::ENABLED | :guest | true :public | ProjectFeature::ENABLED | :anonymous | true + :public | ProjectFeature::PRIVATE | :admin | true + :public | ProjectFeature::PRIVATE | :owner | true :public | ProjectFeature::PRIVATE | :maintainer | true :public | ProjectFeature::PRIVATE | :developer | true :public | ProjectFeature::PRIVATE | :reporter | true :public | ProjectFeature::PRIVATE | :guest | false :public | ProjectFeature::PRIVATE | :anonymous | false + :public | ProjectFeature::DISABLED | :admin | false + :public | ProjectFeature::DISABLED | :owner | false :public | ProjectFeature::DISABLED | :maintainer | false :public | ProjectFeature::DISABLED | :developer | false :public | ProjectFeature::DISABLED | :reporter | false :public | ProjectFeature::DISABLED | :guest | false :public | ProjectFeature::DISABLED | :anonymous | false + :internal | ProjectFeature::ENABLED | :admin | true + :internal | ProjectFeature::ENABLED | :owner | true :internal | ProjectFeature::ENABLED | :maintainer | true :internal | ProjectFeature::ENABLED | :developer | true :internal | ProjectFeature::ENABLED | :reporter | true :internal | ProjectFeature::ENABLED | :guest | true :internal | ProjectFeature::ENABLED | :anonymous | false + :internal | ProjectFeature::PRIVATE | :admin | true + :internal | ProjectFeature::PRIVATE | :owner | true :internal | ProjectFeature::PRIVATE | :maintainer | true :internal | ProjectFeature::PRIVATE | :developer | true :internal | ProjectFeature::PRIVATE | :reporter | true :internal | ProjectFeature::PRIVATE | :guest | false :internal | ProjectFeature::PRIVATE | :anonymous | false + :internal | ProjectFeature::DISABLED | :admin | false + :internal | ProjectFeature::DISABLED | :owner | false :internal | ProjectFeature::DISABLED | :maintainer | false :internal | ProjectFeature::DISABLED | :developer | false :internal | ProjectFeature::DISABLED | :reporter | false :internal | ProjectFeature::DISABLED | :guest | false :internal | ProjectFeature::DISABLED | :anonymous | false + :private | ProjectFeature::ENABLED | :admin | true + :private | ProjectFeature::ENABLED | :owner | true :private | ProjectFeature::ENABLED | :maintainer | true :private | ProjectFeature::ENABLED | :developer | true :private | ProjectFeature::ENABLED | :reporter | true :private | ProjectFeature::ENABLED | :guest | false :private | ProjectFeature::ENABLED | :anonymous | false + :private | ProjectFeature::PRIVATE | :admin | true + :private | ProjectFeature::PRIVATE | :owner | true :private | ProjectFeature::PRIVATE | :maintainer | true :private | ProjectFeature::PRIVATE | :developer | true :private | ProjectFeature::PRIVATE | :reporter | true :private | ProjectFeature::PRIVATE | :guest | false :private | ProjectFeature::PRIVATE | :anonymous | false + :private | ProjectFeature::DISABLED | :admin | false + :private | ProjectFeature::DISABLED | :owner | false :private | ProjectFeature::DISABLED | :maintainer | false :private | ProjectFeature::DISABLED | :developer | false :private | ProjectFeature::DISABLED | :reporter | false @@ -1552,24 +1578,44 @@ RSpec.describe ProjectPolicy do let(:current_user) { send(role) } let(:project) { send("#{project_visibility}_project") } - it 'allows/disallows the abilities based on the container_registry feature access level' do + before do + enable_admin_mode!(admin) if role == :admin project.project_feature.update!(container_registry_access_level: access_level) + end + it 'allows/disallows the abilities based on the container_registry feature access level' do if allowed expect_allowed(*permissions_abilities(role)) + expect_disallowed(*(all_permissions - permissions_abilities(role))) else - expect_disallowed(*permissions_abilities(role)) + expect_disallowed(*all_permissions) + end + end + + it 'allows build_read_container_image to admins who are also team members' do + if allowed && role == :admin + project.add_reporter(current_user) + + expect_allowed(:build_read_container_image) end end def permissions_abilities(role) case role - when :maintainer + when :admin + if project_visibility == :private || access_level == ProjectFeature::PRIVATE + maintainer_operations_permissions - admin_excluded_permissions + else + maintainer_operations_permissions + end + when :maintainer, :owner maintainer_operations_permissions when :developer developer_operations_permissions - when :reporter, :guest, :anonymous + when :reporter, :guest guest_operations_permissions + when :anonymous + anonymous_operations_permissions else raise "Unknown role #{role}" end diff --git a/spec/rubocop/cop/worker_data_consistency_spec.rb b/spec/rubocop/cop/worker_data_consistency_spec.rb new file mode 100644 index 00000000000..5fa42bf2b87 --- /dev/null +++ b/spec/rubocop/cop/worker_data_consistency_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require_relative '../../../rubocop/cop/worker_data_consistency' + +RSpec.describe RuboCop::Cop::WorkerDataConsistency do + subject(:cop) { described_class.new } + + before do + allow(cop) + .to receive(:in_worker?) + .and_return(true) + end + + it 'adds an offense when not defining data_consistency' do + expect_offense(<<~CODE) + class SomeWorker + ^^^^^^^^^^^^^^^^ Should define data_consistency expectation.[...] + include ApplicationWorker + + queue_namespace :pipeline_hooks + feature_category :continuous_integration + urgency :high + end + CODE + end + + it 'adds no offense when defining data_consistency' do + expect_no_offenses(<<~CODE) + class SomeWorker + include ApplicationWorker + + queue_namespace :pipeline_hooks + feature_category :continuous_integration + data_consistency :delayed + urgency :high + end + CODE + end + + it 'adds no offense when worker is not an ApplicationWorker' do + expect_no_offenses(<<~CODE) + class SomeWorker + queue_namespace :pipeline_hooks + feature_category :continuous_integration + urgency :high + end + CODE + end +end diff --git a/spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb b/spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb index 1be4d9b80a4..a403a27adef 100644 --- a/spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/has_repository_shared_examples.rb @@ -152,4 +152,20 @@ RSpec.shared_examples 'model with repository' do it { is_expected.to respond_to(:disk_path) } it { is_expected.to respond_to(:gitlab_shell) } end + + describe '#change_head' do + it 'delegates #change_head to repository' do + expect(stubbed_container.repository).to receive(:change_head).with('foo') + + stubbed_container.change_head('foo') + end + end + + describe '#after_repository_change_head' do + it 'calls #reload_default_branch' do + expect(stubbed_container).to receive(:reload_default_branch) + + stubbed_container.after_repository_change_head + end + end end diff --git a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb index c98d3768748..eafcbd77040 100644 --- a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb +++ b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb @@ -629,6 +629,22 @@ RSpec.shared_examples 'a container registry auth service' do end end end + + context 'for project with private container registry' do + let_it_be(:project, reload: true) { create(:project, :public) } + + before do + project.project_feature.update!(container_registry_access_level: ProjectFeature::PRIVATE) + end + + it_behaves_like 'pullable for being team member' + + context 'when you are admin' do + let_it_be(:current_user) { create(:admin) } + + it_behaves_like 'pullable for being team member' + end + end end context 'when pushing' do diff --git a/spec/workers/jira_connect/sync_branch_worker_spec.rb b/spec/workers/jira_connect/sync_branch_worker_spec.rb index f70a09c1ee2..349ccd10694 100644 --- a/spec/workers/jira_connect/sync_branch_worker_spec.rb +++ b/spec/workers/jira_connect/sync_branch_worker_spec.rb @@ -7,7 +7,6 @@ RSpec.describe JiraConnect::SyncBranchWorker do it_behaves_like 'worker with data consistency', described_class, - feature_flag: :load_balancing_for_jira_connect_workers, data_consistency: :delayed describe '#perform' do diff --git a/spec/workers/jira_connect/sync_builds_worker_spec.rb b/spec/workers/jira_connect/sync_builds_worker_spec.rb index e60d1c58966..9be0cccae2b 100644 --- a/spec/workers/jira_connect/sync_builds_worker_spec.rb +++ b/spec/workers/jira_connect/sync_builds_worker_spec.rb @@ -7,7 +7,6 @@ RSpec.describe ::JiraConnect::SyncBuildsWorker do it_behaves_like 'worker with data consistency', described_class, - feature_flag: :load_balancing_for_jira_connect_workers, data_consistency: :delayed describe '#perform' do diff --git a/spec/workers/jira_connect/sync_deployments_worker_spec.rb b/spec/workers/jira_connect/sync_deployments_worker_spec.rb index 62b879e0130..86ba11ebe9c 100644 --- a/spec/workers/jira_connect/sync_deployments_worker_spec.rb +++ b/spec/workers/jira_connect/sync_deployments_worker_spec.rb @@ -7,7 +7,6 @@ RSpec.describe ::JiraConnect::SyncDeploymentsWorker do it_behaves_like 'worker with data consistency', described_class, - feature_flag: :load_balancing_for_jira_connect_workers, data_consistency: :delayed describe '#perform' do diff --git a/spec/workers/jira_connect/sync_feature_flags_worker_spec.rb b/spec/workers/jira_connect/sync_feature_flags_worker_spec.rb index 1c2f9126b71..6763aefcbec 100644 --- a/spec/workers/jira_connect/sync_feature_flags_worker_spec.rb +++ b/spec/workers/jira_connect/sync_feature_flags_worker_spec.rb @@ -7,7 +7,6 @@ RSpec.describe ::JiraConnect::SyncFeatureFlagsWorker do it_behaves_like 'worker with data consistency', described_class, - feature_flag: :load_balancing_for_jira_connect_workers, data_consistency: :delayed describe '#perform' do diff --git a/spec/workers/jira_connect/sync_merge_request_worker_spec.rb b/spec/workers/jira_connect/sync_merge_request_worker_spec.rb index 7e403de54bb..65976566b22 100644 --- a/spec/workers/jira_connect/sync_merge_request_worker_spec.rb +++ b/spec/workers/jira_connect/sync_merge_request_worker_spec.rb @@ -7,7 +7,6 @@ RSpec.describe JiraConnect::SyncMergeRequestWorker do it_behaves_like 'worker with data consistency', described_class, - feature_flag: :load_balancing_for_jira_connect_workers, data_consistency: :delayed describe '#perform' do diff --git a/spec/workers/jira_connect/sync_project_worker_spec.rb b/spec/workers/jira_connect/sync_project_worker_spec.rb index 727d22d8b08..d172bde2400 100644 --- a/spec/workers/jira_connect/sync_project_worker_spec.rb +++ b/spec/workers/jira_connect/sync_project_worker_spec.rb @@ -7,7 +7,6 @@ RSpec.describe JiraConnect::SyncProjectWorker, factory_default: :keep do it_behaves_like 'worker with data consistency', described_class, - feature_flag: :load_balancing_for_jira_connect_workers, data_consistency: :delayed describe '#perform' do |