Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-09-10 12:10:27 +00:00
parent 16e3c17d3f
commit 8fd149139d
23 changed files with 349 additions and 291 deletions

View File

@ -40,7 +40,7 @@ export const fetchProjects = ({ commit, state }, search) => {
);
} else {
// The .catch() is due to the API method not handling a rejection properly
Api.projects(search, { order_by: 'id' }, callback).catch(() => {
Api.projects(search, { order_by: 'similarity' }, callback).catch(() => {
callback();
});
}

View File

@ -9,6 +9,8 @@ module Clusters
belongs_to :group, class_name: '::Group', optional: false
validates :config, json_schema: { filename: 'cluster_agent_authorization_configuration' }
delegate :project, to: :agent
end
end
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
module Clusters
module Agents
class ImplicitAuthorization
attr_reader :agent
delegate :id, to: :agent, prefix: true
delegate :project, to: :agent
def initialize(agent:)
@agent = agent
end
def config
nil
end
end
end
end

View File

@ -21,11 +21,7 @@ module Packages
try_obtain_lease do
@package_file.transaction do
if use_new_package_file_updater?
new_execute
else
legacy_execute
end
process_package_update
end
end
rescue ActiveRecord::RecordInvalid => e
@ -34,7 +30,7 @@ module Packages
private
def new_execute
def process_package_update
package_to_destroy = nil
target_package = @package_file.package
@ -50,36 +46,11 @@ module Packages
end
update_package(target_package)
::Packages::UpdatePackageFileService.new(@package_file, package_id: target_package.id, file_name: package_filename)
.execute
package_to_destroy&.destroy!
end
def legacy_execute
if existing_package
package = link_to_existing_package
elsif symbol_package?
raise InvalidMetadataError, 'symbol package is invalid, matching package does not exist'
else
package = update_linked_package
end
update_package(package)
# Updating file_name updates the path where the file is stored.
# We must pass the file again so that CarrierWave can handle the update
@package_file.update!(
file_name: package_filename,
file: @package_file.file
)
end
def use_new_package_file_updater?
::Feature.enabled?(:packages_nuget_new_package_file_updater, @package_file.project, default_enabled: :yaml)
end
def update_package(package)
return if symbol_package?

View File

@ -1,34 +1,38 @@
= form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-import-export-limits-settings'), html: { class: 'fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
= html_escape(_("Set any rate limit to %{code_open}0%{code_close} to disable the limit.")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
%fieldset
.form-group
= f.label :project_import_limit, _('Max Project Import requests per minute per user'), class: 'label-bold'
= f.label :project_import_limit, _('Maximum project import requests per minute'), class: 'label-bold'
= f.number_field :project_import_limit, class: 'form-control gl-form-input'
%fieldset
.form-group
= f.label :project_export_limit, _('Max Project Export requests per minute per user'), class: 'label-bold'
= f.label :project_export_limit, _('Maximum project export requests per minute'), class: 'label-bold'
= f.number_field :project_export_limit, class: 'form-control gl-form-input'
%fieldset
.form-group
= f.label :project_download_export_limit, _('Max Project Export Download requests per minute per user'), class: 'label-bold'
= f.label :project_download_export_limit, _('Maximum project export download requests per minute'), class: 'label-bold'
= f.number_field :project_download_export_limit, class: 'form-control gl-form-input'
%fieldset
.form-group
= f.label :group_import_limit, _('Max Group Import requests per minute per user'), class: 'label-bold'
= f.label :group_import_limit, _('Maximum group import requests per minute'), class: 'label-bold'
= f.number_field :group_import_limit, class: 'form-control gl-form-input'
%fieldset
.form-group
= f.label :group_export_limit, _('Max Group Export requests per minute per user'), class: 'label-bold'
= f.label :group_export_limit, _('Maximum group export requests per minute'), class: 'label-bold'
= f.number_field :group_export_limit, class: 'form-control gl-form-input'
%fieldset
.form-group
= f.label :group_download_export_limit, _('Max Group Export Download requests per minute per user'), class: 'label-bold'
= f.label :group_download_export_limit, _('Maximum group export download requests per minute'), class: 'label-bold'
= f.number_field :group_download_export_limit, class: 'form-control gl-form-input'
= f.submit _('Save changes'), class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' }

View File

@ -110,11 +110,12 @@
%section.settings.as-import-export-limits.no-animate#js-import-export-limits-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
= _('Import/Export Rate Limits')
= _('Import and export rate limits')
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
= expanded_by_default? ? _('Collapse') : _('Expand')
%p
= _('Configure limits for Project/Group Import/Export.')
= _('Set per-user rate limits for imports and exports of projects and groups.')
= link_to _('Learn more.'), help_page_path('user/admin_area/settings/import_export_rate_limits.md'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'import_export_limits'

View File

@ -7,7 +7,7 @@
= _('Who can see this group?')
- visibility_docs_path = help_page_path('public_access/public_access')
- docs_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: visibility_docs_path }
= s_('Check the %{docs_link_start}documentation%{docs_link_end}.').html_safe % { docs_link_start: docs_link_start, docs_link_end: '</a>'.html_safe }
= _('%{docs_link_start}Learn about visibility levels.%{docs_link_end}').html_safe % { docs_link_start: docs_link_start, docs_link_end: '</a>'.html_safe }
- if can_change_visibility_level
= render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model)
- else

View File

@ -0,0 +1,8 @@
---
name: group_authorized_agents
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69047
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/340166
milestone: '14.3'
type: development
group: group::configure
default_enabled: false

View File

@ -1,8 +0,0 @@
---
name: packages_nuget_new_package_file_updater
introduced_by_url:
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/336511
milestone: '14.2'
type: development
group: group::package
default_enabled: true

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/321258
milestone: '13.9'
type: development
group: group::container security
default_enabled: false
default_enabled: true

View File

@ -54,7 +54,7 @@ To install the app in Jira:
1. Click **Upload**.
If the install was successful, you should see the **GitLab for Jira** app under **Manage apps**.
If the install was successful, you should see the **GitLab.com for Jira Cloud** app under **Manage apps**.
You can also click **Getting Started** to open the configuration page rendered from your GitLab instance.
_Note that any changes to the app descriptor requires you to uninstall then reinstall the app._

View File

@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
NOTE:
Only Jira users with administrator level access are able to install or configure
the GitLab app for Jira Cloud.
the GitLab.com for Jira Cloud app.
## GitLab.com for Jira Cloud app **(FREE SAAS)**

View File

@ -69,7 +69,7 @@ To simplify administration, we recommend that a GitLab group maintainer or group
| Jira usage | GitLab.com customers need | GitLab self-managed customers need |
|------------|---------------------------|------------------------------------|
| [Atlassian cloud](https://www.atlassian.com/cloud) | The [GitLab.com for Jira Cloud](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud?hosting=cloud&tab=overview) application installed from the [Atlassian Marketplace](https://marketplace.atlassian.com). This offers real-time sync between GitLab and Jira. For more information, see the documentation for the [GitLab.com for Jira Cloud app](connect-app.md). | The [GitLab.com for Jira Cloud](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud?hosting=cloud&tab=overview), using a workaround process. See the documentation for [installing the GitLab Jira Cloud application for self-managed instances](connect-app.md#install-the-gitlabcom-for-jira-cloud-app-for-self-managed-instances) for more information. |
| [Atlassian cloud](https://www.atlassian.com/cloud) | The [GitLab.com for Jira Cloud app](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud?hosting=cloud&tab=overview) installed from the [Atlassian Marketplace](https://marketplace.atlassian.com). This offers real-time sync between GitLab.com and Jira. For more information, see the documentation for the [GitLab.com for Jira Cloud app](connect-app.md). | The [GitLab.com for Jira Cloud app](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud?hosting=cloud&tab=overview), using a workaround process. See the documentation for [installing the GitLab.com for Jira Cloud app for self-managed instances](connect-app.md#install-the-gitlabcom-for-jira-cloud-app-for-self-managed-instances) for more information. |
| Your own server | The [Jira DVCS (distributed version control system) connector](dvcs.md). This syncs data hourly. | The [Jira DVCS Connector](dvcs.md). |
Each GitLab project can be configured to connect to an entire Jira instance. That means after

View File

@ -40,7 +40,7 @@ displays in the [development panel](https://support.atlassian.com/jira-software-
To set up the Jira development panel integration:
- *If your installation uses Jira Cloud,* use the
[GitLab for Jira app](connect-app.md).
[GitLab.com for Jira Cloud app](connect-app.md).
- *If either your Jira or GitLab installation is self-managed,* use the
[Jira DVCS (distributed version control system) connector](dvcs.md).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -5,28 +5,26 @@ group: Import
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Project/group import/export rate limits **(FREE SELF)**
# Rate limits for imports and exports of project and groups **(FREE SELF)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35728) in GitLab 13.2.
The following table includes configurable rate limits. The following table includes limits on a
per minute per user basis:
You can configure the rate limits for imports and exports of projects and groups:
| Limit | Default (per minute per user) |
|--------------------------|-------------------------------|
| Project Import | 6 |
| Project Export | 6 |
| Project Export Download | 1 |
| Group Import | 6 |
| Group Export | 6 |
| Group Export Download | 1 |
To change a rate limit:
All rate limits are:
1. On the top bar, select **Menu > Admin**.
1. On the left sidebar, select **Settings > Network**, then expand **Import and export rate limits**.
1. Change the value of any rate limit. The rate limits are per minute per user, not per IP address.
Set to `0` to disable a rate limit.
- Configurable through the top bar at **Menu > Admin > Settings > Network > Import/Export Rate Limits**
- Applied per minute per user
- Not applied per IP address
- Active by default. To disable, set the option to `0`
- Logged to `auth.log` file if exceed rate limit
| Limit | Default |
|-------------------------|---------|
| Project Import | 6 |
| Project Export | 6 |
| Project Export Download | 1 |
| Group Import | 6 |
| Group Export | 6 |
| Group Export Download | 1 |
![Import/Export rate limits](img/import_export_rate_limits_v13_2.png)
When a user exceeds a rate limit, it is logged in `auth.log`.

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module API
module Entities
module Clusters
class AgentAuthorization < Grape::Entity
expose :agent_id, as: :id
expose :project, with: Entities::ProjectIdentity, as: :config_project
expose :config, as: :configuration
end
end
end
end

View File

@ -559,6 +559,9 @@ msgstr ""
msgid "%{doc_link_start}Advanced search%{doc_link_end} is enabled."
msgstr ""
msgid "%{docs_link_start}Learn about visibility levels.%{docs_link_end}"
msgstr ""
msgid "%{due_date} (Past due)"
msgstr ""
@ -8514,9 +8517,6 @@ msgstr ""
msgid "Configure limit for notes created per minute by web and API requests."
msgstr ""
msgid "Configure limits for Project/Group Import/Export."
msgstr ""
msgid "Configure limits for web and API requests."
msgstr ""
@ -17001,6 +17001,9 @@ msgstr ""
msgid "Import an exported GitLab project"
msgstr ""
msgid "Import and export rate limits"
msgstr ""
msgid "Import failed due to a GitHub error: %{original}"
msgstr ""
@ -17079,9 +17082,6 @@ msgstr ""
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
msgid "Import/Export Rate Limits"
msgstr ""
msgid "ImportButtons|Connect repositories from"
msgstr ""
@ -20654,24 +20654,6 @@ msgstr ""
msgid "Max 20 characters"
msgstr ""
msgid "Max Group Export Download requests per minute per user"
msgstr ""
msgid "Max Group Export requests per minute per user"
msgstr ""
msgid "Max Group Import requests per minute per user"
msgstr ""
msgid "Max Project Export Download requests per minute per user"
msgstr ""
msgid "Max Project Export requests per minute per user"
msgstr ""
msgid "Max Project Import requests per minute per user"
msgstr ""
msgid "Max authenticated API requests per period per user"
msgstr ""
@ -20777,6 +20759,15 @@ msgstr ""
msgid "Maximum files in a diff"
msgstr ""
msgid "Maximum group export download requests per minute"
msgstr ""
msgid "Maximum group export requests per minute"
msgstr ""
msgid "Maximum group import requests per minute"
msgstr ""
msgid "Maximum import size"
msgstr ""
@ -20822,6 +20813,15 @@ msgstr ""
msgid "Maximum page size"
msgstr ""
msgid "Maximum project export download requests per minute"
msgstr ""
msgid "Maximum project export requests per minute"
msgstr ""
msgid "Maximum project import requests per minute"
msgstr ""
msgid "Maximum push size"
msgstr ""
@ -30628,6 +30628,9 @@ msgstr ""
msgid "Set an instance-wide domain that will be available to all clusters when installing Knative."
msgstr ""
msgid "Set any rate limit to %{code_open}0%{code_close} to disable the limit."
msgstr ""
msgid "Set default and restrict visibility levels. Configure import sources and git access protocol."
msgstr ""
@ -30655,6 +30658,9 @@ msgstr ""
msgid "Set parent epic to an epic"
msgstr ""
msgid "Set per-user rate limits for imports and exports of projects and groups."
msgstr ""
msgid "Set projects and maximum size limits, session duration, user options, and check feature availability for namespace plan."
msgstr ""

View File

@ -142,7 +142,13 @@ describe('Global Search Store Actions', () => {
actions.fetchProjects({ commit: mockCommit, state });
expect(Api.groupProjects).not.toHaveBeenCalled();
expect(Api.projects).toHaveBeenCalled();
expect(Api.projects).toHaveBeenCalledWith(
state.query.search,
{
order_by: 'similarity',
},
expect.any(Function),
);
});
});
});

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe API::Entities::Clusters::AgentAuthorization do
let_it_be(:authorization) { create(:agent_group_authorization) }
subject { described_class.new(authorization).as_json }
it 'includes basic fields' do
expect(subject).to include(
id: authorization.agent_id,
config_project: a_hash_including(id: authorization.agent.project_id),
configuration: authorization.config
)
end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Clusters::Agents::ImplicitAuthorization do
let_it_be(:agent) { create(:cluster_agent) }
subject { described_class.new(agent: agent) }
it { expect(subject.agent).to eq(agent) }
it { expect(subject.agent_id).to eq(agent.id) }
it { expect(subject.project).to eq(agent.project) }
it { expect(subject.config).to be_nil }
end

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers
let(:package) { create(:nuget_package, :processing, :with_symbol_package) }
let!(:package) { create(:nuget_package, :processing, :with_symbol_package) }
let(:package_file) { package.package_files.first }
let(:service) { described_class.new(package_file) }
let(:package_name) { 'DummyProject.DummyPackage' }
@ -63,28 +63,152 @@ RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_
end
end
shared_examples 'handling all conditions' do
context 'with no existing package' do
let(:package_id) { package.id }
it 'updates package and package file', :aggregate_failures do
expect { subject }
.to not_change { ::Packages::Package.count }
.and change { Packages::Dependency.count }.by(1)
.and change { Packages::DependencyLink.count }.by(1)
.and change { ::Packages::Nuget::Metadatum.count }.by(0)
expect(package.reload.name).to eq(package_name)
expect(package.version).to eq(package_version)
expect(package).to be_default
expect(package_file.reload.file_name).to eq(package_file_name)
# hard reset needed to properly reload package_file.file
expect(Packages::PackageFile.find(package_file.id).file.size).not_to eq 0
end
it_behaves_like 'taking the lease'
it_behaves_like 'not updating the package if the lease is taken'
end
context 'with existing package' do
let!(:existing_package) { create(:nuget_package, project: package.project, name: package_name, version: package_version) }
let(:package_id) { existing_package.id }
it 'link existing package and updates package file', :aggregate_failures do
expect(service).to receive(:try_obtain_lease).and_call_original
expect { subject }
.to change { ::Packages::Package.count }.by(-1)
.and change { Packages::Dependency.count }.by(0)
.and change { Packages::DependencyLink.count }.by(0)
.and change { Packages::Nuget::DependencyLinkMetadatum.count }.by(0)
.and change { ::Packages::Nuget::Metadatum.count }.by(0)
expect(package_file.reload.file_name).to eq(package_file_name)
expect(package_file.package).to eq(existing_package)
end
it_behaves_like 'taking the lease'
it_behaves_like 'not updating the package if the lease is taken'
end
context 'with a nuspec file with metadata' do
let(:nuspec_filepath) { 'packages/nuget/with_metadata.nuspec' }
let(:expected_tags) { %w(foo bar test tag1 tag2 tag3 tag4 tag5) }
before do
allow_next_instance_of(Packages::Nuget::MetadataExtractionService) do |service|
allow(service)
.to receive(:nuspec_file_content).and_return(fixture_file(nuspec_filepath))
end
end
it 'creates tags' do
expect(service).to receive(:try_obtain_lease).and_call_original
expect { subject }.to change { ::Packages::Tag.count }.by(8)
expect(package.reload.tags.map(&:name)).to contain_exactly(*expected_tags)
end
context 'with existing package and tags' do
let!(:existing_package) { create(:nuget_package, project: package.project, name: 'DummyProject.WithMetadata', version: '1.2.3') }
let!(:tag1) { create(:packages_tag, package: existing_package, name: 'tag1') }
let!(:tag2) { create(:packages_tag, package: existing_package, name: 'tag2') }
let!(:tag3) { create(:packages_tag, package: existing_package, name: 'tag_not_in_metadata') }
it 'creates tags and deletes those not in metadata' do
expect(service).to receive(:try_obtain_lease).and_call_original
expect { subject }.to change { ::Packages::Tag.count }.by(5)
expect(existing_package.tags.map(&:name)).to contain_exactly(*expected_tags)
end
end
it 'creates nuget metadatum', :aggregate_failures do
expect { subject }
.to not_change { ::Packages::Package.count }
.and change { ::Packages::Nuget::Metadatum.count }.by(1)
metadatum = package_file.reload.package.nuget_metadatum
expect(metadatum.license_url).to eq('https://opensource.org/licenses/MIT')
expect(metadatum.project_url).to eq('https://gitlab.com/gitlab-org/gitlab')
expect(metadatum.icon_url).to eq('https://opensource.org/files/osi_keyhole_300X300_90ppi_0.png')
end
context 'with too long url' do
let_it_be(:too_long_url) { "http://localhost/#{'bananas' * 50}" }
let(:metadata) { { package_name: package_name, package_version: package_version, license_url: too_long_url } }
before do
allow(service).to receive(:metadata).and_return(metadata)
end
it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError
end
end
context 'with nuspec file with dependencies' do
let(:nuspec_filepath) { 'packages/nuget/with_dependencies.nuspec' }
let(:package_name) { 'Test.Package' }
let(:package_version) { '3.5.2' }
let(:package_file_name) { 'test.package.3.5.2.nupkg' }
before do
allow_next_instance_of(Packages::Nuget::MetadataExtractionService) do |service|
allow(service)
.to receive(:nuspec_file_content).and_return(fixture_file(nuspec_filepath))
end
end
it 'updates package and package file', :aggregate_failures do
expect { subject }
.to not_change { ::Packages::Package.count }
.and change { Packages::Dependency.count }.by(4)
.and change { Packages::DependencyLink.count }.by(4)
.and change { Packages::Nuget::DependencyLinkMetadatum.count }.by(2)
expect(package.reload.name).to eq(package_name)
expect(package.version).to eq(package_version)
expect(package).to be_default
expect(package_file.reload.file_name).to eq(package_file_name)
# hard reset needed to properly reload package_file.file
expect(Packages::PackageFile.find(package_file.id).file.size).not_to eq 0
end
end
context 'with package file not containing a nuspec file' do
before do
allow_next_instance_of(Zip::File) do |file|
allow(file).to receive(:glob).and_return([])
end
end
it_behaves_like 'raising an', ::Packages::Nuget::MetadataExtractionService::ExtractionError
end
context 'with a symbol package' do
let(:package_file) { package.package_files.last }
let(:package_file_name) { 'dummyproject.dummypackage.1.0.0.snupkg' }
context 'with no existing package' do
let(:package_id) { package.id }
it 'updates package and package file', :aggregate_failures do
expect { subject }
.to not_change { ::Packages::Package.count }
.and change { Packages::Dependency.count }.by(1)
.and change { Packages::DependencyLink.count }.by(1)
.and change { ::Packages::Nuget::Metadatum.count }.by(0)
expect(package.reload.name).to eq(package_name)
expect(package.version).to eq(package_version)
expect(package).to be_default
expect(package_file.reload.file_name).to eq(package_file_name)
# hard reset needed to properly reload package_file.file
expect(Packages::PackageFile.find(package_file.id).file.size).not_to eq 0
end
it_behaves_like 'taking the lease'
it_behaves_like 'not updating the package if the lease is taken'
it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError
end
context 'with existing package' do
@ -93,6 +217,8 @@ RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_
it 'link existing package and updates package file', :aggregate_failures do
expect(service).to receive(:try_obtain_lease).and_call_original
expect(::Packages::Nuget::SyncMetadatumService).not_to receive(:new)
expect(::Packages::UpdateTagsService).not_to receive(:new)
expect { subject }
.to change { ::Packages::Package.count }.by(-1)
@ -108,189 +234,42 @@ RSpec.describe Packages::Nuget::UpdatePackageFromMetadataService, :clean_gitlab_
it_behaves_like 'not updating the package if the lease is taken'
end
end
context 'with a nuspec file with metadata' do
let(:nuspec_filepath) { 'packages/nuget/with_metadata.nuspec' }
let(:expected_tags) { %w(foo bar test tag1 tag2 tag3 tag4 tag5) }
context 'with an invalid package name' do
invalid_names = [
'',
'My/package',
'../../../my_package',
'%2e%2e%2fmy_package'
]
invalid_names.each do |invalid_name|
before do
allow_next_instance_of(Packages::Nuget::MetadataExtractionService) do |service|
allow(service)
.to receive(:nuspec_file_content).and_return(fixture_file(nuspec_filepath))
end
allow(service).to receive(:package_name).and_return(invalid_name)
end
it 'creates tags' do
expect(service).to receive(:try_obtain_lease).and_call_original
expect { subject }.to change { ::Packages::Tag.count }.by(8)
expect(package.reload.tags.map(&:name)).to contain_exactly(*expected_tags)
end
context 'with existing package and tags' do
let!(:existing_package) { create(:nuget_package, project: package.project, name: 'DummyProject.WithMetadata', version: '1.2.3') }
let!(:tag1) { create(:packages_tag, package: existing_package, name: 'tag1') }
let!(:tag2) { create(:packages_tag, package: existing_package, name: 'tag2') }
let!(:tag3) { create(:packages_tag, package: existing_package, name: 'tag_not_in_metadata') }
it 'creates tags and deletes those not in metadata' do
expect(service).to receive(:try_obtain_lease).and_call_original
expect { subject }.to change { ::Packages::Tag.count }.by(5)
expect(existing_package.tags.map(&:name)).to contain_exactly(*expected_tags)
end
end
it 'creates nuget metadatum', :aggregate_failures do
expect { subject }
.to not_change { ::Packages::Package.count }
.and change { ::Packages::Nuget::Metadatum.count }.by(1)
metadatum = package_file.reload.package.nuget_metadatum
expect(metadatum.license_url).to eq('https://opensource.org/licenses/MIT')
expect(metadatum.project_url).to eq('https://gitlab.com/gitlab-org/gitlab')
expect(metadatum.icon_url).to eq('https://opensource.org/files/osi_keyhole_300X300_90ppi_0.png')
end
context 'with too long url' do
let_it_be(:too_long_url) { "http://localhost/#{'bananas' * 50}" }
let(:metadata) { { package_name: package_name, package_version: package_version, license_url: too_long_url } }
before do
allow(service).to receive(:metadata).and_return(metadata)
end
it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError
end
end
context 'with nuspec file with dependencies' do
let(:nuspec_filepath) { 'packages/nuget/with_dependencies.nuspec' }
let(:package_name) { 'Test.Package' }
let(:package_version) { '3.5.2' }
let(:package_file_name) { 'test.package.3.5.2.nupkg' }
before do
allow_next_instance_of(Packages::Nuget::MetadataExtractionService) do |service|
allow(service)
.to receive(:nuspec_file_content).and_return(fixture_file(nuspec_filepath))
end
end
it 'updates package and package file', :aggregate_failures do
expect { subject }
.to not_change { ::Packages::Package.count }
.and change { Packages::Dependency.count }.by(4)
.and change { Packages::DependencyLink.count }.by(4)
.and change { Packages::Nuget::DependencyLinkMetadatum.count }.by(2)
expect(package.reload.name).to eq(package_name)
expect(package.version).to eq(package_version)
expect(package).to be_default
expect(package_file.reload.file_name).to eq(package_file_name)
# hard reset needed to properly reload package_file.file
expect(Packages::PackageFile.find(package_file.id).file.size).not_to eq 0
end
end
context 'with package file not containing a nuspec file' do
before do
allow_next_instance_of(Zip::File) do |file|
allow(file).to receive(:glob).and_return([])
end
end
it_behaves_like 'raising an', ::Packages::Nuget::MetadataExtractionService::ExtractionError
end
context 'with a symbol package' do
let(:package_file) { package.package_files.last }
let(:package_file_name) { 'dummyproject.dummypackage.1.0.0.snupkg' }
context 'with no existing package' do
let(:package_id) { package.id }
it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError
end
context 'with existing package' do
let!(:existing_package) { create(:nuget_package, project: package.project, name: package_name, version: package_version) }
let(:package_id) { existing_package.id }
it 'link existing package and updates package file', :aggregate_failures do
expect(service).to receive(:try_obtain_lease).and_call_original
expect(::Packages::Nuget::SyncMetadatumService).not_to receive(:new)
expect(::Packages::UpdateTagsService).not_to receive(:new)
expect { subject }
.to change { ::Packages::Package.count }.by(-1)
.and change { Packages::Dependency.count }.by(0)
.and change { Packages::DependencyLink.count }.by(0)
.and change { Packages::Nuget::DependencyLinkMetadatum.count }.by(0)
.and change { ::Packages::Nuget::Metadatum.count }.by(0)
expect(package_file.reload.file_name).to eq(package_file_name)
expect(package_file.package).to eq(existing_package)
end
it_behaves_like 'taking the lease'
it_behaves_like 'not updating the package if the lease is taken'
end
end
context 'with an invalid package name' do
invalid_names = [
'',
'My/package',
'../../../my_package',
'%2e%2e%2fmy_package'
]
invalid_names.each do |invalid_name|
before do
allow(service).to receive(:package_name).and_return(invalid_name)
end
it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError
end
end
context 'with an invalid package version' do
invalid_versions = [
'',
'555',
'1.2',
'1./2.3',
'../../../../../1.2.3',
'%2e%2e%2f1.2.3'
]
invalid_versions.each do |invalid_version|
before do
allow(service).to receive(:package_version).and_return(invalid_version)
end
it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError
end
it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError
end
end
context 'with packages_nuget_new_package_file_updater enabled' do
before do
expect(service).not_to receive(:legacy_execute)
context 'with an invalid package version' do
invalid_versions = [
'',
'555',
'1.2',
'1./2.3',
'../../../../../1.2.3',
'%2e%2e%2f1.2.3'
]
invalid_versions.each do |invalid_version|
before do
allow(service).to receive(:package_version).and_return(invalid_version)
end
it_behaves_like 'raising an', ::Packages::Nuget::UpdatePackageFromMetadataService::InvalidMetadataError
end
it_behaves_like 'handling all conditions'
end
context 'with packages_nuget_new_package_file_updater disabled' do
before do
stub_feature_flags(packages_nuget_new_package_file_updater: false)
expect(::Packages::UpdatePackageFileService)
.not_to receive(:new).with(package_file, instance_of(Hash)).and_call_original
expect(service).not_to receive(:new_execute)
end
it_behaves_like 'handling all conditions'
end
end
end

View File

@ -23,12 +23,39 @@ module CycleAnalyticsHelpers
end
end
def select_event_label(sel)
page.within(sel) do
find('.dropdown-toggle').click
page.find(".dropdown-menu").all(".dropdown-item")[1].click
end
end
def fill_in_custom_label_stage_fields
index = page.all('[data-testid="value-stream-stage-fields"]').length
last_stage = page.all('[data-testid="value-stream-stage-fields"]').last
within last_stage do
find('[name*="custom-stage-name-"]').fill_in with: "Cool custom label stage - name #{index}"
select_dropdown_option_by_value "custom-stage-start-event-", :issue_label_added
select_dropdown_option_by_value "custom-stage-end-event-", :issue_label_removed
select_event_label("[data-testid*='custom-stage-start-event-label-']")
select_event_label("[data-testid*='custom-stage-end-event-label-']")
end
end
def add_custom_stage_to_form
page.find_button(s_('CreateValueStreamForm|Add another stage')).click
fill_in_custom_stage_fields
end
def add_custom_label_stage_to_form
page.find_button(s_('CreateValueStreamForm|Add another stage')).click
fill_in_custom_label_stage_fields
end
def save_value_stream(custom_value_stream_name)
fill_in 'create-value-stream-name', with: custom_value_stream_name