Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
3d38e524f1
commit
791054a0a5
|
@ -209,7 +209,6 @@ export default {
|
|||
/>
|
||||
<sidebar-labels-widget
|
||||
class="block labels"
|
||||
data-testid="sidebar-labels"
|
||||
:iid="activeBoardItem.iid"
|
||||
:full-path="projectPathForActiveIssue"
|
||||
:allow-label-remove="allowLabelEdit"
|
||||
|
|
|
@ -16,6 +16,7 @@ export default class CreateLabelDropdown {
|
|||
this.$colorPreview = $('.js-dropdown-label-color-preview', this.$el);
|
||||
this.$addList = $('.js-add-list', this.$el);
|
||||
this.$newLabelError = $('.js-label-error', this.$el);
|
||||
this.$newLabelErrorContent = $('.gl-alert-content', this.$newLabelError);
|
||||
this.$newLabelCreateButton = $('.js-new-label-btn', this.$el);
|
||||
this.$colorSuggestions = $('.suggest-colors-dropdown a', this.$el);
|
||||
|
||||
|
@ -119,7 +120,8 @@ export default class CreateLabelDropdown {
|
|||
.join('<br/>');
|
||||
}
|
||||
|
||||
this.$newLabelError.html(errors).show();
|
||||
this.$newLabelErrorContent.html(errors);
|
||||
this.$newLabelError.show();
|
||||
} else {
|
||||
const addNewList = this.$addList.is(':checked');
|
||||
this.$dropdownBack.trigger('click');
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
<script>
|
||||
import { GlTooltipDirective, GlButton, GlFormInput, GlLink, GlLoadingIcon } from '@gitlab/ui';
|
||||
import {
|
||||
GlAlert,
|
||||
GlTooltipDirective,
|
||||
GlButton,
|
||||
GlFormInput,
|
||||
GlLink,
|
||||
GlLoadingIcon,
|
||||
} from '@gitlab/ui';
|
||||
import produce from 'immer';
|
||||
import createFlash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
|
@ -11,6 +18,7 @@ const errorMessage = __('Error creating label.');
|
|||
|
||||
export default {
|
||||
components: {
|
||||
GlAlert,
|
||||
GlButton,
|
||||
GlFormInput,
|
||||
GlLink,
|
||||
|
@ -42,6 +50,7 @@ export default {
|
|||
labelTitle: '',
|
||||
selectedColor: '',
|
||||
labelCreateInProgress: false,
|
||||
error: undefined,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -111,13 +120,14 @@ export default {
|
|||
) => this.updateLabelsInCache(store, label),
|
||||
});
|
||||
if (labelCreate.errors.length) {
|
||||
createFlash({ message: errorMessage });
|
||||
[this.error] = labelCreate.errors;
|
||||
} else {
|
||||
this.$emit('hideCreateView');
|
||||
}
|
||||
} catch {
|
||||
createFlash({ message: errorMessage });
|
||||
}
|
||||
this.labelCreateInProgress = false;
|
||||
this.$emit('hideCreateView');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -126,6 +136,9 @@ export default {
|
|||
<template>
|
||||
<div class="labels-select-contents-create js-labels-create">
|
||||
<div class="dropdown-input">
|
||||
<gl-alert v-if="error" variant="danger" :dismissible="false" class="gl-mb-3">
|
||||
{{ error }}
|
||||
</gl-alert>
|
||||
<gl-form-input
|
||||
v-model.trim="labelTitle"
|
||||
:placeholder="__('Name new label')"
|
||||
|
|
|
@ -289,6 +289,7 @@ export default {
|
|||
'is-standalone': isDropdownVariantStandalone(variant),
|
||||
'is-embedded': isDropdownVariantEmbedded(variant),
|
||||
}"
|
||||
data-testid="sidebar-labels"
|
||||
data-qa-selector="labels_block"
|
||||
>
|
||||
<template v-if="isDropdownVariantSidebar(variant)">
|
||||
|
|
|
@ -68,13 +68,6 @@
|
|||
color: $white;
|
||||
}
|
||||
|
||||
.dropdown-labels-error {
|
||||
padding: 5px 10px;
|
||||
margin-bottom: 10px;
|
||||
background-color: $red-500;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.manage-labels-list {
|
||||
padding: 0;
|
||||
margin-bottom: 0;
|
||||
|
|
|
@ -158,7 +158,7 @@ class Admin::UsersController < Admin::ApplicationController
|
|||
end
|
||||
|
||||
def confirm
|
||||
if update_user { |user| user.confirm }
|
||||
if update_user { |user| user.force_confirm }
|
||||
redirect_back_or_admin_user(notice: _("Successfully confirmed"))
|
||||
else
|
||||
redirect_back_or_admin_user(alert: _("Error occurred. User was not confirmed"))
|
||||
|
|
|
@ -33,17 +33,15 @@ class Groups::DependencyProxyForContainersController < ::Groups::DependencyProxy
|
|||
end
|
||||
|
||||
def blob
|
||||
return blob_via_workhorse if Feature.enabled?(:dependency_proxy_workhorse, group, default_enabled: :yaml)
|
||||
blob = @group.dependency_proxy_blobs.find_by_file_name(blob_file_name)
|
||||
|
||||
result = DependencyProxy::FindOrCreateBlobService
|
||||
.new(group, image, token, params[:sha]).execute
|
||||
|
||||
if result[:status] == :success
|
||||
event_name = tracking_event_name(object_type: :blob, from_cache: result[:from_cache])
|
||||
if blob.present?
|
||||
event_name = tracking_event_name(object_type: :blob, from_cache: true)
|
||||
track_package_event(event_name, :dependency_proxy, namespace: group, user: auth_user)
|
||||
send_upload(result[:blob].file)
|
||||
|
||||
send_upload(blob.file)
|
||||
else
|
||||
head result[:http_status]
|
||||
send_dependency(token_header, DependencyProxy::Registry.blob_url(image, params[:sha]), blob_file_name)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -99,19 +97,6 @@ class Groups::DependencyProxyForContainersController < ::Groups::DependencyProxy
|
|||
|
||||
private
|
||||
|
||||
def blob_via_workhorse
|
||||
blob = @group.dependency_proxy_blobs.find_by_file_name(blob_file_name)
|
||||
|
||||
if blob.present?
|
||||
event_name = tracking_event_name(object_type: :blob, from_cache: true)
|
||||
track_package_event(event_name, :dependency_proxy, namespace: group, user: auth_user)
|
||||
|
||||
send_upload(blob.file)
|
||||
else
|
||||
send_dependency(token_header, DependencyProxy::Registry.blob_url(image, params[:sha]), blob_file_name)
|
||||
end
|
||||
end
|
||||
|
||||
def send_manifest(manifest, from_cache:)
|
||||
response.headers[DependencyProxy::Manifest::DIGEST_HEADER] = manifest.digest
|
||||
response.headers['Content-Length'] = manifest.size
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ForcedEmailConfirmation
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
attr_accessor :skip_confirmation_period_expiry_check
|
||||
end
|
||||
|
||||
def force_confirm(args = {})
|
||||
self.skip_confirmation_period_expiry_check = true
|
||||
confirm(args)
|
||||
ensure
|
||||
self.skip_confirmation_period_expiry_check = nil
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Override, from Devise::Models::Confirmable
|
||||
# Link: https://github.com/heartcombo/devise/blob/main/lib/devise/models/confirmable.rb
|
||||
def confirmation_period_expired?
|
||||
return false if skip_confirmation_period_expiry_check
|
||||
|
||||
super
|
||||
end
|
||||
end
|
|
@ -19,6 +19,7 @@ class Email < ApplicationRecord
|
|||
# This module adds async behaviour to Devise emails
|
||||
# and should be added after Devise modules are initialized.
|
||||
include AsyncDeviseEmail
|
||||
include ForcedEmailConfirmation
|
||||
|
||||
self.reconfirmable = false # currently email can't be changed, no need to reconfirm
|
||||
|
||||
|
|
|
@ -81,6 +81,7 @@ class User < ApplicationRecord
|
|||
# This module adds async behaviour to Devise emails
|
||||
# and should be added after Devise modules are initialized.
|
||||
include AsyncDeviseEmail
|
||||
include ForcedEmailConfirmation
|
||||
|
||||
MINIMUM_INACTIVE_DAYS = 90
|
||||
|
||||
|
@ -1974,18 +1975,22 @@ class User < ApplicationRecord
|
|||
ci_job_token_scope.present?
|
||||
end
|
||||
|
||||
# override from Devise::Confirmable
|
||||
# override from Devise::Models::Confirmable
|
||||
#
|
||||
# Add the primary email to user.emails (or confirm it if it was already
|
||||
# present) when the primary email is confirmed.
|
||||
def confirm(*args)
|
||||
saved = super(*args)
|
||||
def confirm(args = {})
|
||||
saved = super(args)
|
||||
return false unless saved
|
||||
|
||||
email_to_confirm = self.emails.find_by(email: self.email)
|
||||
|
||||
if email_to_confirm.present?
|
||||
email_to_confirm.confirm(*args)
|
||||
if skip_confirmation_period_expiry_check
|
||||
email_to_confirm.force_confirm(args)
|
||||
else
|
||||
email_to_confirm.confirm(args)
|
||||
end
|
||||
else
|
||||
add_primary_email_to_emails!
|
||||
end
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DependencyProxy
|
||||
class DownloadBlobService < DependencyProxy::BaseService
|
||||
def initialize(image, blob_sha, token)
|
||||
@image = image
|
||||
@blob_sha = blob_sha
|
||||
@token = token
|
||||
@temp_file = Tempfile.new
|
||||
end
|
||||
|
||||
def execute
|
||||
File.open(@temp_file.path, "wb") do |file|
|
||||
Gitlab::HTTP.get(blob_url, headers: auth_headers, stream_body: true) do |fragment|
|
||||
if [301, 302, 307].include?(fragment.code)
|
||||
# do nothing
|
||||
elsif fragment.code == 200
|
||||
file.write(fragment)
|
||||
else
|
||||
raise DownloadError.new('Non-success response code on downloading blob fragment', fragment.code)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
success(file: @temp_file)
|
||||
rescue DownloadError => exception
|
||||
error(exception.message, exception.http_status)
|
||||
rescue Timeout::Error => exception
|
||||
error(exception.message, 599)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def blob_url
|
||||
registry.blob_url(@image, @blob_sha)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,48 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DependencyProxy
|
||||
class FindOrCreateBlobService < DependencyProxy::BaseService
|
||||
def initialize(group, image, token, blob_sha)
|
||||
@group = group
|
||||
@image = image
|
||||
@token = token
|
||||
@blob_sha = blob_sha
|
||||
end
|
||||
|
||||
def execute
|
||||
from_cache = true
|
||||
file_name = @blob_sha.sub('sha256:', '') + '.gz'
|
||||
blob = @group.dependency_proxy_blobs.active.find_or_build(file_name)
|
||||
|
||||
unless blob.persisted?
|
||||
from_cache = false
|
||||
result = DependencyProxy::DownloadBlobService
|
||||
.new(@image, @blob_sha, @token).execute
|
||||
|
||||
if result[:status] == :error
|
||||
log_failure(result)
|
||||
|
||||
return error('Failed to download the blob', result[:http_status])
|
||||
end
|
||||
|
||||
blob.file = result[:file]
|
||||
blob.size = result[:file].size
|
||||
blob.save!
|
||||
end
|
||||
|
||||
blob.read! if from_cache
|
||||
success(blob: blob, from_cache: from_cache)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def log_failure(result)
|
||||
log_error(
|
||||
"Dependency proxy: Failed to download the blob." \
|
||||
"Blob sha: #{@blob_sha}." \
|
||||
"Error message: #{result[:message][0, 100]}" \
|
||||
"HTTP status: #{result[:http_status]}"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -41,20 +41,51 @@ module Projects
|
|||
remote_mirror.update_start!
|
||||
|
||||
# LFS objects must be sent first, or the push has dangling pointers
|
||||
send_lfs_objects!(remote_mirror)
|
||||
lfs_status = send_lfs_objects!(remote_mirror)
|
||||
|
||||
response = remote_mirror.update_repository
|
||||
failed, failure_message = failure_status(lfs_status, response, remote_mirror)
|
||||
|
||||
if response.divergent_refs.any?
|
||||
message = "Some refs have diverged and have not been updated on the remote:"
|
||||
message += "\n\n#{response.divergent_refs.join("\n")}"
|
||||
|
||||
remote_mirror.mark_as_failed!(message)
|
||||
# When the issue https://gitlab.com/gitlab-org/gitlab/-/issues/349262 is closed,
|
||||
# we can move this block within failure_status.
|
||||
if failed
|
||||
remote_mirror.mark_as_failed!(failure_message)
|
||||
else
|
||||
remote_mirror.update_finish!
|
||||
end
|
||||
end
|
||||
|
||||
def failure_status(lfs_status, response, remote_mirror)
|
||||
message = ''
|
||||
failed = false
|
||||
lfs_sync_failed = false
|
||||
|
||||
if lfs_status&.dig(:status) == :error
|
||||
lfs_sync_failed = true
|
||||
message += "Error synchronizing LFS files:"
|
||||
message += "\n\n#{lfs_status[:message]}\n\n"
|
||||
|
||||
failed = Feature.enabled?(:remote_mirror_fail_on_lfs, project, default_enabled: :yaml)
|
||||
end
|
||||
|
||||
if response.divergent_refs.any?
|
||||
message += "Some refs have diverged and have not been updated on the remote:"
|
||||
message += "\n\n#{response.divergent_refs.join("\n")}"
|
||||
failed = true
|
||||
end
|
||||
|
||||
if message.present?
|
||||
Gitlab::AppJsonLogger.info(message: "Error synching remote mirror",
|
||||
project_id: project.id,
|
||||
project_path: project.full_path,
|
||||
remote_mirror_id: remote_mirror.id,
|
||||
lfs_sync_failed: lfs_sync_failed,
|
||||
divergent_ref_list: response.divergent_refs)
|
||||
end
|
||||
|
||||
[failed, message]
|
||||
end
|
||||
|
||||
def send_lfs_objects!(remote_mirror)
|
||||
return unless project.lfs_enabled?
|
||||
|
||||
|
|
|
@ -6,7 +6,10 @@
|
|||
.dropdown-page-two.dropdown-new-label
|
||||
= dropdown_title(create_label_title(subject), options: { back: true, close: show_close })
|
||||
= dropdown_content do
|
||||
.dropdown-labels-error.js-label-error
|
||||
.js-label-error.gl-alert.gl-alert-danger.gl-mb-3
|
||||
.gl-alert-container
|
||||
= sprite_icon('error', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
|
||||
.gl-alert-content
|
||||
%input#new_label_name.default-dropdown-input{ type: "text", placeholder: _('Name new label') }
|
||||
.suggest-colors.suggest-colors-dropdown
|
||||
= render_suggested_colors
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
name: dependency_proxy_workhorse
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68157
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/339639
|
||||
milestone: '14.3'
|
||||
name: remote_mirror_fail_on_lfs
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77339
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349262
|
||||
milestone: '14.7'
|
||||
type: development
|
||||
group: group::source code
|
||||
default_enabled: true
|
||||
default_enabled: false
|
|
@ -421,16 +421,37 @@ RSpec.describe Admin::UsersController do
|
|||
end
|
||||
|
||||
describe 'PUT confirm/:id' do
|
||||
let(:user) { create(:user, confirmed_at: nil) }
|
||||
shared_examples_for 'confirms the user' do
|
||||
it 'confirms the user' do
|
||||
put :confirm, params: { id: user.username }
|
||||
user.reload
|
||||
expect(user.confirmed?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
let(:expired_confirmation_sent_at) { Date.today - User.confirm_within - 7.days }
|
||||
let(:extant_confirmation_sent_at) { Date.today }
|
||||
|
||||
let(:user) do
|
||||
create(:user, :unconfirmed).tap do |user|
|
||||
user.update!(confirmation_sent_at: confirmation_sent_at)
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
request.env["HTTP_REFERER"] = "/"
|
||||
end
|
||||
|
||||
it 'confirms user' do
|
||||
put :confirm, params: { id: user.username }
|
||||
user.reload
|
||||
expect(user.confirmed?).to be_truthy
|
||||
context 'when the confirmation period has expired' do
|
||||
let(:confirmation_sent_at) { expired_confirmation_sent_at }
|
||||
|
||||
it_behaves_like 'confirms the user'
|
||||
end
|
||||
|
||||
context 'when the confirmation period has not expired' do
|
||||
let(:confirmation_sent_at) { extant_confirmation_sent_at }
|
||||
|
||||
it_behaves_like 'confirms the user'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -348,74 +348,6 @@ RSpec.describe Groups::DependencyProxyForContainersController do
|
|||
it_behaves_like 'a successful blob pull'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when dependency_proxy_workhorse disabled' do
|
||||
let(:blob_response) { { status: :success, blob: blob, from_cache: false } }
|
||||
|
||||
before do
|
||||
stub_feature_flags(dependency_proxy_workhorse: false)
|
||||
|
||||
allow_next_instance_of(DependencyProxy::FindOrCreateBlobService) do |instance|
|
||||
allow(instance).to receive(:execute).and_return(blob_response)
|
||||
end
|
||||
end
|
||||
|
||||
context 'remote blob request fails' do
|
||||
let(:blob_response) do
|
||||
{
|
||||
status: :error,
|
||||
http_status: 400,
|
||||
message: ''
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
group.add_guest(user)
|
||||
end
|
||||
|
||||
it 'proxies status from the remote blob request', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(response.body).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'a valid user' do
|
||||
before do
|
||||
group.add_guest(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'a successful blob pull'
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'pull_blob'
|
||||
|
||||
context 'with a cache entry' do
|
||||
let(:blob_response) { { status: :success, blob: blob, from_cache: true } }
|
||||
|
||||
it_behaves_like 'returning response status', :success
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'pull_blob_from_cache'
|
||||
end
|
||||
end
|
||||
|
||||
context 'a valid deploy token' do
|
||||
let_it_be(:user) { create(:deploy_token, :group, :dependency_proxy_scopes) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
it_behaves_like 'a successful blob pull'
|
||||
|
||||
context 'pulling from a subgroup' do
|
||||
let_it_be_with_reload(:parent_group) { create(:group) }
|
||||
let_it_be_with_reload(:group) { create(:group, parent: parent_group) }
|
||||
|
||||
before do
|
||||
parent_group.create_dependency_proxy_setting!(enabled: true)
|
||||
group_deploy_token.update_column(:group_id, parent_group.id)
|
||||
end
|
||||
|
||||
it_behaves_like 'a successful blob pull'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'not found when disabled'
|
||||
|
|
|
@ -6,9 +6,11 @@ RSpec.describe 'Project issue boards sidebar', :js do
|
|||
include BoardHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:project) { create(:project, :public) }
|
||||
let_it_be(:group) { create(:group, :public) }
|
||||
let_it_be(:project) { create(:project, :public, namespace: group) }
|
||||
let_it_be(:board) { create(:board, project: project) }
|
||||
let_it_be(:list) { create(:list, board: board, position: 0) }
|
||||
let_it_be(:label) { create(:label, project: project, name: 'Label') }
|
||||
let_it_be(:list) { create(:list, board: board, label: label, position: 0) }
|
||||
|
||||
let_it_be(:issue, reload: true) { create(:issue, project: project, relative_position: 1) }
|
||||
|
||||
|
|
|
@ -81,28 +81,11 @@ RSpec.describe 'Group Dependency Proxy for containers', :js do
|
|||
let!(:dependency_proxy_blob) { create(:dependency_proxy_blob, group: group) }
|
||||
|
||||
it_behaves_like 'responds with the file'
|
||||
|
||||
context 'dependency_proxy_workhorse feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags({ dependency_proxy_workhorse: false })
|
||||
end
|
||||
|
||||
it_behaves_like 'responds with the file'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the blob must be downloaded' do
|
||||
it_behaves_like 'responds with the file'
|
||||
it_behaves_like 'caches the file'
|
||||
|
||||
context 'dependency_proxy_workhorse feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags({ dependency_proxy_workhorse: false })
|
||||
end
|
||||
|
||||
it_behaves_like 'responds with the file'
|
||||
it_behaves_like 'caches the file'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,10 +8,9 @@ RSpec.describe 'Issue Sidebar' do
|
|||
let_it_be(:group) { create(:group, :nested) }
|
||||
let_it_be(:project) { create(:project, :public, namespace: group) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:label) { create(:label, project: project, title: 'bug') }
|
||||
let_it_be(:issue) { create(:labeled_issue, project: project, labels: [label]) }
|
||||
let_it_be(:issue) { create(:issue, project: project) }
|
||||
let_it_be(:label) { create(:label, project: project, name: 'Label') }
|
||||
let_it_be(:mock_date) { Date.today.at_beginning_of_month + 2.days }
|
||||
let_it_be(:xss_label) { create(:label, project: project, title: '<script>alert("xss");</script>') }
|
||||
|
||||
before do
|
||||
stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab")
|
||||
|
@ -223,14 +222,6 @@ RSpec.describe 'Issue Sidebar' do
|
|||
restore_window_size
|
||||
open_issue_sidebar
|
||||
end
|
||||
|
||||
it 'escapes XSS when viewing issue labels' do
|
||||
page.within('.block.labels') do
|
||||
click_on 'Edit'
|
||||
|
||||
expect(page).to have_content '<script>alert("xss");</script>'
|
||||
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
|
||||
|
|
|
@ -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],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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 '<script>alert("xss");</script>'
|
||||
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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue