diff --git a/app/assets/javascripts/profile/account/components/delete_account_modal.vue b/app/assets/javascripts/profile/account/components/delete_account_modal.vue
index 0fcf200ce28..24b142050d6 100644
--- a/app/assets/javascripts/profile/account/components/delete_account_modal.vue
+++ b/app/assets/javascripts/profile/account/components/delete_account_modal.vue
@@ -1,12 +1,12 @@
-
-
-
+
-
-
-
+
+
+
+
diff --git a/app/assets/javascripts/profile/account/index.js b/app/assets/javascripts/profile/account/index.js
index f0d9642a2b2..5e89002b3bc 100644
--- a/app/assets/javascripts/profile/account/index.js
+++ b/app/assets/javascripts/profile/account/index.js
@@ -30,6 +30,9 @@ export default () => {
},
mounted() {
deleteAccountButton.classList.remove('disabled');
+ deleteAccountButton.addEventListener('click', () => {
+ this.$root.$emit('bv::show::modal', 'delete-account-modal', '#delete-account-button');
+ });
},
render(createElement) {
return createElement('delete-account-modal', {
diff --git a/app/models/ci/pipeline_artifact.rb b/app/models/ci/pipeline_artifact.rb
index e9c40180d20..ea8cb48fc9d 100644
--- a/app/models/ci/pipeline_artifact.rb
+++ b/app/models/ci/pipeline_artifact.rb
@@ -8,6 +8,7 @@ module Ci
include UpdateProjectStatistics
include Artifactable
include FileStoreMounter
+ include Presentable
FILE_STORE_SUPPORTED = [
ObjectStorage::Store::LOCAL,
@@ -44,5 +45,9 @@ module Ci
def self.find_with_code_coverage
find_by(file_type: :code_coverage)
end
+
+ def present
+ super(presenter_class: "Ci::PipelineArtifacts::#{self.file_type.camelize}Presenter".constantize)
+ end
end
end
diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb
index 618d240b7ff..0ab6b337ac0 100644
--- a/app/models/packages/package.rb
+++ b/app/models/packages/package.rb
@@ -38,8 +38,12 @@ class Packages::Package < ApplicationRecord
validates :version, format: { with: Gitlab::Regex.conan_recipe_component_regex }, if: :conan?
validates :version, format: { with: Gitlab::Regex.maven_version_regex }, if: -> { version? && maven? }
validates :version, format: { with: Gitlab::Regex.pypi_version_regex }, if: :pypi?
+ validates :version,
+ presence: true,
+ format: { with: Gitlab::Regex.generic_package_version_regex },
+ if: :generic?
- enum package_type: { maven: 1, npm: 2, conan: 3, nuget: 4, pypi: 5, composer: 6 }
+ enum package_type: { maven: 1, npm: 2, conan: 3, nuget: 4, pypi: 5, composer: 6, generic: 7 }
scope :with_name, ->(name) { where(name: name) }
scope :with_name_like, ->(name) { where(arel_table[:name].matches(name)) }
diff --git a/app/presenters/ci/pipeline_artifacts/code_coverage_presenter.rb b/app/presenters/ci/pipeline_artifacts/code_coverage_presenter.rb
new file mode 100644
index 00000000000..098e839132c
--- /dev/null
+++ b/app/presenters/ci/pipeline_artifacts/code_coverage_presenter.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Ci
+ module PipelineArtifacts
+ class CodeCoveragePresenter < ProcessablePresenter
+ include Gitlab::Utils::StrongMemoize
+
+ def for_files(filenames)
+ coverage_files = raw_report["files"].select { |key| filenames.include?(key) }
+
+ { files: coverage_files }
+ end
+
+ private
+
+ def raw_report
+ strong_memoize(:raw_report) do
+ self.each_blob do |blob|
+ Gitlab::Json.parse(blob)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/serializers/test_suite_entity.rb b/app/serializers/test_suite_entity.rb
index d04fd5f6a84..15eb2891b22 100644
--- a/app/serializers/test_suite_entity.rb
+++ b/app/serializers/test_suite_entity.rb
@@ -13,7 +13,7 @@ class TestSuiteEntity < Grape::Entity
with_options if: -> (_, opts) { opts[:details] } do |test_suite|
expose :suite_error
expose :test_cases, using: TestCaseEntity do |test_suite|
- test_suite.suite_error ? [] : test_suite.test_cases.values.flat_map(&:values)
+ test_suite.suite_error ? [] : test_suite.sorted.test_cases.values.flat_map(&:values)
end
end
end
diff --git a/app/services/ci/generate_coverage_reports_service.rb b/app/services/ci/generate_coverage_reports_service.rb
index 23e67ac1b9e..063fb966183 100644
--- a/app/services/ci/generate_coverage_reports_service.rb
+++ b/app/services/ci/generate_coverage_reports_service.rb
@@ -12,7 +12,7 @@ module Ci
{
status: :parsed,
key: key(base_pipeline, head_pipeline),
- data: Gitlab::Ci::Pipeline::Artifact::CodeCoverage.new(head_pipeline.pipeline_artifacts.find_with_code_coverage).for_files(merge_request.new_paths)
+ data: head_pipeline.pipeline_artifacts.find_with_code_coverage.present.for_files(merge_request.new_paths)
}
rescue => e
Gitlab::ErrorTracking.track_exception(e, project_id: project.id)
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 20660e61f38..c875caca94a 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -55,8 +55,8 @@
= s_('Profiles|Deleting an account has the following effects:')
= render 'users/deletion_guidance', user: current_user
- %button#delete-account-button.btn.btn-danger.disabled{ data: { toggle: 'modal',
- target: '#delete-account-modal', qa_selector: 'delete_account_button' } }
+ -# Delete button here
+ %button#delete-account-button.btn.btn-danger.disabled{ data: { qa_selector: 'delete_account_button' } }
= s_('Profiles|Delete account')
#delete-account-modal{ data: { action_url: user_registration_path,
diff --git a/changelogs/unreleased/235490-generic-packages-generic-type.yml b/changelogs/unreleased/235490-generic-packages-generic-type.yml
new file mode 100644
index 00000000000..72bd2419b8e
--- /dev/null
+++ b/changelogs/unreleased/235490-generic-packages-generic-type.yml
@@ -0,0 +1,5 @@
+---
+title: Add new "generic" package type
+merge_request: 40061
+author:
+type: added
diff --git a/changelogs/unreleased/mo-sort-test-data-by-duration.yml b/changelogs/unreleased/mo-sort-test-data-by-duration.yml
new file mode 100644
index 00000000000..8f8d97e6562
--- /dev/null
+++ b/changelogs/unreleased/mo-sort-test-data-by-duration.yml
@@ -0,0 +1,5 @@
+---
+title: Sort TestCase data by status and execution_time
+merge_request: 40722
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-add-if-exists-pgdump.yml b/changelogs/unreleased/sh-add-if-exists-pgdump.yml
new file mode 100644
index 00000000000..774024591c9
--- /dev/null
+++ b/changelogs/unreleased/sh-add-if-exists-pgdump.yml
@@ -0,0 +1,5 @@
+---
+title: Add --if-exists to pg_dump command-line in backup creation
+merge_request: 40792
+author:
+type: other
diff --git a/db/migrate/20200820204041_create_ci_platform_metrics.rb b/db/migrate/20200820204041_create_ci_platform_metrics.rb
index 357f62c441b..27a5a3dc8eb 100644
--- a/db/migrate/20200820204041_create_ci_platform_metrics.rb
+++ b/db/migrate/20200820204041_create_ci_platform_metrics.rb
@@ -4,6 +4,7 @@ class CreateCiPlatformMetrics < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
+ CI_VARIABLES_KEY_INDEX_NAME = "index_ci_variables_on_key"
disable_ddl_transaction!
@@ -17,11 +18,14 @@ class CreateCiPlatformMetrics < ActiveRecord::Migration[6.0]
end
add_text_limit :ci_platform_metrics, :platform_target, 255
- add_concurrent_index :ci_variables, :key
+ add_concurrent_index :ci_variables, :key, name: CI_VARIABLES_KEY_INDEX_NAME
end
def down
- drop_table :ci_platform_metrics
- remove_concurrent_index :ci_variables, :key, name: 'index_ci_variables_on_key'
+ if table_exists?(:ci_platform_metrics)
+ drop_table :ci_platform_metrics
+ end
+
+ remove_concurrent_index :ci_variables, :key, name: CI_VARIABLES_KEY_INDEX_NAME
end
end
diff --git a/db/migrate/20200821034419_add_unique_index_for_generic_packages.rb b/db/migrate/20200821034419_add_unique_index_for_generic_packages.rb
new file mode 100644
index 00000000000..f0e4749bbb5
--- /dev/null
+++ b/db/migrate/20200821034419_add_unique_index_for_generic_packages.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class AddUniqueIndexForGenericPackages < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ INDEX_NAME = 'index_packages_on_project_id_name_version_unique_when_generic'
+ PACKAGE_TYPE_GENERIC = 7
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :packages_packages, [:project_id, :name, :version], unique: true, where: "package_type = #{PACKAGE_TYPE_GENERIC}", name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name(:packages_packages, INDEX_NAME)
+ end
+end
diff --git a/db/migrate/20200824045812_add_generic_package_max_file_size_to_plan_limits.rb b/db/migrate/20200824045812_add_generic_package_max_file_size_to_plan_limits.rb
new file mode 100644
index 00000000000..eea4f8de7bf
--- /dev/null
+++ b/db/migrate/20200824045812_add_generic_package_max_file_size_to_plan_limits.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddGenericPackageMaxFileSizeToPlanLimits < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def change
+ add_column(:plan_limits, :generic_packages_max_file_size, :bigint, default: 5.gigabytes, null: false)
+ end
+end
diff --git a/db/schema_migrations/20200821034419 b/db/schema_migrations/20200821034419
new file mode 100644
index 00000000000..56ede8eff69
--- /dev/null
+++ b/db/schema_migrations/20200821034419
@@ -0,0 +1 @@
+ddf3452bb44437324d20c9db03e998f8903f5ff9732d29cf85dd5d579507952d
\ No newline at end of file
diff --git a/db/schema_migrations/20200824045812 b/db/schema_migrations/20200824045812
new file mode 100644
index 00000000000..c628ca3eb18
--- /dev/null
+++ b/db/schema_migrations/20200824045812
@@ -0,0 +1 @@
+4f3528d7df6e61c8b14911644f9223ac5f6e678184d1c8370d1e9a60389cd60c
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 791253dc43d..f5a8d4f9f16 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -14195,7 +14195,8 @@ CREATE TABLE public.plan_limits (
maven_max_file_size bigint DEFAULT 52428800 NOT NULL,
npm_max_file_size bigint DEFAULT 52428800 NOT NULL,
nuget_max_file_size bigint DEFAULT 52428800 NOT NULL,
- pypi_max_file_size bigint DEFAULT 52428800 NOT NULL
+ pypi_max_file_size bigint DEFAULT 52428800 NOT NULL,
+ generic_packages_max_file_size bigint DEFAULT '5368709120'::bigint NOT NULL
);
CREATE SEQUENCE public.plan_limits_id_seq
@@ -20414,6 +20415,8 @@ CREATE INDEX index_packages_maven_metadata_on_package_id_and_path ON public.pack
CREATE INDEX index_packages_nuget_dl_metadata_on_dependency_link_id ON public.packages_nuget_dependency_link_metadata USING btree (dependency_link_id);
+CREATE UNIQUE INDEX index_packages_on_project_id_name_version_unique_when_generic ON public.packages_packages USING btree (project_id, name, version) WHERE (package_type = 7);
+
CREATE INDEX index_packages_package_files_file_store_is_null ON public.packages_package_files USING btree (id) WHERE (file_store IS NULL);
CREATE INDEX index_packages_package_files_on_file_store ON public.packages_package_files USING btree (file_store);
diff --git a/doc/.vale/gitlab/British.yml b/doc/.vale/gitlab/British.yml
index 229eb324eb6..7221d7d24aa 100644
--- a/doc/.vale/gitlab/British.yml
+++ b/doc/.vale/gitlab/British.yml
@@ -67,7 +67,6 @@ swap:
matt: matte
meagre: meager
metre: meter
- mitre: miter
modelling: modeling
moustache: mustache
neighbour: neighbor
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index e97ce8b99e3..faa9cd519fd 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -10565,6 +10565,11 @@ enum PackageTypeEnum {
"""
CONAN
+ """
+ Packages from the generic package manager
+ """
+ GENERIC
+
"""
Packages from the maven package manager
"""
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index f7ddefa46d9..5921f3719c1 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -31806,6 +31806,12 @@
"description": "Packages from the composer package manager",
"isDeprecated": false,
"deprecationReason": null
+ },
+ {
+ "name": "GENERIC",
+ "description": "Packages from the generic package manager",
+ "isDeprecated": false,
+ "deprecationReason": null
}
],
"possibleTypes": null
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
index 1bc32546abf..ddddd57bd60 100644
--- a/lib/backup/database.rb
+++ b/lib/backup/database.rb
@@ -27,6 +27,7 @@ module Backup
progress.print "Dumping PostgreSQL database #{config['database']} ... "
pg_env
pgsql_args = ["--clean"] # Pass '--clean' to include 'DROP TABLE' statements in the DB dump.
+ pgsql_args << '--if-exists'
if Gitlab.config.backup.pg_schema
pgsql_args << '-n'
diff --git a/lib/gitlab/ci/pipeline/artifact/code_coverage.rb b/lib/gitlab/ci/pipeline/artifact/code_coverage.rb
deleted file mode 100644
index d8f28bde7ce..00000000000
--- a/lib/gitlab/ci/pipeline/artifact/code_coverage.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Ci
- module Pipeline
- module Artifact
- class CodeCoverage
- include Gitlab::Utils::StrongMemoize
-
- def initialize(pipeline_artifact)
- @pipeline_artifact = pipeline_artifact
- end
-
- def for_files(filenames)
- coverage_files = raw_report["files"].select { |key| filenames.include?(key) }
-
- { files: coverage_files }
- end
-
- private
-
- def raw_report
- strong_memoize(:raw_report) do
- @pipeline_artifact.each_blob do |blob|
- Gitlab::Json.parse(blob)
- end
- end
- end
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/ci/reports/test_case.rb b/lib/gitlab/ci/reports/test_case.rb
index 75898745366..15a3c862c9e 100644
--- a/lib/gitlab/ci/reports/test_case.rb
+++ b/lib/gitlab/ci/reports/test_case.rb
@@ -8,7 +8,7 @@ module Gitlab
STATUS_FAILED = 'failed'
STATUS_SKIPPED = 'skipped'
STATUS_ERROR = 'error'
- STATUS_TYPES = [STATUS_SUCCESS, STATUS_FAILED, STATUS_SKIPPED, STATUS_ERROR].freeze
+ STATUS_TYPES = [STATUS_ERROR, STATUS_FAILED, STATUS_SUCCESS, STATUS_SKIPPED].freeze
attr_reader :name, :classname, :execution_time, :status, :file, :system_output, :stack_trace, :key, :attachment, :job
diff --git a/lib/gitlab/ci/reports/test_suite.rb b/lib/gitlab/ci/reports/test_suite.rb
index 5ee779227ec..e9b78b841e4 100644
--- a/lib/gitlab/ci/reports/test_suite.rb
+++ b/lib/gitlab/ci/reports/test_suite.rb
@@ -78,11 +78,27 @@ module Gitlab
end
end
+ def sorted
+ sort_by_status
+ sort_by_execution_time_desc
+ self
+ end
+
private
def existing_key?(test_case)
@test_cases[test_case.status]&.key?(test_case.key)
end
+
+ def sort_by_status
+ @test_cases = @test_cases.sort_by { |status, _| Gitlab::Ci::Reports::TestCase::STATUS_TYPES.index(status) }.to_h
+ end
+
+ def sort_by_execution_time_desc
+ @test_cases = @test_cases.keys.each_with_object({}) do |key, hash|
+ hash[key] = @test_cases[key].sort_by { |_key, test_case| -test_case.execution_time }.to_h
+ end
+ end
end
end
end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 2702dfc85ae..0f52d8ab95d 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -104,6 +104,10 @@ module Gitlab
\b (?# word boundary)
/ix.freeze
end
+
+ def generic_package_version_regex
+ /\A\d+\.\d+\.\d+\z/
+ end
end
extend self
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index fca2bc8cc3b..9305d66f79d 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -7947,6 +7947,9 @@ msgstr ""
msgid "Delete Snippet"
msgstr ""
+msgid "Delete account"
+msgstr ""
+
msgid "Delete artifacts"
msgstr ""
@@ -18724,15 +18727,9 @@ msgstr ""
msgid "Profiles|Default notification email"
msgstr ""
-msgid "Profiles|Delete Account"
-msgstr ""
-
msgid "Profiles|Delete account"
msgstr ""
-msgid "Profiles|Delete your account?"
-msgstr ""
-
msgid "Profiles|Deleting an account has the following effects:"
msgstr ""
diff --git a/rubocop/cop/migration/complex_indexes_require_name.rb b/rubocop/cop/migration/complex_indexes_require_name.rb
index 173b327be12..82deb36716d 100644
--- a/rubocop/cop/migration/complex_indexes_require_name.rb
+++ b/rubocop/cop/migration/complex_indexes_require_name.rb
@@ -10,8 +10,12 @@ module RuboCop
MSG = 'indexes added with custom options must be explicitly named'
+ def_node_matcher :match_create_table_index_with_options, <<~PATTERN
+ (send _ {:index } _ (hash $...))
+ PATTERN
+
def_node_matcher :match_add_index_with_options, <<~PATTERN
- (send _ {:add_concurrent_index} _ _ (hash $...))
+ (send _ {:add_index :add_concurrent_index} _ _ (hash $...))
PATTERN
def_node_matcher :name_option?, <<~PATTERN
@@ -26,7 +30,7 @@ module RuboCop
return unless in_migration?(node)
node.each_descendant(:send) do |send_node|
- next unless add_index_offense?(send_node)
+ next unless create_table_with_index_offense?(send_node) || add_index_offense?(send_node)
add_offense(send_node, location: :selector)
end
@@ -34,6 +38,10 @@ module RuboCop
private
+ def create_table_with_index_offense?(send_node)
+ match_create_table_index_with_options(send_node) { |option_nodes| needs_name_option?(option_nodes) }
+ end
+
def add_index_offense?(send_node)
match_add_index_with_options(send_node) { |option_nodes| needs_name_option?(option_nodes) }
end
diff --git a/spec/factories/packages.rb b/spec/factories/packages.rb
index 0f9138ddefe..ee9079579e0 100644
--- a/spec/factories/packages.rb
+++ b/spec/factories/packages.rb
@@ -121,6 +121,12 @@ FactoryBot.define do
conan_metadatum { build(:conan_metadatum, package: nil) }
end
end
+
+ factory :generic_package do
+ sequence(:name) { |n| "generic-package-#{n}" }
+ version { '1.0.0' }
+ package_type { :generic }
+ end
end
factory :composer_metadatum, class: 'Packages::Composer::Metadatum' do
diff --git a/spec/frontend/profile/account/components/delete_account_modal_spec.js b/spec/frontend/profile/account/components/delete_account_modal_spec.js
index 4da82152818..7834456f7c4 100644
--- a/spec/frontend/profile/account/components/delete_account_modal_spec.js
+++ b/spec/frontend/profile/account/components/delete_account_modal_spec.js
@@ -1,21 +1,49 @@
import Vue from 'vue';
import { TEST_HOST } from 'helpers/test_constants';
-import mountComponent from 'helpers/vue_mount_component_helper';
+import { merge } from 'lodash';
+import { mount } from '@vue/test-utils';
import deleteAccountModal from '~/profile/account/components/delete_account_modal.vue';
+const GlModalStub = {
+ name: 'gl-modal-stub',
+ template: `
+
+
+
+ `,
+};
+
describe('DeleteAccountModal component', () => {
const actionUrl = `${TEST_HOST}/delete/user`;
const username = 'hasnoname';
- let Component;
+ let wrapper;
let vm;
- beforeEach(() => {
- Component = Vue.extend(deleteAccountModal);
- });
+ const createWrapper = (options = {}) => {
+ wrapper = mount(
+ deleteAccountModal,
+ merge(
+ {},
+ {
+ propsData: {
+ actionUrl,
+ username,
+ },
+ stubs: {
+ GlModal: GlModalStub,
+ },
+ },
+ options,
+ ),
+ );
+ vm = wrapper.vm;
+ };
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
+ wrapper = null;
+ vm = null;
});
const findElements = () => {
@@ -23,16 +51,16 @@ describe('DeleteAccountModal component', () => {
return {
form: vm.$refs.form,
input: vm.$el.querySelector(`[name="${confirmation}"]`),
- submitButton: vm.$el.querySelector('.btn-danger'),
};
};
+ const findModal = () => wrapper.find(GlModalStub);
describe('with password confirmation', () => {
beforeEach(done => {
- vm = mountComponent(Component, {
- actionUrl,
- confirmWithPassword: true,
- username,
+ createWrapper({
+ propsData: {
+ confirmWithPassword: true,
+ },
});
vm.isOpen = true;
@@ -43,7 +71,7 @@ describe('DeleteAccountModal component', () => {
});
it('does not accept empty password', done => {
- const { form, input, submitButton } = findElements();
+ const { form, input } = findElements();
jest.spyOn(form, 'submit').mockImplementation(() => {});
input.value = '';
input.dispatchEvent(new Event('input'));
@@ -51,8 +79,8 @@ describe('DeleteAccountModal component', () => {
Vue.nextTick()
.then(() => {
expect(vm.enteredPassword).toBe(input.value);
- expect(submitButton).toHaveAttr('disabled', 'disabled');
- submitButton.click();
+ expect(findModal().attributes('ok-disabled')).toBe('true');
+ findModal().vm.$emit('primary');
expect(form.submit).not.toHaveBeenCalled();
})
@@ -61,7 +89,7 @@ describe('DeleteAccountModal component', () => {
});
it('submits form with password', done => {
- const { form, input, submitButton } = findElements();
+ const { form, input } = findElements();
jest.spyOn(form, 'submit').mockImplementation(() => {});
input.value = 'anything';
input.dispatchEvent(new Event('input'));
@@ -69,8 +97,8 @@ describe('DeleteAccountModal component', () => {
Vue.nextTick()
.then(() => {
expect(vm.enteredPassword).toBe(input.value);
- expect(submitButton).not.toHaveAttr('disabled', 'disabled');
- submitButton.click();
+ expect(findModal().attributes('ok-disabled')).toBeUndefined();
+ findModal().vm.$emit('primary');
expect(form.submit).toHaveBeenCalled();
})
@@ -81,10 +109,10 @@ describe('DeleteAccountModal component', () => {
describe('with username confirmation', () => {
beforeEach(done => {
- vm = mountComponent(Component, {
- actionUrl,
- confirmWithPassword: false,
- username,
+ createWrapper({
+ propsData: {
+ confirmWithPassword: false,
+ },
});
vm.isOpen = true;
@@ -95,7 +123,7 @@ describe('DeleteAccountModal component', () => {
});
it('does not accept wrong username', done => {
- const { form, input, submitButton } = findElements();
+ const { form, input } = findElements();
jest.spyOn(form, 'submit').mockImplementation(() => {});
input.value = 'this is wrong';
input.dispatchEvent(new Event('input'));
@@ -103,8 +131,8 @@ describe('DeleteAccountModal component', () => {
Vue.nextTick()
.then(() => {
expect(vm.enteredUsername).toBe(input.value);
- expect(submitButton).toHaveAttr('disabled', 'disabled');
- submitButton.click();
+ expect(findModal().attributes('ok-disabled')).toBe('true');
+ findModal().vm.$emit('primary');
expect(form.submit).not.toHaveBeenCalled();
})
@@ -113,7 +141,7 @@ describe('DeleteAccountModal component', () => {
});
it('submits form with correct username', done => {
- const { form, input, submitButton } = findElements();
+ const { form, input } = findElements();
jest.spyOn(form, 'submit').mockImplementation(() => {});
input.value = username;
input.dispatchEvent(new Event('input'));
@@ -121,8 +149,8 @@ describe('DeleteAccountModal component', () => {
Vue.nextTick()
.then(() => {
expect(vm.enteredUsername).toBe(input.value);
- expect(submitButton).not.toHaveAttr('disabled', 'disabled');
- submitButton.click();
+ expect(findModal().attributes('ok-disabled')).toBeUndefined();
+ findModal().vm.$emit('primary');
expect(form.submit).toHaveBeenCalled();
})
diff --git a/spec/graphql/types/package_type_enum_spec.rb b/spec/graphql/types/package_type_enum_spec.rb
index fadec9744ed..80a20a68bc2 100644
--- a/spec/graphql/types/package_type_enum_spec.rb
+++ b/spec/graphql/types/package_type_enum_spec.rb
@@ -4,6 +4,6 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['PackageTypeEnum'] do
it 'exposes all package types' do
- expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER])
+ expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC])
end
end
diff --git a/spec/lib/gitlab/ci/reports/test_suite_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
index fbe3473f6b0..15fa78444e5 100644
--- a/spec/lib/gitlab/ci/reports/test_suite_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
@@ -176,6 +176,37 @@ RSpec.describe Gitlab::Ci::Reports::TestSuite do
end
end
+ describe '#sorted' do
+ subject { test_suite.sorted }
+
+ context 'when there are multiple failed test cases' do
+ before do
+ test_suite.add_test_case(create_test_case_rspec_failed('test_spec_1', 1.11))
+ test_suite.add_test_case(create_test_case_rspec_failed('test_spec_2', 4.44))
+ end
+
+ it 'returns test cases sorted by execution time desc' do
+ expect(subject.test_cases['failed'].each_value.first.execution_time).to eq(4.44)
+ expect(subject.test_cases['failed'].values.second.execution_time).to eq(1.11)
+ end
+ end
+
+ context 'when there are multiple test cases' do
+ let(:status_ordered) { %w(error failed success skipped) }
+
+ before do
+ test_suite.add_test_case(test_case_success)
+ test_suite.add_test_case(test_case_failed)
+ test_suite.add_test_case(test_case_error)
+ test_suite.add_test_case(test_case_skipped)
+ end
+
+ it 'returns test cases sorted by status' do
+ expect(subject.test_cases.keys).to eq(status_ordered)
+ end
+ end
+ end
+
Gitlab::Ci::Reports::TestCase::STATUS_TYPES.each do |status_type|
describe "##{status_type}" do
subject { test_suite.public_send("#{status_type}") }
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index 838667675c7..f736f19ca9a 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -426,4 +426,21 @@ RSpec.describe Gitlab::Regex do
it { is_expected.not_to match('1.2') }
it { is_expected.not_to match('1./2.3') }
end
+
+ describe '.generic_package_version_regex' do
+ subject { described_class.generic_package_version_regex }
+
+ it { is_expected.to match('1.2.3') }
+ it { is_expected.to match('1.3.350') }
+ it { is_expected.not_to match('1.3.350-20201230123456') }
+ it { is_expected.not_to match('..1.2.3') }
+ it { is_expected.not_to match(' 1.2.3') }
+ it { is_expected.not_to match("1.2.3 \r\t") }
+ it { is_expected.not_to match("\r\t 1.2.3") }
+ it { is_expected.not_to match('1.2.3-4/../../') }
+ it { is_expected.not_to match('1.2.3-4%2e%2e%') }
+ it { is_expected.not_to match('../../../../../1.2.3') }
+ it { is_expected.not_to match('%2e%2e%2f1.2.3') }
+ it { is_expected.not_to match('') }
+ end
end
diff --git a/spec/models/ci/pipeline_artifact_spec.rb b/spec/models/ci/pipeline_artifact_spec.rb
index 14f3bcc463e..716ab4d8522 100644
--- a/spec/models/ci/pipeline_artifact_spec.rb
+++ b/spec/models/ci/pipeline_artifact_spec.rb
@@ -109,4 +109,14 @@ RSpec.describe Ci::PipelineArtifact, type: :model do
end
end
end
+
+ describe '#present' do
+ subject { coverage_report.present }
+
+ context 'when file_type is code_coverage' do
+ it 'uses code coverage presenter' do
+ expect(subject.present).to be_kind_of(Ci::PipelineArtifacts::CodeCoveragePresenter)
+ end
+ end
+ end
end
diff --git a/spec/models/packages/package_spec.rb b/spec/models/packages/package_spec.rb
index da15b0f0453..4867ac2a75b 100644
--- a/spec/models/packages/package_spec.rb
+++ b/spec/models/packages/package_spec.rb
@@ -236,6 +236,25 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.not_to allow_value('%2e%2e%2f1.2.3').for(:version) }
end
+ context 'generic package' do
+ subject { build_stubbed(:generic_package) }
+
+ it { is_expected.to validate_presence_of(:version) }
+ it { is_expected.to allow_value('1.2.3').for(:version) }
+ it { is_expected.to allow_value('1.3.350').for(:version) }
+ it { is_expected.not_to allow_value('1.3.350-20201230123456').for(:version) }
+ it { is_expected.not_to allow_value('..1.2.3').for(:version) }
+ it { is_expected.not_to allow_value(' 1.2.3').for(:version) }
+ it { is_expected.not_to allow_value("1.2.3 \r\t").for(:version) }
+ it { is_expected.not_to allow_value("\r\t 1.2.3").for(:version) }
+ it { is_expected.not_to allow_value('1.2.3-4/../../').for(:version) }
+ it { is_expected.not_to allow_value('1.2.3-4%2e%2e%').for(:version) }
+ it { is_expected.not_to allow_value('../../../../../1.2.3').for(:version) }
+ it { is_expected.not_to allow_value('%2e%2e%2f1.2.3').for(:version) }
+ it { is_expected.not_to allow_value('').for(:version) }
+ it { is_expected.not_to allow_value(nil).for(:version) }
+ end
+
it_behaves_like 'validating version to be SemVer compliant for', :npm_package
it_behaves_like 'validating version to be SemVer compliant for', :nuget_package
end
@@ -552,11 +571,17 @@ RSpec.describe Packages::Package, type: :model do
describe 'plan_limits' do
Packages::Package.package_types.keys.without('composer').each do |pt|
+ plan_limit_name = if pt == 'generic'
+ "#{pt}_packages_max_file_size"
+ else
+ "#{pt}_max_file_size"
+ end
+
context "File size limits for #{pt}" do
let(:package) { create("#{pt}_package") }
- it "plan_limits includes column #{pt}_max_file_size" do
- expect { package.project.actual_limits.send("#{pt}_max_file_size") }
+ it "plan_limits includes column #{plan_limit_name}" do
+ expect { package.project.actual_limits.send(plan_limit_name) }
.not_to raise_error(NoMethodError)
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/artifact/code_coverage_spec.rb b/spec/presenters/ci/pipeline_artifacts/code_coverage_presenter_spec.rb
similarity index 89%
rename from spec/lib/gitlab/ci/pipeline/artifact/code_coverage_spec.rb
rename to spec/presenters/ci/pipeline_artifacts/code_coverage_presenter_spec.rb
index f3bc9fd0ec2..e679f5fa144 100644
--- a/spec/lib/gitlab/ci/pipeline/artifact/code_coverage_spec.rb
+++ b/spec/presenters/ci/pipeline_artifacts/code_coverage_presenter_spec.rb
@@ -2,12 +2,13 @@
require 'spec_helper'
-RSpec.describe Gitlab::Ci::Pipeline::Artifact::CodeCoverage do
+RSpec.describe Ci::PipelineArtifacts::CodeCoveragePresenter do
let(:pipeline_artifact) { create(:ci_pipeline_artifact, :with_code_coverage_with_multiple_files) }
- let(:code_coverage) { described_class.new(pipeline_artifact) }
+
+ subject(:presenter) { described_class.new(pipeline_artifact) }
describe '#for_files' do
- subject { code_coverage.for_files(filenames) }
+ subject { presenter.for_files(filenames) }
context 'when code coverage has data' do
context 'when filenames is empty' do
diff --git a/spec/rubocop/cop/migration/complex_indexes_require_name_spec.rb b/spec/rubocop/cop/migration/complex_indexes_require_name_spec.rb
index 5804b4b00fc..b769109057e 100644
--- a/spec/rubocop/cop/migration/complex_indexes_require_name_spec.rb
+++ b/spec/rubocop/cop/migration/complex_indexes_require_name_spec.rb
@@ -14,40 +14,120 @@ RSpec.describe RuboCop::Cop::Migration::ComplexIndexesRequireName, type: :ruboco
allow(cop).to receive(:in_migration?).and_return(true)
end
- context 'when indexes are configured with an options hash, but no name' do
- it 'registers an offense' do
- expect_offense(<<~RUBY)
- class TestComplexIndexes < ActiveRecord::Migration[6.0]
- DOWNTIME = false
+ context 'when creating complex indexes as part of create_table' do
+ context 'when indexes are configured with an options hash, but no name' do
+ it 'registers an offense' do
+ expect_offense(<<~RUBY)
+ class TestComplexIndexes < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
- INDEX_NAME = 'my_test_name'
+ def up
+ create_table :test_table do |t|
+ t.integer :column1, null: false
+ t.integer :column2, null: false
+ t.jsonb :column3
- disable_ddl_transaction!
+ t.index :column1, unique: true
+ t.index :column2, where: 'column1 = 0'
+ ^^^^^ #{described_class::MSG}
+ t.index :column3, using: :gin
+ ^^^^^ #{described_class::MSG}
+ end
+ end
- def up
- add_concurrent_index :test_indexes, :column1
-
- add_concurrent_index :test_indexes, :column2, where: "column2 = 'value'", order: { column4: :desc }
- ^^^^^^^^^^^^^^^^^^^^ #{described_class::MSG}
-
- add_concurrent_index :test_indexes, :column3, where: 'column3 = 10', name: 'idx_equal_to_10'
+ def down
+ drop_table :test_table
+ end
end
+ RUBY
- def down
- add_concurrent_index :test_indexes, :column4, 'unique' => true
+ expect(cop.offenses.map(&:cop_name)).to all(eq("Migration/#{described_class.name.demodulize}"))
+ end
+ end
- add_concurrent_index :test_indexes, :column4, 'unique' => true, where: 'column4 IS NOT NULL'
- ^^^^^^^^^^^^^^^^^^^^ #{described_class::MSG}
+ context 'when indexes are configured with an options hash and name' do
+ it 'registers no offense' do
+ expect_no_offenses(<<~RUBY)
+ class TestComplexIndexes < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
- add_concurrent_index :test_indexes, :column5, using: :gin, name: INDEX_NAME
+ def up
+ create_table :test_table do |t|
+ t.integer :column1, null: false
+ t.integer :column2, null: false
+ t.jsonb :column3
- add_concurrent_index :test_indexes, :column6, using: :gin, opclass: :gin_trgm_ops
- ^^^^^^^^^^^^^^^^^^^^ #{described_class::MSG}
+ t.index :column1, unique: true
+ t.index :column2, where: 'column1 = 0', name: 'my_index_1'
+ t.index :column3, using: :gin, name: 'my_gin_index'
+ end
+ end
+
+ def down
+ drop_table :test_table
+ end
end
- end
- RUBY
+ RUBY
+ end
+ end
+ end
- expect(cop.offenses.map(&:cop_name)).to all(eq("Migration/#{described_class.name.demodulize}"))
+ context 'when indexes are added to an existing table' do
+ context 'when indexes are configured with an options hash, but no name' do
+ it 'registers an offense' do
+ expect_offense(<<~RUBY)
+ class TestComplexIndexes < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_index :test_indexes, :column1
+
+ add_index :test_indexes, :column2, where: "column2 = 'value'", order: { column4: :desc }
+ ^^^^^^^^^ #{described_class::MSG}
+ end
+
+ def down
+ add_index :test_indexes, :column4, 'unique' => true, where: 'column4 IS NOT NULL'
+ ^^^^^^^^^ #{described_class::MSG}
+
+ add_concurrent_index :test_indexes, :column6, using: :gin, opclass: :gin_trgm_ops
+ ^^^^^^^^^^^^^^^^^^^^ #{described_class::MSG}
+ end
+ end
+ RUBY
+
+ expect(cop.offenses.map(&:cop_name)).to all(eq("Migration/#{described_class.name.demodulize}"))
+ end
+ end
+
+ context 'when indexes are configured with an options hash and a name' do
+ it 'registers no offenses' do
+ expect_no_offenses(<<~RUBY)
+ class TestComplexIndexes < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ INDEX_NAME = 'my_test_name'
+
+ disable_ddl_transaction!
+
+ def up
+ add_index :test_indexes, :column1
+
+ add_index :test_indexes, :column2, where: "column2 = 'value'", order: { column4: :desc }, name: 'my_index_1'
+
+ add_concurrent_index :test_indexes, :column3, where: 'column3 = 10', name: 'idx_equal_to_10'
+ end
+
+ def down
+ add_index :test_indexes, :column4, 'unique' => true, where: 'column4 IS NOT NULL', name: 'my_index_2'
+
+ add_concurrent_index :test_indexes, :column6, using: :gin, opclass: :gin_trgm_ops, name: INDEX_NAME
+ end
+ end
+ RUBY
+ end
end
end
end
@@ -65,7 +145,13 @@ RSpec.describe RuboCop::Cop::Migration::ComplexIndexesRequireName, type: :ruboco
disable_ddl_transaction!
def up
- add_concurrent_index :test_indexes, :column1, where: "some_column = 'value'"
+ create_table :test_table do |t|
+ t.integer :column1
+
+ t.index :column1, where: 'column2 IS NOT NULL'
+ end
+
+ add_index :test_indexes, :column1, where: "some_column = 'value'"
end
def down
diff --git a/spec/services/ci/generate_coverage_reports_service_spec.rb b/spec/services/ci/generate_coverage_reports_service_spec.rb
index 722b92ea3b6..d39053adebc 100644
--- a/spec/services/ci/generate_coverage_reports_service_spec.rb
+++ b/spec/services/ci/generate_coverage_reports_service_spec.rb
@@ -16,7 +16,8 @@ RSpec.describe Ci::GenerateCoverageReportsService do
let!(:base_pipeline) { nil }
it 'returns status and data', :aggregate_failures do
- expect_next_instance_of(Gitlab::Ci::Pipeline::Artifact::CodeCoverage) do |instance|
+ expect_any_instance_of(Ci::PipelineArtifact) do |instance|
+ expect(instance).to receive(:present)
expect(instance).to receive(:for_files).with(merge_request.new_paths).and_call_original
end
diff --git a/spec/support/shared_examples/services/packages_shared_examples.rb b/spec/support/shared_examples/services/packages_shared_examples.rb
index 45a4c2bb151..836997e78f5 100644
--- a/spec/support/shared_examples/services/packages_shared_examples.rb
+++ b/spec/support/shared_examples/services/packages_shared_examples.rb
@@ -161,6 +161,7 @@ RSpec.shared_examples 'filters on each package_type' do |is_project: false|
let_it_be(:package4) { create(:nuget_package, project: project) }
let_it_be(:package5) { create(:pypi_package, project: project) }
let_it_be(:package6) { create(:composer_package, project: project) }
+ let_it_be(:package7) { create(:generic_package, project: project) }
Packages::Package.package_types.keys.each do |package_type|
context "for package type #{package_type}" do
diff --git a/spec/support/test_reports/test_reports_helper.rb b/spec/support/test_reports/test_reports_helper.rb
index 6ba50c83b25..ad9ecb6f460 100644
--- a/spec/support/test_reports/test_reports_helper.rb
+++ b/spec/support/test_reports/test_reports_helper.rb
@@ -10,12 +10,12 @@ module TestReportsHelper
status: Gitlab::Ci::Reports::TestCase::STATUS_SUCCESS)
end
- def create_test_case_rspec_failed(name = 'test_spec')
+ def create_test_case_rspec_failed(name = 'test_spec', execution_time = 2.22)
Gitlab::Ci::Reports::TestCase.new(
name: 'Test#sum when a is 1 and b is 3 returns summary',
classname: "spec.#{name}",
file: './spec/test_spec.rb',
- execution_time: 2.22,
+ execution_time: execution_time,
system_output: sample_rspec_failed_message,
status: Gitlab::Ci::Reports::TestCase::STATUS_FAILED)
end