From 791054a0a5e6eee972b0206958a5c00fc30b4761 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 5 Jan 2022 06:13:32 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../components/board_content_sidebar.vue | 1 - .../labels/create_label_dropdown.js | 4 +- .../dropdown_contents_create_view.vue | 19 +- .../labels_select_root.vue | 1 + app/assets/stylesheets/pages/labels.scss | 7 - app/controllers/admin/users_controller.rb | 2 +- ...endency_proxy_for_containers_controller.rb | 27 +-- .../concerns/forced_email_confirmation.rb | 26 +++ app/models/email.rb | 1 + app/models/user.rb | 13 +- .../dependency_proxy/download_blob_service.rb | 38 ---- .../find_or_create_blob_service.rb | 48 ----- .../projects/update_remote_mirror_service.rb | 43 ++++- .../issuable/_label_page_create.html.haml | 5 +- ...orse.yml => remote_mirror_fail_on_lfs.yml} | 10 +- .../admin/users_controller_spec.rb | 31 +++- ...cy_proxy_for_containers_controller_spec.rb | 68 ------- spec/features/boards/sidebar_spec.rb | 6 +- .../dependency_proxy_for_containers_spec.rb | 17 -- spec/features/issues/issue_sidebar_spec.rb | 70 +------ .../dropdown_contents_create_view_spec.js | 26 ++- spec/models/email_spec.rb | 80 ++++++++ spec/models/user_spec.rb | 171 ++++++++++++++++-- spec/requests/rack_attack_global_spec.rb | 12 +- .../download_blob_service_spec.rb | 59 ------ .../find_or_create_blob_service_spec.rb | 71 -------- .../update_remote_mirror_service_spec.rb | 76 ++++++-- .../sidebar/sidebar_labels_shared_examples.rb | 107 +++++++++++ .../features/sidebar_shared_examples.rb | 4 + 29 files changed, 584 insertions(+), 459 deletions(-) create mode 100644 app/models/concerns/forced_email_confirmation.rb delete mode 100644 app/services/dependency_proxy/download_blob_service.rb delete mode 100644 app/services/dependency_proxy/find_or_create_blob_service.rb rename config/feature_flags/development/{dependency_proxy_workhorse.yml => remote_mirror_fail_on_lfs.yml} (64%) delete mode 100644 spec/services/dependency_proxy/download_blob_service_spec.rb delete mode 100644 spec/services/dependency_proxy/find_or_create_blob_service_spec.rb create mode 100644 spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb diff --git a/app/assets/javascripts/boards/components/board_content_sidebar.vue b/app/assets/javascripts/boards/components/board_content_sidebar.vue index 5e9be5c06da..fec2e49172e 100644 --- a/app/assets/javascripts/boards/components/board_content_sidebar.vue +++ b/app/assets/javascripts/boards/components/board_content_sidebar.vue @@ -209,7 +209,6 @@ export default { /> '); } - this.$newLabelError.html(errors).show(); + this.$newLabelErrorContent.html(errors); + this.$newLabelError.show(); } else { const addNewList = this.$addList.is(':checked'); this.$dropdownBack.trigger('click'); diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue index da626a21b14..b99083713a8 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue @@ -1,5 +1,12 @@ ' - end - end end context 'editing issue milestone', :js do @@ -242,62 +233,7 @@ RSpec.describe 'Issue Sidebar' do end context 'editing issue labels', :js do - before do - issue.update!(labels: [label]) - page.within('.block.labels') do - click_on 'Edit' - end - end - - it 'shows the current set of labels' do - page.within('.issuable-show-labels') do - expect(page).to have_content label.title - end - end - - it 'shows option to create a project label' do - page.within('.block.labels') do - expect(page).to have_content 'Create project' - end - end - - context 'creating a project label', :js, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/27992' do - before do - page.within('.block.labels') do - click_link 'Create project' - end - end - - it 'shows dropdown switches to "create label" section' do - page.within('.block.labels') do - expect(page).to have_content 'Create project label' - end - end - - it 'adds new label' do - page.within('.block.labels') do - fill_in 'new_label_name', with: 'wontfix' - page.find('.suggest-colors a', match: :first).click - page.find('button', text: 'Create').click - - page.within('.dropdown-page-one') do - expect(page).to have_content 'wontfix' - end - end - end - - it 'shows error message if label title is taken' do - page.within('.block.labels') do - fill_in 'new_label_name', with: label.title - page.find('.suggest-colors a', match: :first).click - page.find('button', text: 'Create').click - - page.within('.dropdown-page-two') do - expect(page).to have_content 'Title has already been taken' - end - end - end - end + it_behaves_like 'labels sidebar widget' end context 'interacting with collapsed sidebar', :js do diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js index d8491334b5d..3ceed670d77 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view_spec.js @@ -1,4 +1,4 @@ -import { GlLoadingIcon, GlLink } from '@gitlab/ui'; +import { GlAlert, GlLoadingIcon, GlLink } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; @@ -9,6 +9,7 @@ import { workspaceLabelsQueries } from '~/sidebar/constants'; import DropdownContentsCreateView from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue'; import createLabelMutation from '~/vue_shared/components/sidebar/labels_select_widget/graphql/create_label.mutation.graphql'; import { + mockRegularLabel, mockSuggestedColors, createLabelSuccessfulResponse, workspaceLabelsQueryResponse, @@ -25,8 +26,18 @@ const userRecoverableError = { errors: ['Houston, we have a problem'], }; +const titleTakenError = { + data: { + labelCreate: { + label: mockRegularLabel, + errors: ['Title has already been taken'], + }, + }, +}; + const createLabelSuccessHandler = jest.fn().mockResolvedValue(createLabelSuccessfulResponse); const createLabelUserRecoverableErrorHandler = jest.fn().mockResolvedValue(userRecoverableError); +const createLabelDuplicateErrorHandler = jest.fn().mockResolvedValue(titleTakenError); const createLabelErrorHandler = jest.fn().mockRejectedValue('Houston, we have a problem'); describe('DropdownContentsCreateView', () => { @@ -208,4 +219,17 @@ describe('DropdownContentsCreateView', () => { expect(createFlash).toHaveBeenCalled(); }); + + it('displays error in alert if label title is already taken', async () => { + createComponent({ mutationHandler: createLabelDuplicateErrorHandler }); + fillLabelAttributes(); + await nextTick(); + + findCreateButton().vm.$emit('click'); + await waitForPromises(); + + expect(wrapper.findComponent(GlAlert).text()).toEqual( + titleTakenError.data.labelCreate.errors[0], + ); + }); }); diff --git a/spec/models/email_spec.rb b/spec/models/email_spec.rb index 59299a507e4..d1fe988f294 100644 --- a/spec/models/email_spec.rb +++ b/spec/models/email_spec.rb @@ -71,4 +71,84 @@ RSpec.describe Email do end end end + + describe '#confirm' do + let(:expired_confirmation_sent_at) { Date.today - described_class.confirm_within - 7.days } + let(:extant_confirmation_sent_at) { Date.today } + + let(:email) do + create(:email, email: 'test@gitlab.com').tap do |email| + email.update!(confirmation_sent_at: confirmation_sent_at) + end + end + + shared_examples_for 'unconfirmed email' do + it 'returns unconfirmed' do + expect(email.confirmed?).to be_falsey + end + end + + context 'when the confirmation period has expired' do + let(:confirmation_sent_at) { expired_confirmation_sent_at } + + it_behaves_like 'unconfirmed email' + + it 'does not confirm the email' do + email.confirm + + expect(email.confirmed?).to be_falsey + end + end + + context 'when the confirmation period has not expired' do + let(:confirmation_sent_at) { extant_confirmation_sent_at } + + it_behaves_like 'unconfirmed email' + + it 'confirms the email' do + email.confirm + + expect(email.confirmed?).to be_truthy + end + end + end + + describe '#force_confirm' do + let(:expired_confirmation_sent_at) { Date.today - described_class.confirm_within - 7.days } + let(:extant_confirmation_sent_at) { Date.today } + + let(:email) do + create(:email, email: 'test@gitlab.com').tap do |email| + email.update!(confirmation_sent_at: confirmation_sent_at) + end + end + + shared_examples_for 'unconfirmed email' do + it 'returns unconfirmed' do + expect(email.confirmed?).to be_falsey + end + end + + shared_examples_for 'confirms the email on force_confirm' do + it 'confirms an email' do + email.force_confirm + + expect(email.reload.confirmed?).to be_truthy + end + end + + context 'when the confirmation period has expired' do + let(:confirmation_sent_at) { expired_confirmation_sent_at } + + it_behaves_like 'unconfirmed email' + it_behaves_like 'confirms the email on force_confirm' + end + + context 'when the confirmation period has not expired' do + let(:confirmation_sent_at) { extant_confirmation_sent_at } + + it_behaves_like 'unconfirmed email' + it_behaves_like 'confirms the email on force_confirm' + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index e670f669b49..73a241350e9 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1481,27 +1481,176 @@ RSpec.describe User do end describe '#confirm' do + let(:expired_confirmation_sent_at) { Date.today - described_class.confirm_within - 7.days } + let(:extant_confirmation_sent_at) { Date.today } + before do allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true) end - let(:user) { create(:user, :unconfirmed, unconfirmed_email: 'test@gitlab.com') } - - it 'returns unconfirmed' do - expect(user.confirmed?).to be_falsey + let(:user) do + create(:user, :unconfirmed, unconfirmed_email: 'test@gitlab.com').tap do |user| + user.update!(confirmation_sent_at: confirmation_sent_at) + end end - it 'confirms a user' do - user.confirm - expect(user.confirmed?).to be_truthy + shared_examples_for 'unconfirmed user' do + it 'returns unconfirmed' do + expect(user.confirmed?).to be_falsey + end end - it 'adds the confirmed primary email to emails' do - expect(user.emails.confirmed.map(&:email)).not_to include(user.email) + context 'when the confirmation period has expired' do + let(:confirmation_sent_at) { expired_confirmation_sent_at } - user.confirm + it_behaves_like 'unconfirmed user' - expect(user.emails.confirmed.map(&:email)).to include(user.email) + it 'does not confirm the user' do + user.confirm + + expect(user.confirmed?).to be_falsey + end + + it 'does not add the confirmed primary email to emails' do + user.confirm + + expect(user.emails.confirmed.map(&:email)).not_to include(user.email) + end + end + + context 'when the confirmation period has not expired' do + let(:confirmation_sent_at) { extant_confirmation_sent_at } + + it_behaves_like 'unconfirmed user' + + it 'confirms a user' do + user.confirm + expect(user.confirmed?).to be_truthy + end + + it 'adds the confirmed primary email to emails' do + expect(user.emails.confirmed.map(&:email)).not_to include(user.email) + + user.confirm + + expect(user.emails.confirmed.map(&:email)).to include(user.email) + end + + context 'when the primary email is already included in user.emails' do + let(:expired_confirmation_sent_at_for_email) { Date.today - Email.confirm_within - 7.days } + let(:extant_confirmation_sent_at_for_email) { Date.today } + + let!(:email) do + create(:email, email: user.unconfirmed_email, user: user).tap do |email| + email.update!(confirmation_sent_at: confirmation_sent_at_for_email) + end + end + + context 'when the confirmation period of the email record has expired' do + let(:confirmation_sent_at_for_email) { expired_confirmation_sent_at_for_email } + + it 'does not confirm the email record' do + user.confirm + + expect(email.reload.confirmed?).to be_falsey + end + end + + context 'when the confirmation period of the email record has not expired' do + let(:confirmation_sent_at_for_email) { extant_confirmation_sent_at_for_email } + + it 'confirms the email record' do + user.confirm + + expect(email.reload.confirmed?).to be_truthy + end + end + end + end + end + + describe '#force_confirm' do + let(:expired_confirmation_sent_at) { Date.today - described_class.confirm_within - 7.days } + let(:extant_confirmation_sent_at) { Date.today } + + let(:user) do + create(:user, :unconfirmed, unconfirmed_email: 'test@gitlab.com').tap do |user| + user.update!(confirmation_sent_at: confirmation_sent_at) + end + end + + shared_examples_for 'unconfirmed user' do + it 'returns unconfirmed' do + expect(user.confirmed?).to be_falsey + end + end + + shared_examples_for 'confirms the user on force_confirm' do + it 'confirms a user' do + user.force_confirm + expect(user.confirmed?).to be_truthy + end + end + + shared_examples_for 'adds the confirmed primary email to emails' do + it 'adds the confirmed primary email to emails' do + expect(user.emails.confirmed.map(&:email)).not_to include(user.email) + + user.force_confirm + + expect(user.emails.confirmed.map(&:email)).to include(user.email) + end + end + + shared_examples_for 'confirms the email record if the primary email was already present in user.emails' do + context 'when the primary email is already included in user.emails' do + let(:expired_confirmation_sent_at_for_email) { Date.today - Email.confirm_within - 7.days } + let(:extant_confirmation_sent_at_for_email) { Date.today } + + let!(:email) do + create(:email, email: user.unconfirmed_email, user: user).tap do |email| + email.update!(confirmation_sent_at: confirmation_sent_at_for_email) + end + end + + shared_examples_for 'confirms the email record' do + it 'confirms the email record' do + user.force_confirm + + expect(email.reload.confirmed?).to be_truthy + end + end + + context 'when the confirmation period of the email record has expired' do + let(:confirmation_sent_at_for_email) { expired_confirmation_sent_at_for_email } + + it_behaves_like 'confirms the email record' + end + + context 'when the confirmation period of the email record has not expired' do + let(:confirmation_sent_at_for_email) { extant_confirmation_sent_at_for_email } + + it_behaves_like 'confirms the email record' + end + end + end + + context 'when the confirmation period has expired' do + let(:confirmation_sent_at) { expired_confirmation_sent_at } + + it_behaves_like 'unconfirmed user' + it_behaves_like 'confirms the user on force_confirm' + it_behaves_like 'adds the confirmed primary email to emails' + it_behaves_like 'confirms the email record if the primary email was already present in user.emails' + end + + context 'when the confirmation period has not expired' do + let(:confirmation_sent_at) { extant_confirmation_sent_at } + + it_behaves_like 'unconfirmed user' + it_behaves_like 'confirms the user on force_confirm' + it_behaves_like 'adds the confirmed primary email to emails' + it_behaves_like 'confirms the email record if the primary email was already present in user.emails' end end diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb index 244ec111a0c..d953d37221c 100644 --- a/spec/requests/rack_attack_global_spec.rb +++ b/spec/requests/rack_attack_global_spec.rb @@ -533,16 +533,10 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac context 'getting a blob' do let_it_be(:blob) { create(:dependency_proxy_blob) } + let_it_be(:other_blob) { create(:dependency_proxy_blob) } - let(:path) { "/v2/#{group.path}/dependency_proxy/containers/alpine/blobs/sha256:a0d0a0d46f8b52473982a3c466318f479767577551a53ffc9074c9fa7035982e" } - let(:other_path) { "/v2/#{other_group.path}/dependency_proxy/containers/alpine/blobs/sha256:a0d0a0d46f8b52473982a3c466318f479767577551a53ffc9074c9fa7035982e" } - let(:blob_response) { { status: :success, blob: blob, from_cache: false } } - - before do - allow_next_instance_of(DependencyProxy::FindOrCreateBlobService) do |instance| - allow(instance).to receive(:execute).and_return(blob_response) - end - end + let(:path) { "/v2/#{blob.group.path}/dependency_proxy/containers/alpine/blobs/sha256:a0d0a0d46f8b52473982a3c466318f479767577551a53ffc9074c9fa7035982e" } + let(:other_path) { "/v2/#{other_blob.group.path}/dependency_proxy/containers/alpine/blobs/sha256:a0d0a0d46f8b52473982a3c466318f479767577551a53ffc9074c9fa7035982e" } it_behaves_like 'rate-limited token-authenticated requests' end diff --git a/spec/services/dependency_proxy/download_blob_service_spec.rb b/spec/services/dependency_proxy/download_blob_service_spec.rb deleted file mode 100644 index 2f293b8a46b..00000000000 --- a/spec/services/dependency_proxy/download_blob_service_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true -require 'spec_helper' - -RSpec.describe DependencyProxy::DownloadBlobService do - include DependencyProxyHelpers - - let(:image) { 'alpine' } - let(:token) { Digest::SHA256.hexdigest('123') } - let(:blob_sha) { Digest::SHA256.hexdigest('ruby:2.7.0') } - - subject(:download_blob) { described_class.new(image, blob_sha, token).execute } - - context 'remote request is successful' do - before do - stub_blob_download(image, blob_sha) - end - - it { expect(subject[:status]).to eq(:success) } - it { expect(subject[:file]).to be_a(Tempfile) } - it { expect(subject[:file].size).to eq(6) } - - it 'streams the download' do - expected_options = { headers: anything, stream_body: true } - - expect(Gitlab::HTTP).to receive(:perform_request).with(Net::HTTP::Get, anything, expected_options) - - download_blob - end - - it 'skips read_total_timeout', :aggregate_failures do - stub_const('GitLab::HTTP::DEFAULT_READ_TOTAL_TIMEOUT', 0) - - expect(Gitlab::Metrics::System).not_to receive(:monotonic_time) - expect(download_blob).to include(status: :success) - end - end - - context 'remote request is not found' do - before do - stub_blob_download(image, blob_sha, 404) - end - - it { expect(subject[:status]).to eq(:error) } - it { expect(subject[:http_status]).to eq(404) } - it { expect(subject[:message]).to eq('Non-success response code on downloading blob fragment') } - end - - context 'net timeout exception' do - before do - blob_url = DependencyProxy::Registry.blob_url(image, blob_sha) - - stub_full_request(blob_url).to_timeout - end - - it { expect(subject[:status]).to eq(:error) } - it { expect(subject[:http_status]).to eq(599) } - it { expect(subject[:message]).to eq('execution expired') } - end -end diff --git a/spec/services/dependency_proxy/find_or_create_blob_service_spec.rb b/spec/services/dependency_proxy/find_or_create_blob_service_spec.rb deleted file mode 100644 index 5f7afdf699a..00000000000 --- a/spec/services/dependency_proxy/find_or_create_blob_service_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -# frozen_string_literal: true -require 'spec_helper' - -RSpec.describe DependencyProxy::FindOrCreateBlobService do - include DependencyProxyHelpers - - let_it_be_with_reload(:blob) { create(:dependency_proxy_blob) } - - let(:group) { blob.group } - let(:image) { 'alpine' } - let(:tag) { '3.9' } - let(:token) { Digest::SHA256.hexdigest('123') } - let(:blob_sha) { '40bd001563085fc35165329ea1ff5c5ecbdbbeef' } - - subject { described_class.new(group, image, token, blob_sha).execute } - - before do - stub_registry_auth(image, token) - end - - shared_examples 'downloads the remote blob' do - it 'downloads blob from remote registry if there is no cached one' do - expect(subject[:status]).to eq(:success) - expect(subject[:blob]).to be_a(DependencyProxy::Blob) - expect(subject[:blob]).to be_persisted - expect(subject[:from_cache]).to eq false - end - end - - context 'no cache' do - before do - stub_blob_download(image, blob_sha) - end - - it_behaves_like 'downloads the remote blob' - end - - context 'cached blob' do - let(:blob_sha) { blob.file_name.sub('.gz', '') } - - it 'uses cached blob instead of downloading one' do - expect { subject }.to change { blob.reload.read_at } - - expect(subject[:status]).to eq(:success) - expect(subject[:blob]).to be_a(DependencyProxy::Blob) - expect(subject[:blob]).to eq(blob) - expect(subject[:from_cache]).to eq true - end - - context 'when the cached blob is expired' do - before do - blob.update_column(:status, DependencyProxy::Blob.statuses[:expired]) - stub_blob_download(image, blob_sha) - end - - it_behaves_like 'downloads the remote blob' - end - end - - context 'no such blob exists remotely' do - before do - stub_blob_download(image, blob_sha, 404) - end - - it 'returns error message and http status' do - expect(subject[:status]).to eq(:error) - expect(subject[:message]).to eq('Failed to download the blob') - expect(subject[:http_status]).to eq(404) - end - end -end diff --git a/spec/services/projects/update_remote_mirror_service_spec.rb b/spec/services/projects/update_remote_mirror_service_spec.rb index f4a6d1b19e7..3f9ebf410e9 100644 --- a/spec/services/projects/update_remote_mirror_service_spec.rb +++ b/spec/services/projects/update_remote_mirror_service_spec.rb @@ -131,32 +131,82 @@ RSpec.describe Projects::UpdateRemoteMirrorService do expect_next_instance_of(Lfs::PushService) do |service| expect(service).to receive(:execute) end + expect(Gitlab::AppJsonLogger).not_to receive(:info) execute! + + expect(remote_mirror.update_status).to eq('finished') + expect(remote_mirror.last_error).to be_nil end - it 'does nothing to an SSH repository' do - remote_mirror.update!(url: 'ssh://example.com') + context 'when LFS objects fail to push' do + before do + expect_next_instance_of(Lfs::PushService) do |service| + expect(service).to receive(:execute).and_return({ status: :error, message: 'unauthorized' }) + end + end - expect_any_instance_of(Lfs::PushService).not_to receive(:execute) + context 'when remote_mirror_fail_on_lfs feature flag enabled' do + it 'fails update' do + expect(Gitlab::AppJsonLogger).to receive(:info).with( + hash_including(message: "Error synching remote mirror")).and_call_original - execute! + execute! + + expect(remote_mirror.update_status).to eq('failed') + expect(remote_mirror.last_error).to eq("Error synchronizing LFS files:\n\nunauthorized\n\n") + end + end + + context 'when remote_mirror_fail_on_lfs feature flag is disabled' do + before do + stub_feature_flags(remote_mirror_fail_on_lfs: false) + end + + it 'does not fail update' do + expect(Gitlab::AppJsonLogger).to receive(:info).with( + hash_including(message: "Error synching remote mirror")).and_call_original + + execute! + + expect(remote_mirror.update_status).to eq('finished') + expect(remote_mirror.last_error).to be_nil + end + end end - it 'does nothing if LFS is disabled' do - expect(project).to receive(:lfs_enabled?) { false } + context 'with SSH repository' do + let(:ssh_mirror) { create(:remote_mirror, project: project, enabled: true) } - expect_any_instance_of(Lfs::PushService).not_to receive(:execute) + before do + allow(ssh_mirror) + .to receive(:update_repository) + .and_return(double(divergent_refs: [])) + end - execute! - end + it 'does nothing to an SSH repository' do + ssh_mirror.update!(url: 'ssh://example.com') - it 'does nothing if non-password auth is specified' do - remote_mirror.update!(auth_method: 'ssh_public_key') + expect_any_instance_of(Lfs::PushService).not_to receive(:execute) - expect_any_instance_of(Lfs::PushService).not_to receive(:execute) + service.execute(ssh_mirror, retries) + end - execute! + it 'does nothing if LFS is disabled' do + expect(project).to receive(:lfs_enabled?) { false } + + expect_any_instance_of(Lfs::PushService).not_to receive(:execute) + + service.execute(ssh_mirror, retries) + end + + it 'does nothing if non-password auth is specified' do + ssh_mirror.update!(auth_method: 'ssh_public_key') + + expect_any_instance_of(Lfs::PushService).not_to receive(:execute) + + service.execute(ssh_mirror, retries) + end end end end diff --git a/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb b/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb new file mode 100644 index 00000000000..76d677180d1 --- /dev/null +++ b/spec/support/shared_examples/features/sidebar/sidebar_labels_shared_examples.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'labels sidebar widget' do + context 'editing labels' do + let_it_be(:development) { create(:group_label, group: group, name: 'Development') } + let_it_be(:stretch) { create(:label, project: project, name: 'Stretch') } + let_it_be(:xss_label) { create(:label, project: project, title: '<script>alert("xss");</script>') } + + let(:labels_widget) { find('[data-testid="sidebar-labels"]') } + + before do + page.within(labels_widget) do + click_on 'Edit' + end + + wait_for_all_requests + end + + it 'shows labels list in the dropdown' do + expect(labels_widget.find('.gl-new-dropdown-contents')).to have_selector('li.gl-new-dropdown-item', count: 4) + end + + it 'adds a label' do + within(labels_widget) do + adds_label(stretch) + + page.within('[data-testid="value-wrapper"]') do + expect(page).to have_content(stretch.name) + end + end + end + + it 'removes a label' do + within(labels_widget) do + adds_label(stretch) + page.within('[data-testid="value-wrapper"]') do + expect(page).to have_content(stretch.name) + end + + click_on 'Remove label' + + wait_for_requests + + page.within('[data-testid="value-wrapper"]') do + expect(page).not_to have_content(stretch.name) + end + end + end + + it 'escapes XSS when viewing issuable labels' do + page.within(labels_widget) do + expect(page).to have_content '' + end + end + + it 'shows option to create a label' do + page.within(labels_widget) do + expect(page).to have_content 'Create' + end + end + + context 'creating a label', :js do + before do + page.within(labels_widget) do + page.find('[data-testid="create-label-button"]').click + end + end + + it 'shows dropdown switches to "create label" section' do + page.within(labels_widget) do + expect(page.find('[data-testid="dropdown-header"]')).to have_content 'Create' + end + end + + it 'creates new label' do + page.within(labels_widget) do + fill_in 'Name new label', with: 'wontfix' + page.find('.suggest-colors a', match: :first).click + page.find('button', text: 'Create').click + wait_for_requests + + expect(page).to have_content 'wontfix' + end + end + + it 'shows error message if label title is taken' do + page.within(labels_widget) do + fill_in 'Name new label', with: development.title + page.find('.suggest-colors a', match: :first).click + page.find('button', text: 'Create').click + wait_for_requests + + page.within('.dropdown-input') do + expect(page.find('.gl-alert')).to have_content 'Title' + end + end + end + end + end + + def adds_label(label) + click_button label.name + click_button 'Close' + + wait_for_requests + end +end diff --git a/spec/support/shared_examples/features/sidebar_shared_examples.rb b/spec/support/shared_examples/features/sidebar_shared_examples.rb index 615f568420e..11d216ff4b6 100644 --- a/spec/support/shared_examples/features/sidebar_shared_examples.rb +++ b/spec/support/shared_examples/features/sidebar_shared_examples.rb @@ -50,6 +50,10 @@ RSpec.shared_examples 'issue boards sidebar' do it_behaves_like 'date sidebar widget' end + context 'editing issue labels', :js do + it_behaves_like 'labels sidebar widget' + end + context 'in notifications subscription' do it 'displays notifications toggle', :aggregate_failures do page.within('[data-testid="sidebar-notifications"]') do