diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 11:43:02 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-20 11:43:02 +0300 |
commit | d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb (patch) | |
tree | 2341ef426af70ad1e289c38036737e04b0aa5007 /spec/support/shared_examples | |
parent | d6e514dd13db8947884cd58fe2a9c2a063400a9b (diff) |
Add latest changes from gitlab-org/gitlab@14-4-stable-eev14.4.0-rc42
Diffstat (limited to 'spec/support/shared_examples')
24 files changed, 527 insertions, 114 deletions
diff --git a/spec/support/shared_examples/ci/stuck_builds_shared_examples.rb b/spec/support/shared_examples/ci/stuck_builds_shared_examples.rb new file mode 100644 index 00000000000..4fcea18393c --- /dev/null +++ b/spec/support/shared_examples/ci/stuck_builds_shared_examples.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'job is dropped with failure reason' do |failure_reason| + it 'changes status' do + service.execute + job.reload + + expect(job).to be_failed + expect(job.failure_reason).to eq(failure_reason) + end + + context 'when job has data integrity problem' do + it 'drops the job and logs the reason' do + job.update_columns(yaml_variables: '[{"key" => "value"}]') + + expect(Gitlab::ErrorTracking) + .to receive(:track_exception) + .with(anything, a_hash_including(build_id: job.id)) + .once + .and_call_original + + service.execute + job.reload + + expect(job).to be_failed + expect(job.failure_reason).to eq('data_integrity_failure') + end + end +end + +RSpec.shared_examples 'job is unchanged' do + it 'does not change status' do + expect { service.execute }.not_to change(job, :status) + end +end diff --git a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb index e8f7e62d0d7..30710e43357 100644 --- a/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb +++ b/spec/support/shared_examples/controllers/wiki_actions_shared_examples.rb @@ -299,7 +299,7 @@ RSpec.shared_examples 'wiki controller actions' do expect(response.headers['Content-Disposition']).to match(/^inline/) expect(response.headers[Gitlab::Workhorse::DETECT_HEADER]).to eq('true') expect(response.cache_control[:public]).to be(false) - expect(response.headers['Cache-Control']).to eq('no-store') + expect(response.headers['Cache-Control']).to eq('private, no-store') end end end diff --git a/spec/support/shared_examples/features/container_registry_shared_examples.rb b/spec/support/shared_examples/features/container_registry_shared_examples.rb new file mode 100644 index 00000000000..06b2b8c621c --- /dev/null +++ b/spec/support/shared_examples/features/container_registry_shared_examples.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'handling feature network errors with the container registry' do + it 'displays the error message' do + visit_container_registry + + expect(page).to have_content 'We are having trouble connecting to the Container Registry' + end +end diff --git a/spec/support/shared_examples/features/discussion_comments_shared_example.rb b/spec/support/shared_examples/features/discussion_comments_shared_example.rb index 318ba67b9e9..6c06cbf9082 100644 --- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb +++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb @@ -3,9 +3,9 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name| let(:form_selector) { '.js-main-target-form' } let(:dropdown_selector) { "#{form_selector} .comment-type-dropdown" } - let(:toggle_selector) { "#{dropdown_selector} .dropdown-toggle" } + let(:toggle_selector) { "#{dropdown_selector} .gl-dropdown-toggle" } let(:menu_selector) { "#{dropdown_selector} .dropdown-menu" } - let(:submit_selector) { "#{form_selector} .js-comment-submit-button" } + let(:submit_selector) { "#{form_selector} .js-comment-submit-button > button:first-child" } let(:close_selector) { "#{form_selector} .btn-comment-and-close" } let(:comments_selector) { '.timeline > .note.timeline-entry:not(.being-posted)' } let(:comment) { 'My comment' } @@ -43,13 +43,11 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name expect(items.first).to have_content 'Comment' expect(items.first).to have_content "Add a general comment to this #{resource_name}." - expect(items.first).to have_selector '[data-testid="check-icon"]' - expect(items.first['class']).to match 'droplab-item-selected' + expect(items.first).to have_selector '[data-testid="dropdown-item-checkbox"]' expect(items.last).to have_content 'Start thread' expect(items.last).to have_content "Discuss a specific suggestion or question#{' that needs to be resolved' if resource_name == 'merge request'}." - expect(items.last).not_to have_selector '[data-testid="check-icon"]' - expect(items.last['class']).not_to match 'droplab-item-selected' + expect(items.last).not_to have_selector '[data-testid="dropdown-item-checkbox"]' end it 'closes the menu when clicking the toggle or body' do @@ -75,14 +73,14 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name expect(find(dropdown_selector)).to have_content 'Comment' find(toggle_selector).click - execute_script("document.querySelector('#{menu_selector} .divider').click()") + execute_script("document.querySelector('#{menu_selector} .dropdown-divider').click()") else execute_script("document.querySelector('#{menu_selector}').click()") expect(page).to have_selector menu_selector expect(find(dropdown_selector)).to have_content 'Comment' - execute_script("document.querySelector('#{menu_selector} .divider').click()") + execute_script("document.querySelector('#{menu_selector} .dropdown-divider').click()") expect(page).to have_selector menu_selector end @@ -97,7 +95,7 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name end it 'updates the submit button text and closes the dropdown' do - expect(find(submit_selector).value).to eq 'Start thread' + expect(find(submit_selector).text).to eq 'Start thread' expect(page).not_to have_selector menu_selector end @@ -137,12 +135,10 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name items = all("#{menu_selector} li") expect(items.first).to have_content 'Comment' - expect(items.first).not_to have_selector '[data-testid="check-icon"]' - expect(items.first['class']).not_to match 'droplab-item-selected' + expect(items.first).not_to have_selector '[data-testid="dropdown-item-checkbox"]' expect(items.last).to have_content 'Start thread' - expect(items.last).to have_selector '[data-testid="check-icon"]' - expect(items.last['class']).to match 'droplab-item-selected' + expect(items.last).to have_selector '[data-testid="dropdown-item-checkbox"]' end describe 'when selecting "Comment"' do @@ -153,7 +149,7 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name it 'updates the submit button text and closes the dropdown' do button = find(submit_selector) - expect(button.value).to eq 'Comment' + expect(button.text).to eq 'Comment' expect(page).not_to have_selector menu_selector end @@ -166,12 +162,10 @@ RSpec.shared_examples 'thread comments for commit and snippet' do |resource_name aggregate_failures do expect(items.first).to have_content 'Comment' - expect(items.first).to have_selector '[data-testid="check-icon"]' - expect(items.first['class']).to match 'droplab-item-selected' + expect(items.first).to have_selector '[data-testid="dropdown-item-checkbox"]' expect(items.last).to have_content 'Start thread' - expect(items.last).not_to have_selector '[data-testid="check-icon"]' - expect(items.last['class']).not_to match 'droplab-item-selected' + expect(items.last).not_to have_selector '[data-testid="dropdown-item-checkbox"]' end end end diff --git a/spec/support/shared_examples/features/project_upload_files_shared_examples.rb b/spec/support/shared_examples/features/project_upload_files_shared_examples.rb index 7adf303bde4..85434ba7afd 100644 --- a/spec/support/shared_examples/features/project_upload_files_shared_examples.rb +++ b/spec/support/shared_examples/features/project_upload_files_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples 'it uploads and commits a new text file' do +RSpec.shared_examples 'it uploads and commits a new text file' do |drop: false| it 'uploads and commits a new text file', :js do find('.add-to-tree').click @@ -10,7 +10,11 @@ RSpec.shared_examples 'it uploads and commits a new text file' do wait_for_requests end - attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true) + if drop + find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt')) + else + attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true) + end page.within('#modal-upload-blob') do fill_in(:commit_message, with: 'New commit message') @@ -32,7 +36,7 @@ RSpec.shared_examples 'it uploads and commits a new text file' do end end -RSpec.shared_examples 'it uploads and commits a new image file' do +RSpec.shared_examples 'it uploads and commits a new image file' do |drop: false| it 'uploads and commits a new image file', :js do find('.add-to-tree').click @@ -42,7 +46,11 @@ RSpec.shared_examples 'it uploads and commits a new image file' do wait_for_requests end - attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg'), make_visible: true) + if drop + find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg')) + else + attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg'), make_visible: true) + end page.within('#modal-upload-blob') do fill_in(:commit_message, with: 'New commit message') @@ -58,7 +66,7 @@ RSpec.shared_examples 'it uploads and commits a new image file' do end end -RSpec.shared_examples 'it uploads and commits a new pdf file' do +RSpec.shared_examples 'it uploads and commits a new pdf file' do |drop: false| it 'uploads and commits a new pdf file', :js do find('.add-to-tree').click @@ -68,7 +76,11 @@ RSpec.shared_examples 'it uploads and commits a new pdf file' do wait_for_requests end - attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'git-cheat-sheet.pdf'), make_visible: true) + if drop + find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'git-cheat-sheet.pdf')) + else + attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'git-cheat-sheet.pdf'), make_visible: true) + end page.within('#modal-upload-blob') do fill_in(:commit_message, with: 'New commit message') @@ -84,7 +96,7 @@ RSpec.shared_examples 'it uploads and commits a new pdf file' do end end -RSpec.shared_examples 'it uploads and commits a new file to a forked project' do +RSpec.shared_examples 'it uploads and commits a new file to a forked project' do |drop: false| let(:fork_message) do "You're not allowed to make changes to this project directly. "\ "A fork of this project has been created that you can make changes in, so you can submit a merge request." @@ -100,7 +112,12 @@ RSpec.shared_examples 'it uploads and commits a new file to a forked project' do find('.add-to-tree').click click_link('Upload file') - attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true) + + if drop + find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt')) + else + attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true) + end page.within('#modal-upload-blob') do fill_in(:commit_message, with: 'New commit message') @@ -123,7 +140,7 @@ RSpec.shared_examples 'it uploads and commits a new file to a forked project' do end end -RSpec.shared_examples 'it uploads a file to a sub-directory' do +RSpec.shared_examples 'it uploads a file to a sub-directory' do |drop: false| it 'uploads a file to a sub-directory', :js do click_link 'files' @@ -133,7 +150,12 @@ RSpec.shared_examples 'it uploads a file to a sub-directory' do find('.add-to-tree').click click_link('Upload file') - attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true) + + if drop + find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt')) + else + attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true) + end page.within('#modal-upload-blob') do fill_in(:commit_message, with: 'New commit message') @@ -150,11 +172,15 @@ RSpec.shared_examples 'it uploads a file to a sub-directory' do end end -RSpec.shared_examples 'uploads and commits a new text file via "upload file" button' do +RSpec.shared_examples 'uploads and commits a new text file via "upload file" button' do |drop: false| it 'uploads and commits a new text file via "upload file" button', :js do find('[data-testid="upload-file-button"]').click - attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true) + if drop + find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt')) + else + attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true) + end page.within('#details-modal-upload-blob') do fill_in(:commit_message, with: 'New commit message') diff --git a/spec/support/shared_examples/graphql/connection_shared_examples.rb b/spec/support/shared_examples/graphql/connection_shared_examples.rb index 4cba5b5a69d..895bab1f51a 100644 --- a/spec/support/shared_examples/graphql/connection_shared_examples.rb +++ b/spec/support/shared_examples/graphql/connection_shared_examples.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.shared_examples 'a connection with collection methods' do - %i[to_a size include? empty?].each do |method_name| + %i[to_a size map include? empty?].each do |method_name| it "responds to #{method_name}" do expect(connection).to respond_to(method_name) end diff --git a/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb b/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb index eaeb5faee3b..37a805902a9 100644 --- a/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb +++ b/spec/support/shared_examples/graphql/sorted_paginated_query_shared_examples.rb @@ -9,7 +9,7 @@ # data_path: the keys necessary to dig into the return GraphQL data to get the # returned results # first_param: number of items expected (like a page size) -# expected_results: array of comparison data of all items sorted correctly +# all_records: array of comparison data of all items sorted correctly # pagination_query: method that specifies the GraphQL query # pagination_results_data: method that extracts the sorted data used to compare against # the expected results @@ -38,9 +38,9 @@ # let(:ordered_issues) { issues.sort_by(&:weight) } # # it_behaves_like 'sorted paginated query' do -# let(:sort_param) { :WEIGHT_ASC } -# let(:first_param) { 2 } -# let(:expected_results) { ordered_issues.map(&:iid) } +# let(:sort_param) { :WEIGHT_ASC } +# let(:first_param) { 2 } +# let(:all_records) { ordered_issues.map(&:iid) } # end # end # @@ -51,7 +51,7 @@ RSpec.shared_examples 'sorted paginated query' do |conditions = {}| let(:node_path) { ['id'] } it_behaves_like 'requires variables' do - let(:required_variables) { [:sort_param, :first_param, :expected_results, :data_path, :current_user] } + let(:required_variables) { [:sort_param, :first_param, :all_records, :data_path, :current_user] } end describe do @@ -101,13 +101,13 @@ RSpec.shared_examples 'sorted paginated query' do |conditions = {}| context 'when sorting' do it 'sorts correctly' do - expect(results).to eq expected_results + expect(results).to eq all_records end context 'when paginating' do let(:params) { sort_argument.merge(first: first_param) } - let(:first_page) { expected_results.first(first_param) } - let(:rest) { expected_results.drop(first_param) } + let(:first_page) { all_records.first(first_param) } + let(:rest) { all_records.drop(first_param) } it 'paginates correctly' do expect(results).to eq first_page @@ -130,7 +130,7 @@ RSpec.shared_examples 'sorted paginated query' do |conditions = {}| it 'fetches last elements without error' do post_graphql(pagination_query(params), current_user: current_user) - expect(results.first).to eq(expected_results.last) + expect(results.first).to eq(all_records.last) end end end diff --git a/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb index 3760325675a..8b4ecd7d5ae 100644 --- a/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/ci/ci_trace_shared_examples.rb @@ -35,8 +35,8 @@ RSpec.shared_examples 'common trace features' do stub_feature_flags(gitlab_ci_archived_trace_consistent_reads: trace.job.project) end - it 'calls ::Gitlab::Database::LoadBalancing::Sticking.unstick_or_continue_sticking' do - expect(::Gitlab::Database::LoadBalancing::Sticking).to receive(:unstick_or_continue_sticking) + it 'calls ::ApplicationRecord.sticking.unstick_or_continue_sticking' do + expect(::ApplicationRecord.sticking).to receive(:unstick_or_continue_sticking) .with(described_class::LOAD_BALANCING_STICKING_NAMESPACE, trace.job.id) .and_call_original @@ -49,8 +49,8 @@ RSpec.shared_examples 'common trace features' do stub_feature_flags(gitlab_ci_archived_trace_consistent_reads: false) end - it 'does not call ::Gitlab::Database::LoadBalancing::Sticking.unstick_or_continue_sticking' do - expect(::Gitlab::Database::LoadBalancing::Sticking).not_to receive(:unstick_or_continue_sticking) + it 'does not call ::ApplicationRecord.sticking.unstick_or_continue_sticking' do + expect(::ApplicationRecord.sticking).not_to receive(:unstick_or_continue_sticking) trace.read { |stream| stream } end @@ -305,8 +305,8 @@ RSpec.shared_examples 'common trace features' do stub_feature_flags(gitlab_ci_archived_trace_consistent_reads: trace.job.project) end - it 'calls ::Gitlab::Database::LoadBalancing::Sticking.stick' do - expect(::Gitlab::Database::LoadBalancing::Sticking).to receive(:stick) + it 'calls ::ApplicationRecord.sticking.stick' do + expect(::ApplicationRecord.sticking).to receive(:stick) .with(described_class::LOAD_BALANCING_STICKING_NAMESPACE, trace.job.id) .and_call_original @@ -319,8 +319,8 @@ RSpec.shared_examples 'common trace features' do stub_feature_flags(gitlab_ci_archived_trace_consistent_reads: false) end - it 'does not call ::Gitlab::Database::LoadBalancing::Sticking.stick' do - expect(::Gitlab::Database::LoadBalancing::Sticking).not_to receive(:stick) + it 'does not call ::ApplicationRecord.sticking.stick' do + expect(::ApplicationRecord.sticking).not_to receive(:stick) subject end @@ -497,7 +497,7 @@ RSpec.shared_examples 'trace with disabled live trace feature' do expect(build.job_artifacts_trace.file.filename).to eq('job.log') expect(File.exist?(src_path)).to be_falsy expect(src_checksum) - .to eq(described_class.hexdigest(build.job_artifacts_trace.file.path)) + .to eq(described_class.sha256_hexdigest(build.job_artifacts_trace.file.path)) expect(build.job_artifacts_trace.file_sha256).to eq(src_checksum) end end @@ -523,7 +523,7 @@ RSpec.shared_examples 'trace with disabled live trace feature' do expect(build.job_artifacts_trace.file.filename).to eq('job.log') expect(build.old_trace).to be_nil expect(src_checksum) - .to eq(described_class.hexdigest(build.job_artifacts_trace.file.path)) + .to eq(described_class.sha256_hexdigest(build.job_artifacts_trace.file.path)) expect(build.job_artifacts_trace.file_sha256).to eq(src_checksum) end end @@ -861,7 +861,7 @@ RSpec.shared_examples 'trace with enabled live trace feature' do expect(build.job_artifacts_trace.file.filename).to eq('job.log') expect(Ci::BuildTraceChunk.where(build: build)).not_to be_exist expect(src_checksum) - .to eq(described_class.hexdigest(build.job_artifacts_trace.file.path)) + .to eq(described_class.sha256_hexdigest(build.job_artifacts_trace.file.path)) expect(build.job_artifacts_trace.file_sha256).to eq(src_checksum) end end diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb index 6e12b5a0e85..bd8bdd70ce5 100644 --- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/event_shared_examples.rb @@ -33,3 +33,38 @@ RSpec.shared_examples_for 'value stream analytics event' do end end end + +RSpec.shared_examples_for 'LEFT JOIN-able value stream analytics event' do + let(:params) { {} } + let(:instance) { described_class.new(params) } + let(:record_with_data) { nil } + let(:record_without_data) { nil } + let(:scope) { instance.object_type.all } + + let(:records) do + scope_with_left_join = instance.include_in(scope) + scope_with_left_join.select(scope.model.arel_table[:id], instance.timestamp_projection.as('timestamp_column_data')).to_a + end + + it 'can use the event as LEFT JOIN' do + expected_record_count = record_without_data.nil? ? 1 : 2 + + expect(records.count).to eq(expected_record_count) + end + + context 'when looking at the record with data' do + subject(:record) { records.to_a.find { |r| r.id == record_with_data.id } } + + it 'contains the timestamp expression' do + expect(record.timestamp_column_data).not_to eq(nil) + end + end + + context 'when looking at the record without data' do + subject(:record) { records.to_a.find { |r| r.id == record_without_data.id } } + + it 'returns nil for the timestamp expression' do + expect(record.timestamp_column_data).to eq(nil) if record_without_data + end + end +end diff --git a/spec/support/shared_examples/lib/gitlab/import_export/attributes_permitter_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/import_export/attributes_permitter_shared_examples.rb new file mode 100644 index 00000000000..5ce698c4701 --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/import_export/attributes_permitter_shared_examples.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true +RSpec.shared_examples 'a permitted attribute' do |relation_sym, permitted_attributes| + let(:prohibited_attributes) { %i[remote_url my_attributes my_ids token my_id test] } + + let(:import_export_config) { Gitlab::ImportExport::Config.new.to_h } + let(:project_relation_factory) { Gitlab::ImportExport::Project::RelationFactory } + + let(:relation_hash) { (permitted_attributes + prohibited_attributes).map(&:to_s).zip([]).to_h } + let(:relation_name) { project_relation_factory.overrides[relation_sym]&.to_sym || relation_sym } + let(:relation_class) { project_relation_factory.relation_class(relation_name) } + let(:excluded_keys) { import_export_config.dig(:excluded_keys, relation_sym) || [] } + + let(:cleaned_hash) do + Gitlab::ImportExport::AttributeCleaner.new( + relation_hash: relation_hash, + relation_class: relation_class, + excluded_keys: excluded_keys + ).clean + end + + let(:permitted_hash) { subject.permit(relation_sym, relation_hash) } + + if described_class.new.permitted_attributes_defined?(relation_sym) + it 'contains only attributes that are defined as permitted in the import/export config' do + expect(permitted_hash.keys).to contain_exactly(*permitted_attributes.map(&:to_s)) + end + + it 'does not contain attributes that would be cleaned with AttributeCleaner' do + expect(cleaned_hash.keys).to include(*permitted_hash.keys) + end + + it 'does not contain prohibited attributes that are not related to given relation' do + expect(permitted_hash.keys).not_to include(*prohibited_attributes.map(&:to_s)) + end + else + it 'is disabled' do + expect(subject).not_to be_permitted_attributes_defined(relation_sym) + end + end +end diff --git a/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb b/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb index c6d6ff6bc1d..c06083ba952 100644 --- a/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb +++ b/spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb @@ -4,14 +4,20 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role| let(:db_config_name) { ::Gitlab::Database.db_config_names.first } let(:expected_payload_defaults) do + result = {} metrics = ::Gitlab::Metrics::Subscribers::ActiveRecord.load_balancing_metric_counter_keys + - ::Gitlab::Metrics::Subscribers::ActiveRecord.load_balancing_metric_duration_keys + ::Gitlab::Metrics::Subscribers::ActiveRecord.db_counter_keys - metrics.each_with_object({}) do |key, result| + metrics.each do |key| result[key] = 0 end + + ::Gitlab::Metrics::Subscribers::ActiveRecord.load_balancing_metric_duration_keys.each do |key| + result[key] = 0.0 + end + + result end def transform_hash(hash, another_hash) @@ -36,8 +42,8 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role| "db_primary_#{db_config_name}_cached_count": record_cached_query ? 1 : 0, db_primary_count: record_query ? 1 : 0, "db_primary_#{db_config_name}_count": record_query ? 1 : 0, - db_primary_duration_s: record_query ? 0.002 : 0, - "db_primary_#{db_config_name}_duration_s": record_query ? 0.002 : 0, + db_primary_duration_s: record_query ? 0.002 : 0.0, + "db_primary_#{db_config_name}_duration_s": record_query ? 0.002 : 0.0, db_primary_wal_count: record_wal_query ? 1 : 0, "db_primary_#{db_config_name}_wal_count": record_wal_query ? 1 : 0, db_primary_wal_cached_count: record_wal_query && record_cached_query ? 1 : 0, @@ -52,19 +58,29 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role| "db_replica_#{db_config_name}_cached_count": record_cached_query ? 1 : 0, db_replica_count: record_query ? 1 : 0, "db_replica_#{db_config_name}_count": record_query ? 1 : 0, - db_replica_duration_s: record_query ? 0.002 : 0, - "db_replica_#{db_config_name}_duration_s": record_query ? 0.002 : 0, + db_replica_duration_s: record_query ? 0.002 : 0.0, + "db_replica_#{db_config_name}_duration_s": record_query ? 0.002 : 0.0, db_replica_wal_count: record_wal_query ? 1 : 0, "db_replica_#{db_config_name}_wal_count": record_wal_query ? 1 : 0, db_replica_wal_cached_count: record_wal_query && record_cached_query ? 1 : 0, "db_replica_#{db_config_name}_wal_cached_count": record_wal_query && record_cached_query ? 1 : 0 }) else - { + transform_hash(expected_payload_defaults, { db_count: record_query ? 1 : 0, db_write_count: record_write_query ? 1 : 0, - db_cached_count: record_cached_query ? 1 : 0 - } + db_cached_count: record_cached_query ? 1 : 0, + db_primary_cached_count: 0, + "db_primary_#{db_config_name}_cached_count": 0, + db_primary_count: 0, + "db_primary_#{db_config_name}_count": 0, + db_primary_duration_s: 0.0, + "db_primary_#{db_config_name}_duration_s": 0.0, + db_primary_wal_count: 0, + "db_primary_#{db_config_name}_wal_count": 0, + db_primary_wal_cached_count: 0, + "db_primary_#{db_config_name}_wal_cached_count": 0 + }) end expect(described_class.db_counter_payload).to eq(expected) @@ -89,7 +105,7 @@ RSpec.shared_examples 'store ActiveRecord info in RequestStore' do |db_role| end RSpec.shared_examples 'record ActiveRecord metrics in a metrics transaction' do |db_role| - let(:db_config_name) { ::Gitlab::Database.db_config_name(ApplicationRecord.connection) } + let(:db_config_name) { ::Gitlab::Database.db_config_name(ApplicationRecord.retrieve_connection) } it 'increments only db counters' do if record_query diff --git a/spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb b/spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb new file mode 100644 index 00000000000..f928fb1eb43 --- /dev/null +++ b/spec/support/shared_examples/models/concerns/analytics/cycle_analytics/stage_event_model_examples.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'StageEventModel' do + describe '.upsert_data' do + let(:time) { Time.parse(Time.current.to_s(:db)) } # truncating the timestamp so we can compare it with the timestamp loaded from the DB + let(:input_data) do + [ + { + stage_event_hash_id: 1, + issuable_id: 2, + group_id: 3, + project_id: 4, + author_id: 5, + milestone_id: 6, + start_event_timestamp: time, + end_event_timestamp: time + }, + { + stage_event_hash_id: 7, + issuable_id: 8, + group_id: 10, + project_id: 11, + author_id: 12, + milestone_id: 13, + start_event_timestamp: time, + end_event_timestamp: time + } + ] + end + + let(:column_order) do + [ + :stage_event_hash_id, + described_class.issuable_id_column, + :group_id, + :project_id, + :milestone_id, + :author_id, + :start_event_timestamp, + :end_event_timestamp + ] + end + + subject(:upsert_data) { described_class.upsert_data(input_data) } + + it 'inserts the data' do + upsert_data + + expect(described_class.count).to eq(input_data.count) + end + + it 'does not produce duplicate rows' do + 2.times { upsert_data } + + expect(described_class.count).to eq(input_data.count) + end + + it 'inserts the data correctly' do + upsert_data + + output_data = described_class.all.map do |record| + column_order.map { |column| record[column] } + end.sort + + expect(input_data.map(&:values).sort).to eq(output_data) + end + end +end diff --git a/spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb b/spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb new file mode 100644 index 00000000000..a4e0d6c871e --- /dev/null +++ b/spec/support/shared_examples/models/concerns/ttl_expirable_shared_examples.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.shared_examples 'ttl_expirable' do + let_it_be(:class_symbol) { described_class.model_name.param_key.to_sym } + + it_behaves_like 'having unique enum values' + + describe 'validations' do + it { is_expected.to validate_presence_of(:status) } + end + + describe '.updated_before' do + # rubocop:disable Rails/SaveBang + let_it_be_with_reload(:item1) { create(class_symbol) } + let_it_be(:item2) { create(class_symbol) } + # rubocop:enable Rails/SaveBang + + before do + item1.update_column(:updated_at, 1.month.ago) + end + + it 'returns items with created at older than the supplied number of days' do + expect(described_class.updated_before(10)).to contain_exactly(item1) + end + end + + describe '.active' do + # rubocop:disable Rails/SaveBang + let_it_be(:item1) { create(class_symbol) } + let_it_be(:item2) { create(class_symbol, :expired) } + let_it_be(:item3) { create(class_symbol, status: :error) } + # rubocop:enable Rails/SaveBang + + it 'returns only active items' do + expect(described_class.active).to contain_exactly(item1) + end + end + + describe '.lock_next_by' do + let_it_be(:item1) { create(class_symbol, created_at: 1.month.ago, updated_at: 1.day.ago) } + let_it_be(:item2) { create(class_symbol, created_at: 1.year.ago, updated_at: 1.year.ago) } + let_it_be(:item3) { create(class_symbol, created_at: 2.years.ago, updated_at: 1.month.ago) } + + it 'returns the first item sorted by the argument' do + expect(described_class.lock_next_by(:updated_at)).to contain_exactly(item2) + expect(described_class.lock_next_by(:created_at)).to contain_exactly(item3) + end + end +end diff --git a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb index 274fbae3dfd..750d3dd11e3 100644 --- a/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb +++ b/spec/support/shared_examples/models/packages/debian/distribution_shared_examples.rb @@ -193,42 +193,6 @@ RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze| end end - describe '#needs_update?' do - subject { distribution.needs_update? } - - context 'with new distribution' do - let(:distribution) { create(factory, container: distribution_with_suite.container) } - - it { is_expected.to be_truthy } - end - - context 'with file' do - context 'without valid_time_duration_seconds' do - let(:distribution) { create(factory, :with_file, container: distribution_with_suite.container) } - - it { is_expected.to be_falsey } - end - - context 'with valid_time_duration_seconds' do - let(:distribution) { create(factory, :with_file, container: distribution_with_suite.container, valid_time_duration_seconds: 2.days.to_i) } - - context 'when not yet expired' do - it { is_expected.to be_falsey } - end - - context 'when expired' do - it do - distribution - - travel_to(4.days.from_now) do - is_expected.to be_truthy - end - end - end - end - end - end - if container == :project describe 'project distribution specifics' do describe 'relationships' do diff --git a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb index b86c0529338..e45be21f152 100644 --- a/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/composer_packages_shared_examples.rb @@ -85,7 +85,18 @@ RSpec.shared_examples 'Composer package creation' do |user_type, status, add_mem expect(response).to have_gitlab_http_status(status) end + it_behaves_like 'a package tracking event', described_class.name, 'push_package' + + context 'when package creation fails' do + before do + allow_next_instance_of(::Packages::Composer::CreatePackageService) do |create_package_service| + allow(create_package_service).to receive(:execute).and_raise(StandardError) + end + end + + it_behaves_like 'not a package tracking event' + end end end diff --git a/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb b/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb index e776cf13217..e1e75be2494 100644 --- a/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb @@ -79,3 +79,40 @@ RSpec.shared_examples 'returns repositories for allowed users' do |user_type, sc end end end + +RSpec.shared_examples 'handling network errors with the container registry' do + before do + stub_container_registry_network_error(client_method: :repository_tags) + end + + it 'returns a connection error' do + subject + + expect(response).to have_gitlab_http_status(:service_unavailable) + expect(json_response['message']).to include('We are having trouble connecting to the Container Registry') + end +end + +RSpec.shared_examples 'handling graphql network errors with the container registry' do + before do + stub_container_registry_network_error(client_method: :repository_tags) + end + + it 'returns a connection error' do + subject + + expect_graphql_errors_to_include('We are having trouble connecting to the Container Registry') + end +end + +RSpec.shared_examples 'not hitting graphql network errors with the container registry' do + before do + stub_container_registry_network_error(client_method: :repository_tags) + end + + it 'does not return any error' do + subject + + expect_graphql_errors_to_be_empty + end +end diff --git a/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb index 274516cd87b..01ed6c26576 100644 --- a/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb @@ -62,9 +62,10 @@ RSpec.shared_examples 'group and project boards query' do context 'when ascending' do it_behaves_like 'sorted paginated query' do - let(:sort_param) { } - let(:first_param) { 2 } - let(:expected_results) do + let(:sort_param) { } + let(:first_param) { 2 } + + let(:all_records) do if board_parent.multiple_issue_boards_available? boards.map { |board| global_id_of(board) } else diff --git a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb index af4c9286e7c..367c6d4fa3a 100644 --- a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb @@ -17,9 +17,11 @@ RSpec.shared_examples 'group and project packages query' do let(:package_names) { graphql_data_at(resource_type, :packages, :nodes, :name) } let(:target_shas) { graphql_data_at(resource_type, :packages, :nodes, :metadata, :target_sha) } let(:packages) { graphql_data_at(resource_type, :packages, :nodes) } + let(:packages_count) { graphql_data_at(resource_type, :packages, :count) } let(:fields) do <<~QUERY + count nodes { #{all_graphql_fields_for('packages'.classify, excluded: ['project'])} metadata { #{query_graphql_fragment('ComposerMetadata')} } @@ -55,6 +57,10 @@ RSpec.shared_examples 'group and project packages query' do it 'deals with metadata' do expect(target_shas).to contain_exactly(composer_metadatum.target_sha) end + + it 'returns the count of the packages' do + expect(packages_count).to eq(4) + end end context 'when the user does not have access to the resource' do @@ -95,7 +101,7 @@ RSpec.shared_examples 'group and project packages query' do it_behaves_like 'sorted paginated query' do let(:sort_param) { order } let(:first_param) { 4 } - let(:expected_results) { ascending_packages } + let(:all_records) { ascending_packages } end end end @@ -105,7 +111,7 @@ RSpec.shared_examples 'group and project packages query' do it_behaves_like 'sorted paginated query' do let(:sort_param) { order } let(:first_param) { 4 } - let(:expected_results) { ascending_packages.reverse } + let(:all_records) { ascending_packages.reverse } end end end diff --git a/spec/support/shared_examples/requests/api/issuable_search_shared_examples.rb b/spec/support/shared_examples/requests/api/issuable_search_shared_examples.rb new file mode 100644 index 00000000000..9f67bd69560 --- /dev/null +++ b/spec/support/shared_examples/requests/api/issuable_search_shared_examples.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'issuable anonymous search' do + context 'with anonymous user' do + context 'with disable_anonymous_search disabled' do + before do + stub_feature_flags(disable_anonymous_search: false) + end + + it 'returns issuables matching given search string for title' do + get api(url), params: { scope: 'all', search: issuable.title } + + expect_paginated_array_response(result) + end + + it 'returns issuables matching given search string for description' do + get api(url), params: { scope: 'all', search: issuable.description } + + expect_paginated_array_response(result) + end + end + + context 'with disable_anonymous_search enabled' do + before do + stub_feature_flags(disable_anonymous_search: true) + end + + it "returns 422 error" do + get api(url), params: { scope: 'all', search: issuable.title } + + expect(response).to have_gitlab_http_status(:unprocessable_entity) + expect(json_response['message']).to eq('User must be authenticated to use search') + end + end + end +end diff --git a/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb b/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb index cb06c9fa596..3e9c4a5eb68 100644 --- a/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb @@ -15,6 +15,22 @@ RSpec.shared_examples 'storing arguments in the application context' do end end +# For the API we need a slightly different approach as `client_id` is +# calculated in API logging code. +RSpec.shared_examples 'storing arguments in the application context for the API' do + it 'places the expected params in the application context' do + expect(::API::API::LOG_FORMATTER).to receive(:call) do + expect(Gitlab::ApplicationContext.current).to include(log_hash(expected_params)) + end + + subject + end + + def log_hash(hash) + hash.transform_keys! { |key| "meta.#{key}" } + end +end + RSpec.shared_examples 'not executing any extra queries for the application context' do |expected_extra_queries = 0| it 'does not execute more queries than without adding anything to the application context' do # Call the subject once to memoize all factories being used for the spec, so they won't 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 2a19ff6f590..b294467d482 100644 --- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb +++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # # Requires let variables: -# * throttle_setting_prefix: "throttle_authenticated_api", "throttle_authenticated_web", "throttle_protected_paths", "throttle_authenticated_packages_api", "throttle_authenticated_git_lfs", "throttle_authenticated_files_api" +# * throttle_setting_prefix: "throttle_authenticated_api", "throttle_authenticated_web", "throttle_protected_paths", "throttle_authenticated_packages_api", "throttle_authenticated_git_lfs", "throttle_authenticated_files_api", "throttle_authenticated_deprecated_api" # * request_method # * request_args # * other_user_request_args @@ -16,7 +16,8 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do "throttle_authenticated_web" => "throttle_authenticated_web", "throttle_authenticated_packages_api" => "throttle_authenticated_packages_api", "throttle_authenticated_git_lfs" => "throttle_authenticated_git_lfs", - "throttle_authenticated_files_api" => "throttle_authenticated_files_api" + "throttle_authenticated_files_api" => "throttle_authenticated_files_api", + "throttle_authenticated_deprecated_api" => "throttle_authenticated_deprecated_api" } end diff --git a/spec/support/shared_examples/services/dependency_proxy_settings_shared_examples.rb b/spec/support/shared_examples/services/dependency_proxy_settings_shared_examples.rb new file mode 100644 index 00000000000..2c1dc2da560 --- /dev/null +++ b/spec/support/shared_examples/services/dependency_proxy_settings_shared_examples.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'updating the dependency proxy group settings attributes' do |from: {}, to: {}| + it 'updates the dependency proxy settings' do + expect { subject } + .to change { group_settings.reload.enabled }.from(from[:enabled]).to(to[:enabled]) + end +end diff --git a/spec/support/shared_examples/workers/concerns/dependency_proxy/cleanup_worker_shared_examples.rb b/spec/support/shared_examples/workers/concerns/dependency_proxy/cleanup_worker_shared_examples.rb new file mode 100644 index 00000000000..c9014ad549c --- /dev/null +++ b/spec/support/shared_examples/workers/concerns/dependency_proxy/cleanup_worker_shared_examples.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'dependency_proxy_cleanup_worker' do + let_it_be(:group) { create(:group) } + + let(:worker) { described_class.new } + + describe '#perform_work' do + subject(:perform_work) { worker.perform_work } + + context 'with no work to do' do + it { is_expected.to be_nil } + end + + context 'with work to do' do + let_it_be(:artifact1) { create(factory_type, :expired, group: group) } + let_it_be(:artifact2) { create(factory_type, :expired, group: group, updated_at: 6.months.ago, created_at: 2.years.ago) } + let_it_be_with_reload(:artifact3) { create(factory_type, :expired, group: group, updated_at: 1.year.ago, created_at: 1.year.ago) } + let_it_be(:artifact4) { create(factory_type, group: group, updated_at: 2.years.ago, created_at: 2.years.ago) } + + it 'deletes the oldest expired artifact based on updated_at', :aggregate_failures do + expect(worker).to receive(:log_extra_metadata_on_done).with("#{factory_type}_id".to_sym, artifact3.id) + expect(worker).to receive(:log_extra_metadata_on_done).with(:group_id, group.id) + + expect { perform_work }.to change { artifact1.class.count }.by(-1) + end + end + end + + describe '#max_running_jobs' do + let(:capacity) { 5 } + + subject { worker.max_running_jobs } + + before do + stub_application_setting(dependency_proxy_ttl_group_policy_worker_capacity: capacity) + end + + it { is_expected.to eq(capacity) } + end + + describe '#remaining_work_count' do + let_it_be(:expired_artifacts) do + (1..3).map do |_| + create(factory_type, :expired, group: group) + end + end + + subject { worker.remaining_work_count } + + it { is_expected.to eq(3) } + end +end diff --git a/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb b/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb index 1b09b5fe613..d6e96ef37d6 100644 --- a/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb +++ b/spec/support/shared_examples/workers/concerns/reenqueuer_shared_examples.rb @@ -1,11 +1,14 @@ # frozen_string_literal: true -# Expects `subject` to be a job/worker instance +# Expects `subject` to be a job/worker instance and +# `job_args` to be arguments to #perform if it takes arguments RSpec.shared_examples 'reenqueuer' do before do allow(subject).to receive(:sleep) # faster tests end + let(:subject_perform) { defined?(job_args) ? subject.perform(job_args) : subject.perform } + it 'implements lease_timeout' do expect(subject.lease_timeout).to be_a(ActiveSupport::Duration) end @@ -18,12 +21,13 @@ RSpec.shared_examples 'reenqueuer' do it 'tries to obtain a lease' do expect_to_obtain_exclusive_lease(subject.lease_key) - subject.perform + subject_perform end end end -# Expects `subject` to be a job/worker instance +# Expects `subject` to be a job/worker instance and +# `job_args` to be arguments to #perform if it takes arguments RSpec.shared_examples '#perform is rate limited to 1 call per' do |minimum_duration| before do # Allow Timecop freeze and travel without the block form @@ -38,13 +42,15 @@ RSpec.shared_examples '#perform is rate limited to 1 call per' do |minimum_durat Timecop.safe_mode = true end + let(:subject_perform) { defined?(job_args) ? subject.perform(job_args) : subject.perform } + context 'when the work finishes in 0 seconds' do let(:actual_duration) { 0 } it 'sleeps exactly the minimum duration' do expect(subject).to receive(:sleep).with(a_value_within(0.01).of(minimum_duration)) - subject.perform + subject_perform end end @@ -54,7 +60,7 @@ RSpec.shared_examples '#perform is rate limited to 1 call per' do |minimum_durat it 'sleeps 90% of minimum duration' do expect(subject).to receive(:sleep).with(a_value_within(0.01).of(0.9 * minimum_duration)) - subject.perform + subject_perform end end @@ -64,7 +70,7 @@ RSpec.shared_examples '#perform is rate limited to 1 call per' do |minimum_durat it 'sleeps 10% of minimum duration' do expect(subject).to receive(:sleep).with(a_value_within(0.01).of(0.1 * minimum_duration)) - subject.perform + subject_perform end end @@ -74,7 +80,7 @@ RSpec.shared_examples '#perform is rate limited to 1 call per' do |minimum_durat it 'does not sleep' do expect(subject).not_to receive(:sleep) - subject.perform + subject_perform end end @@ -84,7 +90,7 @@ RSpec.shared_examples '#perform is rate limited to 1 call per' do |minimum_durat it 'does not sleep' do expect(subject).not_to receive(:sleep) - subject.perform + subject_perform end end @@ -94,7 +100,7 @@ RSpec.shared_examples '#perform is rate limited to 1 call per' do |minimum_durat it 'does not sleep' do expect(subject).not_to receive(:sleep) - subject.perform + subject_perform end end |