Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
304e230182
commit
5b8f2c8a24
|
@ -1,12 +1,12 @@
|
|||
<script>
|
||||
/* eslint-disable vue/no-v-html */
|
||||
import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
|
||||
import { GlModal } from '@gitlab/ui';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import csrf from '~/lib/utils/csrf';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DeprecatedModal,
|
||||
GlModal,
|
||||
},
|
||||
props: {
|
||||
actionUrl: {
|
||||
|
@ -55,21 +55,34 @@ You are about to permanently delete %{yourAccount}, and all of the issues, merge
|
|||
Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
|
||||
{
|
||||
yourAccount: `<strong>${s__('Profiles|your account')}</strong>`,
|
||||
deleteAccount: `<strong>${s__('Profiles|Delete Account')}</strong>`,
|
||||
deleteAccount: `<strong>${s__('Profiles|Delete account')}</strong>`,
|
||||
},
|
||||
false,
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
primaryProps() {
|
||||
return {
|
||||
text: s__('Delete account'),
|
||||
attributes: [{ variant: 'danger' }, { category: 'primary' }, { disabled: !this.canSubmit }],
|
||||
};
|
||||
},
|
||||
cancelProps() {
|
||||
return {
|
||||
text: s__('Cancel'),
|
||||
};
|
||||
},
|
||||
canSubmit() {
|
||||
if (this.confirmWithPassword) {
|
||||
return this.enteredPassword !== '';
|
||||
}
|
||||
|
||||
return this.enteredUsername === this.username;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onSubmit() {
|
||||
if (!this.canSubmit) {
|
||||
return;
|
||||
}
|
||||
this.$refs.form.submit();
|
||||
},
|
||||
},
|
||||
|
@ -77,42 +90,39 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<deprecated-modal
|
||||
id="delete-account-modal"
|
||||
:title="s__('Profiles|Delete your account?')"
|
||||
:text="text"
|
||||
:primary-button-label="s__('Profiles|Delete account')"
|
||||
:submit-disabled="!canSubmit()"
|
||||
kind="danger"
|
||||
@submit="onSubmit"
|
||||
<gl-modal
|
||||
modal-id="delete-account-modal"
|
||||
title="Profiles"
|
||||
:action-primary="primaryProps"
|
||||
:action-cancel="cancelProps"
|
||||
:ok-disabled="!canSubmit"
|
||||
@primary="onSubmit"
|
||||
>
|
||||
<template #body="props">
|
||||
<p v-html="props.text"></p>
|
||||
<p v-html="text"></p>
|
||||
|
||||
<form ref="form" :action="actionUrl" method="post">
|
||||
<input type="hidden" name="_method" value="delete" />
|
||||
<input :value="csrfToken" type="hidden" name="authenticity_token" />
|
||||
<form ref="form" :action="actionUrl" method="post">
|
||||
<input type="hidden" name="_method" value="delete" />
|
||||
<input :value="csrfToken" type="hidden" name="authenticity_token" />
|
||||
|
||||
<p id="input-label" v-html="inputLabel"></p>
|
||||
<p id="input-label" v-html="inputLabel"></p>
|
||||
|
||||
<input
|
||||
v-if="confirmWithPassword"
|
||||
v-model="enteredPassword"
|
||||
name="password"
|
||||
class="form-control"
|
||||
type="password"
|
||||
data-qa-selector="password_confirmation_field"
|
||||
aria-labelledby="input-label"
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
v-model="enteredUsername"
|
||||
name="username"
|
||||
class="form-control"
|
||||
type="text"
|
||||
aria-labelledby="input-label"
|
||||
/>
|
||||
</form>
|
||||
</template>
|
||||
</deprecated-modal>
|
||||
<input
|
||||
v-if="confirmWithPassword"
|
||||
v-model="enteredPassword"
|
||||
name="password"
|
||||
class="form-control"
|
||||
type="password"
|
||||
data-qa-selector="password_confirmation_field"
|
||||
aria-labelledby="input-label"
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
v-model="enteredUsername"
|
||||
name="username"
|
||||
class="form-control"
|
||||
type="text"
|
||||
aria-labelledby="input-label"
|
||||
/>
|
||||
</form>
|
||||
</gl-modal>
|
||||
</template>
|
||||
|
|
|
@ -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', {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)) }
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add new "generic" package type
|
||||
merge_request: 40061
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Sort TestCase data by status and execution_time
|
||||
merge_request: 40722
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add --if-exists to pg_dump command-line in backup creation
|
||||
merge_request: 40792
|
||||
author:
|
||||
type: other
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
ddf3452bb44437324d20c9db03e998f8903f5ff9732d29cf85dd5d579507952d
|
|
@ -0,0 +1 @@
|
|||
4f3528d7df6e61c8b14911644f9223ac5f6e678184d1c8370d1e9a60389cd60c
|
|
@ -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);
|
||||
|
|
|
@ -67,7 +67,6 @@ swap:
|
|||
matt: matte
|
||||
meagre: meager
|
||||
metre: meter
|
||||
mitre: miter
|
||||
modelling: modeling
|
||||
moustache: mustache
|
||||
neighbour: neighbor
|
||||
|
|
|
@ -10565,6 +10565,11 @@ enum PackageTypeEnum {
|
|||
"""
|
||||
CONAN
|
||||
|
||||
"""
|
||||
Packages from the generic package manager
|
||||
"""
|
||||
GENERIC
|
||||
|
||||
"""
|
||||
Packages from the maven package manager
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: `
|
||||
<div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
|
||||
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();
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}") }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue