diff options
Diffstat (limited to 'spec/support/shared_examples')
100 files changed, 1813 insertions, 511 deletions
diff --git a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb index 0e6f6f12c3f..fa048b76e18 100644 --- a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb +++ b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb @@ -125,6 +125,31 @@ RSpec.shared_examples 'multiple issue boards' do wait_for_requests end + it 'shows current board name' do + page.within('.boards-switcher') do + expect(page).to have_content(board.name) + end + end + + it 'shows a list of boards' do + in_boards_switcher_dropdown do + expect(page).to have_content(board.name) + expect(page).to have_content(board2.name) + end + end + + it 'switches current board' do + in_boards_switcher_dropdown do + click_button board2.name + end + + wait_for_requests + + page.within('.boards-switcher') do + expect(page).to have_content(board2.name) + end + end + it 'does not show action links' do in_boards_switcher_dropdown do expect(page).not_to have_content('Create new board') diff --git a/spec/support/shared_examples/ci/edit_job_token_scope_shared_examples.rb b/spec/support/shared_examples/ci/edit_job_token_scope_shared_examples.rb index 05b2b5f5de1..d8333ae25ad 100644 --- a/spec/support/shared_examples/ci/edit_job_token_scope_shared_examples.rb +++ b/spec/support/shared_examples/ci/edit_job_token_scope_shared_examples.rb @@ -8,14 +8,6 @@ RSpec.shared_examples 'editable job token scope' do end end - context 'when job token scope is disabled for the given project' do - before do - allow(project).to receive(:ci_job_token_scope_enabled?).and_return(false) - end - - it_behaves_like 'returns error', 'Job token scope is disabled for this project' - end - context 'when user does not have permissions to edit the job token scope' do it_behaves_like 'returns error', 'Insufficient permissions to modify the job token scope' end diff --git a/spec/support/shared_examples/controllers/concerns/web_hooks/integrations_hook_log_actions_shared_examples.rb b/spec/support/shared_examples/controllers/concerns/web_hooks/integrations_hook_log_actions_shared_examples.rb new file mode 100644 index 00000000000..62c9c3508a8 --- /dev/null +++ b/spec/support/shared_examples/controllers/concerns/web_hooks/integrations_hook_log_actions_shared_examples.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +RSpec.shared_examples WebHooks::HookLogActions do + let!(:show_path) { web_hook_log.present.details_path } + let!(:retry_path) { web_hook_log.present.retry_path } + + before do + sign_in(user) + end + + describe 'GET #show' do + it 'renders a 200 if the hook exists' do + get show_path + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template('hook_logs/show') + end + + it 'renders a 404 if the hook does not exist' do + web_hook.destroy! + get show_path + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + describe 'POST #retry' do + it 'executes the hook and redirects to the service form' do + stub_request(:post, web_hook.url) + + expect_next_found_instance_of(web_hook.class) do |hook| + expect(hook).to receive(:execute).and_call_original + end + + post retry_path + + expect(response).to redirect_to(edit_hook_path) + end + + it 'renders a 404 if the hook does not exist' do + web_hook.destroy! + post retry_path + + expect(response).to have_gitlab_http_status(:not_found) + end + end +end diff --git a/spec/support/shared_examples/controllers/error_tracking_shared_examples.rb b/spec/support/shared_examples/controllers/error_tracking_shared_examples.rb index 08e5efcf63c..1bf2f158504 100644 --- a/spec/support/shared_examples/controllers/error_tracking_shared_examples.rb +++ b/spec/support/shared_examples/controllers/error_tracking_shared_examples.rb @@ -3,5 +3,5 @@ RSpec.shared_examples 'sets the polling header' do subject { response.headers[Gitlab::PollingInterval::HEADER_NAME] } - it { is_expected.to eq '1000'} + it { is_expected.to eq '1000' } end diff --git a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb index 5faf462c23c..bbbe93a644f 100644 --- a/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb +++ b/spec/support/shared_examples/controllers/githubish_import_controller_shared_examples.rb @@ -241,12 +241,11 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do let(:provider_user) { double('user', login: provider_username) } let(:project) { create(:project, import_type: provider, import_status: :finished, import_source: "#{provider_username}/vim") } let(:provider_repo) do - double( - 'provider', + { name: 'vim', full_name: "#{provider_username}/vim", owner: double('owner', login: provider_username) - ) + } end before do @@ -256,7 +255,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do it 'returns 200 response when the project is imported successfully' do allow(Gitlab::LegacyGithubImport::ProjectCreator) - .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, type: provider, **access_params) + .to receive(:new).with(provider_repo, provider_repo[:name], user.namespace, user, type: provider, **access_params) .and_return(double(execute: project)) post :create, format: :json @@ -270,7 +269,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do project.errors.add(:path, 'is old') allow(Gitlab::LegacyGithubImport::ProjectCreator) - .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, type: provider, **access_params) + .to receive(:new).with(provider_repo, provider_repo[:name], user.namespace, user, type: provider, **access_params) .and_return(double(execute: project)) post :create, format: :json @@ -281,7 +280,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do it "touches the etag cache store" do allow(Gitlab::LegacyGithubImport::ProjectCreator) - .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, type: provider, **access_params) + .to receive(:new).with(provider_repo, provider_repo[:name], user.namespace, user, type: provider, **access_params) .and_return(double(execute: project)) expect_next_instance_of(Gitlab::EtagCaching::Store) do |store| expect(store).to receive(:touch) { "realtime_changes_import_#{provider}_path" } @@ -294,7 +293,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do context "when the provider user and GitLab user's usernames match" do it "takes the current user's namespace" do expect(Gitlab::LegacyGithubImport::ProjectCreator) - .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, type: provider, **access_params) + .to receive(:new).with(provider_repo, provider_repo[:name], user.namespace, user, type: provider, **access_params) .and_return(double(execute: project)) post :create, format: :json @@ -306,7 +305,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do it "takes the current user's namespace" do expect(Gitlab::LegacyGithubImport::ProjectCreator) - .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, type: provider, **access_params) + .to receive(:new).with(provider_repo, provider_repo[:name], user.namespace, user, type: provider, **access_params) .and_return(double(execute: project)) post :create, format: :json @@ -331,7 +330,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do it "takes the existing namespace" do expect(Gitlab::LegacyGithubImport::ProjectCreator) - .to receive(:new).with(provider_repo, provider_repo.name, existing_namespace, user, type: provider, **access_params) + .to receive(:new).with(provider_repo, provider_repo[:name], existing_namespace, user, type: provider, **access_params) .and_return(double(execute: project)) post :create, format: :json @@ -343,7 +342,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do create(:user, username: provider_username) expect(Gitlab::LegacyGithubImport::ProjectCreator) - .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, type: provider, **access_params) + .to receive(:new).with(provider_repo, provider_repo[:name], user.namespace, user, type: provider, **access_params) .and_return(double(execute: project)) post :create, format: :json @@ -357,15 +356,15 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do expect(Gitlab::LegacyGithubImport::ProjectCreator) .to receive(:new).and_return(double(execute: project)) - expect { post :create, params: { target_namespace: provider_repo.name }, format: :json }.to change(Namespace, :count).by(1) + expect { post :create, params: { target_namespace: provider_repo[:name] }, format: :json }.to change(Namespace, :count).by(1) end it "takes the new namespace" do expect(Gitlab::LegacyGithubImport::ProjectCreator) - .to receive(:new).with(provider_repo, provider_repo.name, an_instance_of(Group), user, type: provider, **access_params) + .to receive(:new).with(provider_repo, provider_repo[:name], an_instance_of(Group), user, type: provider, **access_params) .and_return(double(execute: project)) - post :create, params: { target_namespace: provider_repo.name }, format: :json + post :create, params: { target_namespace: provider_repo[:name] }, format: :json end end @@ -383,7 +382,7 @@ RSpec.shared_examples 'a GitHub-ish import controller: POST create' do it "takes the current user's namespace" do expect(Gitlab::LegacyGithubImport::ProjectCreator) - .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, type: provider, **access_params) + .to receive(:new).with(provider_repo, provider_repo[:name], user.namespace, user, type: provider, **access_params) .and_return(double(execute: project)) post :create, format: :json diff --git a/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb b/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb index aa4d78b23f4..112b9cbb204 100644 --- a/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb +++ b/spec/support/shared_examples/controllers/snippets_sort_order_shared_examples.rb @@ -3,7 +3,7 @@ RSpec.shared_examples 'snippets sort order' do let(:params) { {} } let(:sort_argument) { {} } - let(:sort_params) { params.merge(sort_argument)} + let(:sort_params) { params.merge(sort_argument) } before do sign_in(user) diff --git a/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb b/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb index 2e691d1b36f..4af3c0cc6cc 100644 --- a/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb +++ b/spec/support/shared_examples/controllers/snowplow_event_tracking_examples.rb @@ -13,7 +13,7 @@ # - label # - **extra -shared_examples 'Snowplow event tracking' do |overrides: {}| +RSpec.shared_examples 'Snowplow event tracking' do |overrides: {}| let(:extra) { {} } it 'is not emitted if FF is disabled' do diff --git a/spec/support/shared_examples/features/board_sidebar_labels_examples.rb b/spec/support/shared_examples/features/board_sidebar_labels_examples.rb index 520980c2615..4e5b371c18d 100644 --- a/spec/support/shared_examples/features/board_sidebar_labels_examples.rb +++ b/spec/support/shared_examples/features/board_sidebar_labels_examples.rb @@ -17,7 +17,7 @@ RSpec.shared_context 'labels from nested groups and projects' do let_it_be(:maintainer) { create(:user) } let(:labels_select) { find("[data-testid='sidebar-labels']") } - let(:labels_dropdown) { labels_select.find('[data-testid="dropdown-content"]')} + let(:labels_dropdown) { labels_select.find('[data-testid="dropdown-content"]') } before do group.add_maintainer(maintainer) diff --git a/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb index 8a07e52019c..f7cdc4c61ec 100644 --- a/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb +++ b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true RSpec.shared_examples 'comment on merge request file' do + before do + stub_feature_flags(remove_user_attributes_projects: false) + end + it 'adds a comment' do click_diff_line(find_by_scrolling("[id='#{sample_commit.line_code}']")) diff --git a/spec/support/shared_examples/features/content_editor_shared_examples.rb b/spec/support/shared_examples/features/content_editor_shared_examples.rb index 3fa7beea97e..21f264a8b6a 100644 --- a/spec/support/shared_examples/features/content_editor_shared_examples.rb +++ b/spec/support/shared_examples/features/content_editor_shared_examples.rb @@ -1,22 +1,87 @@ # frozen_string_literal: true RSpec.shared_examples 'edits content using the content editor' do - content_editor_testid = '[data-testid="content-editor"] [contenteditable].ProseMirror' + let(:content_editor_testid) { '[data-testid="content-editor"] [contenteditable].ProseMirror' } + + def switch_to_content_editor + find('[data-testid="toggle-editing-mode-button"] label', text: 'Rich text').click + end + + def type_in_content_editor(keys) + find(content_editor_testid).send_keys keys + end + + def open_insert_media_dropdown + page.find('svg[data-testid="media-icon"]').click + end + + def set_source_editor_content(content) + find('.js-gfm-input').set content + end + + def expect_formatting_menu_to_be_visible + expect(page).to have_css('[data-testid="formatting-bubble-menu"]') + end + + def expect_formatting_menu_to_be_hidden + expect(page).not_to have_css('[data-testid="formatting-bubble-menu"]') + end + + def expect_media_bubble_menu_to_be_visible + expect(page).to have_css('[data-testid="media-bubble-menu"]') + end + + def upload_asset(fixture_name) + attach_file('content_editor_image', Rails.root.join('spec', 'fixtures', fixture_name), make_visible: true) + end describe 'formatting bubble menu' do - it 'shows a formatting bubble menu for a regular paragraph' do + it 'shows a formatting bubble menu for a regular paragraph and headings' do + switch_to_content_editor + expect(page).to have_css(content_editor_testid) - find(content_editor_testid).send_keys 'Typing text in the content editor' - find(content_editor_testid).send_keys [:shift, :left] + type_in_content_editor 'Typing text in the content editor' + type_in_content_editor [:shift, :left] + + expect_formatting_menu_to_be_visible + + type_in_content_editor [:right, :right, :enter, '## Heading'] - expect(page).to have_css('[data-testid="formatting-bubble-menu"]') + expect_formatting_menu_to_be_hidden + + type_in_content_editor [:shift, :left] + + expect_formatting_menu_to_be_visible + end + end + + describe 'media elements bubble menu' do + before do + switch_to_content_editor + + open_insert_media_dropdown + end + + def test_displays_media_bubble_menu(media_element_selector, fixture_file) + upload_asset fixture_file + + wait_for_requests + + expect(page).to have_css(media_element_selector) + + page.find(media_element_selector).click + + expect_formatting_menu_to_be_hidden + expect_media_bubble_menu_to_be_visible end - it 'does not show a formatting bubble menu for code blocks' do - find(content_editor_testid).send_keys '```js ' + it 'displays correct media bubble menu for images', :js do + test_displays_media_bubble_menu '[data-testid="content_editor_editablebox"] img[src]', 'dk.png' + end - expect(page).not_to have_css('[data-testid="formatting-bubble-menu"]') + it 'displays correct media bubble menu for video', :js do + test_displays_media_bubble_menu '[data-testid="content_editor_editablebox"] video', 'video_sample.mp4' end end @@ -30,45 +95,50 @@ RSpec.shared_examples 'edits content using the content editor' do page.go_back refresh + switch_to_content_editor end it 'applies theme classes to code blocks' do expect(page).not_to have_css('.content-editor-code-block.code.highlight.dark') - find(content_editor_testid).send_keys [:enter, :enter] - find(content_editor_testid).send_keys '```js ' # trigger input rule - find(content_editor_testid).send_keys 'var a = 0' + type_in_content_editor [:enter, :enter] + type_in_content_editor '```js ' # trigger input rule + type_in_content_editor 'var a = 0' expect(page).to have_css('.content-editor-code-block.code.highlight.dark') end end describe 'code block bubble menu' do + before do + switch_to_content_editor + end + it 'shows a code block bubble menu for a code block' do - find(content_editor_testid).send_keys [:enter, :enter] + type_in_content_editor [:enter, :enter] - find(content_editor_testid).send_keys '```js ' # trigger input rule - find(content_editor_testid).send_keys 'var a = 0' - find(content_editor_testid).send_keys [:shift, :left] + type_in_content_editor '```js ' # trigger input rule + type_in_content_editor 'var a = 0' + type_in_content_editor [:shift, :left] - expect(page).not_to have_css('[data-testid="formatting-bubble-menu"]') + expect_formatting_menu_to_be_hidden expect(page).to have_css('[data-testid="code-block-bubble-menu"]') end it 'sets code block type to "javascript" for `js`' do - find(content_editor_testid).send_keys [:enter, :enter] + type_in_content_editor [:enter, :enter] - find(content_editor_testid).send_keys '```js ' - find(content_editor_testid).send_keys 'var a = 0' + type_in_content_editor '```js ' + type_in_content_editor 'var a = 0' expect(find('[data-testid="code-block-bubble-menu"]')).to have_text('Javascript') end it 'sets code block type to "Custom (nomnoml)" for `nomnoml`' do - find(content_editor_testid).send_keys [:enter, :enter] + type_in_content_editor [:enter, :enter] - find(content_editor_testid).send_keys '```nomnoml ' - find(content_editor_testid).send_keys 'test' + type_in_content_editor '```nomnoml ' + type_in_content_editor 'test' expect(find('[data-testid="code-block-bubble-menu"]')).to have_text('Custom (nomnoml)') end @@ -76,10 +146,11 @@ RSpec.shared_examples 'edits content using the content editor' do describe 'mermaid diagram' do before do - find(content_editor_testid).send_keys [:enter, :enter] + switch_to_content_editor - find(content_editor_testid).send_keys '```mermaid ' - find(content_editor_testid).send_keys ['graph TD;', :enter, ' JohnDoe12 --> HelloWorld34'] + type_in_content_editor [:enter, :enter] + type_in_content_editor '```mermaid ' + type_in_content_editor ['graph TD;', :enter, ' JohnDoe12 --> HelloWorld34'] end it 'renders and updates the diagram correctly in a sandboxed iframe' do diff --git a/spec/support/shared_examples/features/deploy_token_shared_examples.rb b/spec/support/shared_examples/features/deploy_token_shared_examples.rb index 25dfe089f51..79ad5bd6c7f 100644 --- a/spec/support/shared_examples/features/deploy_token_shared_examples.rb +++ b/spec/support/shared_examples/features/deploy_token_shared_examples.rb @@ -30,6 +30,27 @@ RSpec.shared_examples 'a deploy token in settings' do expect(page).to have_selector("input[name='deploy-token-user'][value='deployer']") expect(page).to have_selector("input[name='deploy-token'][readonly='readonly']") end + + expect(find("input#deploy_token_name").value).to eq nil + expect(find("input#deploy_token_read_repository").checked?).to eq false + end + + context "with form errors" do + before do + visit page_path + fill_in "deploy_token_name", with: "new_deploy_key" + fill_in "deploy_token_username", with: "deployer" + click_button "Create deploy token" + end + + it "shows form errors" do + expect(page).to have_text("Scopes can't be blank") + end + + it "keeps form inputs" do + expect(find("input#deploy_token_name").value).to eq "new_deploy_key" + expect(find("input#deploy_token_username").value).to eq "deployer" + end end context 'when User#time_display_relative is false', :js do 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 24dc4bcfc59..f209070d82a 100644 --- a/spec/support/shared_examples/features/discussion_comments_shared_example.rb +++ b/spec/support/shared_examples/features/discussion_comments_shared_example.rb @@ -304,7 +304,7 @@ RSpec.shared_examples 'thread comments for issue, epic and merge request' do |re let(:reply_id) { find("#{comments_selector} .note:last-of-type", match: :first)['data-note-id'] } it 'can be replied to after resolving' do - find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage + find('button[data-testid="resolve-discussion-button"]').click wait_for_requests refresh @@ -316,7 +316,7 @@ RSpec.shared_examples 'thread comments for issue, epic and merge request' do |re it 'shows resolved thread when toggled' do submit_reply('a') - find('button[data-qa-selector="resolve_discussion_button"]').click # rubocop:disable QA/SelectorUsage + find('button[data-testid="resolve-discussion-button"]').click wait_for_requests expect(page).to have_selector(".note-row-#{note_id}", visible: true) diff --git a/spec/support/shared_examples/features/manage_applications_shared_examples.rb b/spec/support/shared_examples/features/manage_applications_shared_examples.rb index 442264e7ae4..b59f3f1e27b 100644 --- a/spec/support/shared_examples/features/manage_applications_shared_examples.rb +++ b/spec/support/shared_examples/features/manage_applications_shared_examples.rb @@ -5,39 +5,87 @@ RSpec.shared_examples 'manage applications' do let_it_be(:application_name_changed) { "#{application_name} changed" } let_it_be(:application_redirect_uri) { 'https://foo.bar' } - it 'allows user to manage applications', :js do - visit new_application_path + context 'when hash_oauth_secrets flag set' do + before do + stub_feature_flags(hash_oauth_secrets: true) + end + + it 'allows user to manage applications', :js do + visit new_application_path - expect(page).to have_content 'Add new application' + expect(page).to have_content 'Add new application' - fill_in :doorkeeper_application_name, with: application_name - fill_in :doorkeeper_application_redirect_uri, with: application_redirect_uri - check :doorkeeper_application_scopes_read_user - click_on 'Save application' + fill_in :doorkeeper_application_name, with: application_name + fill_in :doorkeeper_application_redirect_uri, with: application_redirect_uri + check :doorkeeper_application_scopes_read_user + click_on 'Save application' - validate_application(application_name, 'Yes') - expect(page).to have_link('Continue', href: index_path) + validate_application(application_name, 'Yes') + expect(page).to have_content _('This is the only time the secret is accessible. Copy the secret and store it securely') + expect(page).to have_link('Continue', href: index_path) - application = Doorkeeper::Application.find_by(name: application_name) - expect(page).to have_css("button[title=\"Copy secret\"][data-clipboard-text=\"#{application.secret}\"]", text: 'Copy') + expect(page).to have_css("button[title=\"Copy secret\"]", text: 'Copy') - click_on 'Edit' + click_on 'Edit' - application_name_changed = "#{application_name} changed" + application_name_changed = "#{application_name} changed" - fill_in :doorkeeper_application_name, with: application_name_changed - uncheck :doorkeeper_application_confidential - click_on 'Save application' + fill_in :doorkeeper_application_name, with: application_name_changed + uncheck :doorkeeper_application_confidential + click_on 'Save application' + + validate_application(application_name_changed, 'No') + expect(page).not_to have_link('Continue') + expect(page).to have_content _('The secret is only available when you first create the application') + + visit_applications_path + + page.within '.oauth-applications' do + click_on 'Destroy' + end + expect(page.find('.oauth-applications')).not_to have_content 'test_changed' + end + end + + context 'when hash_oauth_secrets flag not set' do + before do + stub_feature_flags(hash_oauth_secrets: false) + end + + it 'allows user to manage applications', :js do + visit new_application_path + + expect(page).to have_content 'Add new application' + + fill_in :doorkeeper_application_name, with: application_name + fill_in :doorkeeper_application_redirect_uri, with: application_redirect_uri + check :doorkeeper_application_scopes_read_user + click_on 'Save application' + + validate_application(application_name, 'Yes') + expect(page).to have_link('Continue', href: index_path) + + application = Doorkeeper::Application.find_by(name: application_name) + expect(page).to have_css("button[title=\"Copy secret\"][data-clipboard-text=\"#{application.secret}\"]", text: 'Copy') + + click_on 'Edit' + + application_name_changed = "#{application_name} changed" + + fill_in :doorkeeper_application_name, with: application_name_changed + uncheck :doorkeeper_application_confidential + click_on 'Save application' - validate_application(application_name_changed, 'No') - expect(page).not_to have_link('Continue') + validate_application(application_name_changed, 'No') + expect(page).not_to have_link('Continue') - visit_applications_path + visit_applications_path - page.within '.oauth-applications' do - click_on 'Destroy' + page.within '.oauth-applications' do + click_on 'Destroy' + end + expect(page.find('.oauth-applications')).not_to have_content 'test_changed' end - expect(page.find('.oauth-applications')).not_to have_content 'test_changed' end context 'when scopes are blank' do diff --git a/spec/support/shared_examples/features/packages_shared_examples.rb b/spec/support/shared_examples/features/packages_shared_examples.rb index 323bd4f5171..7aad5e2de80 100644 --- a/spec/support/shared_examples/features/packages_shared_examples.rb +++ b/spec/support/shared_examples/features/packages_shared_examples.rb @@ -14,7 +14,7 @@ RSpec.shared_examples 'packages list' do |check_project_name: false| end def package_table_row(index) - page.all("#{packages_table_selector} > [data-qa-selector=\"package_row\"]")[index].text # rubocop:disable QA/SelectorUsage + page.all("#{packages_table_selector} > [data-testid=\"package-row\"]")[index].text end end @@ -84,7 +84,7 @@ RSpec.shared_examples 'shared package sorting' do end def packages_table_selector - '[data-qa-selector="packages-table"]' # rubocop:disable QA/SelectorUsage + '[data-testid="packages-table"]' end def click_sort_option(option, ascending) diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb index 8212f14d6be..81d548e000a 100644 --- a/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb +++ b/spec/support/shared_examples/features/protected_branches_access_control_ce_shared_examples.rb @@ -8,7 +8,7 @@ RSpec.shared_examples "protected branches > access control > CE" do set_protected_branch_name('master') find(".js-allowed-to-merge").click - within('.rspec-allowed-to-merge-dropdown') do + within('[data-testid="allowed-to-merge-dropdown"]') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end @@ -35,13 +35,13 @@ RSpec.shared_examples "protected branches > access control > CE" do set_protected_branch_name('master') find(".js-allowed-to-merge").click - within('.rspec-allowed-to-merge-dropdown') do + within('[data-testid="allowed-to-merge-dropdown"]') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end find(".js-allowed-to-push").click - within('.rspec-allowed-to-push-dropdown') do + within('[data-testid="allowed-to-push-dropdown"]') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end @@ -83,7 +83,7 @@ RSpec.shared_examples "protected branches > access control > CE" do end find(".js-allowed-to-push").click - within('.rspec-allowed-to-push-dropdown') do + within('[data-testid="allowed-to-push-dropdown"]') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end @@ -100,13 +100,13 @@ RSpec.shared_examples "protected branches > access control > CE" do set_protected_branch_name('master') find(".js-allowed-to-merge").click - within('.rspec-allowed-to-merge-dropdown') do + within('[data-testid="allowed-to-merge-dropdown"]') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end find(".js-allowed-to-push").click - within('.rspec-allowed-to-push-dropdown') do + within('[data-testid="allowed-to-push-dropdown"]') do expect(first("li")).to have_content("Roles") find(:link, 'No one').click end diff --git a/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb b/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb index 14142793a0d..90b0e600228 100644 --- a/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb +++ b/spec/support/shared_examples/features/protected_branches_with_deploy_keys_examples.rb @@ -23,7 +23,7 @@ RSpec.shared_examples 'Deploy keys with protected branches' do find(".js-allowed-to-push").click wait_for_requests - within('.qa-allowed-to-push-dropdown') do # rubocop:disable QA/SelectorUsage + within('[data-testid="allowed-to-push-dropdown"]') do dropdown_headers = page.all('.dropdown-header').map(&:text) expect(dropdown_headers).to contain_exactly(*all_dropdown_sections) @@ -38,7 +38,7 @@ RSpec.shared_examples 'Deploy keys with protected branches' do find(".js-allowed-to-merge").click wait_for_requests - within('.qa-allowed-to-merge-dropdown') do # rubocop:disable QA/SelectorUsage + within('[data-testid="allowed-to-merge-dropdown"]') do dropdown_headers = page.all('.dropdown-header').map(&:text) expect(dropdown_headers).to contain_exactly(*dropdown_sections_minus_deploy_keys) @@ -68,7 +68,7 @@ RSpec.shared_examples 'Deploy keys with protected branches' do find(".js-allowed-to-push").click wait_for_requests - within('.qa-allowed-to-push-dropdown') do # rubocop:disable QA/SelectorUsage + within('[data-testid="allowed-to-push-dropdown"]') do dropdown_headers = page.all('.dropdown-header').map(&:text) expect(dropdown_headers).to contain_exactly(*dropdown_sections_minus_deploy_keys) diff --git a/spec/support/shared_examples/features/rss_shared_examples.rb b/spec/support/shared_examples/features/rss_shared_examples.rb index 0991de21d8d..ad865b084e1 100644 --- a/spec/support/shared_examples/features/rss_shared_examples.rb +++ b/spec/support/shared_examples/features/rss_shared_examples.rb @@ -9,7 +9,7 @@ end RSpec.shared_examples "it has an RSS button with current_user's feed token" do it "shows the RSS button with current_user's feed token" do expect(page) - .to have_css("a:has(.qa-rss-icon)[href*='feed_token=#{user.feed_token}']") # rubocop:disable QA/SelectorUsage + .to have_css("a:has([data-testid='rss-icon'])[href*='feed_token=#{user.feed_token}']") end end @@ -22,7 +22,7 @@ end RSpec.shared_examples "it has an RSS button without a feed token" do it "shows the RSS button without a feed token" do expect(page) - .to have_css("a:has(.qa-rss-icon):not([href*='feed_token'])") # rubocop:disable QA/SelectorUsage + .to have_css("a:has([data-testid='rss-icon']):not([href*='feed_token'])") end end diff --git a/spec/support/shared_examples/features/runners_shared_examples.rb b/spec/support/shared_examples/features/runners_shared_examples.rb index 52f3fd60c07..31ee08ea9db 100644 --- a/spec/support/shared_examples/features/runners_shared_examples.rb +++ b/spec/support/shared_examples/features/runners_shared_examples.rb @@ -64,9 +64,9 @@ end RSpec.shared_examples 'shows no runners registered' do it 'shows counts with 0' do - expect(page).to have_text "Online runners 0" - expect(page).to have_text "Offline runners 0" - expect(page).to have_text "Stale runners 0" + expect(page).to have_text "#{s_('Runners|Online')} 0" + expect(page).to have_text "#{s_('Runners|Offline')} 0" + expect(page).to have_text "#{s_('Runners|Stale')} 0" end it 'shows "no runners" message' do @@ -101,7 +101,7 @@ RSpec.shared_examples 'pauses, resumes and deletes a runner' do within_runner_row(runner.id) do click_button "Pause" - expect(page).to have_text 'paused' + expect(page).to have_text s_('Runners|Paused') expect(page).to have_button 'Resume' expect(page).not_to have_button 'Pause' @@ -145,3 +145,39 @@ RSpec.shared_examples 'pauses, resumes and deletes a runner' do end end end + +RSpec.shared_examples 'submits edit runner form' do + it 'breadcrumb contains runner id and token' do + page.within '[data-testid="breadcrumb-links"]' do + expect(page).to have_link("##{runner.id} (#{runner.short_sha})") + expect(page.find('[data-testid="breadcrumb-current-link"]')).to have_content("Edit") + end + end + + describe 'runner header', :js do + it 'contains the runner id' do + expect(page).to have_content("Runner ##{runner.id} created") + end + end + + context 'when a runner is updated', :js do + before do + find('[data-testid="runner-field-description"] input').set('new-runner-description') + + click_on _('Save changes') + wait_for_requests + end + + it 'redirects to runner page' do + expect(current_url).to match(runner_page_path) + end + + it 'show success alert' do + expect(page.find('[data-testid="alert-success"]')).to have_content('saved') + end + + it 'shows updated information' do + expect(page).to have_content("#{s_('Runners|Description')} new-runner-description") + end + end +end diff --git a/spec/support/shared_examples/features/snippets_shared_examples.rb b/spec/support/shared_examples/features/snippets_shared_examples.rb index c402333107c..bf870b3ce66 100644 --- a/spec/support/shared_examples/features/snippets_shared_examples.rb +++ b/spec/support/shared_examples/features/snippets_shared_examples.rb @@ -194,7 +194,7 @@ end RSpec.shared_examples 'personal snippet with references' do let_it_be(:project) { create(:project, :repository) } let_it_be(:merge_request) { create(:merge_request, source_project: project) } - let_it_be(:project_snippet) { create(:project_snippet, :repository, project: project)} + let_it_be(:project_snippet) { create(:project_snippet, :repository, project: project) } let_it_be(:issue) { create(:issue, project: project) } let_it_be(:commit) { project.commit } diff --git a/spec/support/shared_examples/features/variable_list_shared_examples.rb b/spec/support/shared_examples/features/variable_list_shared_examples.rb index 9d81c0e9a3e..d1e5046a39e 100644 --- a/spec/support/shared_examples/features/variable_list_shared_examples.rb +++ b/spec/support/shared_examples/features/variable_list_shared_examples.rb @@ -91,7 +91,7 @@ RSpec.shared_examples 'variable list' do |is_admin| end page.within('#add-ci-variable') do - find('[data-qa-selector="ci_variable_key_field"] input').set('new_key') # rubocop:disable QA/SelectorUsage + find('[data-testid="pipeline-form-ci-variable-key"] input').set('new_key') click_button('Update variable') end @@ -173,7 +173,7 @@ RSpec.shared_examples 'variable list' do |is_admin| click_button('Add variable') page.within('#add-ci-variable') do - find('[data-qa-selector="ci_variable_key_field"] input').set('empty_mask_key') # rubocop:disable QA/SelectorUsage + find('[data-testid="pipeline-form-ci-variable-key"] input').set('empty_mask_key') find('[data-testid="ci-variable-protected-checkbox"]').click find('[data-testid="ci-variable-masked-checkbox"]').click @@ -290,8 +290,8 @@ RSpec.shared_examples 'variable list' do |is_admin| wait_for_requests page.within('#add-ci-variable') do - find('[data-qa-selector="ci_variable_key_field"] input').set(key) # rubocop:disable QA/SelectorUsage - find('[data-qa-selector="ci_variable_value_field"]').set(value) if value.present? # rubocop:disable QA/SelectorUsage + find('[data-testid="pipeline-form-ci-variable-key"] input').set(key) + find('[data-testid="pipeline-form-ci-variable-value"]').set(value) if value.present? find('[data-testid="ci-variable-protected-checkbox"]').click if protected find('[data-testid="ci-variable-masked-checkbox"]').click if masked diff --git a/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb index 2285d9a17e2..3e285bb8ad7 100644 --- a/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/user_previews_wiki_changes_shared_examples.rb @@ -64,7 +64,7 @@ RSpec.shared_examples 'User previews wiki changes' do end it_behaves_like 'relative links' do - let(:element) { page.find('[data-testid="wiki_page_content"]') } + let(:element) { page.find('[data-testid="wiki-page-content"]') } end end diff --git a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb index 87067336a36..5c63d6a973d 100644 --- a/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/user_updates_wiki_page_shared_examples.rb @@ -137,16 +137,7 @@ RSpec.shared_examples 'User updates wiki page' do end end - context 'when using the content editor' do - context 'with feature flag on' do - before do - find('[data-testid="toggle-editing-mode-button"] label', text: 'Rich text').click - end - - it_behaves_like 'edits content using the content editor' - end - end - + it_behaves_like 'edits content using the content editor' it_behaves_like 'autocompletes items' end diff --git a/spec/support/shared_examples/features/wiki/user_views_asciidoc_page_with_includes_shared_examples.rb b/spec/support/shared_examples/features/wiki/user_views_asciidoc_page_with_includes_shared_examples.rb index 6fdc5ecae73..fde38df558f 100644 --- a/spec/support/shared_examples/features/wiki/user_views_asciidoc_page_with_includes_shared_examples.rb +++ b/spec/support/shared_examples/features/wiki/user_views_asciidoc_page_with_includes_shared_examples.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true RSpec.shared_examples 'User views AsciiDoc page with includes' do - let_it_be(:wiki_content_selector) { '[data-qa-selector=wiki_page_content]' } # rubocop:disable QA/SelectorUsage - let!(:included_wiki_page) { create_wiki_page('included_page', content: 'Content from the included page')} + let_it_be(:wiki_content_selector) { '[data-testid=wiki-page-content]' } + let!(:included_wiki_page) { create_wiki_page('included_page', content: 'Content from the included page') } let!(:wiki_page) { create_wiki_page('home', content: "Content from the main page.\ninclude::included_page.asciidoc[]") } def create_wiki_page(title, content:) diff --git a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb index 049ead9fb89..f62c9c00006 100644 --- a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb +++ b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb @@ -365,7 +365,7 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context let!(:created_items) do milestones.map do |milestone| create(factory, project: milestone.project || project_in_group, - milestone: milestone, author: user, assignees: [user]) + milestone: milestone, author: user, assignees: [user]) end end diff --git a/spec/support/shared_examples/graphql/members_shared_examples.rb b/spec/support/shared_examples/graphql/members_shared_examples.rb index 110706c730b..5cba8baa829 100644 --- a/spec/support/shared_examples/graphql/members_shared_examples.rb +++ b/spec/support/shared_examples/graphql/members_shared_examples.rb @@ -40,7 +40,7 @@ RSpec.shared_examples 'querying members with a group' do subject do resolve(described_class, obj: resource, args: base_args.merge(args), - ctx: { current_user: user_4 }, arg_style: :internal) + ctx: { current_user: user_4 }, arg_style: :internal) end describe '#resolve' do @@ -52,6 +52,15 @@ RSpec.shared_examples 'querying members with a group' do expect(subject).to contain_exactly(resource_member, group_1_member, root_group_member) end + context 'with sort options' do + let(:args) { { sort: 'name_asc' } } + + it 'searches users by user name' do + # the order is important here + expect(subject.items).to eq([root_group_member, resource_member, group_1_member]) + end + end + context 'with search' do context 'when the search term matches a user' do let(:args) { { search: 'test' } } @@ -75,7 +84,7 @@ RSpec.shared_examples 'querying members with a group' do subject do resolve(described_class, obj: resource, args: base_args.merge(args), - ctx: { current_user: other_user }, arg_style: :internal) + ctx: { current_user: other_user }, arg_style: :internal) end it 'generates an error' do diff --git a/spec/support/shared_examples/graphql/n_plus_one_query_examples.rb b/spec/support/shared_examples/graphql/n_plus_one_query_examples.rb index 738edd43c92..faf1bb204c9 100644 --- a/spec/support/shared_examples/graphql/n_plus_one_query_examples.rb +++ b/spec/support/shared_examples/graphql/n_plus_one_query_examples.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -shared_examples 'N+1 query check' do +RSpec.shared_examples 'N+1 query check' do it 'prevents N+1 queries' do execute_query # "warm up" to prevent undeterministic counts expect(graphql_errors).to be_blank # Sanity check - ex falso quodlibet! diff --git a/spec/support/shared_examples/graphql/resolvers/issuable_resolvers_shared_examples.rb b/spec/support/shared_examples/graphql/resolvers/issuable_resolvers_shared_examples.rb new file mode 100644 index 00000000000..25008bca619 --- /dev/null +++ b/spec/support/shared_examples/graphql/resolvers/issuable_resolvers_shared_examples.rb @@ -0,0 +1,99 @@ +# frozen_string_literal: true + +# Requires `parent`, issuable1`, `issuable2`, `issuable3`, `issuable4`, +# `finder_class` and `optimization_param` bindings. +RSpec.shared_examples 'graphql query for searching issuables' do + it 'uses search optimization' do + expected_arguments = a_hash_including( + search: 'text', + optimization_param => true + ) + expect(finder_class).to receive(:new).with(anything, expected_arguments).and_call_original + + resolve_issuables(search: 'text') + end + + it 'filters issuables by title' do + issuables = resolve_issuables(search: 'created') + + expect(issuables).to contain_exactly(issuable1, issuable2) + end + + it 'filters issuables by description' do + issuables = resolve_issuables(search: 'text') + + expect(issuables).to contain_exactly(issuable2, issuable3) + end + + context 'with in param' do + it 'generates an error if param search is missing' do + error_message = "`search` should be present when including the `in` argument" + + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, error_message) do + resolve_issuables(in: ['title']) + end + end + + it 'filters issuables by title and description' do + issuable4.update!(title: 'fourth text') + issuables = resolve_issuables(search: 'text', in: %w[title description]) + + expect(issuables).to contain_exactly(issuable2, issuable3, issuable4) + end + + it 'filters issuables by description only' do + with_text = resolve_issuables(search: 'text', in: ['description']) + with_created = resolve_issuables(search: 'created', in: ['description']) + + expect(with_created).to be_empty + expect(with_text).to contain_exactly(issuable2, issuable3) + end + + it 'filters issuables by title only' do + with_text = resolve_issuables(search: 'text', in: ['title']) + with_created = resolve_issuables(search: 'created', in: ['title']) + + expect(with_created).to contain_exactly(issuable1, issuable2) + expect(with_text).to be_empty + end + end + + context 'with anonymous user' do + let_it_be(:current_user) { nil } + + context 'with disable_anonymous_search as `true`' do + before do + stub_feature_flags(disable_anonymous_search: true) + end + + it 'returns an error' do + error_message = "User must be authenticated to include the `search` argument." + + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, error_message) do + resolve_issuables(search: 'created') + end + end + + it 'does not return error if search term is not present' do + expect(resolve_issuables).not_to be_instance_of(Gitlab::Graphql::Errors::ArgumentError) + end + end + + context 'with disable_anonymous_search as `false`' do + before do + stub_feature_flags(disable_anonymous_search: false) + parent.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + end + + it 'filters issuables by search term' do + issuables = resolve_issuables(search: 'created') + + expect(issuables).to contain_exactly(issuable1, issuable2) + end + end + end + + def resolve_issuables(args = {}, obj = parent, context = { current_user: current_user }) + resolve(described_class, obj: obj, args: args, ctx: context, arg_style: :internal) + end +end diff --git a/spec/support/shared_examples/lib/banzai/filters/sanitization_filter_shared_examples.rb b/spec/support/shared_examples/lib/banzai/filters/sanitization_filter_shared_examples.rb index b5c07f45d59..47655f86558 100644 --- a/spec/support/shared_examples/lib/banzai/filters/sanitization_filter_shared_examples.rb +++ b/spec/support/shared_examples/lib/banzai/filters/sanitization_filter_shared_examples.rb @@ -45,62 +45,62 @@ RSpec.shared_examples 'XSS prevention' do # Adapted from the Sanitize test suite: http://git.io/vczrM protocols = { 'protocol-based JS injection: simple, no spaces' => { - input: '<a href="javascript:alert(\'XSS\');">foo</a>', + input: '<a href="javascript:alert(\'XSS\');">foo</a>', output: '<a>foo</a>' }, 'protocol-based JS injection: simple, spaces before' => { - input: '<a href="javascript :alert(\'XSS\');">foo</a>', + input: '<a href="javascript :alert(\'XSS\');">foo</a>', output: '<a>foo</a>' }, 'protocol-based JS injection: simple, spaces after' => { - input: '<a href="javascript: alert(\'XSS\');">foo</a>', + input: '<a href="javascript: alert(\'XSS\');">foo</a>', output: '<a>foo</a>' }, 'protocol-based JS injection: simple, spaces before and after' => { - input: '<a href="javascript : alert(\'XSS\');">foo</a>', + input: '<a href="javascript : alert(\'XSS\');">foo</a>', output: '<a>foo</a>' }, 'protocol-based JS injection: preceding colon' => { - input: '<a href=":javascript:alert(\'XSS\');">foo</a>', + input: '<a href=":javascript:alert(\'XSS\');">foo</a>', output: '<a>foo</a>' }, 'protocol-based JS injection: UTF-8 encoding' => { - input: '<a href="javascript:">foo</a>', + input: '<a href="javascript:">foo</a>', output: '<a>foo</a>' }, 'protocol-based JS injection: long UTF-8 encoding' => { - input: '<a href="javascript:">foo</a>', + input: '<a href="javascript:">foo</a>', output: '<a>foo</a>' }, 'protocol-based JS injection: long UTF-8 encoding without semicolons' => { - input: '<a href=javascript:alert('XSS')>foo</a>', + input: '<a href=javascript:alert('XSS')>foo</a>', output: '<a>foo</a>' }, 'protocol-based JS injection: hex encoding' => { - input: '<a href="javascript:">foo</a>', + input: '<a href="javascript:">foo</a>', output: '<a>foo</a>' }, 'protocol-based JS injection: long hex encoding' => { - input: '<a href="javascript:">foo</a>', + input: '<a href="javascript:">foo</a>', output: '<a>foo</a>' }, 'protocol-based JS injection: hex encoding without semicolons' => { - input: '<a href=javascript:alert('XSS')>foo</a>', + input: '<a href=javascript:alert('XSS')>foo</a>', output: '<a>foo</a>' }, 'protocol-based JS injection: null char' => { - input: "<a href=java\0script:alert(\"XSS\")>foo</a>", + input: "<a href=java\0script:alert(\"XSS\")>foo</a>", output: '<a href="java"></a>' }, @@ -115,7 +115,7 @@ RSpec.shared_examples 'XSS prevention' do }, 'protocol-based JS injection: spaces and entities' => { - input: '<a href="  javascript:alert(\'XSS\');">foo</a>', + input: '<a href="  javascript:alert(\'XSS\');">foo</a>', output: '<a href="">foo</a>' }, diff --git a/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb b/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb index 845fa78a827..82a9e8130f7 100644 --- a/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb +++ b/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb @@ -43,6 +43,54 @@ RSpec.shared_examples_for 'object cache helper' do subject end end + + context 'when a caller id is present' do + let(:transaction) { Gitlab::Metrics::WebTransaction.new({}) } + let(:caller_id) { 'caller_id' } + + before do + allow(::Gitlab::Metrics::WebTransaction).to receive(:current).and_return(transaction) + allow(transaction).to receive(:increment) + allow(Gitlab::ApplicationContext).to receive(:current_context_attribute).with(:caller_id).and_return(caller_id) + end + + context 'when feature flag is off' do + before do + stub_feature_flags(add_timing_to_certain_cache_actions: false) + end + + it 'does not call increment' do + expect(transaction).not_to receive(:increment).with(:cached_object_operations_total, any_args) + + subject + end + + it 'does not call histogram' do + expect(Gitlab::Metrics).not_to receive(:histogram) + + subject + end + + it "is valid JSON" do + parsed = Gitlab::Json.parse(subject.to_s) + + expect(parsed).to be_a(Hash) + expect(parsed["id"]).to eq(presentable.id) + end + end + + it 'increments the counter' do + expect(transaction) + .to receive(:increment) + .with(:cached_object_operations_total, 1, { caller_id: caller_id, render_type: :object, cache_hit: false }).once + + expect(transaction) + .to receive(:increment) + .with(:cached_object_operations_total, 0, { caller_id: caller_id, render_type: :object, cache_hit: true }).once + + subject + end + end end RSpec.shared_examples_for 'collection cache helper' do @@ -98,4 +146,95 @@ RSpec.shared_examples_for 'collection cache helper' do subject end end + + context 'when a caller id is present' do + let(:transaction) { Gitlab::Metrics::WebTransaction.new({}) } + let(:caller_id) { 'caller_id' } + + before do + allow(::Gitlab::Metrics::WebTransaction).to receive(:current).and_return(transaction) + allow(transaction).to receive(:increment) + allow(Gitlab::ApplicationContext).to receive(:current_context_attribute).with(:caller_id).and_return(caller_id) + end + + context 'when feature flag is off' do + before do + stub_feature_flags(add_timing_to_certain_cache_actions: false) + end + + it 'does not call increment' do + expect(transaction).not_to receive(:increment).with(:cached_object_operations_total, any_args) + + subject + end + + it 'does not call histogram' do + expect(Gitlab::Metrics).not_to receive(:histogram) + + subject + end + + it "is valid JSON" do + parsed = Gitlab::Json.parse(subject.to_s) + + expect(parsed).to be_an(Array) + + presentable.each_with_index do |item, i| + expect(parsed[i]["id"]).to eq(item.id) + end + end + end + + context 'when presentable has a group by clause' do + let(:presentable) { MergeRequest.group(:id) } + + it "returns the presentables" do + expect(transaction) + .to receive(:increment) + .with(:cached_object_operations_total, 0, { caller_id: caller_id, render_type: :collection, cache_hit: true }).once + + expect(transaction) + .to receive(:increment) + .with(:cached_object_operations_total, MergeRequest.count, { caller_id: caller_id, render_type: :collection, cache_hit: false }).once + + parsed = Gitlab::Json.parse(subject.to_s) + + expect(parsed).to be_an(Array) + + presentable.each_with_index do |item, i| + expect(parsed[i]["id"]).to eq(item.id) + end + end + end + + context 'when the presentables all miss' do + it 'increments the counters' do + expect(transaction) + .to receive(:increment) + .with(:cached_object_operations_total, 0, { caller_id: caller_id, render_type: :collection, cache_hit: true }).once + + expect(transaction) + .to receive(:increment) + .with(:cached_object_operations_total, presentable.size, { caller_id: caller_id, render_type: :collection, cache_hit: false }).once + + subject + end + end + + context 'when the presents hit' do + it 'increments the counters' do + subject + + expect(transaction) + .to receive(:increment) + .with(:cached_object_operations_total, presentable.size, { caller_id: caller_id, render_type: :collection, cache_hit: true }).once + + expect(transaction) + .to receive(:increment) + .with(:cached_object_operations_total, 0, { caller_id: caller_id, render_type: :collection, cache_hit: false }).once + + instance.public_send(method, presentable, **kwargs) + end + 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 b786d7e5527..10f58748698 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 @@ -131,7 +131,7 @@ RSpec.shared_examples 'common trace features' do end context 'logs contains "section_start"' do - let(:log) { "section_start:1506417476:a_section\r\033[0Klooks like a section_start:invalid\nsection_end:1506417477:a_section\r\033[0K"} + let(:log) { "section_start:1506417476:a_section\r\033[0Klooks like a section_start:invalid\nsection_end:1506417477:a_section\r\033[0K" } it "returns only one section" do expect(sections).not_to be_empty @@ -144,7 +144,7 @@ RSpec.shared_examples 'common trace features' do end context 'missing section_end' do - let(:log) { "section_start:1506417476:a_section\r\033[0KSome logs\nNo section_end\n"} + let(:log) { "section_start:1506417476:a_section\r\033[0KSome logs\nNo section_end\n" } it "returns no sections" do expect(sections).to be_empty @@ -152,7 +152,7 @@ RSpec.shared_examples 'common trace features' do end context 'missing section_start' do - let(:log) { "Some logs\nNo section_start\nsection_end:1506417476:a_section\r\033[0K"} + let(:log) { "Some logs\nNo section_start\nsection_end:1506417476:a_section\r\033[0K" } it "returns no sections" do expect(sections).to be_empty @@ -160,7 +160,7 @@ RSpec.shared_examples 'common trace features' do end context 'inverted section_start section_end' do - let(:log) { "section_end:1506417476:a_section\r\033[0Klooks like a section_start:invalid\nsection_start:1506417477:a_section\r\033[0K"} + let(:log) { "section_end:1506417476:a_section\r\033[0Klooks like a section_start:invalid\nsection_start:1506417477:a_section\r\033[0K" } it "returns no sections" do expect(sections).to be_empty @@ -169,7 +169,7 @@ RSpec.shared_examples 'common trace features' do end describe '#write' do - subject { trace.send(:write, mode) { } } + subject { trace.send(:write, mode) {} } let(:mode) { 'wb' } @@ -370,15 +370,6 @@ RSpec.shared_examples 'trace with disabled live trace feature' do end end - shared_examples 'read successfully with StringIO' do - it 'yields with source' do - trace.read do |stream| - expect(stream).to be_a(Gitlab::Ci::Trace::Stream) - expect(stream.stream).to be_a(StringIO) - end - end - end - shared_examples 'failed to read' do it 'yields without source' do trace.read do |stream| @@ -404,14 +395,6 @@ RSpec.shared_examples 'trace with disabled live trace feature' do it_behaves_like 'read successfully with IO' end - context 'when db trace exists' do - before do - build.send(:write_attribute, :trace, "data") - end - - it_behaves_like 'read successfully with StringIO' - end - context 'when no sources exist' do it_behaves_like 'failed to read' end @@ -462,25 +445,6 @@ RSpec.shared_examples 'trace with disabled live trace feature' do expect(trace.exist?).to be(false) end end - - context 'stored in database' do - before do - build.send(:write_attribute, :trace, "data") - end - - it "trace exist" do - expect(trace.exist?).to be(true) - end - - it "can be erased" do - trace.erase! - expect(trace.exist?).to be(false) - end - - it "returns database data" do - expect(trace.raw).to eq("data") - end - end end describe '#archive!' do @@ -520,24 +484,12 @@ RSpec.shared_examples 'trace with disabled live trace feature' do expect(build.trace.exist?).to be_truthy expect(build.job_artifacts_trace.file.exists?).to be_truthy 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.sha256_hexdigest(build.job_artifacts_trace.file.path)) expect(build.job_artifacts_trace.file_sha256).to eq(src_checksum) end end - shared_examples 'source trace in database stays intact' do |error:| - it do - expect { subject }.to raise_error(error) - - build.reload - expect(build.trace.exist?).to be_truthy - expect(build.job_artifacts_trace).to be_nil - expect(build.old_trace).to eq(trace_content) - end - end - context 'when job does not have trace artifact' do context 'when trace file stored in default path' do let!(:build) { create(:ci_build, :success, :trace_live) } @@ -564,58 +516,6 @@ RSpec.shared_examples 'trace with disabled live trace feature' do it_behaves_like 'source trace file stays intact', error: ActiveRecord::RecordInvalid end end - - context 'when trace is stored in database' do - let(:build) { create(:ci_build, :success) } - let(:trace_content) { 'Sample trace' } - let(:src_checksum) { Digest::SHA256.hexdigest(trace_content) } - - before do - build.update_column(:trace, trace_content) - end - - it_behaves_like 'archive trace in database' - - context 'when failed to create clone file' do - before do - allow(IO).to receive(:copy_stream).and_return(0) - end - - it_behaves_like 'source trace in database stays intact', error: Gitlab::Ci::Trace::ArchiveError - end - - context 'when failed to create job artifact record' do - before do - allow_any_instance_of(Ci::JobArtifact).to receive(:save).and_return(false) - allow_any_instance_of(Ci::JobArtifact).to receive_message_chain(:errors, :full_messages) - .and_return(%w[Error Error]) - end - - it_behaves_like 'source trace in database stays intact', error: ActiveRecord::RecordInvalid - end - - context 'when there is a validation error on Ci::Build' do - before do - allow_any_instance_of(Ci::Build).to receive(:save).and_return(false) - allow_any_instance_of(Ci::Build).to receive_message_chain(:errors, :full_messages) - .and_return(%w[Error Error]) - end - - context "when erase old trace with 'save'" do - before do - build.send(:write_attribute, :trace, nil) - build.save # rubocop:disable Rails/SaveBang - end - - it 'old trace is not deleted' do - build.reload - expect(build.trace.raw).to eq(trace_content) - end - end - - it_behaves_like 'archive trace in database' - end - end end context 'when job has trace artifact' do @@ -645,22 +545,6 @@ RSpec.shared_examples 'trace with disabled live trace feature' do subject { trace.erase! } context 'when it is a live trace' do - context 'when trace is stored in database' do - let(:build) { create(:ci_build) } - - before do - build.update_column(:trace, 'sample trace') - end - - it { expect(trace.raw).not_to be_nil } - - it "removes trace" do - subject - - expect(trace.raw).to be_nil - end - end - context 'when trace is stored in file storage' do let(:build) { create(:ci_build, :trace_live) } diff --git a/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb b/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb index beec072e474..9ffc55f7e7e 100644 --- a/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb +++ b/spec/support/shared_examples/lib/gitlab/cycle_analytics/deployment_metrics.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -shared_examples 'deployment metrics examples' do +RSpec.shared_examples 'deployment metrics examples' do def create_deployment(args) project = args[:project] environment = project.environments.production.first || create(:environment, :production, project: project) diff --git a/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb index 771ab89972c..a28fefcfc58 100644 --- a/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/database/background_migration_job_shared_examples.rb @@ -3,7 +3,7 @@ RSpec.shared_examples 'marks background migration job records' do it 'marks each job record as succeeded after processing' do create(:background_migration_job, class_name: "::#{described_class.name.demodulize}", - arguments: arguments) + arguments: arguments) expect(::Gitlab::Database::BackgroundMigrationJob).to receive(:mark_all_as_succeeded).and_call_original @@ -14,7 +14,7 @@ RSpec.shared_examples 'marks background migration job records' do it 'returns the number of job records marked as succeeded' do create(:background_migration_job, class_name: "::#{described_class.name.demodulize}", - arguments: arguments) + arguments: arguments) jobs_updated = subject.perform(*arguments) diff --git a/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb index 1f7325df11a..243dc1d195b 100644 --- a/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/diff_file_collections_shared_examples.rb @@ -144,7 +144,7 @@ RSpec.shared_examples 'cacheable diff collection' do end end -shared_examples_for 'sortable diff files' do +RSpec.shared_examples_for 'sortable diff files' do subject { described_class.new(diffable, **collection_default_args) } describe '#raw_diff_files' do @@ -170,7 +170,7 @@ shared_examples_for 'sortable diff files' do end end -shared_examples_for 'unsortable diff files' do +RSpec.shared_examples_for 'unsortable diff files' do subject { described_class.new(diffable, **collection_default_args) } describe '#raw_diff_files' do diff --git a/spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb index ead8b174d46..ec7b2794703 100644 --- a/spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/sql/set_operator_shared_examples.rb @@ -25,7 +25,7 @@ RSpec.shared_examples 'SQL set operator' do |operator_keyword| empty_relation = User.none.select(:id) set_operator = described_class.new([empty_relation, relation_1, relation_2]) - expect {User.where("users.id IN (#{set_operator.to_sql})").to_a}.not_to raise_error + expect { User.where("users.id IN (#{set_operator.to_sql})").to_a }.not_to raise_error expect(set_operator.to_sql).to eq("(#{to_sql(relation_1)})\n#{operator_keyword}\n(#{to_sql(relation_2)})") end diff --git a/spec/support/shared_examples/lib/sentry/client_shared_examples.rb b/spec/support/shared_examples/lib/sentry/client_shared_examples.rb index d73c7b6848d..1c0e0061385 100644 --- a/spec/support/shared_examples/lib/sentry/client_shared_examples.rb +++ b/spec/support/shared_examples/lib/sentry/client_shared_examples.rb @@ -43,7 +43,7 @@ RSpec.shared_examples 'maps Sentry exceptions' do |http_method| } exceptions.each do |exception, message| - context "#{exception}" do + context exception do before do stub_request( http_method || :get, @@ -58,3 +58,50 @@ RSpec.shared_examples 'maps Sentry exceptions' do |http_method| end end end + +# Expects to following variables: +# - subject +# - sentry_api_response +# - sentry_url, token - only if enabled_by_default: false +RSpec.shared_examples 'Sentry API response size limit' do |enabled_by_default: false| + let(:invalid_deep_size) { instance_double(Gitlab::Utils::DeepSize, valid?: false) } + + before do + allow(Gitlab::Utils::DeepSize) + .to receive(:new) + .with(sentry_api_response, any_args) + .and_return(invalid_deep_size) + end + + if enabled_by_default + it 'raises an exception when response is too large' do + expect { subject }.to raise_error(ErrorTracking::SentryClient::ResponseInvalidSizeError, + 'Sentry API response is too big. Limit is 1 MB.') + end + else + context 'when guarded by feature flag' do + let(:client) do + ErrorTracking::SentryClient.new(sentry_url, token, validate_size_guarded_by_feature_flag: feature_flag) + end + + context 'with feature flag enabled' do + let(:feature_flag) { true } + + it 'raises an exception when response is too large' do + expect { subject }.to raise_error(ErrorTracking::SentryClient::ResponseInvalidSizeError, + 'Sentry API response is too big. Limit is 1 MB.') + end + end + + context 'with feature flag disabled' do + let(:feature_flag) { false } + + it 'does not check the limit and thus not raise' do + expect { subject }.not_to raise_error + + expect(Gitlab::Utils::DeepSize).not_to have_received(:new) + end + end + end + end +end diff --git a/spec/support/shared_examples/models/chat_integration_shared_examples.rb b/spec/support/shared_examples/models/chat_integration_shared_examples.rb index fb08784f34f..6cfeeabc952 100644 --- a/spec/support/shared_examples/models/chat_integration_shared_examples.rb +++ b/spec/support/shared_examples/models/chat_integration_shared_examples.rb @@ -32,9 +32,11 @@ RSpec.shared_examples "chat integration" do |integration_name| end describe "#execute" do - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } + let_it_be(:user) { create(:user) } + let_it_be_with_reload(:project) { create(:project, :repository) } + let(:webhook_url) { "https://example.gitlab.com/" } + let(:webhook_url_regex) { /\A#{webhook_url}.*/ } before do allow(subject).to receive_messages( @@ -44,7 +46,7 @@ RSpec.shared_examples "chat integration" do |integration_name| webhook: webhook_url ) - WebMock.stub_request(:post, webhook_url) + WebMock.stub_request(:post, webhook_url_regex) end shared_examples "triggered #{integration_name} integration" do |branches_to_be_notified: nil| @@ -56,7 +58,7 @@ RSpec.shared_examples "chat integration" do |integration_name| result = subject.execute(sample_data) expect(result).to be(true) - expect(WebMock).to have_requested(:post, webhook_url).once.with { |req| + expect(WebMock).to have_requested(:post, webhook_url_regex).once.with { |req| json_body = Gitlab::Json.parse(req.body).with_indifferent_access expect(json_body).to include(payload) } @@ -72,7 +74,7 @@ RSpec.shared_examples "chat integration" do |integration_name| result = subject.execute(sample_data) expect(result).to be_falsy - expect(WebMock).not_to have_requested(:post, webhook_url) + expect(WebMock).not_to have_requested(:post, webhook_url_regex) end end @@ -112,14 +114,14 @@ RSpec.shared_examples "chat integration" do |integration_name| end context "with protected branch" do - before do - create(:protected_branch, :create_branch_on_repository, project: project, name: "a-protected-branch") - end - let(:sample_data) do Gitlab::DataBuilder::Push.build(project: project, user: user, ref: "a-protected-branch") end + before_all do + create(:protected_branch, :create_branch_on_repository, project: project, name: "a-protected-branch") + end + context "when only default branch are to be notified" do it_behaves_like "untriggered #{integration_name} integration", branches_to_be_notified: "default" end @@ -214,7 +216,7 @@ RSpec.shared_examples "chat integration" do |integration_name| let(:sample_data) { Gitlab::DataBuilder::Note.build(note, user) } context "with commit comment" do - let(:note) do + let_it_be(:note) do create(:note_on_commit, author: user, project: project, @@ -226,7 +228,7 @@ RSpec.shared_examples "chat integration" do |integration_name| end context "with merge request comment" do - let(:note) do + let_it_be(:note) do create(:note_on_merge_request, project: project, note: "merge request note") end @@ -234,7 +236,7 @@ RSpec.shared_examples "chat integration" do |integration_name| end context "with issue comment" do - let(:note) do + let_it_be(:note) do create(:note_on_issue, project: project, note: "issue note") end @@ -242,7 +244,7 @@ RSpec.shared_examples "chat integration" do |integration_name| end context "with snippet comment" do - let(:note) do + let_it_be(:note) do create(:note_on_project_snippet, project: project, note: "snippet note") end @@ -251,22 +253,24 @@ RSpec.shared_examples "chat integration" do |integration_name| end context "with pipeline events" do - let(:pipeline) do - create(:ci_pipeline, - project: project, status: status, - sha: project.commit.sha, ref: project.default_branch) - end - let(:sample_data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } context "with failed pipeline" do - let(:status) { "failed" } + let_it_be(:pipeline) do + create(:ci_pipeline, + project: project, status: "failed", + sha: project.commit.sha, ref: project.default_branch) + end it_behaves_like "triggered #{integration_name} integration" end context "with succeeded pipeline" do - let(:status) { "success" } + let_it_be(:pipeline) do + create(:ci_pipeline, + project: project, status: "success", + sha: project.commit.sha, ref: project.default_branch) + end context "with default notify_only_broken_pipelines" do it "does not call #{integration_name} API" do @@ -308,7 +312,7 @@ RSpec.shared_examples "chat integration" do |integration_name| end context "with protected branch" do - before do + before_all do create(:protected_branch, :create_branch_on_repository, project: project, name: "a-protected-branch") end @@ -357,7 +361,8 @@ RSpec.shared_examples "chat integration" do |integration_name| end context 'deployment events' do - let(:deployment) { create(:deployment) } + let_it_be(:deployment) { create(:deployment) } + let(:sample_data) { Gitlab::DataBuilder::Deployment.build(deployment, deployment.status, Time.now) } it_behaves_like "untriggered #{integration_name} integration" diff --git a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb index 51071ae47c3..ca9122bf61f 100644 --- a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb @@ -37,7 +37,7 @@ RSpec.shared_examples 'cluster application core specs' do |application_name| with_them do subject { described_class.new(cluster: cluster).helm_command_module } - let(:cluster) { build(:cluster, helm_major_version: helm_major_version)} + let(:cluster) { build(:cluster, helm_major_version: helm_major_version) } it { is_expected.to eq(expected_helm_command_module) } end 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 index 8ff30021d6e..6f104f400bc 100644 --- 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 @@ -89,7 +89,7 @@ RSpec.shared_examples 'StageEventModel' do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:user) } let_it_be(:milestone) { create(:milestone) } - let_it_be(:issuable_with_assignee) { create(issuable_factory, assignees: [user])} + let_it_be(:issuable_with_assignee) { create(issuable_factory, assignees: [user]) } let_it_be(:record) { create(stage_event_factory, start_event_timestamp: 3.years.ago.to_date, end_event_timestamp: 2.years.ago.to_date) } let_it_be(:record_with_author) { create(stage_event_factory, author_id: user.id) } diff --git a/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb b/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb index f4d5ab3d5c6..f3a12578912 100644 --- a/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/counter_attribute_shared_examples.rb @@ -75,9 +75,9 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes| end context 'when attribute is not a counter attribute' do - it 'delegates to ActiveRecord update!' do + it 'raises ArgumentError' do expect { model.delayed_increment_counter(:unknown_attribute, 10) } - .to raise_error(ActiveModel::MissingAttributeError) + .to raise_error(ArgumentError, 'unknown_attribute is not a counter attribute') end 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 a403a27adef..0a07c9d677b 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 @@ -125,17 +125,17 @@ RSpec.shared_examples 'model with repository' do end describe '#valid_repo?' do - it { expect(stubbed_container.valid_repo?).to be(false)} + it { expect(stubbed_container.valid_repo?).to be(false) } it { expect(container.valid_repo?).to be(true) } end describe '#repository_exists?' do - it { expect(stubbed_container.repository_exists?).to be(false)} + it { expect(stubbed_container.repository_exists?).to be(false) } it { expect(container.repository_exists?).to be(true) } end describe '#repo_exists?' do - it { expect(stubbed_container.repo_exists?).to be(false)} + it { expect(stubbed_container.repo_exists?).to be(false) } it { expect(container.repo_exists?).to be(true) } end diff --git a/spec/support/shared_examples/models/concerns/incident_management/escalatable_shared_examples.rb b/spec/support/shared_examples/models/concerns/incident_management/escalatable_shared_examples.rb index 8ee76efc896..a5970f134d9 100644 --- a/spec/support/shared_examples/models/concerns/incident_management/escalatable_shared_examples.rb +++ b/spec/support/shared_examples/models/concerns/incident_management/escalatable_shared_examples.rb @@ -77,7 +77,7 @@ RSpec.shared_examples 'a model including Escalatable' do end context 'scopes' do - let(:all_escalatables) { described_class.where(id: [triggered_escalatable, acknowledged_escalatable, ignored_escalatable, resolved_escalatable])} + let(:all_escalatables) { described_class.where(id: [triggered_escalatable, acknowledged_escalatable, ignored_escalatable, resolved_escalatable]) } describe '.order_status' do subject { all_escalatables.order_status(order) } diff --git a/spec/support/shared_examples/models/label_note_shared_examples.rb b/spec/support/shared_examples/models/label_note_shared_examples.rb index 73066fb631a..f61007f57fd 100644 --- a/spec/support/shared_examples/models/label_note_shared_examples.rb +++ b/spec/support/shared_examples/models/label_note_shared_examples.rb @@ -12,7 +12,7 @@ RSpec.shared_examples 'label note created from events' do def label_refs(events) labels = events.map(&:label).compact - labels.map { |l| l.to_reference}.sort.join(' ') + labels.map { |l| l.to_reference }.sort.join(' ') end let(:time) { Time.now } diff --git a/spec/support/shared_examples/models/members_notifications_shared_example.rb b/spec/support/shared_examples/models/members_notifications_shared_example.rb index 75eed0203a7..e74aab95e46 100644 --- a/spec/support/shared_examples/models/members_notifications_shared_example.rb +++ b/spec/support/shared_examples/models/members_notifications_shared_example.rb @@ -8,7 +8,7 @@ RSpec.shared_examples 'members notifications' do |entity_type| end describe "#after_create" do - let(:member) { build(:"#{entity_type}_member") } + let(:member) { build(:"#{entity_type}_member", "#{entity_type}": create(entity_type.to_s)) } it "sends email to user" do expect(notification_service).to receive(:"new_#{entity_type}_member").with(member) @@ -35,7 +35,7 @@ RSpec.shared_examples 'members notifications' do |entity_type| describe '#after_commit' do context 'on creation of a member requesting access' do - let(:member) { build(:"#{entity_type}_member", :access_request) } + let(:member) { build(:"#{entity_type}_member", :access_request, "#{entity_type}": create(entity_type.to_s)) } it "calls NotificationService.new_access_request" do expect(notification_service).to receive(:new_access_request).with(member) 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 6b0ae589efb..3d7d97bbeae 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 @@ -202,17 +202,17 @@ RSpec.shared_examples 'Debian Distribution' do |factory, container, can_freeze| end else describe 'group distribution specifics' do - let_it_be(:public_project) { create(:project, :public, group: distribution_with_suite.container)} + let_it_be(:public_project) { create(:project, :public, group: distribution_with_suite.container) } let_it_be(:public_distribution_with_same_codename) { create(:debian_project_distribution, container: public_project, codename: distribution_with_suite.codename) } - let_it_be(:public_package_with_same_codename) { create(:debian_package, project: public_project, published_in: public_distribution_with_same_codename)} + let_it_be(:public_package_with_same_codename) { create(:debian_package, project: public_project, published_in: public_distribution_with_same_codename) } let_it_be(:public_distribution_with_same_suite) { create(:debian_project_distribution, container: public_project, suite: distribution_with_suite.suite) } - let_it_be(:public_package_with_same_suite) { create(:debian_package, project: public_project, published_in: public_distribution_with_same_suite)} + let_it_be(:public_package_with_same_suite) { create(:debian_package, project: public_project, published_in: public_distribution_with_same_suite) } - let_it_be(:private_project) { create(:project, :private, group: distribution_with_suite.container)} + let_it_be(:private_project) { create(:project, :private, group: distribution_with_suite.container) } let_it_be(:private_distribution_with_same_codename) { create(:debian_project_distribution, container: private_project, codename: distribution_with_suite.codename) } - let_it_be(:private_package_with_same_codename) { create(:debian_package, project: private_project, published_in: private_distribution_with_same_codename)} + let_it_be(:private_package_with_same_codename) { create(:debian_package, project: private_project, published_in: private_distribution_with_same_codename) } let_it_be(:private_distribution_with_same_suite) { create(:debian_project_distribution, container: private_project, suite: distribution_with_suite.suite) } - let_it_be(:private_package_with_same_suite) { create(:debian_package, project: private_project, published_in: private_distribution_with_same_codename)} + let_it_be(:private_package_with_same_suite) { create(:debian_package, project: private_project, published_in: private_distribution_with_same_codename) } describe '#packages' do subject { distribution_with_suite.packages } diff --git a/spec/support/shared_examples/models/project_latest_successful_build_for_shared_examples.rb b/spec/support/shared_examples/models/project_latest_successful_build_for_shared_examples.rb index 66cd8d1df12..9093b386a5d 100644 --- a/spec/support/shared_examples/models/project_latest_successful_build_for_shared_examples.rb +++ b/spec/support/shared_examples/models/project_latest_successful_build_for_shared_examples.rb @@ -64,7 +64,7 @@ RSpec.shared_examples 'latest successful build for sha or ref' do context 'with build belonging to a child pipeline' do let(:child_pipeline) { create_pipeline(project) } let(:parent_bridge) { create(:ci_bridge, pipeline: pipeline, project: pipeline.project) } - let!(:pipeline_source) { create(:ci_sources_pipeline, source_job: parent_bridge, pipeline: child_pipeline)} + let!(:pipeline_source) { create(:ci_sources_pipeline, source_job: parent_bridge, pipeline: child_pipeline) } let!(:child_build) { create_build(child_pipeline, 'child-build') } let(:build_name) { child_build.name } diff --git a/spec/support/shared_examples/models/synthetic_note_shared_examples.rb b/spec/support/shared_examples/models/synthetic_note_shared_examples.rb index a41ade2950a..12e865b1312 100644 --- a/spec/support/shared_examples/models/synthetic_note_shared_examples.rb +++ b/spec/support/shared_examples/models/synthetic_note_shared_examples.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.shared_examples 'a synthetic note' do |action| - it_behaves_like 'a system note', exclude_project: true do + it_behaves_like 'a system note', exclude_project: true, skip_persistence_check: true do let(:action) { action } end diff --git a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb index ad0bbc0aeff..b81bd514d0a 100644 --- a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb +++ b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb @@ -26,9 +26,6 @@ RSpec.shared_examples 'UpdateProjectStatistics' do |with_counter_attribute| expect(FlushCounterIncrementsWorker) .to receive(:perform_in) .with(CounterAttribute::WORKER_DELAY, project.statistics.class.name, project.statistics.id, project_statistics_name) - expect(FlushCounterIncrementsWorker) - .to receive(:perform_in) - .with(CounterAttribute::WORKER_DELAY, project.statistics.class.name, project.statistics.id, :storage_size) yield diff --git a/spec/support/shared_examples/models/wiki_shared_examples.rb b/spec/support/shared_examples/models/wiki_shared_examples.rb index 604c57768fe..5f6a10bd754 100644 --- a/spec/support/shared_examples/models/wiki_shared_examples.rb +++ b/spec/support/shared_examples/models/wiki_shared_examples.rb @@ -286,67 +286,134 @@ RSpec.shared_examples 'wiki model' do end describe '#find_page' do - before do - subject.create_page('index page', 'This is an awesome Gollum Wiki') - end + shared_examples 'wiki model #find_page' do + before do + subject.create_page('index page', 'This is an awesome Gollum Wiki') + end - it 'returns the latest version of the page if it exists' do - page = subject.find_page('index page') + it 'returns the latest version of the page if it exists' do + page = subject.find_page('index page') - expect(page.title).to eq('index page') - end + expect(page.title).to eq('index page') + end - it 'returns nil if the page or version does not exist' do - expect(subject.find_page('non-existent')).to be_nil - expect(subject.find_page('index page', 'non-existent')).to be_nil - end + it 'returns nil if the page or version does not exist' do + expect(subject.find_page('non-existent')).to be_nil + expect(subject.find_page('index page', 'non-existent')).to be_nil + end - it 'can find a page by slug' do - page = subject.find_page('index-page') + it 'can find a page by slug' do + page = subject.find_page('index-page') - expect(page.title).to eq('index page') - end + expect(page.title).to eq('index page') + end - it 'returns a WikiPage instance' do - page = subject.find_page('index page') + it 'returns a WikiPage instance' do + page = subject.find_page('index page') - expect(page).to be_a WikiPage - end + expect(page).to be_a WikiPage + end - context 'pages with multibyte-character title' do - before do - subject.create_page('autre pagé', "C'est un génial Gollum Wiki") + context 'pages with multibyte-character title' do + before do + subject.create_page('autre pagé', "C'est un génial Gollum Wiki") + end + + it 'can find a page by slug' do + page = subject.find_page('autre pagé') + + expect(page.title).to eq('autre pagé') + end end - it 'can find a page by slug' do - page = subject.find_page('autre pagé') + context 'pages with invalidly-encoded content' do + before do + subject.create_page('encoding is fun', "f\xFCr".b) + end + + it 'can find the page' do + page = subject.find_page('encoding is fun') + + expect(page.content).to eq('fr') + end + end + + context 'pages with different file extensions' do + where(:extension, :path, :title) do + [ + [:md, "wiki-markdown.md", "wiki markdown"], + [:markdown, "wiki-markdown-2.md", "wiki markdown 2"], + [:rdoc, "wiki-rdoc.rdoc", "wiki rdoc"], + [:asciidoc, "wiki-asciidoc.asciidoc", "wiki asciidoc"], + [:adoc, "wiki-asciidoc-2.adoc", "wiki asciidoc 2"], + [:org, "wiki-org.org", "wiki org"], + [:textile, "wiki-textile.textile", "wiki textile"], + [:creole, "wiki-creole.creole", "wiki creole"], + [:rest, "wiki-rest.rest", "wiki rest"], + [:rst, "wiki-rest-2.rst", "wiki rest 2"], + [:mediawiki, "wiki-mediawiki.mediawiki", "wiki mediawiki"], + [:wiki, "wiki-mediawiki-2.wiki", "wiki mediawiki 2"], + [:pod, "wiki-pod.pod", "wiki pod"], + [:text, "wiki-text.txt", "wiki text"] + ] + end - expect(page.title).to eq('autre pagé') + with_them do + before do + wiki.repository.create_file( + user, path, "content of wiki file", + branch_name: wiki.default_branch, + message: "created page #{path}", + author_email: user.email, + author_name: user.name + ) + end + + it "can find page with #{params[:extension]} extension" do + page = subject.find_page(title) + + expect(page.content).to eq("content of wiki file") + end + end end end - context 'pages with invalidly-encoded content' do + context 'find page with legacy wiki service' do before do - subject.create_page('encoding is fun', "f\xFCr".b) + stub_feature_flags(wiki_find_page_with_normal_repository_rpcs: false) end - it 'can find the page' do - page = subject.find_page('encoding is fun') + it_behaves_like 'wiki model #find_page' + end - expect(page.content).to eq('fr') - end + context 'find page with normal repository RPCs' do + it_behaves_like 'wiki model #find_page' end end describe '#find_sidebar' do - before do - subject.create_page(described_class::SIDEBAR, 'This is an awesome Sidebar') + shared_examples 'wiki model #find_sidebar' do + before do + subject.create_page(described_class::SIDEBAR, 'This is an awesome Sidebar') + end + + it 'finds the page defined as _sidebar' do + page = subject.find_sidebar + + expect(page.content).to eq('This is an awesome Sidebar') + end end - it 'finds the page defined as _sidebar' do - page = subject.find_sidebar + context 'find sidebar with legacy wiki service' do + before do + stub_feature_flags(wiki_find_page_with_normal_repository_rpcs: false) + end - expect(page.content).to eq('This is an awesome Sidebar') + it_behaves_like 'wiki model #find_sidebar' + end + + context 'find sidebar with normal repository RPCs' do + it_behaves_like 'wiki model #find_sidebar' end end @@ -450,9 +517,7 @@ RSpec.shared_examples 'wiki model' do expect(subject.error_message).to match(/Duplicate page:/) end - end - it_behaves_like 'create_page tests' do it 'returns false if a page exists already in the repository', :aggregate_failures do subject.create_page('test page', 'content') @@ -540,6 +605,16 @@ RSpec.shared_examples 'wiki model' do end end end + + it_behaves_like 'create_page tests' + + context 'create page with legacy find_page wiki service' do + it_behaves_like 'create_page tests' do + before do + stub_feature_flags(wiki_find_page_with_normal_repository_rpcs: false) + end + end + end end describe '#update_page' do @@ -636,6 +711,17 @@ RSpec.shared_examples 'wiki model' do include_context 'extended examples' end + context 'update page with legacy find_page wiki service' do + it_behaves_like 'update_page tests' do + before do + stub_feature_flags(wiki_find_page_with_normal_repository_rpcs: false) + end + + include_context 'common examples' + include_context 'extended examples' + end + end + context 'when format is invalid' do let!(:page) { create(:wiki_page, wiki: subject, title: 'test page') } diff --git a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb index 807295f8442..4afed5139d8 100644 --- a/spec/support/shared_examples/namespaces/traversal_scope_examples.rb +++ b/spec/support/shared_examples/namespaces/traversal_scope_examples.rb @@ -265,14 +265,6 @@ RSpec.shared_examples 'namespace traversal scopes' do describe '.self_and_descendants' do include_examples '.self_and_descendants' - - context 'with traversal_ids_btree feature flag disabled' do - before do - stub_feature_flags(traversal_ids_btree: false) - end - - include_examples '.self_and_descendants' - end end shared_examples '.self_and_descendant_ids' do @@ -308,14 +300,6 @@ RSpec.shared_examples 'namespace traversal scopes' do describe '.self_and_descendant_ids' do include_examples '.self_and_descendant_ids' - - context 'with traversal_ids_btree feature flag disabled' do - before do - stub_feature_flags(traversal_ids_btree: false) - end - - include_examples '.self_and_descendant_ids' - end end shared_examples '.self_and_hierarchy' do diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb index c4083df47e2..cfcc3615e13 100644 --- a/spec/support/shared_examples/policies/project_policy_shared_examples.rb +++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb @@ -107,70 +107,88 @@ RSpec.shared_examples 'deploy token does not get confused with user' do end RSpec.shared_examples 'project policies as guest' do - context 'abilities for public projects' do - let(:project) { public_project } - let(:current_user) { guest } - - it do - expect_allowed(*guest_permissions) - expect_allowed(*public_permissions) - expect_disallowed(*developer_permissions) - expect_disallowed(*maintainer_permissions) - expect_disallowed(*owner_permissions) - end + let(:reporter_public_build_permissions) do + reporter_permissions - [:read_build, :read_pipeline] end - context 'abilities for non-public projects' do - let(:project) { private_project } - let(:current_user) { guest } + context 'as a direct project member' do + context 'abilities for public projects' do + let(:project) { public_project } + let(:current_user) { guest } - let(:reporter_public_build_permissions) do - reporter_permissions - [:read_build, :read_pipeline] + specify do + expect_allowed(*guest_permissions) + expect_allowed(*public_permissions) + expect_disallowed(*developer_permissions) + expect_disallowed(*maintainer_permissions) + expect_disallowed(*owner_permissions) + end end - it do - expect_allowed(*guest_permissions) - expect_disallowed(*reporter_public_build_permissions) - expect_disallowed(*team_member_reporter_permissions) - expect_disallowed(*developer_permissions) - expect_disallowed(*maintainer_permissions) - expect_disallowed(*owner_permissions) - end + context 'abilities for non-public projects' do + let(:project) { private_project } + let(:current_user) { guest } - it_behaves_like 'deploy token does not get confused with user' do - let(:user_id) { guest.id } - end + specify do + expect_allowed(*guest_permissions) + expect_disallowed(*reporter_public_build_permissions) + expect_disallowed(*team_member_reporter_permissions) + expect_disallowed(*developer_permissions) + expect_disallowed(*maintainer_permissions) + expect_disallowed(*owner_permissions) + end - it_behaves_like 'archived project policies' do - let(:regular_abilities) { guest_permissions } - end + it_behaves_like 'deploy token does not get confused with user' do + let(:user_id) { guest.id } + end - context 'public builds enabled' do - it do - expect_allowed(*guest_permissions) - expect_allowed(:read_build, :read_pipeline) + it_behaves_like 'archived project policies' do + let(:regular_abilities) { guest_permissions } end - end - context 'when public builds disabled' do - before do - project.update!(public_builds: false) + context 'public builds enabled' do + specify do + expect_allowed(*guest_permissions) + expect_allowed(:read_build, :read_pipeline) + end end - it do - expect_allowed(*guest_permissions) - expect_disallowed(:read_build, :read_pipeline) + context 'when public builds disabled' do + before do + project.update!(public_builds: false) + end + + specify do + expect_allowed(*guest_permissions) + expect_disallowed(:read_build, :read_pipeline) + end end - end - context 'when builds are disabled' do - before do - project.project_feature.update!(builds_access_level: ProjectFeature::DISABLED) + context 'when builds are disabled' do + before do + project.project_feature.update!(builds_access_level: ProjectFeature::DISABLED) + end + + specify do + expect_disallowed(:read_build) + expect_allowed(:read_pipeline) + end end + end + end - it do - expect_disallowed(:read_build) - expect_allowed(:read_pipeline) + context 'as an inherited member from the group' do + context 'abilities for private projects' do + let(:project) { private_project_in_group } + let(:current_user) { inherited_guest } + + specify do + expect_allowed(*guest_permissions) + expect_disallowed(*reporter_public_build_permissions) + expect_disallowed(*team_member_reporter_permissions) + expect_disallowed(*developer_permissions) + expect_disallowed(*maintainer_permissions) + expect_disallowed(*owner_permissions) end end end @@ -181,7 +199,7 @@ RSpec.shared_examples 'project policies as reporter' do let(:project) { private_project } let(:current_user) { reporter } - it do + specify do expect_allowed(*guest_permissions) expect_allowed(*reporter_permissions) expect_allowed(*team_member_reporter_permissions) @@ -198,6 +216,22 @@ RSpec.shared_examples 'project policies as reporter' do let(:regular_abilities) { reporter_permissions } end end + + context 'as an inherited member from the group' do + context 'abilities for private projects' do + let(:project) { private_project_in_group } + let(:current_user) { inherited_reporter } + + specify do + expect_allowed(*guest_permissions) + expect_allowed(*reporter_permissions) + expect_allowed(*team_member_reporter_permissions) + expect_disallowed(*developer_permissions) + expect_disallowed(*maintainer_permissions) + expect_disallowed(*owner_permissions) + end + end + end end RSpec.shared_examples 'project policies as developer' do @@ -205,7 +239,7 @@ RSpec.shared_examples 'project policies as developer' do let(:project) { private_project } let(:current_user) { developer } - it do + specify do expect_allowed(*guest_permissions) expect_allowed(*reporter_permissions) expect_allowed(*team_member_reporter_permissions) @@ -222,6 +256,22 @@ RSpec.shared_examples 'project policies as developer' do let(:regular_abilities) { developer_permissions } end end + + context 'as an inherited member from the group' do + context 'abilities for private projects' do + let(:project) { private_project_in_group } + let(:current_user) { inherited_developer } + + specify do + expect_allowed(*guest_permissions) + expect_allowed(*reporter_permissions) + expect_allowed(*team_member_reporter_permissions) + expect_allowed(*developer_permissions) + expect_disallowed(*maintainer_permissions) + expect_disallowed(*owner_permissions) + end + end + end end RSpec.shared_examples 'project policies as maintainer' do diff --git a/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb b/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb new file mode 100644 index 00000000000..9c2d30a9c8c --- /dev/null +++ b/spec/support/shared_examples/projects/container_repository/cleanup_tags_service_shared_examples.rb @@ -0,0 +1,263 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'handling invalid params' do |service_response_extra: {}, supports_caching: false| + context 'when no params are specified' do + let(:params) { {} } + + it_behaves_like 'not removing anything', + service_response_extra: service_response_extra, + supports_caching: supports_caching + end + + context 'with invalid regular expressions' do + shared_examples 'handling an invalid regex' do + it 'keeps all tags' do + expect(Projects::ContainerRepository::DeleteTagsService) + .not_to receive(:new) + expect_no_caching unless supports_caching + + subject + end + + it { is_expected.to eq(status: :error, message: 'invalid regex') } + + it 'calls error tracking service' do + expect(Gitlab::ErrorTracking).to receive(:log_exception).and_call_original + + subject + end + end + + context 'when name_regex_delete is invalid' do + let(:params) { { 'name_regex_delete' => '*test*' } } + + it_behaves_like 'handling an invalid regex' + end + + context 'when name_regex is invalid' do + let(:params) { { 'name_regex' => '*test*' } } + + it_behaves_like 'handling an invalid regex' + end + + context 'when name_regex_keep is invalid' do + let(:params) { { 'name_regex_keep' => '*test*' } } + + it_behaves_like 'handling an invalid regex' + end + end +end + +RSpec.shared_examples 'when regex matching everything is specified' do + |service_response_extra: {}, supports_caching: false, delete_expectations:| + let(:params) do + { 'name_regex_delete' => '.*' } + end + + it_behaves_like 'removing the expected tags', + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: delete_expectations + + context 'with deprecated name_regex param' do + let(:params) do + { 'name_regex' => '.*' } + end + + it_behaves_like 'removing the expected tags', + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: delete_expectations + end +end + +RSpec.shared_examples 'when delete regex matching specific tags is used' do + |service_response_extra: {}, supports_caching: false| + let(:params) do + { 'name_regex_delete' => 'C|D' } + end + + it_behaves_like 'removing the expected tags', + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: [%w[C D]] +end + +RSpec.shared_examples 'when delete regex matching specific tags is used with overriding allow regex' do + |service_response_extra: {}, supports_caching: false| + let(:params) do + { + 'name_regex_delete' => 'C|D', + 'name_regex_keep' => 'C' + } + end + + it_behaves_like 'removing the expected tags', + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: [%w[D]] + + context 'with name_regex_delete overriding deprecated name_regex' do + let(:params) do + { + 'name_regex' => 'C|D', + 'name_regex_delete' => 'D' + } + end + + it_behaves_like 'removing the expected tags', + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: [%w[D]] + end +end + +RSpec.shared_examples 'with allow regex value' do + |service_response_extra: {}, supports_caching: false, delete_expectations:| + let(:params) do + { + 'name_regex_delete' => '.*', + 'name_regex_keep' => 'B.*' + } + end + + it_behaves_like 'removing the expected tags', + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: delete_expectations +end + +RSpec.shared_examples 'when keeping only N tags' do + |service_response_extra: {}, supports_caching: false, delete_expectations:| + let(:params) do + { + 'name_regex' => 'A|B.*|C', + 'keep_n' => 1 + } + end + + it 'sorts tags by date' do + delete_expectations.each { |expectation| expect_delete(expectation) } + expect_no_caching unless supports_caching + + expect(service).to receive(:order_by_date_desc).at_least(:once).and_call_original + + is_expected.to eq(expected_service_response(deleted: delete_expectations.flatten).merge(service_response_extra)) + end +end + +RSpec.shared_examples 'when not keeping N tags' do + |service_response_extra: {}, supports_caching: false, delete_expectations:| + let(:params) do + { 'name_regex' => 'A|B.*|C' } + end + + it 'does not sort tags by date' do + delete_expectations.each { |expectation| expect_delete(expectation) } + expect_no_caching unless supports_caching + + expect(service).not_to receive(:order_by_date_desc) + + is_expected.to eq(expected_service_response(deleted: delete_expectations.flatten).merge(service_response_extra)) + end +end + +RSpec.shared_examples 'when removing keeping only 3' do + |service_response_extra: {}, supports_caching: false, delete_expectations:| + let(:params) do + { 'name_regex_delete' => '.*', + 'keep_n' => 3 } + end + + it_behaves_like 'removing the expected tags', + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: delete_expectations +end + +RSpec.shared_examples 'when removing older than 1 day' do + |service_response_extra: {}, supports_caching: false, delete_expectations:| + let(:params) do + { + 'name_regex_delete' => '.*', + 'older_than' => '1 day' + } + end + + it_behaves_like 'removing the expected tags', + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: delete_expectations +end + +RSpec.shared_examples 'when combining all parameters' do + |service_response_extra: {}, supports_caching: false, delete_expectations:| + let(:params) do + { + 'name_regex_delete' => '.*', + 'keep_n' => 1, + 'older_than' => '1 day' + } + end + + it_behaves_like 'removing the expected tags', + service_response_extra: service_response_extra, + supports_caching: supports_caching, + delete_expectations: delete_expectations +end + +RSpec.shared_examples 'when running a container_expiration_policy' do + |service_response_extra: {}, supports_caching: false, delete_expectations:| + let(:user) { nil } + + context 'with valid container_expiration_policy param' do + let(:params) do + { + 'name_regex_delete' => '.*', + 'keep_n' => 1, + 'older_than' => '1 day', + 'container_expiration_policy' => true + } + end + + it 'removes the expected tags' do + delete_expectations.each { |expectation| expect_delete(expectation, container_expiration_policy: true) } + expect_no_caching unless supports_caching + + is_expected.to eq(expected_service_response(deleted: delete_expectations.flatten).merge(service_response_extra)) + end + end + + context 'without container_expiration_policy param' do + let(:params) do + { + 'name_regex_delete' => '.*', + 'keep_n' => 1, + 'older_than' => '1 day' + } + end + + it 'fails' do + is_expected.to eq(status: :error, message: 'access denied') + end + end +end + +RSpec.shared_examples 'not removing anything' do |service_response_extra: {}, supports_caching: false| + it 'does not remove anything' do + expect(Projects::ContainerRepository::DeleteTagsService).not_to receive(:new) + expect_no_caching unless supports_caching + + is_expected.to eq(expected_service_response(deleted: []).merge(service_response_extra)) + end +end + +RSpec.shared_examples 'removing the expected tags' do + |service_response_extra: {}, supports_caching: false, delete_expectations:| + it 'removes the expected tags' do + delete_expectations.each { |expectation| expect_delete(expectation) } + expect_no_caching unless supports_caching + + is_expected.to eq(expected_service_response(deleted: delete_expectations.flatten).merge(service_response_extra)) + end +end diff --git a/spec/support/shared_examples/quick_actions/incident/timeline_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/incident/timeline_quick_action_shared_examples.rb new file mode 100644 index 00000000000..ae7e511a739 --- /dev/null +++ b/spec/support/shared_examples/quick_actions/incident/timeline_quick_action_shared_examples.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'timeline quick action' do + describe '/timeline' do + context 'with valid args' do + where(:timeline_text, :date_time_arg) do + [ + ['timeline comment', '2022-09-09 09:30'], + ['new timeline comment', '09:30'], + ['another timeline comment', ' 2022-09-09 09:15'] + ] + end + + with_them do + it 'adds a timeline event' do + add_note("/timeline #{timeline_text} | #{date_time_arg}") + + expect(page).to have_content('Timeline event added successfully.') + expect(issue.incident_management_timeline_events.first.note).to eq(timeline_text) + expect(issue.incident_management_timeline_events.first.occurred_at).to eq(DateTime.parse(date_time_arg)) + end + end + + it 'adds a timeline event when no date is passed' do + freeze_time do + add_note('/timeline timeline event with not date') + + expect(page).to have_content('Timeline event added successfully.') + expect(issue.incident_management_timeline_events.first.note).to eq('timeline event with not date') + expect(issue.incident_management_timeline_events.first.occurred_at).to eq(DateTime + .current.strftime("%Y-%m-%d %H:%M:00 UTC")) + end + end + + it 'adds a timeline event when only date is passed' do + freeze_time do + add_note('/timeline timeline event with not date | 2022-10-11') + + expect(page).to have_content('Timeline event added successfully.') + expect(issue.incident_management_timeline_events.first.note).to eq('timeline event with not date') + expect(issue.incident_management_timeline_events.first.occurred_at).to eq(DateTime + .current.strftime("%Y-%m-%d %H:%M:00 UTC")) + end + end + end + + context 'with invalid args' do + where(:timeline_text, :date_time_arg) do + [ + ['timeline comment', '2022-13-13 09:30'], + ['timeline comment 2', '2022-09-06 24:30'] + ] + end + + with_them do + it 'does not add a timeline event' do + add_note("/timeline #{timeline_text} | #{date_time_arg}") + + expect(page).to have_content('Failed to apply commands.') + expect(issue.incident_management_timeline_events.length).to eq(0) + end + end + end + + context 'when create service fails' do + before do + allow_next_instance_of(::IncidentManagement::TimelineEvents::CreateService) do |service| + allow(service).to receive(:execute).and_return( + ServiceResponse.error(payload: { timeline_event: nil }, message: 'Some error') + ) + end + end + + it 'does not add a timeline event' do + add_note('/timeline text | 2022-09-10 09:30') + + expect(page).to have_content('Something went wrong while adding timeline event.') + expect(issue.incident_management_timeline_events.length).to eq(0) + end + end + end +end diff --git a/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb index f414500f202..18304951e41 100644 --- a/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issuable/time_tracking_quick_action_shared_examples.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true RSpec.shared_examples 'issuable time tracker' do |issuable_type| + let_it_be(:time_tracker_selector) { '[data-testid="time-tracker"]' } + before do project.add_maintainer(maintainer) gitlab_sign_in(maintainer) @@ -12,6 +14,14 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| wait_for_requests end + def open_time_tracking_report + page.within time_tracker_selector do + click_link 'Time tracking report' + + wait_for_requests + end + end + it 'renders the sidebar component empty state' do page.within '[data-testid="noTrackingPane"]' do expect(page).to have_content 'No estimate or time spent' @@ -50,7 +60,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| submit_time('/estimate 3w 1d 1h') submit_time('/remove_estimate') - page.within '.time-tracking-component-wrap' do + page.within time_tracker_selector do expect(page).to have_content 'No estimate or time spent' end end @@ -59,13 +69,13 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| submit_time('/spend 3w 1d 1h') submit_time('/remove_time_spent') - page.within '.time-tracking-component-wrap' do + page.within time_tracker_selector do expect(page).to have_content 'No estimate or time spent' end end it 'shows the help state when icon is clicked' do - page.within '.time-tracking-component-wrap' do + page.within time_tracker_selector do find('[data-testid="helpButton"]').click expect(page).to have_content 'Track time with quick actions' expect(page).to have_content 'Learn more' @@ -78,11 +88,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| wait_for_requests - page.within '.time-tracking-component-wrap' do - click_link 'Time tracking report' - - wait_for_requests - end + open_time_tracking_report page.within '#time-tracking-report' do expect(find('tbody')).to have_content maintainer.name @@ -90,8 +96,36 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| end end + it 'removes time log when delete is clicked in time tracking report' do + submit_time('/estimate 1w') + submit_time('/spend 1d') + submit_time('/spend 3d') + + wait_for_requests + + open_time_tracking_report + + page.within '#time-tracking-report tbody tr:nth-child(2)' do + click_button test_id: 'deleteButton' + + wait_for_requests + end + + # Assert that 2nd row was removed + expect(all('#time-tracking-report tbody tr').length).to eq(1) + expect(find('#time-tracking-report tbody')).not_to have_content('3d') + + # Assert that summary line was updated + expect(find('#time-tracking-report tfoot')).to have_content('1d', exact: true) + + # Assert that the time tracking widget was reactively updated + page.within '[data-testid="timeTrackingComparisonPane"]' do + expect(page).to have_content '1d' + end + end + it 'hides the help state when close icon is clicked' do - page.within '.time-tracking-component-wrap' do + page.within time_tracker_selector do find('[data-testid="helpButton"]').click find('[data-testid="closeHelpButton"]').click @@ -101,7 +135,7 @@ RSpec.shared_examples 'issuable time tracker' do |issuable_type| end it 'displays the correct help url' do - page.within '.time-tracking-component-wrap' do + page.within time_tracker_selector do find('[data-testid="helpButton"]').click expect(find_link('Learn more')[:href]).to have_content('/help/user/project/time_tracking.md') diff --git a/spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb index 2258bdd2c79..92705fc1b4d 100644 --- a/spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/merge_request/rebase_quick_action_shared_examples.rb @@ -75,7 +75,16 @@ RSpec.shared_examples 'rebase quick action' do end context 'when the merge request branch is protected from force push' do - let!(:protected_branch) { create(:protected_branch, project: project, name: merge_request.source_branch, allow_force_push: false) } + let!(:protected_branch) do + ProtectedBranches::CreateService.new( + project, + user, + name: merge_request.source_branch, + allow_force_push: false, + push_access_levels_attributes: [{ access_level: Gitlab::Access::DEVELOPER }], + merge_access_levels_attributes: [{ access_level: Gitlab::Access::DEVELOPER }] + ).execute + end it 'does not rebase the MR' do add_note("/rebase") diff --git a/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb b/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb index 6cd871d354c..017e6274cb0 100644 --- a/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb +++ b/spec/support/shared_examples/requests/access_tokens_controller_shared_examples.rb @@ -108,18 +108,31 @@ RSpec.shared_examples 'PUT resource access tokens available' do expect(resource.reload.bots).not_to include(bot_user) end - it 'converts issuables of the bot user to ghost user' do - issue = create(:issue, author: bot_user) + context 'when user_destroy_with_limited_execution_time_worker is enabled' do + it 'creates GhostUserMigration records to handle migration in a worker' do + expect { subject }.to( + change { Users::GhostUserMigration.count }.from(0).to(1)) + end + end - subject + context 'when user_destroy_with_limited_execution_time_worker is disabled' do + before do + stub_feature_flags(user_destroy_with_limited_execution_time_worker: false) + end - expect(issue.reload.author.ghost?).to be true - end + it 'converts issuables of the bot user to ghost user' do + issue = create(:issue, author: bot_user) - it 'deletes project bot user' do - subject + subject + + expect(issue.reload.author.ghost?).to be true + end - expect(User.exists?(bot_user.id)).to be_falsy + it 'deletes project bot user' do + subject + + expect(User.exists?(bot_user.id)).to be_falsy + end end context 'when unsuccessful' 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 dc2c4f890b1..6a77de4266f 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 @@ -108,6 +108,7 @@ RSpec.shared_examples 'process Composer api request' do |user_type, status, add_ end it_behaves_like 'returning response status', status + it_behaves_like 'bumping the package last downloaded at field' if status == :success end end diff --git a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb index bb2f8965294..629d93676eb 100644 --- a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb @@ -355,7 +355,7 @@ RSpec.shared_examples 'recipe download_urls' do it 'returns the download_urls for the recipe files' do expected_response = { - 'conanfile.py' => "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanfile.py", + 'conanfile.py' => "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanfile.py", 'conanmanifest.txt' => "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanmanifest.txt" } @@ -372,7 +372,7 @@ RSpec.shared_examples 'package download_urls' do it 'returns the download_urls for the package files' do expected_response = { - 'conaninfo.txt' => "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conaninfo.txt", + 'conaninfo.txt' => "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conaninfo.txt", 'conanmanifest.txt' => "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conanmanifest.txt", 'conan_package.tgz' => "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conan_package.tgz" } @@ -412,7 +412,7 @@ RSpec.shared_examples 'recipe snapshot endpoint' do conan_manifest_file = package.package_files.find_by(file_name: 'conanmanifest.txt') expected_response = { - 'conanfile.py' => conan_file_file.file_md5, + 'conanfile.py' => conan_file_file.file_md5, 'conanmanifest.txt' => conan_manifest_file.file_md5 } @@ -435,7 +435,7 @@ RSpec.shared_examples 'package snapshot endpoint' do context 'with existing package' do it 'returns a hash of md5 values for the files' do expected_response = { - 'conaninfo.txt' => "12345abcde", + 'conaninfo.txt' => "12345abcde", 'conanmanifest.txt' => "12345abcde", 'conan_package.tgz' => "12345abcde" } @@ -486,7 +486,7 @@ RSpec.shared_examples 'recipe upload_urls endpoint' do subject expected_response = { - 'conanfile.py': "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanfile.py", + 'conanfile.py': "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanfile.py", 'conanmanifest.txt': "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanmanifest.txt" } @@ -505,7 +505,7 @@ RSpec.shared_examples 'recipe upload_urls endpoint' do expected_response = { 'conan_sources.tgz': "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conan_sources.tgz", - 'conan_export.tgz': "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conan_export.tgz", + 'conan_export.tgz': "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conan_export.tgz", 'conanmanifest.txt': "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/export/conanmanifest.txt" } @@ -547,7 +547,7 @@ RSpec.shared_examples 'package upload_urls endpoint' do it 'returns a set of upload urls for the files requested' do expected_response = { - 'conaninfo.txt': "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conaninfo.txt", + 'conaninfo.txt': "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conaninfo.txt", 'conanmanifest.txt': "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conanmanifest.txt", 'conan_package.tgz': "#{url_prefix}/packages/conan/v1/files/#{package.conan_recipe_path}/0/package/123456789/0/conan_package.tgz" } @@ -631,6 +631,7 @@ RSpec.shared_examples 'a public project with packages' do end it_behaves_like 'allows download with no token' + it_behaves_like 'bumping the package last downloaded at field' it 'returns the file' do subject @@ -647,6 +648,7 @@ RSpec.shared_examples 'an internal project with packages' do end it_behaves_like 'denies download with no token' + it_behaves_like 'bumping the package last downloaded at field' it 'returns the file' do subject @@ -662,6 +664,7 @@ RSpec.shared_examples 'a private project with packages' do end it_behaves_like 'denies download with no token' + it_behaves_like 'bumping the package last downloaded at field' it 'returns the file' do subject diff --git a/spec/support/shared_examples/requests/api/graphql/issuable_search_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/issuable_search_shared_examples.rb new file mode 100644 index 00000000000..22805cf7aed --- /dev/null +++ b/spec/support/shared_examples/requests/api/graphql/issuable_search_shared_examples.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Requires `query(params)` , `user`, `issuable_data` and `issuable` bindings +RSpec.shared_examples 'query with a search term' do + it 'returns only matching issuables' do + filter_params = { search: 'bar', in: [:DESCRIPTION] } + graphql_query = query(filter_params) + + post_graphql(graphql_query, current_user: user) + ids = graphql_dig_at(issuable_data, :node, :id) + + expect(ids).to contain_exactly(issuable.to_global_id.to_s) + end +end 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 9f7ec6e90e9..1b609915f32 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 @@ -9,9 +9,10 @@ RSpec.shared_examples 'group and project packages query' do let_it_be(:composer_package) { create(:composer_package, project: project2, name: 'dab', version: '4.0.0', created_at: 3.days.ago) } let_it_be(:debian_package) { create(:debian_package, project: project2, name: 'aab', version: '5.0.0', created_at: 2.days.ago) } let_it_be(:composer_metadatum) do - create(:composer_metadatum, package: composer_package, - target_sha: 'afdeh', - composer_json: { name: 'x', type: 'y', license: 'z', version: 1 }) + create(:composer_metadatum, + package: composer_package, + target_sha: 'afdeh', + composer_json: { name: 'x', type: 'y', license: 'z', version: 1 }) end let(:package_names) { graphql_data_at(resource_type, :packages, :nodes, :name) } diff --git a/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb index acbcf4f7f3d..06ed0448b50 100644 --- a/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/helm_packages_shared_examples.rb @@ -191,14 +191,15 @@ RSpec.shared_examples 'process helm download content request' do |user_type, sta end end - it_behaves_like 'a package tracking event', 'API::HelmPackages', 'pull_package' - it 'returns expected status and a valid package archive' do subject expect(response).to have_gitlab_http_status(status) expect(response.media_type).to eq('application/octet-stream') end + + it_behaves_like 'a package tracking event', 'API::HelmPackages', 'pull_package' + it_behaves_like 'bumping the package last downloaded at field' end end @@ -278,7 +279,6 @@ RSpec.shared_examples 'handling helm chart index requests' do end it_behaves_like 'deploy token for package GET requests' - it_behaves_like 'rejects helm access with unknown project id' do subject { get api(url) } end diff --git a/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb b/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb index 971b21b5b32..8c4ff120471 100644 --- a/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true def get_issue - json_response.is_a?(Array) ? json_response.detect {|issue| issue['id'] == target_issue.id} : json_response + json_response.is_a?(Array) ? json_response.detect { |issue| issue['id'] == target_issue.id } : json_response end RSpec.shared_examples 'accessible merge requests count' do diff --git a/spec/support/shared_examples/requests/api/labels_api_shared_examples.rb b/spec/support/shared_examples/requests/api/labels_api_shared_examples.rb index 02e50b789cc..41d21490343 100644 --- a/spec/support/shared_examples/requests/api/labels_api_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/labels_api_shared_examples.rb @@ -9,6 +9,6 @@ RSpec.shared_examples 'fetches labels' do expect(json_response).to be_an Array expect(json_response).to all(match_schema('public_api/v4/labels/label')) expect(json_response.size).to eq(expected_labels.size) - expect(json_response.map {|r| r['name'] }).to match_array(expected_labels) + expect(json_response.map { |r| r['name'] }).to match_array(expected_labels) end end diff --git a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb index 6568d51b90e..fdd55893deb 100644 --- a/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/nuget_packages_shared_examples.rb @@ -293,6 +293,8 @@ RSpec.shared_examples 'process nuget download content request' do |user_type, st it_behaves_like 'a package tracking event', 'API::NugetPackages', 'pull_package' + it_behaves_like 'bumping the package last downloaded at field' + it 'returns a valid package archive' do subject @@ -315,6 +317,8 @@ RSpec.shared_examples 'process nuget download content request' do |user_type, st end it_behaves_like 'a package tracking event', 'API::NugetPackages', 'pull_symbol_package' + + it_behaves_like 'bumping the package last downloaded at field' end context 'with lower case package name' do diff --git a/spec/support/shared_examples/requests/api/packages_shared_examples.rb b/spec/support/shared_examples/requests/api/packages_shared_examples.rb index eb650b7a09f..860cb1b1d86 100644 --- a/spec/support/shared_examples/requests/api/packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/packages_shared_examples.rb @@ -165,3 +165,10 @@ RSpec.shared_examples 'not a package tracking event' do expect_no_snowplow_event end end + +RSpec.shared_examples 'bumping the package last downloaded at field' do + it 'bumps last_downloaded_at' do + expect { subject } + .to change { package.reload.last_downloaded_at }.from(nil).to(instance_of(ActiveSupport::TimeWithZone)) + end +end diff --git a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb index ba8311bf0be..f411b5699a9 100644 --- a/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/pypi_packages_shared_examples.rb @@ -167,6 +167,7 @@ RSpec.shared_examples 'PyPI package download' do |user_type, status, add_member it_behaves_like 'returning response status', status it_behaves_like 'a package tracking event', described_class.name, 'pull_package' + it_behaves_like 'bumping the package last downloaded at field' end end diff --git a/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb b/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb index 3ca2b9fa6de..2d036cb2aa3 100644 --- a/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/repository_storage_moves_shared_examples.rb @@ -70,7 +70,7 @@ RSpec.shared_examples 'repository_storage_moves API' do |container_type| get_container_repository_storage_moves - json_ids = json_response.map {|storage_move| storage_move['id'] } + json_ids = json_response.map { |storage_move| storage_move['id'] } expect(json_ids).to eq([ storage_move.id, storage_move_middle.id, diff --git a/spec/support/shared_examples/requests/api/resource_state_events_api_shared_examples.rb b/spec/support/shared_examples/requests/api/resource_state_events_api_shared_examples.rb new file mode 100644 index 00000000000..c1850a0d0c9 --- /dev/null +++ b/spec/support/shared_examples/requests/api/resource_state_events_api_shared_examples.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'resource_state_events API' do |parent_type, eventable_type, id_name| + let(:base_path) { "/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}" } + + describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_state_events" do + let!(:event) { create_event } + + it "returns an array of resource state events" do + url = "#{base_path}/resource_state_events" + get api(url, user) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.first['id']).to eq(event.id) + expect(json_response.first['state']).to eq(event.state.to_s) + end + + it "returns a 404 error when eventable id not found" do + get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{non_existing_record_id}/resource_state_events", user) + + expect(response).to have_gitlab_http_status(:not_found) + end + + it "returns 404 when not authorized" do + parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + private_user = create(:user) + + get api("#{base_path}/resource_state_events", private_user) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_state_events/:event_id" do + let!(:event) { create_event } + + it "returns a resource state event by id" do + get api("#{base_path}/resource_state_events/#{event.id}", user) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['id']).to eq(event.id) + expect(json_response['state']).to eq(event.state.to_s) + end + + it "returns 404 when not authorized" do + parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + private_user = create(:user) + + get api("#{base_path}/resource_state_events/#{event.id}", private_user) + + expect(response).to have_gitlab_http_status(:not_found) + end + + it "returns a 404 error if resource state event not found" do + get api("#{base_path}/resource_state_events/#{non_existing_record_id}", user) + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + describe 'pagination' do + # https://gitlab.com/gitlab-org/gitlab/-/issues/220192 + it 'returns the second page' do + create_event + event2 = create_event + + get api("#{base_path}/resource_state_events?page=2&per_page=1", user) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(response.headers['X-Total']).to eq '2' + expect(json_response.count).to eq(1) + expect(json_response.first['id']).to eq(event2.id) + end + end + + def create_event(state: :opened) + create(:resource_state_event, eventable.class.name.underscore => eventable, state: state) + end +end diff --git a/spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb index abdb468353a..f075927e7bf 100644 --- a/spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/rubygems_packages_shared_examples.rb @@ -203,5 +203,6 @@ RSpec.shared_examples 'Rubygems gem download' do |user_type, status, add_member end it_behaves_like 'a package tracking event', described_class.name, 'pull_package' + it_behaves_like 'bumping the package last downloaded at field' end end diff --git a/spec/support/shared_examples/requests/api/snippets_shared_examples.rb b/spec/support/shared_examples/requests/api/snippets_shared_examples.rb index 2b72c69cb37..1b92eb56f54 100644 --- a/spec/support/shared_examples/requests/api/snippets_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/snippets_shared_examples.rb @@ -133,7 +133,7 @@ RSpec.shared_examples 'snippet file updates' do context 'when save fails due to a repository commit error' do before do allow_next_instance_of(Repository) do |instance| - allow(instance).to receive(:multi_action).and_raise(Gitlab::Git::CommitError) + allow(instance).to receive(:commit_files).and_raise(Gitlab::Git::CommitError) end update_snippet(params: { files: [create_action] }) diff --git a/spec/support/shared_examples/requests/applications_controller_shared_examples.rb b/spec/support/shared_examples/requests/applications_controller_shared_examples.rb index 8f852d42c2c..642930dd982 100644 --- a/spec/support/shared_examples/requests/applications_controller_shared_examples.rb +++ b/spec/support/shared_examples/requests/applications_controller_shared_examples.rb @@ -11,6 +11,7 @@ RSpec.shared_examples 'applications controller - GET #show' do context 'when application is viewed after being created' do before do create_application + stub_feature_flags(hash_oauth_secrets: false) end it 'sets `@created` instance variable to `true`' do @@ -21,6 +22,10 @@ RSpec.shared_examples 'applications controller - GET #show' do end context 'when application is reviewed' do + before do + stub_feature_flags(hash_oauth_secrets: false) + end + it 'sets `@created` instance variable to `false`' do get show_path @@ -32,6 +37,7 @@ end RSpec.shared_examples 'applications controller - POST #create' do it "sets `#{OauthApplications::CREATED_SESSION_KEY}` session key to `true`" do + stub_feature_flags(hash_oauth_secrets: false) create_application expect(session[OauthApplications::CREATED_SESSION_KEY]).to eq(true) diff --git a/spec/support/shared_examples/requests/lfs_http_shared_examples.rb b/spec/support/shared_examples/requests/lfs_http_shared_examples.rb index 294ceffd77b..83be0cc1fe3 100644 --- a/spec/support/shared_examples/requests/lfs_http_shared_examples.rb +++ b/spec/support/shared_examples/requests/lfs_http_shared_examples.rb @@ -49,7 +49,7 @@ RSpec.shared_examples 'LFS http 404 response' do end RSpec.shared_examples 'LFS http expected response code and message' do - let(:response_code) { } + let(:response_code) {} let(:response_headers) { {} } let(:content_type) { LfsRequest::CONTENT_TYPE } let(:message) {} diff --git a/spec/support/shared_examples/requests/projects/google_cloud/google_cloud_ff_examples.rb b/spec/support/shared_examples/requests/projects/google_cloud/google_cloud_ff_examples.rb new file mode 100644 index 00000000000..d49fe517c60 --- /dev/null +++ b/spec/support/shared_examples/requests/projects/google_cloud/google_cloud_ff_examples.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'requires feature flag `incubation_5mp_google_cloud` enabled' do + context 'when feature flag is disabled' do + before do + project.add_maintainer(user) + stub_feature_flags(incubation_5mp_google_cloud: false) + end + + it 'renders not found' do + sign_in(user) + + subject + + expect(response).to have_gitlab_http_status(:not_found) + end + end +end diff --git a/spec/support/shared_examples/requests/projects/google_cloud/google_cloud_role_examples.rb b/spec/support/shared_examples/requests/projects/google_cloud/google_cloud_role_examples.rb new file mode 100644 index 00000000000..4c616b59be0 --- /dev/null +++ b/spec/support/shared_examples/requests/projects/google_cloud/google_cloud_role_examples.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'requires `admin_project_google_cloud` role' do + shared_examples 'returns not_found' do + it 'returns not found' do + sign_in(user) + + subject + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + shared_examples 'redirects to authorize url' do + it 'redirects to authorize url' do + sign_in(user) + + subject + + expect(response).to redirect_to(assigns(:authorize_url)) + end + end + + context 'when requested by users with different roles' do + let_it_be(:guest) { create(:user) } + let_it_be(:developer) { create(:user) } + let_it_be(:maintainer) { create(:user) } + + before do + project.add_guest(guest) + project.add_developer(developer) + project.add_maintainer(maintainer) + end + + context 'for unauthorized users' do + include_examples 'returns not_found' do + let(:user) { guest } + end + + include_examples 'returns not_found' do + let(:user) { developer } + end + end + + context 'for authorized users' do + include_examples 'redirects to authorize url' do + let(:user) { maintainer } + end + + include_examples 'redirects to authorize url' do + let(:user) { project.owner } + end + end + end +end diff --git a/spec/support/shared_examples/requests/projects/google_cloud/google_oauth2_config_examples.rb b/spec/support/shared_examples/requests/projects/google_cloud/google_oauth2_config_examples.rb new file mode 100644 index 00000000000..63f6cffb3a0 --- /dev/null +++ b/spec/support/shared_examples/requests/projects/google_cloud/google_oauth2_config_examples.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'requires valid Google OAuth2 configuration' do + context 'when GitLab instance does not have valid Google OAuth2 configuration ' do + before do + project.add_maintainer(user) + unconfigured_google_oauth2 = Struct.new(:app_id, :app_secret) + .new('', '') + allow(Gitlab::Auth::OAuth::Provider).to receive(:config_for) + .with('google_oauth2') + .and_return(unconfigured_google_oauth2) + end + + it 'renders forbidden' do + sign_in(user) + + subject + + expect(response).to have_gitlab_http_status(:forbidden) + end + end +end diff --git a/spec/support/shared_examples/requests/projects/google_cloud/google_oauth2_token_examples.rb b/spec/support/shared_examples/requests/projects/google_cloud/google_oauth2_token_examples.rb new file mode 100644 index 00000000000..379327be0db --- /dev/null +++ b/spec/support/shared_examples/requests/projects/google_cloud/google_oauth2_token_examples.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'requires valid Google Oauth2 token' do + context 'when a valid Google OAuth2 token does not exist' do + before do + project.add_maintainer(user) + sign_in(user) + end + + it 'triggers Google OAuth2 flow on request' do + subject + + expect(response).to redirect_to(assigns(:authorize_url)) + end + + context 'and a valid Google OAuth2 token gets created' do + before do + allow_next_instance_of(GoogleApi::CloudPlatform::Client) do |client| + allow(client).to receive(:validate_token).and_return(true) + allow(client).to receive(:list_projects).and_return(mock_gcp_projects) if mock_gcp_projects + end + + allow_next_instance_of(BranchesFinder) do |finder| + allow(finder).to receive(:execute).and_return(mock_branches) if mock_branches + end + + allow_next_instance_of(TagsFinder) do |finder| + allow(finder).to receive(:execute).and_return(mock_branches) if mock_branches + end + end + + it 'renders template as expected' do + if renders_template + subject + expect(response).to render_template(renders_template) + end + end + + it 'redirects as expected' do + if redirects_to + subject + expect(response).to redirect_to(redirects_to) + end + end + end + end +end 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 d4417b23a5f..11759b6671f 100644 --- a/spec/support/shared_examples/requests/rack_attack_shared_examples.rb +++ b/spec/support/shared_examples/requests/rack_attack_shared_examples.rb @@ -518,7 +518,7 @@ RSpec.shared_examples 'rate-limited unauthenticated requests' do context 'when the request is to the api internal endpoints' do it 'allows requests over the rate limit' do (1 + requests_per_period).times do - get '/api/v4/internal/check', params: { secret_token: Gitlab::Shell.secret_token } + get '/api/v4/internal/check', headers: GitlabShellHelpers.gitlab_shell_internal_api_request_header expect(response).to have_gitlab_http_status(:ok) end end diff --git a/spec/support/shared_examples/routing/resource_routing_shared_examples.rb b/spec/support/shared_examples/routing/resource_routing_shared_examples.rb index b98901a57ea..d7a674f3522 100644 --- a/spec/support/shared_examples/routing/resource_routing_shared_examples.rb +++ b/spec/support/shared_examples/routing/resource_routing_shared_examples.rb @@ -43,12 +43,12 @@ RSpec.shared_examples 'resource routing' do let(:default_actions) do { - index: [:get, ''], - show: [:get, '/:id'], - new: [:get, '/new'], - create: [:post, ''], - edit: [:get, '/:id/edit'], - update: [:put, '/:id'], + index: [:get, ''], + show: [:get, '/:id'], + new: [:get, '/new'], + create: [:post, ''], + edit: [:get, '/:id/edit'], + update: [:put, '/:id'], destroy: [:delete, '/:id'] } end diff --git a/spec/support/shared_examples/routing/wiki_routing_shared_examples.rb b/spec/support/shared_examples/routing/wiki_routing_shared_examples.rb index 9289934677e..64f237f0d4d 100644 --- a/spec/support/shared_examples/routing/wiki_routing_shared_examples.rb +++ b/spec/support/shared_examples/routing/wiki_routing_shared_examples.rb @@ -6,9 +6,9 @@ RSpec.shared_examples 'wiki routing' do let(:actions) { %i[show new create edit update destroy] } let(:additional_actions) do { - pages: [:get, '/pages'], - history: [:get, '/:id/history'], - git_access: [:get, '/git_access'], + pages: [:get, '/pages'], + history: [:get, '/:id/history'], + git_access: [:get, '/git_access'], preview_markdown: [:post, '/:id/preview_markdown'] } end diff --git a/spec/support/shared_examples/security_training_providers_importer.rb b/spec/support/shared_examples/security_training_providers_importer.rb new file mode 100644 index 00000000000..568e3e1a4f2 --- /dev/null +++ b/spec/support/shared_examples/security_training_providers_importer.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'security training providers importer' do + let(:security_training_providers) do + Class.new(ApplicationRecord) do + self.table_name = 'security_training_providers' + end + end + + it 'upserts security training providers' do + expect { 2.times { subject } }.to change(security_training_providers, :count).from(0).to(2) + expect(security_training_providers.all.map(&:name)).to match_array(['Kontra', 'Secure Code Warrior']) + end +end diff --git a/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb b/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb index 6d59943d91c..9eaad541df7 100644 --- a/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb +++ b/spec/support/shared_examples/serializers/environment_serializer_shared_examples.rb @@ -9,7 +9,8 @@ RSpec.shared_examples 'avoid N+1 on environments serialization' do create_environment_with_associations(project) # See issue: https://gitlab.com/gitlab-org/gitlab/-/issues/363317 - relax_count = 1 + # See also: https://gitlab.com/gitlab-org/gitlab/-/issues/373151 + relax_count = 4 expect { serialize(grouping: true) }.not_to exceed_query_limit(control.count + relax_count) end @@ -23,7 +24,8 @@ RSpec.shared_examples 'avoid N+1 on environments serialization' do create_environment_with_associations(project) # See issue: https://gitlab.com/gitlab-org/gitlab/-/issues/363317 - relax_count = 1 + # See also: https://gitlab.com/gitlab-org/gitlab/-/issues/373151 + relax_count = 5 expect { serialize(grouping: false) }.not_to exceed_query_limit(control.count + relax_count) end diff --git a/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb b/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb index 6cae7d8e00f..0db9519f760 100644 --- a/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb +++ b/spec/support/shared_examples/services/alert_management/alert_processing/alert_firing_shared_examples.rb @@ -23,12 +23,10 @@ RSpec.shared_examples 'creates an alert management alert or errors' do end context 'and fails to save' do - let(:errors) { double(messages: { hosts: ['hosts array is over 255 chars'] }, '[]': [] )} - before do - allow(service).to receive(:alert).and_call_original - allow(service).to receive_message_chain(:alert, :save).and_return(false) - allow(service).to receive_message_chain(:alert, :errors).and_return(errors) + allow(AlertManagement::Alert).to receive(:new).and_wrap_original do |m, **args| + m.call(**args, hosts: ['a' * 256]) # hosts should be 255 + end end it_behaves_like 'alerts service responds with an error', :bad_request diff --git a/spec/support/shared_examples/services/boards/issues_move_service_shared_examples.rb b/spec/support/shared_examples/services/boards/issues_move_service_shared_examples.rb index a46c2f0ac5c..162be24fe8f 100644 --- a/spec/support/shared_examples/services/boards/issues_move_service_shared_examples.rb +++ b/spec/support/shared_examples/services/boards/issues_move_service_shared_examples.rb @@ -3,8 +3,8 @@ RSpec.shared_examples 'issues move service' do |group| shared_examples 'updating timestamps' do it 'updates updated_at' do - expect {described_class.new(parent, user, params).execute(issue)} - .to change {issue.reload.updated_at} + expect { described_class.new(parent, user, params).execute(issue) } + .to change { issue.reload.updated_at } end end @@ -140,6 +140,40 @@ RSpec.shared_examples 'issues move service' do |group| expect(issue2.reload.updated_at.change(usec: 0)).to eq updated_at2.change(usec: 0) end + context 'when moving to a specific list position' do + before do + [issue1, issue2, issue].each do |issue| + issue.move_to_end && issue.save! + end + end + + it 'moves issue to the top of the list' do + described_class.new(parent, user, params.merge({ position_in_list: 0 })).execute(issue) + + expect(issue.relative_position).to be < issue1.relative_position + end + + it 'moves issue to a position in the middle of the list' do + described_class.new(parent, user, params.merge({ position_in_list: 1 })).execute(issue) + + expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position) + end + + it 'moves issue to the bottom of the list' do + described_class.new(parent, user, params.merge({ position_in_list: -1 })).execute(issue1) + + expect(issue1.relative_position).to be > issue.relative_position + end + + context 'when given position is greater than number of issues in the list' do + it 'moves the issue to the bottom of the list' do + described_class.new(parent, user, params.merge({ position_in_list: 5 })).execute(issue1) + + expect(issue1.relative_position).to be > issue.relative_position + end + end + end + def reorder_issues(params, issues: []) issues.each do |issue| issue.move_to_end && issue.save! diff --git a/spec/support/shared_examples/services/common_system_notes_shared_examples.rb b/spec/support/shared_examples/services/common_system_notes_shared_examples.rb index ce412ef55de..1887b38b50e 100644 --- a/spec/support/shared_examples/services/common_system_notes_shared_examples.rb +++ b/spec/support/shared_examples/services/common_system_notes_shared_examples.rb @@ -42,6 +42,7 @@ RSpec.shared_examples 'a system note' do |params| it 'has the correct attributes', :aggregate_failures do exclude_project = !params.nil? && params[:exclude_project] + skip_persistence_check = !params.nil? && params[:skip_persistence_check] expect(subject).to be_valid expect(subject).to be_system @@ -50,6 +51,7 @@ RSpec.shared_examples 'a system note' do |params| expect(subject.project).to eq project unless exclude_project expect(subject.author).to eq author + expect(subject.system_note_metadata).to be_persisted unless skip_persistence_check expect(subject.system_note_metadata.action).to eq(action) expect(subject.system_note_metadata.commit_count).to eq(commit_count) 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 3be59af6a37..58659775d8c 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 @@ -54,6 +54,12 @@ RSpec.shared_examples 'a valid token' do end end +RSpec.shared_examples 'with auth_type' do + let(:current_params) { super().merge(auth_type: :foo) } + + it { expect(payload['auth_type']).to eq('foo') } +end + RSpec.shared_examples 'a browsable' do let(:access) do [{ 'type' => 'registry', @@ -199,8 +205,8 @@ RSpec.shared_examples 'a container registry auth service' do describe '.import_access_token' do let(:access) do [{ 'type' => 'registry', - 'name' => 'import', - 'actions' => ['*'] }] + 'name' => 'import', + 'actions' => ['*'] }] end let(:token) { described_class.import_access_token } @@ -286,6 +292,7 @@ RSpec.shared_examples 'a container registry auth service' do shared_examples 'private project' do context 'allow to use scope-less authentication' do it_behaves_like 'a valid token' + it_behaves_like 'with auth_type' end context 'allow developer to push images' do @@ -299,6 +306,7 @@ RSpec.shared_examples 'a container registry auth service' do it_behaves_like 'a pushable' it_behaves_like 'container repository factory' + it_behaves_like 'with auth_type' end context 'disallow developer to delete images' do @@ -341,6 +349,7 @@ RSpec.shared_examples 'a container registry auth service' do it_behaves_like 'a pullable' it_behaves_like 'not a container repository factory' + it_behaves_like 'with auth_type' end end @@ -381,6 +390,7 @@ RSpec.shared_examples 'a container registry auth service' do it_behaves_like 'a pullable' it_behaves_like 'not a container repository factory' + it_behaves_like 'with auth_type' end context 'disallow guest to pull or push images' do @@ -445,6 +455,7 @@ RSpec.shared_examples 'a container registry auth service' do it_behaves_like 'a pullable' it_behaves_like 'not a container repository factory' + it_behaves_like 'with auth_type' end context 'disallow anyone to push images' do @@ -495,6 +506,7 @@ RSpec.shared_examples 'a container registry auth service' do it_behaves_like 'a pullable' it_behaves_like 'not a container repository factory' + it_behaves_like 'with auth_type' end context 'disallow anyone to push images' do @@ -600,6 +612,7 @@ RSpec.shared_examples 'a container registry auth service' do end it_behaves_like 'a valid token' + it_behaves_like 'with auth_type' context 'allow to pull and push images' do let(:current_params) do @@ -944,10 +957,11 @@ RSpec.shared_examples 'a container registry auth service' do shared_examples 'able to login' do context 'registry provides read_container_image authentication_abilities' do - let(:current_params) { { deploy_token: deploy_token } } + let(:current_params) { { deploy_token: deploy_token, auth_type: :deploy_token } } let(:authentication_abilities) { [:read_container_image] } it_behaves_like 'an authenticated' + it { expect(payload['auth_type']).to eq('deploy_token') } end end diff --git a/spec/support/shared_examples/services/feature_flags/client_shared_examples.rb b/spec/support/shared_examples/services/feature_flags/client_shared_examples.rb index a62cffc0e1b..73a02905914 100644 --- a/spec/support/shared_examples/services/feature_flags/client_shared_examples.rb +++ b/spec/support/shared_examples/services/feature_flags/client_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -shared_examples_for 'update feature flag client' do +RSpec.shared_examples_for 'update feature flag client' do let!(:client) { create(:operations_feature_flags_client, project: project) } it 'updates last feature flag updated at' do @@ -10,7 +10,7 @@ shared_examples_for 'update feature flag client' do end end -shared_examples_for 'does not update feature flag client' do +RSpec.shared_examples_for 'does not update feature flag client' do let!(:client) { create(:operations_feature_flags_client, project: project) } it 'does not update last feature flag updated at' do diff --git a/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb b/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb index 2aac7e328f0..366fa4763e1 100644 --- a/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb +++ b/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb @@ -33,7 +33,7 @@ RSpec.shared_examples 'gitlab projects import validations' do context 'when there is a project with the same path' do let(:existing_project) { create(:project, namespace: namespace) } - let(:path) { existing_project.path} + let(:path) { existing_project.path } it 'does not create the project' do project = subject.execute diff --git a/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb b/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb index 31571b1ffb9..92681b8ba79 100644 --- a/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb +++ b/spec/support/shared_examples/services/issuable/destroy_service_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -shared_examples_for 'service scheduling async deletes' do +RSpec.shared_examples_for 'service scheduling async deletes' do it 'destroys associated todos asynchronously' do expect(worker_class) .to receive(:perform_async) @@ -20,13 +20,13 @@ shared_examples_for 'service scheduling async deletes' do end end -shared_examples_for 'service deleting todos' do +RSpec.shared_examples_for 'service deleting todos' do it_behaves_like 'service scheduling async deletes' do let(:worker_class) { TodosDestroyer::DestroyedIssuableWorker } end end -shared_examples_for 'service deleting label links' do +RSpec.shared_examples_for 'service deleting label links' do it_behaves_like 'service scheduling async deletes' do let(:worker_class) { Issuable::LabelLinksDestroyWorker } end diff --git a/spec/support/shared_examples/services/issuable/update_service_shared_examples.rb b/spec/support/shared_examples/services/issuable/update_service_shared_examples.rb new file mode 100644 index 00000000000..3d90885dd6f --- /dev/null +++ b/spec/support/shared_examples/services/issuable/update_service_shared_examples.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +RSpec.shared_examples_for 'issuable update service updating last_edited_at values' do + context 'when updating the title of the issuable' do + let(:update_params) { { title: 'updated title' } } + + it 'does not update last_edited values' do + expect { update_issuable }.to change(issuable, :title).from(issuable.title).to('updated title').and( + not_change(issuable, :last_edited_at) + ).and( + not_change(issuable, :last_edited_by) + ) + end + end + + context 'when updating the description of the issuable' do + let(:update_params) { { description: 'updated description' } } + + it 'updates last_edited values' do + expect do + update_issuable + end.to change(issuable, :description).from(issuable.description).to('updated description').and( + change(issuable, :last_edited_at) + ).and( + change(issuable, :last_edited_by) + ) + end + end +end diff --git a/spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb b/spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb index 9610cdd18a3..65351ac94ab 100644 --- a/spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb +++ b/spec/support/shared_examples/services/issuable_links/create_links_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -shared_examples 'issuable link creation' do +RSpec.shared_examples 'issuable link creation' do describe '#execute' do subject { described_class.new(issuable, user, params).execute } diff --git a/spec/support/shared_examples/services/issuable_links/destroyable_issuable_links_shared_examples.rb b/spec/support/shared_examples/services/issuable_links/destroyable_issuable_links_shared_examples.rb index 53d637a9094..5e80014da1d 100644 --- a/spec/support/shared_examples/services/issuable_links/destroyable_issuable_links_shared_examples.rb +++ b/spec/support/shared_examples/services/issuable_links/destroyable_issuable_links_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -shared_examples 'a destroyable issuable link' do +RSpec.shared_examples 'a destroyable issuable link' do context 'when successfully removes an issuable link' do before do issuable_link.source.resource_parent.add_reporter(user) diff --git a/spec/support/shared_examples/services/merge_request_shared_examples.rb b/spec/support/shared_examples/services/merge_request_shared_examples.rb index d2595b92cbc..b3ba0a1be93 100644 --- a/spec/support/shared_examples/services/merge_request_shared_examples.rb +++ b/spec/support/shared_examples/services/merge_request_shared_examples.rb @@ -123,7 +123,7 @@ end RSpec.shared_examples 'with an existing branch that has a merge request open' do |count| let(:changes) { existing_branch_changes } - let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)} + let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch) } it_behaves_like 'a service that does not create a merge request' it_behaves_like 'a service that can change assignees of a merge request', count diff --git a/spec/support/shared_examples/services/onboarding_progress_shared_examples.rb b/spec/support/shared_examples/services/onboarding_progress_shared_examples.rb index 8c6c2271af3..07025dac689 100644 --- a/spec/support/shared_examples/services/onboarding_progress_shared_examples.rb +++ b/spec/support/shared_examples/services/onboarding_progress_shared_examples.rb @@ -4,7 +4,7 @@ RSpec.shared_examples 'records an onboarding progress action' do |action| include AfterNextHelpers it do - expect_next(OnboardingProgressService, namespace) + expect_next(Onboarding::ProgressService, namespace) .to receive(:execute).with(action: action).and_call_original subject @@ -13,7 +13,7 @@ end RSpec.shared_examples 'does not record an onboarding progress action' do it do - expect(OnboardingProgressService).not_to receive(:new) + expect(Onboarding::ProgressService).not_to receive(:new) subject end diff --git a/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb b/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb index 7fd20fc3909..ea79dc674a1 100644 --- a/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb +++ b/spec/support/shared_examples/services/packages/debian/generate_distribution_shared_examples.rb @@ -190,6 +190,7 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do Codename: unstable Date: Sat, 25 Jan 2020 15:17:18 +0000 Valid-Until: Mon, 27 Jan 2020 15:17:18 +0000 + Acquire-By-Hash: yes Architectures: all amd64 arm64 Components: contrib main MD5Sum: @@ -249,6 +250,7 @@ RSpec.shared_examples 'Generate Debian Distribution and component files' do Codename: unstable Date: Sat, 25 Jan 2020 15:17:18 +0000 Valid-Until: Mon, 27 Jan 2020 15:17:18 +0000 + Acquire-By-Hash: yes MD5Sum: SHA256: EOF diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb index 704a4bbe0b8..ca4dea90c55 100644 --- a/spec/support/shared_examples/services/packages_shared_examples.rb +++ b/spec/support/shared_examples/services/packages_shared_examples.rb @@ -227,6 +227,7 @@ RSpec.shared_examples 'filters on each package_type' do |is_project: false| let_it_be(:package10) { create(:rubygems_package, project: project) } let_it_be(:package11) { create(:helm_package, project: project) } let_it_be(:package12) { create(:terraform_module_package, project: project) } + let_it_be(:package13) { create(:rpm_package, project: project) } Packages::Package.package_types.keys.each do |package_type| context "for package type #{package_type}" do diff --git a/spec/support/shared_examples/services/resource_events/synthetic_notes_builder_shared_examples.rb b/spec/support/shared_examples/services/resource_events/synthetic_notes_builder_shared_examples.rb index 716bee39fca..a7e51408032 100644 --- a/spec/support/shared_examples/services/resource_events/synthetic_notes_builder_shared_examples.rb +++ b/spec/support/shared_examples/services/resource_events/synthetic_notes_builder_shared_examples.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.shared_examples 'filters by paginated notes' do |event_type| - let(:event) { create(event_type) } # rubocop:disable Rails/SaveBang + let(:event) { create(event_type, issue: create(:issue)) } before do create(event_type, issue: event.issue) diff --git a/spec/support/shared_examples/services/snippets_shared_examples.rb b/spec/support/shared_examples/services/snippets_shared_examples.rb index 5a44f739b27..65893d84798 100644 --- a/spec/support/shared_examples/services/snippets_shared_examples.rb +++ b/spec/support/shared_examples/services/snippets_shared_examples.rb @@ -14,7 +14,8 @@ RSpec.shared_examples 'checking spam' do spammable: kind_of(Snippet), spam_params: spam_params, user: an_instance_of(User), - action: action + action: action, + extra_features: { files: an_instance_of(Array) } } ) do |instance| expect(instance).to receive(:execute) @@ -24,7 +25,7 @@ RSpec.shared_examples 'checking spam' do end end -shared_examples 'invalid params error response' do +RSpec.shared_examples 'invalid params error response' do before do allow_next_instance_of(described_class) do |service| allow(service).to receive(:valid_params?).and_return false diff --git a/spec/support/shared_examples/services/snowplow_tracking_shared_examples.rb b/spec/support/shared_examples/services/snowplow_tracking_shared_examples.rb index 0687be6f429..31919a4263d 100644 --- a/spec/support/shared_examples/services/snowplow_tracking_shared_examples.rb +++ b/spec/support/shared_examples/services/snowplow_tracking_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -shared_examples 'issue_edit snowplow tracking' do +RSpec.shared_examples 'issue_edit snowplow tracking' do let(:category) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_CATEGORY } let(:action) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_ACTION } let(:label) { Gitlab::UsageDataCounters::IssueActivityUniqueCounter::ISSUE_LABEL } diff --git a/spec/support/shared_examples/tasks/gitlab/uploads/migration_shared_examples.rb b/spec/support/shared_examples/tasks/gitlab/uploads/migration_shared_examples.rb deleted file mode 100644 index b37a8059574..00000000000 --- a/spec/support/shared_examples/tasks/gitlab/uploads/migration_shared_examples.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -# Expects the calling spec to define: -# - uploader_class -# - model_class -# - mounted_as -RSpec.shared_examples 'enqueue upload migration jobs in batch' do |batch:| - def run(task) - args = [uploader_class.to_s, model_class.to_s, mounted_as].compact - run_rake_task(task, *args) - end - - it 'migrates local storage to remote object storage' do - expect(ObjectStorage::MigrateUploadsWorker) - .to receive(:perform_async).exactly(batch).times - .and_return("A fake job.") - - run('gitlab:uploads:migrate') - end - - it 'migrates remote object storage to local storage' do - expect(Upload).to receive(:where).exactly(batch + 1).times { Upload.all } - expect(ObjectStorage::MigrateUploadsWorker) - .to receive(:perform_async) - .with(anything, model_class.name, mounted_as, ObjectStorage::Store::LOCAL) - .exactly(batch).times - .and_return("A fake job.") - - run('gitlab:uploads:migrate_to_local') - end -end diff --git a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb index f8b00d1e4c0..3c977e62a10 100644 --- a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb +++ b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb @@ -56,8 +56,8 @@ RSpec.shared_examples "migrates" do |to_store:, from_store: nil| it 'can access to the original file during migration' do file = subject.file - allow(subject).to receive(:delete_migrated_file) { } # Remove as a callback of :migrate - allow(subject).to receive(:record_upload) { } # Remove as a callback of :store (:record_upload) + allow(subject).to receive(:delete_migrated_file) {} # Remove as a callback of :migrate + allow(subject).to receive(:record_upload) {} # Remove as a callback of :store (:record_upload) expect(file.exists?).to be_truthy expect { migrate(to) }.not_to change { file.exists? } diff --git a/spec/support/shared_examples/users/migrate_records_to_ghost_user_service_shared_examples.rb b/spec/support/shared_examples/users/migrate_records_to_ghost_user_service_shared_examples.rb new file mode 100644 index 00000000000..eb03f0888b9 --- /dev/null +++ b/spec/support/shared_examples/users/migrate_records_to_ghost_user_service_shared_examples.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'migrating records to the ghost user' do |record_class, fields| + record_class_name = record_class.to_s.titleize.downcase + + let(:project) do + case record_class + when MergeRequest + create(:project, :repository) + else + create(:project) + end + end + + before do + project.add_developer(user) + end + + context "for a #{record_class_name} the user has created" do + let!(:record) { created_record } + let(:migrated_fields) { fields || [:author] } + + it "does not delete the #{record_class_name}" do + service.execute + + expect(record_class.find_by_id(record.id)).to be_present + end + + it 'migrates all associated fields to the "Ghost user"' do + service.execute + + migrated_record = record_class.find_by_id(record.id) + + migrated_fields.each do |field| + expect(migrated_record.public_send(field)).to eq(User.ghost) + end + end + end +end |