Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-03-15 21:14:36 +00:00
parent f6b58d1490
commit 885275c832
87 changed files with 893 additions and 552 deletions

View File

@ -106,28 +106,28 @@ workflow:
# For tags, create a pipeline. # For tags, create a pipeline.
- if: '$CI_COMMIT_TAG' - if: '$CI_COMMIT_TAG'
variables: variables:
<<: *ruby2-variables <<: *ruby3-variables
PIPELINE_NAME: 'Ruby 2 $CI_COMMIT_TAG tag pipeline' PIPELINE_NAME: 'Ruby 3 $CI_COMMIT_TAG tag pipeline'
# If `$GITLAB_INTERNAL` isn't set, don't create a pipeline. # If `$GITLAB_INTERNAL` isn't set, don't create a pipeline.
- if: '$GITLAB_INTERNAL == null' - if: '$GITLAB_INTERNAL == null'
when: never when: never
# For stable, auto-deploy, and security branches, create a pipeline. # For stable, auto-deploy, and security branches, create a pipeline.
- if: '$CI_COMMIT_BRANCH =~ /^[\d-]+-stable(-ee)?$/' - if: '$CI_COMMIT_BRANCH =~ /^[\d-]+-stable(-ee)?$/'
variables: variables:
<<: *ruby2-variables <<: *ruby3-variables
NOTIFY_PIPELINE_FAILURE_CHANNEL: "releases" NOTIFY_PIPELINE_FAILURE_CHANNEL: "releases"
PIPELINE_NAME: 'Ruby 2 $CI_COMMIT_BRANCH branch pipeline' PIPELINE_NAME: 'Ruby 3 $CI_COMMIT_BRANCH branch pipeline'
CREATE_INCIDENT_FOR_PIPELINE_FAILURE: "true" CREATE_INCIDENT_FOR_PIPELINE_FAILURE: "true"
BROKEN_BRANCH_INCIDENTS_PROJECT: "gitlab-org/release/tasks" BROKEN_BRANCH_INCIDENTS_PROJECT: "gitlab-org/release/tasks"
BROKEN_BRANCH_INCIDENTS_PROJECT_TOKEN: "${BROKEN_STABLE_INCIDENTS_PROJECT_TOKEN}" BROKEN_BRANCH_INCIDENTS_PROJECT_TOKEN: "${BROKEN_STABLE_INCIDENTS_PROJECT_TOKEN}"
- if: '$CI_COMMIT_BRANCH =~ /^\d+-\d+-auto-deploy-\d+$/' - if: '$CI_COMMIT_BRANCH =~ /^\d+-\d+-auto-deploy-\d+$/'
variables: variables:
<<: *ruby2-variables <<: *ruby3-variables
PIPELINE_NAME: 'Ruby 2 $CI_COMMIT_BRANCH branch pipeline' PIPELINE_NAME: 'Ruby 3 $CI_COMMIT_BRANCH branch pipeline'
- if: '$CI_COMMIT_BRANCH =~ /^security\//' - if: '$CI_COMMIT_BRANCH =~ /^security\//'
variables: variables:
<<: *ruby2-variables <<: *ruby3-variables
PIPELINE_NAME: 'Ruby 2 $CI_COMMIT_BRANCH branch pipeline' PIPELINE_NAME: 'Ruby 3 $CI_COMMIT_BRANCH branch pipeline'
variables: variables:
PG_VERSION: "12" PG_VERSION: "12"

View File

@ -1 +1 @@
71dbaa115c10ae08292a7df74d3f8297ab38f208 705e00092a936f0233fe16339aba3a1c65db36b0

View File

@ -36,6 +36,10 @@ export const searchQuery = (state) => {
}; };
export const scopedIssuesPath = (state) => { export const scopedIssuesPath = (state) => {
if (state.searchContext?.project?.id && !state.searchContext?.project_metadata?.issues_path) {
return false;
}
return ( return (
state.searchContext?.project_metadata?.issues_path || state.searchContext?.project_metadata?.issues_path ||
state.searchContext?.group_metadata?.issues_path || state.searchContext?.group_metadata?.issues_path ||
@ -54,7 +58,7 @@ export const scopedMRPath = (state) => {
export const defaultSearchOptions = (state, getters) => { export const defaultSearchOptions = (state, getters) => {
const userName = gon.current_username; const userName = gon.current_username;
return [ const issues = [
{ {
html_id: 'default-issues-assigned', html_id: 'default-issues-assigned',
title: MSG_ISSUES_ASSIGNED_TO_ME, title: MSG_ISSUES_ASSIGNED_TO_ME,
@ -65,6 +69,9 @@ export const defaultSearchOptions = (state, getters) => {
title: MSG_ISSUES_IVE_CREATED, title: MSG_ISSUES_IVE_CREATED,
url: `${getters.scopedIssuesPath}/?author_username=${userName}`, url: `${getters.scopedIssuesPath}/?author_username=${userName}`,
}, },
];
const mergeRequests = [
{ {
html_id: 'default-mrs-assigned', html_id: 'default-mrs-assigned',
title: MSG_MR_ASSIGNED_TO_ME, title: MSG_MR_ASSIGNED_TO_ME,
@ -81,6 +88,7 @@ export const defaultSearchOptions = (state, getters) => {
url: `${getters.scopedMRPath}/?author_username=${userName}`, url: `${getters.scopedMRPath}/?author_username=${userName}`,
}, },
]; ];
return [...(getters.scopedIssuesPath ? issues : []), ...mergeRequests];
}; };
export const projectUrl = (state) => { export const projectUrl = (state) => {

View File

@ -1,16 +1,9 @@
<script> <script>
import { import { GlDropdown, GlDropdownItem, GlIcon, GlTooltipDirective as GlTooltip } from '@gitlab/ui';
GlButton,
GlDropdown,
GlDropdownItem,
GlIcon,
GlTooltipDirective as GlTooltip,
} from '@gitlab/ui';
export default { export default {
components: { components: {
GlIcon, GlIcon,
GlButton,
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
}, },
@ -18,10 +11,6 @@ export default {
GlTooltip, GlTooltip,
}, },
props: { props: {
isProjectsImportEnabled: {
type: Boolean,
required: true,
},
isFinished: { isFinished: {
type: Boolean, type: Boolean,
required: true, required: true,
@ -46,7 +35,7 @@ export default {
<template> <template>
<span class="gl-white-space-nowrap gl-inline-flex gl-align-items-center"> <span class="gl-white-space-nowrap gl-inline-flex gl-align-items-center">
<gl-dropdown <gl-dropdown
v-if="isProjectsImportEnabled && (isAvailableForImport || isFinished)" v-if="isAvailableForImport || isFinished"
:text="isFinished ? __('Re-import with projects') : __('Import with projects')" :text="isFinished ? __('Re-import with projects') : __('Import with projects')"
:disabled="isInvalid" :disabled="isInvalid"
variant="confirm" variant="confirm"
@ -59,16 +48,6 @@ export default {
isFinished ? __('Re-import without projects') : __('Import without projects') isFinished ? __('Re-import without projects') : __('Import without projects')
}}</gl-dropdown-item> }}</gl-dropdown-item>
</gl-dropdown> </gl-dropdown>
<gl-button
v-else-if="isAvailableForImport || isFinished"
:disabled="isInvalid"
variant="confirm"
category="secondary"
data-qa-selector="import_group_button"
@click="$emit('import-group')"
>
{{ isFinished ? __('Re-import') : __('Import') }}
</gl-button>
<gl-icon <gl-icon
v-if="isFinished" v-if="isFinished"
v-gl-tooltip v-gl-tooltip

View File

@ -1,7 +1,6 @@
<script> <script>
import { import {
GlAlert, GlAlert,
GlButton,
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
GlEmptyState, GlEmptyState,
@ -50,7 +49,6 @@ const DEFAULT_TD_CLASSES = 'gl-vertical-align-top!';
export default { export default {
components: { components: {
GlAlert, GlAlert,
GlButton,
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
GlEmptyState, GlEmptyState,
@ -165,10 +163,6 @@ export default {
], ],
computed: { computed: {
isProjectsImportEnabled() {
return Boolean(this.glFeatures.bulkImportProjects);
},
groups() { groups() {
return this.bulkImportSourceGroups?.nodes ?? []; return this.bulkImportSourceGroups?.nodes ?? [];
}, },
@ -707,11 +701,11 @@ export default {
</gl-sprintf> </gl-sprintf>
</span> </span>
<gl-dropdown <gl-dropdown
v-if="isProjectsImportEnabled"
:text="s__('BulkImport|Import with projects')" :text="s__('BulkImport|Import with projects')"
:disabled="!hasSelectedGroups" :disabled="!hasSelectedGroups"
variant="confirm" variant="confirm"
category="primary" category="primary"
data-testid="import-selected-groups-dropdown"
class="gl-ml-4" class="gl-ml-4"
split split
@click="importSelectedGroups({ migrateProjects: true })" @click="importSelectedGroups({ migrateProjects: true })"
@ -720,15 +714,6 @@ export default {
{{ s__('BulkImport|Import without projects') }} {{ s__('BulkImport|Import without projects') }}
</gl-dropdown-item> </gl-dropdown-item>
</gl-dropdown> </gl-dropdown>
<gl-button
v-else
category="primary"
variant="confirm"
class="gl-ml-4"
:disabled="!hasSelectedGroups"
@click="importSelectedGroups"
>{{ s__('BulkImport|Import selected') }}</gl-button
>
<span class="gl-ml-3"> <span class="gl-ml-3">
<gl-icon name="information-o" :size="12" class="gl-text-blue-600" /> <gl-icon name="information-o" :size="12" class="gl-text-blue-600" />
<gl-sprintf <gl-sprintf
@ -804,7 +789,6 @@ export default {
</template> </template>
<template #cell(actions)="{ item: group, index }"> <template #cell(actions)="{ item: group, index }">
<import-actions-cell <import-actions-cell
:is-projects-import-enabled="isProjectsImportEnabled"
:is-finished="group.flags.isFinished" :is-finished="group.flags.isFinished"
:is-available-for-import="group.flags.isAvailableForImport" :is-available-for-import="group.flags.isAvailableForImport"
:is-invalid="group.flags.isInvalid" :is-invalid="group.flags.isInvalid"

View File

@ -68,7 +68,7 @@ export default {
<div class="card card-slim gl-mt-5 gl-mb-0 gl-bg-gray-10"> <div class="card card-slim gl-mt-5 gl-mb-0 gl-bg-gray-10">
<div class="card-header gl-px-5 gl-py-4 gl-bg-white"> <div class="card-header gl-px-5 gl-py-4 gl-bg-white">
<div <div
class="card-title gl-relative gl-display-flex gl-align-items-center gl-line-height-20 gl-font-weight-bold gl-m-0" class="card-title gl-relative gl-display-flex gl-flex-wrap gl-align-items-center gl-line-height-20 gl-font-weight-bold gl-m-0"
> >
<gl-link <gl-link
class="anchor gl-absolute gl-text-decoration-none" class="anchor gl-absolute gl-text-decoration-none"
@ -82,6 +82,12 @@ export default {
<gl-icon name="merge-request" class="gl-ml-3 gl-mr-2 gl-text-gray-500" /> <gl-icon name="merge-request" class="gl-ml-3 gl-mr-2 gl-text-gray-500" />
<span data-testid="count" class="gl-text-gray-500">{{ totalCount }}</span> <span data-testid="count" class="gl-text-gray-500">{{ totalCount }}</span>
</template> </template>
<p
v-if="hasClosingMergeRequest && !isFetchingMergeRequests"
class="gl-font-sm gl-font-weight-normal gl-flex-basis-full gl-mb-0 gl-text-gray-500"
>
{{ closingMergeRequestsText }}
</p>
</div> </div>
</div> </div>
<gl-loading-icon <gl-loading-icon
@ -110,11 +116,5 @@ export default {
</li> </li>
</ul> </ul>
</div> </div>
<div
v-if="hasClosingMergeRequest && !isFetchingMergeRequests"
class="issue-closed-by-widget second-block gl-mt-3"
>
{{ closingMergeRequestsText }}
</div>
</div> </div>
</template> </template>

View File

@ -87,6 +87,9 @@ export default {
tags() { tags() {
return this.containerRepository?.tags?.nodes || []; return this.containerRepository?.tags?.nodes || [];
}, },
hideBulkDelete() {
return !(this.containerRepository?.canDelete || false);
},
tagsPageInfo() { tagsPageInfo() {
return this.containerRepository?.tags?.pageInfo; return this.containerRepository?.tags?.pageInfo;
}, },
@ -98,9 +101,6 @@ export default {
sort: this.sort, sort: this.sort,
}; };
}, },
showMultiDeleteButton() {
return this.tags.some((tag) => tag.canDelete) && !this.isMobile;
},
hasNoTags() { hasNoTags() {
return this.tags.length === 0; return this.tags.length === 0;
}, },
@ -186,6 +186,7 @@ export default {
/> />
<template v-else> <template v-else>
<registry-list <registry-list
:hidden-delete="hideBulkDelete"
:title="listTitle" :title="listTitle"
:pagination="tagsPageInfo" :pagination="tagsPageInfo"
:items="tags" :items="tags"

View File

@ -109,9 +109,6 @@ export default {
isInvalidTag() { isInvalidTag() {
return !this.tag.digest; return !this.tag.digest;
}, },
isDeleteDisabled() {
return this.disabled || !this.tag.canDelete;
},
}, },
}; };
</script> </script>
@ -179,16 +176,16 @@ export default {
</gl-sprintf> </gl-sprintf>
</span> </span>
</template> </template>
<template #right-action> <template v-if="tag.canDelete" #right-action>
<gl-dropdown <gl-dropdown
:disabled="isDeleteDisabled" :disabled="disabled"
icon="ellipsis_v" icon="ellipsis_v"
:text="$options.i18n.MORE_ACTIONS_TEXT" :text="$options.i18n.MORE_ACTIONS_TEXT"
:text-sr-only="true" :text-sr-only="true"
category="tertiary" category="tertiary"
no-caret no-caret
right right
:class="{ 'gl-opacity-0 gl-pointer-events-none': isDeleteDisabled }" :class="{ 'gl-opacity-0 gl-pointer-events-none': disabled }"
data-testid="additional-actions" data-testid="additional-actions"
data-qa-selector="more_actions_menu" data-qa-selector="more_actions_menu"
> >

View File

@ -12,6 +12,7 @@ query getContainerRepositoryTags(
containerRepository(id: $id) { containerRepository(id: $id) {
id id
tagsCount tagsCount
canDelete
tags(after: $after, before: $before, first: $first, last: $last, name: $name, sort: $sort) { tags(after: $after, before: $before, first: $first, last: $last, name: $name, sort: $sort) {
nodes { nodes {
digest digest

View File

@ -122,11 +122,8 @@ export default {
@next-page="$emit('next-page')" @next-page="$emit('next-page')"
> >
<template #default="{ first, item, isSelected, selectItem }"> <template #default="{ first, item, isSelected, selectItem }">
<!-- `first` prop is used to decide whether to show the top border
for the first element. We want to show the top border only when
user has permission to bulk delete versions. -->
<version-row <version-row
:first="canDestroy && first" :first="first"
:package-entity="item" :package-entity="item"
:selected="isSelected(item)" :selected="isSelected(item)"
@delete="setItemToBeDeleted(item)" @delete="setItemToBeDeleted(item)"

View File

@ -125,7 +125,7 @@ export default {
:select-item="selectItem" :select-item="selectItem"
:is-selected="isSelected" :is-selected="isSelected"
:item="item" :item="item"
:first="index === 0" :first="!hiddenDelete && index === 0"
></slot> ></slot>
</div> </div>

View File

@ -204,7 +204,9 @@ module SearchHelper
if search_has_project? if search_has_project?
hash[:project] = { id: @project.id, name: @project.name } hash[:project] = { id: @project.id, name: @project.name }
hash[:project_metadata] = { issues_path: project_issues_path(@project), mr_path: project_merge_requests_path(@project) } hash[:project_metadata] = { mr_path: project_merge_requests_path(@project) }
hash[:project_metadata][:issues_path] = project_issues_path(@project) if @project.feature_available?(:issues, current_user)
hash[:code_search] = search_scope.nil? hash[:code_search] = search_scope.nil?
hash[:ref] = @ref if @ref && can?(current_user, :read_code, @project) hash[:ref] = @ref if @ref && can?(current_user, :read_code, @project)
end end

View File

@ -14,6 +14,7 @@ module HasUserType
migration_bot: 7, migration_bot: 7,
security_bot: 8, security_bot: 8,
automation_bot: 9, automation_bot: 9,
security_policy_bot: 10, # Currently not in use. See https://gitlab.com/gitlab-org/gitlab/-/issues/384174
admin_bot: 11, admin_bot: 11,
suggested_reviewers_bot: 12, suggested_reviewers_bot: 12,
service_account: 13 service_account: 13
@ -27,6 +28,7 @@ module HasUserType
migration_bot migration_bot
security_bot security_bot
automation_bot automation_bot
security_policy_bot
admin_bot admin_bot
suggested_reviewers_bot suggested_reviewers_bot
service_account service_account

View File

@ -1,4 +1,4 @@
= form_for @application_setting, url: preferences_admin_application_settings_path(anchor: 'js-realtime-settings'), html: { class: 'fieldset-form' } do |f| = gitlab_ui_form_for @application_setting, url: preferences_admin_application_settings_path(anchor: 'js-realtime-settings'), html: { class: 'fieldset-form' } do |f|
= form_errors(@application_setting) = form_errors(@application_setting)
%fieldset %fieldset
@ -8,4 +8,4 @@
.form-text.text-muted .form-text.text-muted
= _('Multiplier to apply to polling intervals. Decimal values are supported. Defaults to 1.') = _('Multiplier to apply to polling intervals. Decimal values are supported. Defaults to 1.')
= f.submit _('Save changes'), class: "gl-button btn btn-confirm" = f.submit _('Save changes'), pajamas_button: true

View File

@ -1,7 +1,7 @@
= render 'devise/shared/tab_single', tab_title: 'Resend confirmation instructions' = render 'devise/shared/tab_single', tab_title: 'Resend confirmation instructions'
.login-box.gl-p-5 .login-box.gl-p-5
.login-body .login-body
= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f| = gitlab_ui_form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'gl-show-field-errors' }) do |f|
.devise-errors .devise-errors
= render "devise/shared/error_messages", resource: resource = render "devise/shared/error_messages", resource: resource
.form-group .form-group
@ -13,7 +13,8 @@
= recaptcha_tags nonce: content_security_policy_nonce = recaptcha_tags nonce: content_security_policy_nonce
.gl-mt-5 .gl-mt-5
= f.submit _("Resend"), class: 'gl-button btn btn-confirm' = render Pajamas::ButtonComponent.new(block: true, type: :submit, variant: :confirm) do
= _("Resend")
.clearfix.prepend-top-20 .clearfix.prepend-top-20
= render 'devise/shared/sign_in_link' = render 'devise/shared/sign_in_link'

View File

@ -6,7 +6,7 @@
"error-state-svg-path" => image_path('illustrations/feature_flag.svg'), "error-state-svg-path" => image_path('illustrations/feature_flag.svg'),
"feature-flags-help-page-path" => help_page_path("operations/feature_flags"), "feature-flags-help-page-path" => help_page_path("operations/feature_flags"),
"feature-flags-client-libraries-help-page-path" => help_page_path("operations/feature_flags", anchor: "choose-a-client-library"), "feature-flags-client-libraries-help-page-path" => help_page_path("operations/feature_flags", anchor: "choose-a-client-library"),
"feature-flags-client-example-help-page-path" => help_page_path("operations/feature_flags", anchor: "golang-application-example"), "feature-flags-client-example-help-page-path" => help_page_path("operations/feature_flags", anchor: "go-application-example"),
"feature-flags-limit-exceeded" => @project.actual_limits.exceeded?(:project_feature_flags, @project.operations_feature_flags.count), "feature-flags-limit-exceeded" => @project.actual_limits.exceeded?(:project_feature_flags, @project.operations_feature_flags.count),
"feature-flags-limit" => @project.actual_limits.project_feature_flags, "feature-flags-limit" => @project.actual_limits.project_feature_flags,
"unleash-api-url" => (unleash_api_url(@project) if can?(current_user, :admin_feature_flag, @project)), "unleash-api-url" => (unleash_api_url(@project) if can?(current_user, :admin_feature_flag, @project)),

View File

@ -27,4 +27,4 @@
= s_("GitLabPages|When enabled, a unique domain is generated to access pages.").html_safe = s_("GitLabPages|When enabled, a unique domain is generated to access pages.").html_safe
.gl-mt-3 .gl-mt-3
= f.submit s_('GitLabPages|Save changes'), class: 'btn btn-confirm gl-button' = f.submit s_('GitLabPages|Save changes'), pajamas_button: true

View File

@ -38,4 +38,5 @@
- if partial_exists? "registrations/welcome/button" - if partial_exists? "registrations/welcome/button"
= render "registrations/welcome/button" = render "registrations/welcome/button"
- else - else
= f.submit _('Get started!'), class: 'btn-confirm gl-button btn btn-block gl-mb-0 gl-p-3', data: { qa_selector: 'get_started_button' } = render Pajamas::ButtonComponent.new(block: true, type: :submit, variant: :confirm, button_options: { class: 'gl-mb-0', data: { qa_selector: 'get_started_button' }}) do
= _('Get started!')

View File

@ -1,8 +0,0 @@
---
name: cache_client_with_metrics
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111210
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/392622
milestone: '15.10'
type: development
group: group::source code
default_enabled: false

View File

@ -9,17 +9,10 @@ class ScheduleMigrationForRemediation < Gitlab::Database::Migration[2.1]
BATCH_SIZE = 5000 BATCH_SIZE = 5000
def up def up
queue_batched_background_migration( # no-op as described in https://docs.gitlab.com/ee/development/database/batched_background_migrations.html
MIGRATION,
:vulnerability_occurrences,
:id,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end end
def down def down
delete_batched_background_migration(MIGRATION, :vulnerability_occurrences, :id, []) # no-op as described in https://docs.gitlab.com/ee/development/database/batched_background_migrations.html
end end
end end

View File

@ -8,6 +8,8 @@ class FinalizeCiBuildNeedsBigIntConversion < Gitlab::Database::Migration[2.1]
TABLE_NAME = 'ci_build_needs' TABLE_NAME = 'ci_build_needs'
def up def up
return unless should_run?
ensure_batched_background_migration_is_finished( ensure_batched_background_migration_is_finished(
job_class_name: 'CopyColumnUsingBackgroundMigrationJob', job_class_name: 'CopyColumnUsingBackgroundMigrationJob',
table_name: TABLE_NAME, table_name: TABLE_NAME,
@ -17,4 +19,10 @@ class FinalizeCiBuildNeedsBigIntConversion < Gitlab::Database::Migration[2.1]
end end
def down; end def down; end
private
def should_run?
!Gitlab.jh? && (Gitlab.com? || Gitlab.dev_or_test_env?)
end
end end

View File

@ -0,0 +1,57 @@
# frozen_string_literal: true
class SwapColumnsCiBuildNeedsBigIntConversion < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
TABLE_NAME = 'ci_build_needs'
def up
return unless should_run?
swap
end
def down
return unless should_run?
swap
end
private
def should_run?
!Gitlab.jh? && (Gitlab.com? || Gitlab.dev_or_test_env?)
end
def swap
add_concurrent_index TABLE_NAME, :id_convert_to_bigint, unique: true, name:
'index_ci_build_needs_on_id_convert_to_bigint'
with_lock_retries(raise_on_exhaustion: true) do
execute "LOCK TABLE #{TABLE_NAME} IN ACCESS EXCLUSIVE MODE"
id_name = quote_column_name(:id)
temp_name = quote_column_name('id_tmp')
id_convert_to_bigint_name = quote_column_name(:id_convert_to_bigint)
execute "ALTER TABLE #{TABLE_NAME} RENAME COLUMN #{id_name} TO #{temp_name}"
execute "ALTER TABLE #{TABLE_NAME} RENAME COLUMN #{id_convert_to_bigint_name} TO #{id_name}"
execute "ALTER TABLE #{TABLE_NAME} RENAME COLUMN #{temp_name} TO #{id_convert_to_bigint_name}"
function_name = Gitlab::Database::UnidirectionalCopyTrigger.on_table(
TABLE_NAME, connection: Ci::ApplicationRecord.connection
).name(
:id, :id_convert_to_bigint
)
execute "ALTER FUNCTION #{quote_table_name(function_name)} RESET ALL"
execute "ALTER SEQUENCE ci_build_needs_id_seq OWNED BY #{TABLE_NAME}.id"
change_column_default TABLE_NAME, :id, -> { "nextval('ci_build_needs_id_seq'::regclass)" }
change_column_default TABLE_NAME, :id_convert_to_bigint, 0
execute "ALTER TABLE #{TABLE_NAME} DROP CONSTRAINT ci_build_needs_pkey CASCADE"
rename_index TABLE_NAME, 'index_ci_build_needs_on_id_convert_to_bigint', 'ci_build_needs_pkey'
execute "ALTER TABLE #{TABLE_NAME} ADD CONSTRAINT ci_build_needs_pkey PRIMARY KEY USING INDEX ci_build_needs_pkey"
end
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class DeleteSecurityPolicyBotUsers < Gitlab::Database::Migration[2.1]
restrict_gitlab_migration gitlab_schema: :gitlab_main
class User < MigrationRecord
self.table_name = 'users'
enum user_type: { security_policy_bot: 10 }
end
def up
User.where(user_type: :security_policy_bot).delete_all
end
def down
# no-op
# Deleted records can't be restored
end
end

View File

@ -0,0 +1,32 @@
# frozen_string_literal: true
# rubocop: disable BackgroundMigration/MissingDictionaryFile
class RescheduleMigrationForRemediation < Gitlab::Database::Migration[2.1]
MIGRATION = 'MigrateRemediationsForVulnerabilityFindings'
DELAY_INTERVAL = 2.minutes
SUB_BATCH_SIZE = 500
BATCH_SIZE = 5000
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main
def up
delete_batched_background_migration(MIGRATION, :vulnerability_occurrences, :id, [])
queue_batched_background_migration(
MIGRATION,
:vulnerability_occurrences,
:id,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(MIGRATION, :vulnerability_occurrences, :id, [])
end
end
# rubocop: enable BackgroundMigration/MissingDictionaryFile

View File

@ -0,0 +1 @@
7df50689f7e9311ee8e5bd2513f4361be0fceef3962344d2d16bf511132c7a33

View File

@ -0,0 +1 @@
094eb5044e841050288c7362cc58c1b63ce4a349fe49a4c5ebee6b83a05feb56

View File

@ -0,0 +1 @@
e2f19bbc322127e439fffc4c1e2718288538aa6cb2d50a5248f12470b1c9491e

View File

@ -12896,13 +12896,13 @@ CREATE SEQUENCE chat_teams_id_seq
ALTER SEQUENCE chat_teams_id_seq OWNED BY chat_teams.id; ALTER SEQUENCE chat_teams_id_seq OWNED BY chat_teams.id;
CREATE TABLE ci_build_needs ( CREATE TABLE ci_build_needs (
id integer NOT NULL, id_convert_to_bigint integer DEFAULT 0 NOT NULL,
name text NOT NULL, name text NOT NULL,
artifacts boolean DEFAULT true NOT NULL, artifacts boolean DEFAULT true NOT NULL,
optional boolean DEFAULT false NOT NULL, optional boolean DEFAULT false NOT NULL,
build_id bigint NOT NULL, build_id bigint NOT NULL,
partition_id bigint DEFAULT 100 NOT NULL, partition_id bigint DEFAULT 100 NOT NULL,
id_convert_to_bigint bigint DEFAULT 0 NOT NULL id bigint NOT NULL
); );
CREATE SEQUENCE ci_build_needs_id_seq CREATE SEQUENCE ci_build_needs_id_seq

View File

@ -5,9 +5,9 @@
# #
# For a list of all options, see https://vale.sh/docs/topics/styles/ # For a list of all options, see https://vale.sh/docs/topics/styles/
extends: existence extends: existence
message: "Instead of '%s' for the code block, use yaml, ruby, plaintext, markdown, javascript, shell, golang, python, dockerfile, or typescript." message: "Instead of '%s' for the code block, use yaml, ruby, plaintext, markdown, javascript, shell, go, python, dockerfile, or typescript."
link: https://docs.gitlab.com/ee/development/documentation/styleguide/index.html#code-blocks link: https://docs.gitlab.com/ee/development/documentation/styleguide/index.html#code-blocks
level: error level: error
scope: raw scope: raw
raw: raw:
- '\`\`\`(yml|rb|text|md|bash|sh\n|js\n|go\n|py\n|docker\n|ts)' - '\`\`\`(yml|rb|text|md|bash|sh\n|js\n|golang\n|py\n|docker\n|ts)'

View File

@ -23,6 +23,7 @@ swap:
params: parameters params: parameters
pg: PostgreSQL pg: PostgreSQL
'postgres$': PostgreSQL 'postgres$': PostgreSQL
golang: Go
raketask: Rake task raketask: Rake task
raketasks: Rake tasks raketasks: Rake tasks
rspec: RSpec rspec: RSpec

View File

@ -720,7 +720,7 @@ This error occurs when `praefect['database_port']` or `praefect['database_direct
## Profiling Gitaly ## Profiling Gitaly
Gitaly exposes several of the Golang built-in performance profiling tools on the Prometheus listen port. For example, if Prometheus is listening Gitaly exposes several of the Go built-in performance profiling tools on the Prometheus listen port. For example, if Prometheus is listening
on port `9236` of the GitLab server: on port `9236` of the GitLab server:
- Get a list of running `goroutines` and their backtraces: - Get a list of running `goroutines` and their backtraces:

View File

@ -863,7 +863,7 @@ Incorrect configuration of these values may result in intermittent
or persistent errors, or the Pages Daemon serving old content. or persistent errors, or the Pages Daemon serving old content.
NOTE: NOTE:
Expiry, interval and timeout flags use [Golang's duration formatting](https://pkg.go.dev/time#ParseDuration). Expiry, interval and timeout flags use [Go duration formatting](https://pkg.go.dev/time#ParseDuration).
A duration string is a possibly signed sequence of decimal numbers, A duration string is a possibly signed sequence of decimal numbers,
each with optional fraction and a unit suffix, such as `300ms`, `1.5h` or `2h45m`. each with optional fraction and a unit suffix, such as `300ms`, `1.5h` or `2h45m`.
Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`.

View File

@ -233,3 +233,35 @@ Returns:
- `204 No Content` if the license is successfully deleted. - `204 No Content` if the license is successfully deleted.
- `403 Forbidden` if the current user in not permitted to delete the license. - `403 Forbidden` if the current user in not permitted to delete the license.
- `404 Not Found` if the license to delete could not be found. - `404 Not Found` if the license to delete could not be found.
## Trigger recalculation of billable users
```plaintext
PUT /license/:id/refresh_billable_users
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer | yes | ID of the GitLab license. |
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/license/:id/refresh_billable_users"
```
Example response:
```json
{
"success": true
}
```
Returns:
- `202 Accepted` if the request to refresh billable users is successfully initiated.
- `403 Forbidden` if the current user in not permitted to refresh billable users for the license.
- `404 Not Found` if the license could not be found.
| Attribute | Type | Description |
|:-----------------------------|:--------------|:------------------------------------------|
| `success` | boolean | Whether the request succeeded or not. |

View File

@ -93,9 +93,9 @@ Additionally, since we intend to ingest data via Prometheus `remote_write` API,
We also need to make sure to avoid writing a lot of small writes into Clickhouse, therefore itd be prudent to batch data before writing it into Clickhouse. We also need to make sure to avoid writing a lot of small writes into Clickhouse, therefore itd be prudent to batch data before writing it into Clickhouse.
We must also make sure ingestion remains decoupled with `Storage` so as to reduce undue dependence on a given storage implementation. While we do intend to use Clickhouse as our backing storage for any foreseeable future, this ensures we do not tie ourselves in into Clickhouse too much should future business requirements warrant the usage of a different backend/technology. A good way to implement this in Golang would be our implementations adhering to a standard interface, the following for example: We must also make sure ingestion remains decoupled with `Storage` so as to reduce undue dependence on a given storage implementation. While we do intend to use Clickhouse as our backing storage for any foreseeable future, this ensures we do not tie ourselves in into Clickhouse too much should future business requirements warrant the usage of a different backend/technology. A good way to implement this in Go would be our implementations adhering to a standard interface, the following for example:
```golang ```go
type Storage interface { type Storage interface {
Read( Read(
ctx context.Context, ctx context.Context,

View File

@ -53,7 +53,7 @@ This has led to increased complexity across the board, from development
[we no longer recommend](../../../administration/nfs.md) to our [we no longer recommend](../../../administration/nfs.md) to our
users and is no longer in use on GitLab.com. users and is no longer in use on GitLab.com.
- Understanding all the moving parts and the flow is extremely - Understanding all the moving parts and the flow is extremely
complicated: we have CarrierWave, Fog, Golang S3/Azure SDKs, all complicated: we have CarrierWave, Fog, Go S3/Azure SDKs, all
being used, and that complicates testing as well. being used, and that complicates testing as well.
- Fog and CarrierWave are not maintained to the level of the native - Fog and CarrierWave are not maintained to the level of the native
SDKs (for example, AWS S3 SDK), so we have to maintain or monkey SDKs (for example, AWS S3 SDK), so we have to maintain or monkey

View File

@ -375,7 +375,7 @@ hierarchy. Choosing a proper solution will require a thoughtful research.
- Implementing a separate Go library which uses the same backend (for example, Redis) for rate limiting. - Implementing a separate Go library which uses the same backend (for example, Redis) for rate limiting.
1. **SDK for Satellite Services (Owning Team)** 1. **SDK for Satellite Services (Owning Team)**
- Build Golang SDK. - Build Go SDK.
- Create examples showcasing usage of the new rate limits SDK. - Create examples showcasing usage of the new rate limits SDK.
1. **Team fan out for Satellite Services (Stage Groups)** 1. **Team fan out for Satellite Services (Stage Groups)**

View File

@ -76,7 +76,7 @@ choose one of these templates:
- [dotNET Core (`dotNET-Core.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/dotNET-Core.gitlab-ci.yml) - [dotNET Core (`dotNET-Core.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/dotNET-Core.gitlab-ci.yml)
- [Elixir (`Elixir.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Elixir.gitlab-ci.yml) - [Elixir (`Elixir.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Elixir.gitlab-ci.yml)
- [Flutter (`Flutter.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Flutter.gitlab-ci.yml) - [Flutter (`Flutter.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Flutter.gitlab-ci.yml)
- [Golang (`Go.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Go.gitlab-ci.yml) - [Go (`Go.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Go.gitlab-ci.yml)
- [Gradle (`Gradle.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Gradle.gitlab-ci.yml) - [Gradle (`Gradle.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Gradle.gitlab-ci.yml)
- [Grails (`Grails.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Grails.gitlab-ci.yml) - [Grails (`Grails.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Grails.gitlab-ci.yml)
- [iOS with fastlane (`iOS-Fastlane.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml) - [iOS with fastlane (`iOS-Fastlane.gitlab-ci.yml`)](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml)

View File

@ -33,7 +33,7 @@ This format was originally developed for Java, but most coverage analysis framew
for other languages have plugins to add support for it, like: for other languages have plugins to add support for it, like:
- [simplecov-cobertura](https://rubygems.org/gems/simplecov-cobertura) (Ruby) - [simplecov-cobertura](https://rubygems.org/gems/simplecov-cobertura) (Ruby)
- [gocover-cobertura](https://github.com/boumenot/gocover-cobertura) (Golang) - [gocover-cobertura](https://github.com/boumenot/gocover-cobertura) (Go)
Other coverage analysis frameworks support the format out of the box, for example: Other coverage analysis frameworks support the format out of the box, for example:

View File

@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
## RPCs ## RPCs
Gitaly is a wrapper around the `git` binary, running in a [Gitaly Cluster](../../../administration/gitaly/index.md). It provides managed access to the file system housing the `git` repositories, via Golang Remote Procedure Calls (RPCs). Other functions are access optimization, caching, and a form of pagination against the file system. Gitaly is a wrapper around the `git` binary, running in a [Gitaly Cluster](../../../administration/gitaly/index.md). It provides managed access to the file system housing the `git` repositories, using Go Remote Procedure Calls (RPCs). Other functions are access optimization, caching, and a form of pagination against the file system.
The comprehensive [Beginner's guide to Gitaly contributions](https://gitlab.com/gitlab-org/gitaly/-/blob/master/doc/beginners_guide.md) is focused on making updates to Gitaly, and offers many insights into how to understand the Gitaly code. The comprehensive [Beginner's guide to Gitaly contributions](https://gitlab.com/gitlab-org/gitaly/-/blob/master/doc/beginners_guide.md) is focused on making updates to Gitaly, and offers many insights into how to understand the Gitaly code.

View File

@ -39,7 +39,7 @@ To learn about the reasoning behind our creation of `gitlab-sshd`, read the blog
### Gitaly touch points ### Gitaly touch points
Gitaly is a Golang RPC service which handles all the `git` calls made by GitLab. Gitaly is a Go RPC service which handles all the `git` calls made by GitLab.
GitLab is not exposed directly, and all traffic comes through Create: Source Code. GitLab is not exposed directly, and all traffic comes through Create: Source Code.
For more information, read [Gitaly touch points](gitaly_touch_points.md). For more information, read [Gitaly touch points](gitaly_touch_points.md).

View File

@ -741,7 +741,7 @@ Do not use **limitations**. Use **known issues** instead.
## log in, log on ## log in, log on
Do not use **log in** or **log on**. Use [sign in](#sign-in) instead. If the user interface has **Log in**, you can use it. Do not use **log in** or **log on**. Use [sign in](#sign-in-sign-in) instead. If the user interface has **Log in**, you can use it.
## logged-in user, logged in user ## logged-in user, logged in user
@ -1154,14 +1154,17 @@ Use **setup** as a noun, and **set up** as a verb. For example:
- Your remote office setup is amazing. - Your remote office setup is amazing.
- To set up your remote office correctly, consider the ergonomics of your work area. - To set up your remote office correctly, consider the ergonomics of your work area.
## sign in ## sign in, sign-in
Use **sign in** or **sign in to**. Use **sign in** or **sign in to** as a verb to describe the action of signing in.
Do not use **sign on** or **sign into**, or **log on**, **log in**, or **log into**. Do not use **sign on** or **sign into**, or **log on**, **log in**, or **log into**.
If the user interface has different words, use those. If the user interface has different words, use those.
You can use **sign-in** as a noun or adjective. For example, **sign-in page** or
**sign-in restrictions**.
You can use **single sign-on**. You can use **single sign-on**.
## sign up ## sign up

View File

@ -446,7 +446,7 @@ irb(main):001:0> require 'openssl'; OpenSSL.fips_mode
### Go ### Go
Google maintains a [`dev.boringcrypto` branch](https://github.com/golang/go/tree/dev.boringcrypto) in the Golang compiler Google maintains a [`dev.boringcrypto` branch](https://github.com/golang/go/tree/dev.boringcrypto) in the Go compiler
that makes it possible to statically link BoringSSL, a FIPS-validated module forked from OpenSSL. that makes it possible to statically link BoringSSL, a FIPS-validated module forked from OpenSSL.
However, BoringSSL is not intended for public use. However, BoringSSL is not intended for public use.

View File

@ -257,13 +257,13 @@ Here are the steps to gate a new feature in Gitaly behind a feature flag.
1. Create a package scoped flag name: 1. Create a package scoped flag name:
```golang ```go
var findAllTagsFeatureFlag = "go-find-all-tags" var findAllTagsFeatureFlag = "go-find-all-tags"
``` ```
1. Create a switch in the code using the `featureflag` package: 1. Create a switch in the code using the `featureflag` package:
```golang ```go
if featureflag.IsEnabled(ctx, findAllTagsFeatureFlag) { if featureflag.IsEnabled(ctx, findAllTagsFeatureFlag) {
// go implementation // go implementation
} else { } else {
@ -273,7 +273,7 @@ Here are the steps to gate a new feature in Gitaly behind a feature flag.
1. Create Prometheus metrics: 1. Create Prometheus metrics:
```golang ```go
var findAllTagsRequests = prometheus.NewCounterVec( var findAllTagsRequests = prometheus.NewCounterVec(
prometheus.CounterOpts{ prometheus.CounterOpts{
Name: "gitaly_find_all_tags_requests_total", Name: "gitaly_find_all_tags_requests_total",
@ -297,7 +297,7 @@ Here are the steps to gate a new feature in Gitaly behind a feature flag.
1. Set headers in tests: 1. Set headers in tests:
```golang ```go
import ( import (
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"

View File

@ -21,8 +21,8 @@ Ruby to build and test, but not to run.
GitLab Shell runs on `port 22` on an Omnibus installation. To use a regular SSH GitLab Shell runs on `port 22` on an Omnibus installation. To use a regular SSH
service, configure it on an alternative port. service, configure it on an alternative port.
Download and install the current version of Go from [golang.org](https://go.dev/dl/). Download and install the [current version of Go](https://go.dev/dl/).
We follow the [Golang Release Policy](https://golang.org/doc/devel/release.html#policy) We follow the [Go Release Policy](https://go.dev/doc/devel/release#policy)
and support: and support:
- The current stable version. - The current stable version.

View File

@ -26,7 +26,7 @@ by Distribution:
## Supporting multiple Go versions ## Supporting multiple Go versions
Individual Golang projects need to support multiple Go versions because: Individual Go projects need to support multiple Go versions because:
- When a new version of Go is released, we should start integrating it into the CI pipelines to verify compatibility with the new compiler. - When a new version of Go is released, we should start integrating it into the CI pipelines to verify compatibility with the new compiler.
- We must support the [official Omnibus GitLab Go version](#updating-go-version), which may be behind the latest minor release. - We must support the [official Omnibus GitLab Go version](#updating-go-version), which may be behind the latest minor release.

View File

@ -216,7 +216,7 @@ When comparing expected and actual values in tests, use
and others to improve readability when comparing structs, errors, and others to improve readability when comparing structs, errors,
large portions of text, or JSON documents: large portions of text, or JSON documents:
```golang ```go
type TestData struct { type TestData struct {
// ... // ...
} }
@ -291,7 +291,7 @@ easier to debug.
For example: For example:
```golang ```go
// Wrap the error // Wrap the error
return nil, fmt.Errorf("get cache %s: %w", f.Name, err) return nil, fmt.Errorf("get cache %s: %w", f.Name, err)
@ -462,7 +462,7 @@ allocations.
**Don't:** **Don't:**
```golang ```go
var s2 []string var s2 []string
for _, val := range s1 { for _, val := range s1 {
s2 = append(s2, val) s2 = append(s2, val)
@ -471,7 +471,7 @@ for _, val := range s1 {
**Do:** **Do:**
```golang ```go
s2 := make([]string, 0, len(s1)) s2 := make([]string, 0, len(s1))
for _, val := range s1 { for _, val := range s1 {
s2 = append(s2, val) s2 = append(s2, val)
@ -494,7 +494,7 @@ If the scanner report is small, less than 35 lines, then feel free to [inline th
The [go-cmp](https://github.com/google/go-cmp) package should be used when comparing large structs in tests. It makes it possible to output a specific diff where the two structs differ, rather than seeing the whole of both structs printed out in the test logs. Here is a small example: The [go-cmp](https://github.com/google/go-cmp) package should be used when comparing large structs in tests. It makes it possible to output a specific diff where the two structs differ, rather than seeing the whole of both structs printed out in the test logs. Here is a small example:
```golang ```go
package main package main
import ( import (

View File

@ -542,11 +542,11 @@ print(p.join('log', '/etc/passwd', ''))
# renders the path to "/etc/passwd", which is not what we expect! # renders the path to "/etc/passwd", which is not what we expect!
``` ```
#### Golang #### Go
Golang has similar behavior with [`path.Clean`](https://pkg.go.dev/path#example-Clean). Remember that with many file systems, using `../../../../` traverses up to the root directory. Any remaining `../` are ignored. This example may give an attacker access to `/etc/passwd`: Go has similar behavior with [`path.Clean`](https://pkg.go.dev/path#example-Clean). Remember that with many file systems, using `../../../../` traverses up to the root directory. Any remaining `../` are ignored. This example may give an attacker access to `/etc/passwd`:
```golang ```go
path.Clean("/../../etc/passwd") path.Clean("/../../etc/passwd")
// renders the path to "etc/passwd"; the file path is relative to whatever the current directory is // renders the path to "etc/passwd"; the file path is relative to whatever the current directory is
path.Clean("../../etc/passwd") path.Clean("../../etc/passwd")
@ -601,7 +601,7 @@ Go has built-in protections that usually prevent an attacker from successfully i
Consider the following example: Consider the following example:
```golang ```go
package main package main
import ( import (
@ -620,7 +620,7 @@ This echoes `"1; cat /etc/passwd"`.
**Do not** use `sh`, as it bypasses internal protections: **Do not** use `sh`, as it bypasses internal protections:
```golang ```go
out, _ = exec.Command("sh", "-c", "echo 1 | cat /etc/passwd").Output() out, _ = exec.Command("sh", "-c", "echo 1 | cat /etc/passwd").Output()
``` ```
@ -646,15 +646,15 @@ And the following cipher suites (according to the [RFC 8446](https://datatracker
- `TLS_AES_128_GCM_SHA256` - `TLS_AES_128_GCM_SHA256`
- `TLS_AES_256_GCM_SHA384` - `TLS_AES_256_GCM_SHA384`
*Note*: **Golang** does [not support](https://github.com/golang/go/blob/go1.17/src/crypto/tls/cipher_suites.go#L676) all cipher suites with TLS 1.3. *Note*: **Go** does [not support](https://github.com/golang/go/blob/go1.17/src/crypto/tls/cipher_suites.go#L676) all cipher suites with TLS 1.3.
##### Implementation examples ##### Implementation examples
##### TLS 1.3 ##### TLS 1.3
For TLS 1.3, **Golang** only supports [3 cipher suites](https://github.com/golang/go/blob/go1.17/src/crypto/tls/cipher_suites.go#L676), as such we only need to set the TLS version: For TLS 1.3, **Go** only supports [3 cipher suites](https://github.com/golang/go/blob/go1.17/src/crypto/tls/cipher_suites.go#L676), as such we only need to set the TLS version:
```golang ```go
cfg := &tls.Config{ cfg := &tls.Config{
MinVersion: tls.VersionTLS13, MinVersion: tls.VersionTLS13,
} }
@ -678,9 +678,9 @@ response = GitLab::HTTP.perform_request(Net::HTTP::Get, 'https://gitlab.com', ss
##### TLS 1.2 ##### TLS 1.2
**Golang** does support multiple cipher suites that we do not want to use with TLS 1.2. We need to explicitly list authorized ciphers: **Go** does support multiple cipher suites that we do not want to use with TLS 1.2. We need to explicitly list authorized ciphers:
```golang ```go
func secureCipherSuites() []uint16 { func secureCipherSuites() []uint16 {
return []uint16{ return []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
@ -692,7 +692,7 @@ func secureCipherSuites() []uint16 {
And then use `secureCipherSuites()` in `tls.Config`: And then use `secureCipherSuites()` in `tls.Config`:
```golang ```go
tls.Config{ tls.Config{
(...), (...),
CipherSuites: secureCipherSuites(), CipherSuites: secureCipherSuites(),
@ -920,7 +920,7 @@ end
#### Go #### Go
```golang ```go
// unzip INSECURELY extracts source zip file to destination. // unzip INSECURELY extracts source zip file to destination.
func unzip(src, dest string) error { func unzip(src, dest string) error {
r, err := zip.OpenReader(src) r, err := zip.OpenReader(src)
@ -1016,7 +1016,7 @@ end
You are encouraged to use the secure archive utilities provided by [LabSec](https://gitlab.com/gitlab-com/gl-security/appsec/labsec) which will handle Zip Slip and other types of vulnerabilities for you. The LabSec utilities are also context aware which makes it possible to cancel or timeout extractions: You are encouraged to use the secure archive utilities provided by [LabSec](https://gitlab.com/gitlab-com/gl-security/appsec/labsec) which will handle Zip Slip and other types of vulnerabilities for you. The LabSec utilities are also context aware which makes it possible to cancel or timeout extractions:
```golang ```go
package main package main
import "gitlab-com/gl-security/appsec/labsec/archive/zip" import "gitlab-com/gl-security/appsec/labsec/archive/zip"
@ -1041,7 +1041,7 @@ func main() {
In case the LabSec utilities do not fit your needs, here is an example for extracting a zip file with protection against Zip Slip attacks: In case the LabSec utilities do not fit your needs, here is an example for extracting a zip file with protection against Zip Slip attacks:
```golang ```go
// unzip extracts source zip file to destination with protection against Zip Slip attacks. // unzip extracts source zip file to destination with protection against Zip Slip attacks.
func unzip(src, dest string) error { func unzip(src, dest string) error {
r, err := zip.OpenReader(src) r, err := zip.OpenReader(src)
@ -1118,7 +1118,7 @@ end
#### Go #### Go
```golang ```go
// printZipContents INSECURELY prints contents of files in a zip file. // printZipContents INSECURELY prints contents of files in a zip file.
func printZipContents(src string) error { func printZipContents(src string) error {
r, err := zip.OpenReader(src) r, err := zip.OpenReader(src)
@ -1186,7 +1186,7 @@ You are encouraged to use the secure archive utilities provided by [LabSec](http
In case the LabSec utilities do not fit your needs, here is an example for extracting a zip file with protection against symlink attacks: In case the LabSec utilities do not fit your needs, here is an example for extracting a zip file with protection against symlink attacks:
```golang ```go
// printZipContents prints contents of files in a zip file with protection against symlink attacks. // printZipContents prints contents of files in a zip file with protection against symlink attacks.
func printZipContents(src string) error { func printZipContents(src string) error {
r, err := zip.OpenReader(src) r, err := zip.OpenReader(src)

View File

@ -95,7 +95,7 @@ They consist of:
Example: Example:
```golang ```go
u.route("PUT", apiProjectPattern+`packages/nuget/`, mimeMultipartUploader), u.route("PUT", apiProjectPattern+`packages/nuget/`, mimeMultipartUploader),
``` ```

View File

@ -227,8 +227,8 @@ The following Elasticsearch settings are available:
| `AWS Secret Access Key` | The AWS secret access key. | | `AWS Secret Access Key` | The AWS secret access key. |
| `Maximum file size indexed` | See [the explanation in instance limits.](../../administration/instance_limits.md#maximum-file-size-indexed). | | `Maximum file size indexed` | See [the explanation in instance limits.](../../administration/instance_limits.md#maximum-file-size-indexed). |
| `Maximum field length` | See [the explanation in instance limits.](../../administration/instance_limits.md#maximum-field-length). | | `Maximum field length` | See [the explanation in instance limits.](../../administration/instance_limits.md#maximum-field-length). |
| `Maximum bulk request size (MiB)` | Used by the GitLab Ruby and Golang-based indexer processes. This setting indicates how much data must be collected (and stored in memory) in a given indexing process before submitting the payload to the Elasticsearch Bulk API. For the GitLab Golang-based indexer, you should use this setting with `Bulk request concurrency`. `Maximum bulk request size (MiB)` must accommodate the resource constraints of both the Elasticsearch hosts and the hosts running the GitLab Golang-based indexer from either the `gitlab-rake` command or the Sidekiq tasks. | | `Maximum bulk request size (MiB)` | Used by the GitLab Ruby and Go-based indexer processes. This setting indicates how much data must be collected (and stored in memory) in a given indexing process before submitting the payload to the Elasticsearch Bulk API. For the GitLab Go-based indexer, you should use this setting with `Bulk request concurrency`. `Maximum bulk request size (MiB)` must accommodate the resource constraints of both the Elasticsearch hosts and the hosts running the GitLab Go-based indexer from either the `gitlab-rake` command or the Sidekiq tasks. |
| `Bulk request concurrency` | The Bulk request concurrency indicates how many of the GitLab Golang-based indexer processes (or threads) can run in parallel to collect data to subsequently submit to the Elasticsearch Bulk API. This increases indexing performance, but fills the Elasticsearch bulk requests queue faster. This setting should be used together with the Maximum bulk request size setting (see above) and needs to accommodate the resource constraints of both the Elasticsearch hosts and the hosts running the GitLab Golang-based indexer either from the `gitlab-rake` command or the Sidekiq tasks. | | `Bulk request concurrency` | The Bulk request concurrency indicates how many of the GitLab Go-based indexer processes (or threads) can run in parallel to collect data to subsequently submit to the Elasticsearch Bulk API. This increases indexing performance, but fills the Elasticsearch bulk requests queue faster. This setting should be used together with the Maximum bulk request size setting (see above) and needs to accommodate the resource constraints of both the Elasticsearch hosts and the hosts running the GitLab Go-based indexer either from the `gitlab-rake` command or the Sidekiq tasks. |
| `Client request timeout` | Elasticsearch HTTP client request timeout value in seconds. `0` means using the system default timeout value, which depends on the libraries that GitLab application is built upon. | | `Client request timeout` | Elasticsearch HTTP client request timeout value in seconds. `0` means using the system default timeout value, which depends on the libraries that GitLab application is built upon. |
WARNING: WARNING:

View File

@ -297,11 +297,11 @@ For API content, see:
- [Feature flags API](../api/feature_flags.md) - [Feature flags API](../api/feature_flags.md)
- [Feature flag user lists API](../api/feature_flag_user_lists.md) - [Feature flag user lists API](../api/feature_flag_user_lists.md)
### Golang application example ### Go application example
Here's an example of how to integrate feature flags in a Golang application: Here's an example of how to integrate feature flags in a Go application:
```golang ```go
package main package main
import ( import (

View File

@ -40,7 +40,7 @@ You can use the following fuzzing engines to test the specified languages.
| Language | Fuzzing Engine | Example | | Language | Fuzzing Engine | Example |
|---------------------------------------------|------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------| |---------------------------------------------|------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------|
| C/C++ | [libFuzzer](https://llvm.org/docs/LibFuzzer.html) | [c-cpp-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/c-cpp-fuzzing-example) | | C/C++ | [libFuzzer](https://llvm.org/docs/LibFuzzer.html) | [c-cpp-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/c-cpp-fuzzing-example) |
| GoLang | [go-fuzz (libFuzzer support)](https://github.com/dvyukov/go-fuzz) | [go-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example) | | Go | [go-fuzz (libFuzzer support)](https://github.com/dvyukov/go-fuzz) | [go-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example) |
| Swift | [libFuzzer](https://github.com/apple/swift/blob/master/docs/libFuzzerIntegration.md) | [swift-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/swift-fuzzing-example) | | Swift | [libFuzzer](https://github.com/apple/swift/blob/master/docs/libFuzzerIntegration.md) | [swift-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/swift-fuzzing-example) |
| Rust | [cargo-fuzz (libFuzzer support)](https://github.com/rust-fuzz/cargo-fuzz) | [rust-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/rust-fuzzing-example) | | Rust | [cargo-fuzz (libFuzzer support)](https://github.com/rust-fuzz/cargo-fuzz) | [rust-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/rust-fuzzing-example) |
| Java | [Javafuzz](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/javafuzz) (recommended) | [javafuzz-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/javafuzz-fuzzing-example) | | Java | [Javafuzz](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/javafuzz) (recommended) | [javafuzz-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/javafuzz-fuzzing-example) |

View File

@ -281,7 +281,7 @@ Here are some other options you can use to reduce the Container Registry storage
If you see this error message, check the regex patterns to ensure they are valid. If you see this error message, check the regex patterns to ensure they are valid.
GitLab uses [RE2 syntax](https://github.com/google/re2/wiki/Syntax) for regular expressions in the cleanup policy. You can test them with the [regex101 regex tester](https://regex101.com/) using the Golang flavor. GitLab uses [RE2 syntax](https://github.com/google/re2/wiki/Syntax) for regular expressions in the cleanup policy. You can test them with the [regex101 regex tester](https://regex101.com/) using the `Golang` flavor.
View some common [regex pattern examples](#regex-pattern-examples). View some common [regex pattern examples](#regex-pattern-examples).
### The cleanup policy doesn't delete any tags ### The cleanup policy doesn't delete any tags

View File

@ -74,7 +74,7 @@ go env -w GOPROXY='https://gitlab.example.com/api/v4/projects/1234/packages/go,h
With this configuration, Go fetches dependencies in this order: With this configuration, Go fetches dependencies in this order:
1. Go attempts to fetch from the project-specific Go proxy. 1. Go attempts to fetch from the project-specific Go proxy.
1. Go attempts to fetch from [proxy.golang.org](https://proxy.golang.org). 1. Go attempts to fetch from [`proxy.golang.org`](https://proxy.golang.org).
1. Go fetches directly with version control system operations (like `git clone`, 1. Go fetches directly with version control system operations (like `git clone`,
`svn checkout`, and so on). `svn checkout`, and so on).

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,61 @@
---
stage: Create
group: Code Review
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
type: howto
---
# Saved replies **(FREE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/352956) in GitLab 14.9 [with a flag](../../administration/feature_flags.md) named `saved_replies`. Disabled by default.
With saved replies, create and reuse text for any text area in:
- Merge requests, including diffs.
- Issues, including design management comments.
- Epics.
- Work items.
Saved replies can be small, like approving a merge request and unassigning yourself from it,
or large, like chunks of boilerplate text you use frequently:
![Saved replies dropdown list](img/saved_replies_dropdown_v15_10.png)
## Use saved replies in a text area
To include the text of a saved reply in your comment:
1. In the editor toolbar for your comment, select **Saved replies** (**{symlink}**).
1. Select your desired saved reply.
## Create saved replies
To create a saved reply for future use:
1. On the top bar, in the upper-right corner, select your avatar.
1. From the dropdown list, select **Preferences**.
1. On the left sidebar, select **Saved replies** (**{symlink}**).
1. Provide a **Name** for your saved reply.
1. Enter the **Content** of your reply. You can use any formatting you use in
other GitLab text areas.
1. Select **Save**, and the page reloads with your saved reply shown.
## View your saved replies
To go to your saved replies:
1. On the top bar, in the upper-right corner, select your avatar.
1. From the dropdown list, select **Preferences**.
1. On the left sidebar, select **Saved replies** (**{symlink}**).
1. Scroll to **My saved replies**.
## Edit or delete saved replies
To edit or delete a previously saved reply:
1. On the top bar, in the upper-right corner, select your avatar.
1. From the dropdown list, select **Preferences**.
1. On the left sidebar, select **Saved replies** (**{symlink}**).
1. Scroll to **My saved replies**, and identify the saved reply you want to edit.
1. To edit, select **Edit** (**{pencil}**).
1. To delete, select **Delete** (**{remove}**), then select **Delete** again from the modal window.

View File

@ -57,15 +57,11 @@ module API
end end
def cache_client def cache_client
if Feature.enabled?(:cache_client_with_metrics, user_project) Gitlab::Cache::Client.build_with_metadata(
Gitlab::Cache::Client.build_with_metadata( cache_identifier: 'API::Files#content_sha',
cache_identifier: 'API::Files#content_sha', feature_category: :source_code_management,
feature_category: :source_code_management, backing_resource: :gitaly
backing_resource: :gitaly )
)
else
Rails.cache
end
end end
def fetch_blame_range(blame_params) def fetch_blame_range(blame_params)

View File

@ -150,7 +150,7 @@ module Gitlab
return [] unless parsed_metadata['remediations'] return [] unless parsed_metadata['remediations']
parsed_metadata['remediations'].filter_map do |remediation| parsed_metadata['remediations'].filter_map do |remediation|
next unless remediation next unless remediation && remediation['diff'].present?
remediation.merge('checksum' => DiffFile.checksum(remediation['diff'])) remediation.merge('checksum' => DiffFile.checksum(remediation['diff']))
end.compact.uniq end.compact.uniq

View File

@ -7732,9 +7732,6 @@ msgstr ""
msgid "BulkImport|Import is finished. Pick another name for re-import" msgid "BulkImport|Import is finished. Pick another name for re-import"
msgstr "" msgstr ""
msgid "BulkImport|Import selected"
msgstr ""
msgid "BulkImport|Import with projects" msgid "BulkImport|Import with projects"
msgstr "" msgstr ""

View File

@ -56,7 +56,7 @@
"@gitlab/favicon-overlay": "2.0.0", "@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.2.0", "@gitlab/fonts": "^1.2.0",
"@gitlab/svgs": "3.24.0", "@gitlab/svgs": "3.24.0",
"@gitlab/ui": "56.4.0", "@gitlab/ui": "56.4.1",
"@gitlab/visual-review-tools": "1.7.3", "@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "0.0.1-dev-20230223005157", "@gitlab/web-ide": "0.0.1-dev-20230223005157",
"@mattiasbuelens/web-streams-adapter": "^0.1.0", "@mattiasbuelens/web-streams-adapter": "^0.1.0",

View File

@ -241,6 +241,13 @@ describe('Header Search Store Getters', () => {
MOCK_DEFAULT_SEARCH_OPTIONS, MOCK_DEFAULT_SEARCH_OPTIONS,
); );
}); });
it('returns the correct array if issues path is false', () => {
mockGetters.scopedIssuesPath = undefined;
expect(getters.defaultSearchOptions(state, mockGetters)).toStrictEqual(
MOCK_DEFAULT_SEARCH_OPTIONS.slice(2, MOCK_DEFAULT_SEARCH_OPTIONS.length),
);
});
}); });
describe('scopedSearchOptions', () => { describe('scopedSearchOptions', () => {

View File

@ -1,4 +1,4 @@
import { GlButton, GlIcon, GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { GlDropdown, GlIcon, GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import ImportActionsCell from '~/import_entities/import_groups/components/import_actions_cell.vue'; import ImportActionsCell from '~/import_entities/import_groups/components/import_actions_cell.vue';
@ -8,7 +8,6 @@ describe('import actions cell', () => {
const createComponent = (props) => { const createComponent = (props) => {
wrapper = shallowMount(ImportActionsCell, { wrapper = shallowMount(ImportActionsCell, {
propsData: { propsData: {
isProjectsImportEnabled: false,
isFinished: false, isFinished: false,
isAvailableForImport: false, isAvailableForImport: false,
isInvalid: false, isInvalid: false,
@ -22,10 +21,10 @@ describe('import actions cell', () => {
createComponent({ isAvailableForImport: true }); createComponent({ isAvailableForImport: true });
}); });
it('renders import button', () => { it('renders import dropdown', () => {
const button = wrapper.findComponent(GlButton); const dropdown = wrapper.findComponent(GlDropdown);
expect(button.exists()).toBe(true); expect(dropdown.exists()).toBe(true);
expect(button.text()).toBe('Import'); expect(dropdown.props('text')).toBe('Import with projects');
}); });
it('does not render icon with a hint', () => { it('does not render icon with a hint', () => {
@ -38,10 +37,10 @@ describe('import actions cell', () => {
createComponent({ isAvailableForImport: false, isFinished: true }); createComponent({ isAvailableForImport: false, isFinished: true });
}); });
it('renders re-import button', () => { it('renders re-import dropdown', () => {
const button = wrapper.findComponent(GlButton); const dropdown = wrapper.findComponent(GlDropdown);
expect(button.exists()).toBe(true); expect(dropdown.exists()).toBe(true);
expect(button.text()).toBe('Re-import'); expect(dropdown.props('text')).toBe('Re-import with projects');
}); });
it('renders icon with a hint', () => { it('renders icon with a hint', () => {
@ -53,25 +52,25 @@ describe('import actions cell', () => {
}); });
}); });
it('does not render import button when group is not available for import', () => { it('does not render import dropdown when group is not available for import', () => {
createComponent({ isAvailableForImport: false }); createComponent({ isAvailableForImport: false });
const button = wrapper.findComponent(GlButton); const dropdown = wrapper.findComponent(GlDropdown);
expect(button.exists()).toBe(false); expect(dropdown.exists()).toBe(false);
}); });
it('renders import button as disabled when group is invalid', () => { it('renders import dropdown as disabled when group is invalid', () => {
createComponent({ isInvalid: true, isAvailableForImport: true }); createComponent({ isInvalid: true, isAvailableForImport: true });
const button = wrapper.findComponent(GlButton); const dropdown = wrapper.findComponent(GlDropdown);
expect(button.props().disabled).toBe(true); expect(dropdown.props().disabled).toBe(true);
}); });
it('emits import-group event when import button is clicked', () => { it('emits import-group event when import button is clicked', () => {
createComponent({ isAvailableForImport: true }); createComponent({ isAvailableForImport: true });
const button = wrapper.findComponent(GlButton); const dropdown = wrapper.findComponent(GlDropdown);
button.vm.$emit('click'); dropdown.vm.$emit('click');
expect(wrapper.emitted('import-group')).toHaveLength(1); expect(wrapper.emitted('import-group')).toHaveLength(1);
}); });
@ -81,10 +80,10 @@ describe('import actions cell', () => {
${false} | ${'Import'} ${false} | ${'Import'}
${true} | ${'Re-import'} ${true} | ${'Re-import'}
`( `(
'when import projects is enabled, group is available for import and finish status is $status', 'group is available for import and finish status is $isFinished',
({ isFinished, expectedAction }) => { ({ isFinished, expectedAction }) => {
beforeEach(() => { beforeEach(() => {
createComponent({ isProjectsImportEnabled: true, isAvailableForImport: true, isFinished }); createComponent({ isAvailableForImport: true, isFinished });
}); });
it('render import dropdown', () => { it('render import dropdown', () => {

View File

@ -49,12 +49,12 @@ describe('import table', () => {
}, },
}; };
const findImportSelectedButton = () =>
wrapper.findAll('button').wrappers.find((w) => w.text() === 'Import selected');
const findImportSelectedDropdown = () => const findImportSelectedDropdown = () =>
wrapper.findAll('.gl-dropdown').wrappers.find((w) => w.text().includes('Import with projects')); wrapper.find('[data-testid="import-selected-groups-dropdown"]');
const findImportButtons = () => const findRowImportDropdownAtIndex = (idx) =>
wrapper.findAll('button').wrappers.filter((w) => w.text() === 'Import'); wrapper.findAll('tbody td button').wrappers.filter((w) => w.text() === 'Import with projects')[
idx
];
const findPaginationDropdown = () => wrapper.find('[data-testid="page-size"]'); const findPaginationDropdown = () => wrapper.find('[data-testid="page-size"]');
const findTargetNamespaceDropdown = (rowWrapper) => const findTargetNamespaceDropdown = (rowWrapper) =>
rowWrapper.find('[data-testid="target-namespace-selector"]'); rowWrapper.find('[data-testid="target-namespace-selector"]');
@ -70,12 +70,7 @@ describe('import table', () => {
const findRowCheckbox = (idx) => wrapper.findAll('tbody td input[type=checkbox]').at(idx); const findRowCheckbox = (idx) => wrapper.findAll('tbody td input[type=checkbox]').at(idx);
const selectRow = (idx) => findRowCheckbox(idx).setChecked(true); const selectRow = (idx) => findRowCheckbox(idx).setChecked(true);
const createComponent = ({ const createComponent = ({ bulkImportSourceGroups, importGroups, defaultTargetNamespace }) => {
bulkImportSourceGroups,
importGroups,
defaultTargetNamespace,
glFeatures = {},
}) => {
apolloProvider = createMockApollo( apolloProvider = createMockApollo(
[ [
[ [
@ -104,9 +99,6 @@ describe('import table', () => {
directives: { directives: {
GlTooltip: createMockDirective('gl-tooltip'), GlTooltip: createMockDirective('gl-tooltip'),
}, },
provide: {
glFeatures,
},
apolloProvider, apolloProvider,
}); });
}; };
@ -130,7 +122,7 @@ describe('import table', () => {
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
}); });
it('does not renders loading icon when request is completed', async () => { it('does not render loading icon when request is completed', async () => {
createComponent({ createComponent({
bulkImportSourceGroups: () => [], bulkImportSourceGroups: () => [],
}); });
@ -241,12 +233,13 @@ describe('import table', () => {
await waitForPromises(); await waitForPromises();
await findImportButtons()[0].trigger('click'); await findRowImportDropdownAtIndex(0).trigger('click');
expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({ expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({
mutation: importGroupsMutation, mutation: importGroupsMutation,
variables: { variables: {
importRequests: [ importRequests: [
{ {
migrateProjects: true,
newName: FAKE_GROUP.lastImportTarget.newName, newName: FAKE_GROUP.lastImportTarget.newName,
sourceGroupId: FAKE_GROUP.id, sourceGroupId: FAKE_GROUP.id,
targetNamespace: AVAILABLE_NAMESPACES[0].fullPath, targetNamespace: AVAILABLE_NAMESPACES[0].fullPath,
@ -269,7 +262,7 @@ describe('import table', () => {
}); });
await waitForPromises(); await waitForPromises();
await findImportButtons()[0].trigger('click'); await findRowImportDropdownAtIndex(0).trigger('click');
await waitForPromises(); await waitForPromises();
expect(createAlert).toHaveBeenCalledWith( expect(createAlert).toHaveBeenCalledWith(
@ -294,7 +287,7 @@ describe('import table', () => {
}); });
await waitForPromises(); await waitForPromises();
await findImportButtons()[0].trigger('click'); await findRowImportDropdownAtIndex(0).trigger('click');
await waitForPromises(); await waitForPromises();
expect(createAlert).not.toHaveBeenCalled(); expect(createAlert).not.toHaveBeenCalled();
@ -472,7 +465,7 @@ describe('import table', () => {
}); });
await waitForPromises(); await waitForPromises();
expect(findImportSelectedButton().props().disabled).toBe(true); expect(findImportSelectedDropdown().props().disabled).toBe(true);
}); });
it('import selected button is enabled when groups were selected for import', async () => { it('import selected button is enabled when groups were selected for import', async () => {
@ -487,7 +480,7 @@ describe('import table', () => {
await selectRow(0); await selectRow(0);
expect(findImportSelectedButton().props().disabled).toBe(false); expect(findImportSelectedDropdown().props().disabled).toBe(false);
}); });
it('does not allow selecting already started groups', async () => { it('does not allow selecting already started groups', async () => {
@ -505,7 +498,7 @@ describe('import table', () => {
await selectRow(0); await selectRow(0);
await nextTick(); await nextTick();
expect(findImportSelectedButton().props().disabled).toBe(true); expect(findImportSelectedDropdown().props().disabled).toBe(true);
}); });
it('does not allow selecting groups with validation errors', async () => { it('does not allow selecting groups with validation errors', async () => {
@ -530,10 +523,10 @@ describe('import table', () => {
await selectRow(0); await selectRow(0);
await nextTick(); await nextTick();
expect(findImportSelectedButton().props().disabled).toBe(true); expect(findImportSelectedDropdown().props().disabled).toBe(true);
}); });
it('invokes importGroups mutation when import selected button is clicked', async () => { it('invokes importGroups mutation when import selected dropdown is clicked', async () => {
const NEW_GROUPS = [ const NEW_GROUPS = [
generateFakeEntry({ id: 1, status: STATUSES.NONE }), generateFakeEntry({ id: 1, status: STATUSES.NONE }),
generateFakeEntry({ id: 2, status: STATUSES.NONE }), generateFakeEntry({ id: 2, status: STATUSES.NONE }),
@ -554,7 +547,7 @@ describe('import table', () => {
await selectRow(1); await selectRow(1);
await nextTick(); await nextTick();
await findImportSelectedButton().trigger('click'); await findImportSelectedDropdown().find('button').trigger('click');
expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({ expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({
mutation: importGroupsMutation, mutation: importGroupsMutation,
@ -675,7 +668,7 @@ describe('import table', () => {
}); });
}); });
describe('when import projects is enabled', () => { describe('importing projects', () => {
const NEW_GROUPS = [ const NEW_GROUPS = [
generateFakeEntry({ id: 1, status: STATUSES.NONE }), generateFakeEntry({ id: 1, status: STATUSES.NONE }),
generateFakeEntry({ id: 2, status: STATUSES.NONE }), generateFakeEntry({ id: 2, status: STATUSES.NONE }),
@ -689,9 +682,6 @@ describe('import table', () => {
pageInfo: FAKE_PAGE_INFO, pageInfo: FAKE_PAGE_INFO,
versionValidation: FAKE_VERSION_VALIDATION, versionValidation: FAKE_VERSION_VALIDATION,
}), }),
glFeatures: {
bulkImportProjects: true,
},
}); });
jest.spyOn(apolloProvider.defaultClient, 'mutate'); jest.spyOn(apolloProvider.defaultClient, 'mutate');
return waitForPromises(); return waitForPromises();

View File

@ -278,26 +278,30 @@ describe('tags list row', () => {
textSrOnly: true, textSrOnly: true,
category: 'tertiary', category: 'tertiary',
right: true, right: true,
disabled: false,
}); });
}); });
it.each` it('has the correct classes', () => {
canDelete | digest | disabled | buttonDisabled mountComponent();
${true} | ${null} | ${true} | ${true}
${false} | ${'foo'} | ${true} | ${true}
${false} | ${null} | ${true} | ${true}
${true} | ${'foo'} | ${true} | ${true}
${true} | ${'foo'} | ${false} | ${false}
`(
'is $visible that is visible when canDelete is $canDelete and digest is $digest and disabled is $disabled',
({ canDelete, digest, disabled, buttonDisabled }) => {
mountComponent({ ...defaultProps, tag: { ...tag, canDelete, digest }, disabled });
expect(findAdditionalActionsMenu().props('disabled')).toBe(buttonDisabled); expect(findAdditionalActionsMenu().classes('gl-opacity-0')).toBe(false);
expect(findAdditionalActionsMenu().classes('gl-opacity-0')).toBe(buttonDisabled); expect(findAdditionalActionsMenu().classes('gl-pointer-events-none')).toBe(false);
expect(findAdditionalActionsMenu().classes('gl-pointer-events-none')).toBe(buttonDisabled); });
},
); it('is not rendered when tag.canDelete is false', () => {
mountComponent({ ...defaultProps, tag: { ...tag, canDelete: false } });
expect(findAdditionalActionsMenu().exists()).toBe(false);
});
it('is hidden when disabled prop is set to true', () => {
mountComponent({ ...defaultProps, disabled: true });
expect(findAdditionalActionsMenu().props('disabled')).toBe(true);
expect(findAdditionalActionsMenu().classes('gl-opacity-0')).toBe(true);
expect(findAdditionalActionsMenu().classes('gl-pointer-events-none')).toBe(true);
});
describe('delete button', () => { describe('delete button', () => {
it('exists and has the correct attrs', () => { it('exists and has the correct attrs', () => {

View File

@ -69,10 +69,10 @@ describe('Tags List', () => {
}); });
describe('registry list', () => { describe('registry list', () => {
beforeEach(() => { beforeEach(async () => {
mountComponent(); mountComponent();
fireFirstSortUpdate(); fireFirstSortUpdate();
return waitForApolloRequestRender(); await waitForApolloRequestRender();
}); });
it('has a persisted search', () => { it('has a persisted search', () => {
@ -94,6 +94,7 @@ describe('Tags List', () => {
pagination: tagsPageInfo, pagination: tagsPageInfo,
items: tags, items: tags,
idProperty: 'name', idProperty: 'name',
hiddenDelete: false,
}); });
}); });
@ -182,12 +183,23 @@ describe('Tags List', () => {
}); });
}); });
describe('when the list of tags is empty', () => { describe('when user does not have permission to delete list rows', () => {
beforeEach(() => { it('sets registry list hiddenDelete prop to true', async () => {
resolver = jest.fn().mockResolvedValue(imageTagsMock([])); resolver = jest.fn().mockResolvedValue(imageTagsMock({ canDelete: false }));
mountComponent(); mountComponent();
fireFirstSortUpdate(); fireFirstSortUpdate();
return waitForApolloRequestRender(); await waitForApolloRequestRender();
expect(findRegistryList().props('hiddenDelete')).toBe(true);
});
});
describe('when the list of tags is empty', () => {
beforeEach(async () => {
resolver = jest.fn().mockResolvedValue(imageTagsMock({ nodes: [] }));
mountComponent();
fireFirstSortUpdate();
await waitForApolloRequestRender();
}); });
it('does not show the loader', () => { it('does not show the loader', () => {

View File

@ -177,11 +177,12 @@ export const tagsMock = [
}, },
]; ];
export const imageTagsMock = (nodes = tagsMock) => ({ export const imageTagsMock = ({ nodes = tagsMock, canDelete = true } = {}) => ({
data: { data: {
containerRepository: { containerRepository: {
id: containerRepositoryMock.id, id: containerRepositoryMock.id,
tagsCount: nodes.length, tagsCount: nodes.length,
canDelete,
tags: { tags: {
nodes, nodes,
pageInfo: { ...tagsPageInfo }, pageInfo: { ...tagsPageInfo },

View File

@ -107,10 +107,21 @@ describe('Registry List', () => {
expect(findDeleteSelected().text()).toBe(component.i18n.deleteSelected); expect(findDeleteSelected().text()).toBe(component.i18n.deleteSelected);
}); });
it('is hidden when hiddenDelete is true', () => { describe('when hiddenDelete is true', () => {
mountComponent({ propsData: { ...defaultPropsData, hiddenDelete: true } }); beforeEach(() => {
mountComponent({ propsData: { ...defaultPropsData, hiddenDelete: true } });
});
expect(findDeleteSelected().exists()).toBe(false); it('is hidden', () => {
expect(findDeleteSelected().exists()).toBe(false);
});
it('populates the first slot prop correctly', async () => {
expect(findScopedSlots().at(0).exists()).toBe(true);
// it's the first slot
expect(findScopedSlotFirstValue(0).text()).toBe('false');
});
}); });
it('is disabled when isLoading is true', () => { it('is disabled when isLoading is true', () => {

View File

@ -829,6 +829,21 @@ RSpec.describe SearchHelper, feature_category: :global_search do
expect(header_search_context[:project_metadata]).to eq(project_metadata) expect(header_search_context[:project_metadata]).to eq(project_metadata)
end end
context 'feature issues is not available' do
let(:feature_available) { false }
let(:project_metadata) { { mr_path: project_merge_requests_path(project) } }
before do
allow(project).to receive(:feature_available?).and_call_original
allow(project).to receive(:feature_available?).with(:issues, current_user).and_return(feature_available)
end
it 'adds the :project and :project-metadata correctly to hash' do
expect(header_search_context[:project]).to eq({ id: project.id, name: project.name })
expect(header_search_context[:project_metadata]).to eq(project_metadata)
end
end
context 'with scope' do context 'with scope' do
let(:scope) { 'issues' } let(:scope) { 'issues' }

View File

@ -57,6 +57,18 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateRemediationsForVulnerabilityF
end end
end end
context 'with remediation with empty string as the diff key' do
let!(:finding) do
create_finding!(project1.id, scanner1.id, { remediations: [{ summary: 'summary', diff: '' }] })
end
it 'does not create any remediation' do
expect(Gitlab::AppLogger).not_to receive(:error)
expect { perform_migration }.not_to change { vulnerability_remediations.count }
end
end
context 'with remediation equals to an array of duplicated elements' do context 'with remediation equals to an array of duplicated elements' do
let!(:finding) do let!(:finding) do
create_finding!(project1.id, scanner1.id, { remediations: [remediation_hash, remediation_hash] }) create_finding!(project1.id, scanner1.id, { remediations: [remediation_hash, remediation_hash] })

View File

@ -10,13 +10,7 @@ RSpec.describe ScheduleMigrationForRemediation, :migration, feature_category: :v
it 'schedules a batched background migration' do it 'schedules a batched background migration' do
migrate! migrate!
expect(migration).to have_scheduled_batched_migration( expect(migration).not_to have_scheduled_batched_migration
table_name: :vulnerability_occurrences,
column_name: :id,
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE
)
end end
end end

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe FinalizeCiBuildNeedsBigIntConversion, migration: :gitlab_ci, feature_category: :continuous_integration do
describe '#up' do
using RSpec::Parameterized::TableSyntax
where(:dot_com, :dev_or_test, :jh, :expectation) do
true | true | true | :not_to
true | false | true | :not_to
false | true | true | :not_to
false | false | true | :not_to
true | true | false | :to
true | false | false | :to
false | true | false | :to
false | false | false | :not_to
end
with_them do
it 'ensures the migration is completed for GitLab.com, dev, or test' do
allow(Gitlab).to receive(:com?).and_return(dot_com)
allow(Gitlab).to receive(:dev_or_test_env?).and_return(dev_or_test)
allow(Gitlab).to receive(:jh?).and_return(jh)
migration_arguments = {
job_class_name: 'CopyColumnUsingBackgroundMigrationJob',
table_name: 'ci_build_needs',
column_name: 'id',
job_arguments: [['id'], ['id_convert_to_bigint']]
}
expect(described_class).send(
expectation,
ensure_batched_background_migration_is_finished_for(migration_arguments)
)
migrate!
end
end
end
end

View File

@ -0,0 +1,60 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe SwapColumnsCiBuildNeedsBigIntConversion, feature_category: :continuous_integration do
describe '#up' do
using RSpec::Parameterized::TableSyntax
where(:dot_com, :dev_or_test, :jh, :swap) do
true | true | true | false
true | false | true | false
false | true | true | false
false | false | true | false
true | true | false | true
true | false | false | true
false | true | false | true
false | false | false | false
end
with_them do
before do
connection = described_class.new.connection
connection.execute('ALTER TABLE ci_build_needs ALTER COLUMN id TYPE integer')
connection.execute('ALTER TABLE ci_build_needs ALTER COLUMN id_convert_to_bigint TYPE bigint')
end
it 'swaps the integer and bigint columns for GitLab.com, dev, or test' do
allow(Gitlab).to receive(:com?).and_return(dot_com)
allow(Gitlab).to receive(:dev_or_test_env?).and_return(dev_or_test)
allow(Gitlab).to receive(:jh?).and_return(jh)
ci_build_needs = table(:ci_build_needs)
disable_migrations_output do
reversible_migration do |migration|
migration.before -> {
ci_build_needs.reset_column_information
expect(ci_build_needs.columns.find { |c| c.name == 'id' }.sql_type).to eq('integer')
expect(ci_build_needs.columns.find { |c| c.name == 'id_convert_to_bigint' }.sql_type).to eq('bigint')
}
migration.after -> {
ci_build_needs.reset_column_information
if swap
expect(ci_build_needs.columns.find { |c| c.name == 'id' }.sql_type).to eq('bigint')
expect(ci_build_needs.columns.find { |c| c.name == 'id_convert_to_bigint' }.sql_type).to eq('integer')
else
expect(ci_build_needs.columns.find { |c| c.name == 'id' }.sql_type).to eq('integer')
expect(ci_build_needs.columns.find { |c| c.name == 'id_convert_to_bigint' }.sql_type).to eq('bigint')
end
}
end
end
end
end
end
end

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe DeleteSecurityPolicyBotUsers, feature_category: :security_policy_management do
let(:users) { table(:users) }
before do
users.create!(user_type: 10, projects_limit: 0, email: 'security_policy_bot@example.com')
users.create!(user_type: 1, projects_limit: 0, email: 'support_bot@example.com')
users.create!(projects_limit: 0, email: 'human@example.com')
end
describe '#up' do
it 'deletes security_policy_bot users' do
expect { migrate! }.to change { users.count }.by(-1)
expect(users.where(user_type: 10).count).to eq(0)
expect(users.where(user_type: 1).count).to eq(1)
expect(users.where(user_type: nil).count).to eq(1)
end
end
end

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe RescheduleMigrationForRemediation, :migration, feature_category: :vulnerability_management do
let(:migration) { described_class::MIGRATION }
describe '#up' do
it 'schedules a batched background migration' do
migrate!
expect(migration).to have_scheduled_batched_migration(
table_name: :vulnerability_occurrences,
column_name: :id,
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE
)
end
end
describe '#down' do
it 'deletes all batched migration records' do
migrate!
schema_migrate_down!
expect(migration).not_to have_scheduled_batched_migration
end
end
end

View File

@ -6,7 +6,7 @@ RSpec.describe User, feature_category: :system_access do
specify 'types consistency checks', :aggregate_failures do specify 'types consistency checks', :aggregate_failures do
expect(described_class::USER_TYPES.keys) expect(described_class::USER_TYPES.keys)
.to match_array(%w[human ghost alert_bot project_bot support_bot service_user security_bot visual_review_bot .to match_array(%w[human ghost alert_bot project_bot support_bot service_user security_bot visual_review_bot
migration_bot automation_bot admin_bot suggested_reviewers_bot service_account]) migration_bot automation_bot security_policy_bot admin_bot suggested_reviewers_bot service_account])
expect(described_class::USER_TYPES).to include(*described_class::BOT_USER_TYPES) expect(described_class::USER_TYPES).to include(*described_class::BOT_USER_TYPES)
expect(described_class::USER_TYPES).to include(*described_class::NON_INTERNAL_USER_TYPES) expect(described_class::USER_TYPES).to include(*described_class::NON_INTERNAL_USER_TYPES)
expect(described_class::USER_TYPES).to include(*described_class::INTERNAL_USER_TYPES) expect(described_class::USER_TYPES).to include(*described_class::INTERNAL_USER_TYPES)

View File

@ -157,27 +157,6 @@ RSpec.describe API::Files, feature_category: :source_code_management do
head api(route(file_path), current_user, **options), params: params head api(route(file_path), current_user, **options), params: params
end end
context 'when feature flag "cache_client_with_metrics" is disabled' do
before do
stub_feature_flags(cache_client_with_metrics: false)
end
it 'caches sha256 of the content', :use_clean_rails_redis_caching do
head api(route(file_path), current_user, **options), params: params
expect(Gitlab::Cache::Client).not_to receive(:build_with_metadata)
expect(Rails.cache.fetch("blob_content_sha256:#{project.full_path}:#{response.headers['X-Gitlab-Blob-Id']}"))
.to eq(content_sha256)
expect_next_instance_of(Gitlab::Git::Blob) do |instance|
expect(instance).not_to receive(:load_all_data!)
end
head api(route(file_path), current_user, **options), params: params
end
end
it 'returns file by commit sha' do it 'returns file by commit sha' do
# This file is deleted on HEAD # This file is deleted on HEAD
file_path = 'files%2Fjs%2Fcommit%2Ejs%2Ecoffee' file_path = 'files%2Fjs%2Fcommit%2Ejs%2Ecoffee'

View File

@ -74,7 +74,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
let(:base_url) { "/groups/#{group.id}/issues" } let(:base_url) { "/groups/#{group.id}/issues" }
shared_examples 'group issues statistics' do shared_examples 'group issues statistics' do
it 'returns issues statistics' do it 'returns issues statistics', :aggregate_failures do
get api("/groups/#{group.id}/issues_statistics", user), params: params get api("/groups/#{group.id}/issues_statistics", user), params: params
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -346,7 +346,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
group_project.add_reporter(user) group_project.add_reporter(user)
end end
it 'exposes known attributes' do it 'exposes known attributes', :aggregate_failures do
get api(base_url, admin) get api(base_url, admin)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -355,7 +355,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
it 'returns all group issues (including opened and closed)' do it 'returns all group issues (including opened and closed)' do
get api(base_url, admin) get api(base_url, admin, admin_mode: true)
expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id]) expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id])
end end
@ -385,7 +385,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
it 'returns group confidential issues for admin' do it 'returns group confidential issues for admin' do
get api(base_url, admin), params: { state: :opened } get api(base_url, admin, admin_mode: true), params: { state: :opened }
expect_paginated_array_response([group_confidential_issue.id, group_issue.id]) expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
end end
@ -403,7 +403,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'labels parameter' do context 'labels parameter' do
it 'returns an array of labeled group issues' do it 'returns an array of labeled group issues', :aggregate_failures do
get api(base_url, user), params: { labels: group_label.title } get api(base_url, user), params: { labels: group_label.title }
expect_paginated_array_response(group_issue.id) expect_paginated_array_response(group_issue.id)
@ -486,7 +486,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
end end
it 'returns an array of issues found by iids' do it 'returns an array of issues found by iids', :aggregate_failures do
get api(base_url, user), params: { iids: [group_issue.iid] } get api(base_url, user), params: { iids: [group_issue.iid] }
expect_paginated_array_response(group_issue.id) expect_paginated_array_response(group_issue.id)
@ -505,14 +505,14 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect_paginated_array_response([]) expect_paginated_array_response([])
end end
it 'returns an array of group issues with any label' do it 'returns an array of group issues with any label', :aggregate_failures do
get api(base_url, user), params: { labels: IssuableFinder::Params::FILTER_ANY } get api(base_url, user), params: { labels: IssuableFinder::Params::FILTER_ANY }
expect_paginated_array_response(group_issue.id) expect_paginated_array_response(group_issue.id)
expect(json_response.first['id']).to eq(group_issue.id) expect(json_response.first['id']).to eq(group_issue.id)
end end
it 'returns an array of group issues with any label with labels param as array' do it 'returns an array of group issues with any label with labels param as array', :aggregate_failures do
get api(base_url, user), params: { labels: [IssuableFinder::Params::FILTER_ANY] } get api(base_url, user), params: { labels: [IssuableFinder::Params::FILTER_ANY] }
expect_paginated_array_response(group_issue.id) expect_paginated_array_response(group_issue.id)
@ -555,7 +555,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect_paginated_array_response(group_closed_issue.id) expect_paginated_array_response(group_closed_issue.id)
end end
it 'returns an array of issues with no milestone' do it 'returns an array of issues with no milestone', :aggregate_failures do
get api(base_url, user), params: { milestone: no_milestone_title } get api(base_url, user), params: { milestone: no_milestone_title }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -688,28 +688,28 @@ RSpec.describe API::Issues, feature_category: :team_planning do
let!(:issue2) { create(:issue, author: user2, project: group_project, created_at: 2.days.ago) } let!(:issue2) { create(:issue, author: user2, project: group_project, created_at: 2.days.ago) }
let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: group_project, created_at: 1.day.ago) } let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: group_project, created_at: 1.day.ago) }
it 'returns issues with by assignee_username' do it 'returns issues with by assignee_username', :aggregate_failures do
get api(base_url, user), params: { assignee_username: [assignee.username], scope: 'all' } get api(base_url, user), params: { assignee_username: [assignee.username], scope: 'all' }
expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id]) expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
expect_paginated_array_response([issue3.id, group_confidential_issue.id]) expect_paginated_array_response([issue3.id, group_confidential_issue.id])
end end
it 'returns issues by assignee_username as string' do it 'returns issues by assignee_username as string', :aggregate_failures do
get api(base_url, user), params: { assignee_username: assignee.username, scope: 'all' } get api(base_url, user), params: { assignee_username: assignee.username, scope: 'all' }
expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id]) expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
expect_paginated_array_response([issue3.id, group_confidential_issue.id]) expect_paginated_array_response([issue3.id, group_confidential_issue.id])
end end
it 'returns error when multiple assignees are passed' do it 'returns error when multiple assignees are passed', :aggregate_failures do
get api(base_url, user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' } get api(base_url, user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' }
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response["error"]).to include("allows one value, but found 2") expect(json_response["error"]).to include("allows one value, but found 2")
end end
it 'returns error when assignee_username and assignee_id are passed together' do it 'returns error when assignee_username and assignee_id are passed together', :aggregate_failures do
get api(base_url, user), params: { assignee_username: [assignee.username], assignee_id: another_assignee.id, scope: 'all' } get api(base_url, user), params: { assignee_username: [assignee.username], assignee_id: another_assignee.id, scope: 'all' }
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
@ -719,7 +719,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
describe "#to_reference" do describe "#to_reference" do
it 'exposes reference path in context of group' do it 'exposes reference path in context of group', :aggregate_failures do
get api(base_url, user) get api(base_url, user)
expect(json_response.first['references']['short']).to eq("##{group_closed_issue.iid}") expect(json_response.first['references']['short']).to eq("##{group_closed_issue.iid}")
@ -735,7 +735,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
group_closed_issue.reload group_closed_issue.reload
end end
it 'exposes reference path in context of parent group' do it 'exposes reference path in context of parent group', :aggregate_failures do
get api("/groups/#{parent_group.id}/issues") get api("/groups/#{parent_group.id}/issues")
expect(json_response.first['references']['short']).to eq("##{group_closed_issue.iid}") expect(json_response.first['references']['short']).to eq("##{group_closed_issue.iid}")

View File

@ -99,7 +99,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
shared_examples 'project issues statistics' do shared_examples 'project issues statistics' do
it 'returns project issues statistics' do it 'returns project issues statistics', :aggregate_failures do
get api("/projects/#{project.id}/issues_statistics", current_user), params: params get api("/projects/#{project.id}/issues_statistics", current_user), params: params
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -317,7 +317,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
it 'returns project confidential issues for admin' do it 'returns project confidential issues for admin' do
get api("#{base_url}/issues", admin) get api("#{base_url}/issues", admin, admin_mode: true)
expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id]) expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
end end
@ -526,7 +526,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id]) expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id])
end end
it 'exposes known attributes' do it 'exposes known attributes', :aggregate_failures do
get api("#{base_url}/issues", user) get api("#{base_url}/issues", user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -607,28 +607,28 @@ RSpec.describe API::Issues, feature_category: :team_planning do
let!(:issue2) { create(:issue, author: user2, project: project, created_at: 2.days.ago) } let!(:issue2) { create(:issue, author: user2, project: project, created_at: 2.days.ago) }
let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: project, created_at: 1.day.ago) } let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: project, created_at: 1.day.ago) }
it 'returns issues by assignee_username' do it 'returns issues by assignee_username', :aggregate_failures do
get api("/issues", user), params: { assignee_username: [assignee.username], scope: 'all' } get api("/issues", user), params: { assignee_username: [assignee.username], scope: 'all' }
expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id]) expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
expect_paginated_array_response([confidential_issue.id, issue3.id]) expect_paginated_array_response([confidential_issue.id, issue3.id])
end end
it 'returns issues by assignee_username as string' do it 'returns issues by assignee_username as string', :aggregate_failures do
get api("/issues", user), params: { assignee_username: assignee.username, scope: 'all' } get api("/issues", user), params: { assignee_username: assignee.username, scope: 'all' }
expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id]) expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
expect_paginated_array_response([confidential_issue.id, issue3.id]) expect_paginated_array_response([confidential_issue.id, issue3.id])
end end
it 'returns error when multiple assignees are passed' do it 'returns error when multiple assignees are passed', :aggregate_failures do
get api("/issues", user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' } get api("/issues", user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' }
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response["error"]).to include("allows one value, but found 2") expect(json_response["error"]).to include("allows one value, but found 2")
end end
it 'returns error when assignee_username and assignee_id are passed together' do it 'returns error when assignee_username and assignee_id are passed together', :aggregate_failures do
get api("/issues", user), params: { assignee_username: [assignee.username], assignee_id: another_assignee.id, scope: 'all' } get api("/issues", user), params: { assignee_username: [assignee.username], assignee_id: another_assignee.id, scope: 'all' }
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
@ -646,7 +646,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
end end
it 'exposes known attributes' do it 'exposes known attributes', :aggregate_failures do
get api("/projects/#{project.id}/issues/#{issue.iid}", user) get api("/projects/#{project.id}/issues/#{issue.iid}", user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -686,7 +686,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
end end
it 'exposes the closed_at attribute' do it 'exposes the closed_at attribute', :aggregate_failures do
get api("/projects/#{project.id}/issues/#{closed_issue.iid}", user) get api("/projects/#{project.id}/issues/#{closed_issue.iid}", user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -694,7 +694,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'links exposure' do context 'links exposure' do
it 'exposes related resources full URIs' do it 'exposes related resources full URIs', :aggregate_failures do
get api("/projects/#{project.id}/issues/#{issue.iid}", user) get api("/projects/#{project.id}/issues/#{issue.iid}", user)
links = json_response['_links'] links = json_response['_links']
@ -706,7 +706,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
end end
it 'returns a project issue by internal id' do it 'returns a project issue by internal id', :aggregate_failures do
get api("/projects/#{project.id}/issues/#{issue.iid}", user) get api("/projects/#{project.id}/issues/#{issue.iid}", user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -738,7 +738,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
it 'returns confidential issue for project members' do it 'returns confidential issue for project members', :aggregate_failures do
get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user) get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -746,7 +746,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(json_response['iid']).to eq(confidential_issue.iid) expect(json_response['iid']).to eq(confidential_issue.iid)
end end
it 'returns confidential issue for author' do it 'returns confidential issue for author', :aggregate_failures do
get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author) get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", author)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -754,7 +754,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(json_response['iid']).to eq(confidential_issue.iid) expect(json_response['iid']).to eq(confidential_issue.iid)
end end
it 'returns confidential issue for assignee' do it 'returns confidential issue for assignee', :aggregate_failures do
get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", assignee) get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", assignee)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -762,8 +762,8 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(json_response['iid']).to eq(confidential_issue.iid) expect(json_response['iid']).to eq(confidential_issue.iid)
end end
it 'returns confidential issue for admin' do it 'returns confidential issue for admin', :aggregate_failures do
get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin) get api("/projects/#{project.id}/issues/#{confidential_issue.iid}", admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['title']).to eq(confidential_issue.title) expect(json_response['title']).to eq(confidential_issue.title)
@ -829,7 +829,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
let!(:related_mr) { create_referencing_mr(user, project, issue) } let!(:related_mr) { create_referencing_mr(user, project, issue) }
context 'when unauthenticated' do context 'when unauthenticated' do
it 'return list of referenced merge requests from issue' do it 'return list of referenced merge requests from issue', :aggregate_failures do
get_related_merge_requests(project.id, issue.iid) get_related_merge_requests(project.id, issue.iid)
expect_paginated_array_response(related_mr.id) expect_paginated_array_response(related_mr.id)
@ -898,8 +898,8 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
end end
it 'exposes known attributes' do it 'exposes known attributes', :aggregate_failures do
get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", admin) get api("/projects/#{project.id}/issues/#{issue.iid}/user_agent_detail", admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['user_agent']).to eq(user_agent_detail.user_agent) expect(json_response['user_agent']).to eq(user_agent_detail.user_agent)
@ -936,7 +936,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
) )
end end
it 'returns a full list of participants' do it 'returns a full list of participants', :aggregate_failures do
get api("/projects/#{project.id}/issues/#{issue.iid}/participants", user) get api("/projects/#{project.id}/issues/#{issue.iid}/participants", user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -945,7 +945,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'when user cannot see a confidential note' do context 'when user cannot see a confidential note' do
it 'returns a limited list of participants' do it 'returns a limited list of participants', :aggregate_failures do
get api("/projects/#{project.id}/issues/#{issue.iid}/participants", create(:user)) get api("/projects/#{project.id}/issues/#{issue.iid}/participants", create(:user))
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)

View File

@ -78,7 +78,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
shared_examples 'issues statistics' do shared_examples 'issues statistics' do
it 'returns issues statistics' do it 'returns issues statistics', :aggregate_failures do
get api("/issues_statistics", user), params: params get api("/issues_statistics", user), params: params
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -109,8 +109,8 @@ RSpec.describe API::Issues, feature_category: :team_planning do
context 'as an admin' do context 'as an admin' do
context 'when issue exists' do context 'when issue exists' do
it 'returns the issue' do it 'returns the issue', :aggregate_failures do
get api("/issues/#{issue.id}", admin) get api("/issues/#{issue.id}", admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response.dig('author', 'id')).to eq(issue.author.id) expect(json_response.dig('author', 'id')).to eq(issue.author.id)
@ -121,7 +121,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
context 'when issue does not exist' do context 'when issue does not exist' do
it 'returns 404' do it 'returns 404' do
get api("/issues/0", admin) get api("/issues/0", admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
@ -132,7 +132,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
describe 'GET /issues' do describe 'GET /issues' do
context 'when unauthenticated' do context 'when unauthenticated' do
it 'returns an array of all issues' do it 'returns an array of all issues', :aggregate_failures do
get api('/issues'), params: { scope: 'all' } get api('/issues'), params: { scope: 'all' }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -162,14 +162,14 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(response).to have_gitlab_http_status(:unauthorized) expect(response).to have_gitlab_http_status(:unauthorized)
end end
it 'returns an array of issues matching state in milestone' do it 'returns an array of issues matching state in milestone', :aggregate_failures do
get api('/issues'), params: { milestone: 'foo', scope: 'all' } get api('/issues'), params: { milestone: 'foo', scope: 'all' }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect_paginated_array_response([]) expect_paginated_array_response([])
end end
it 'returns an array of issues matching state in milestone' do it 'returns an array of issues matching state in milestone', :aggregate_failures do
get api('/issues'), params: { milestone: milestone.title, scope: 'all' } get api('/issues'), params: { milestone: milestone.title, scope: 'all' }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -273,7 +273,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'when authenticated' do context 'when authenticated' do
it 'returns an array of issues' do it 'returns an array of issues', :aggregate_failures do
get api('/issues', user) get api('/issues', user)
expect_paginated_array_response([issue.id, closed_issue.id]) expect_paginated_array_response([issue.id, closed_issue.id])
@ -532,7 +532,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
context 'with incident issues' do context 'with incident issues' do
let_it_be(:incident) { create(:incident, project: project) } let_it_be(:incident) { create(:incident, project: project) }
it 'avoids N+1 queries' do it 'avoids N+1 queries', :aggregate_failures do
get api('/issues', user) # warm up get api('/issues', user) # warm up
control = ActiveRecord::QueryRecorder.new do control = ActiveRecord::QueryRecorder.new do
@ -553,7 +553,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
context 'with issues closed as duplicates' do context 'with issues closed as duplicates' do
let_it_be(:dup_issue_1) { create(:issue, :closed_as_duplicate, project: project) } let_it_be(:dup_issue_1) { create(:issue, :closed_as_duplicate, project: project) }
it 'avoids N+1 queries' do it 'avoids N+1 queries', :aggregate_failures do
get api('/issues', user) # warm up get api('/issues', user) # warm up
control = ActiveRecord::QueryRecorder.new do control = ActiveRecord::QueryRecorder.new do
@ -639,7 +639,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect_paginated_array_response([]) expect_paginated_array_response([])
end end
it 'returns an array of labeled issues matching given state' do it 'returns an array of labeled issues matching given state', :aggregate_failures do
get api('/issues', user), params: { labels: label.title, state: :opened } get api('/issues', user), params: { labels: label.title, state: :opened }
expect_paginated_array_response(issue.id) expect_paginated_array_response(issue.id)
@ -647,7 +647,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(json_response.first['state']).to eq('opened') expect(json_response.first['state']).to eq('opened')
end end
it 'returns an array of labeled issues matching given state with labels param as array' do it 'returns an array of labeled issues matching given state with labels param as array', :aggregate_failures do
get api('/issues', user), params: { labels: [label.title], state: :opened } get api('/issues', user), params: { labels: [label.title], state: :opened }
expect_paginated_array_response(issue.id) expect_paginated_array_response(issue.id)
@ -917,14 +917,14 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
end end
it 'matches V4 response schema' do it 'matches V4 response schema', :aggregate_failures do
get api('/issues', user) get api('/issues', user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/issues') expect(response).to match_response_schema('public_api/v4/issues')
end end
it 'returns a related merge request count of 0 if there are no related merge requests' do it 'returns a related merge request count of 0 if there are no related merge requests', :aggregate_failures do
get api('/issues', user) get api('/issues', user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -932,7 +932,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(json_response.first).to include('merge_requests_count' => 0) expect(json_response.first).to include('merge_requests_count' => 0)
end end
it 'returns a related merge request count > 0 if there are related merge requests' do it 'returns a related merge request count > 0 if there are related merge requests', :aggregate_failures do
create(:merge_requests_closing_issues, issue: issue) create(:merge_requests_closing_issues, issue: issue)
get api('/issues', user) get api('/issues', user)
@ -1013,28 +1013,28 @@ RSpec.describe API::Issues, feature_category: :team_planning do
let!(:issue2) { create(:issue, author: user2, project: project, created_at: 2.days.ago) } let!(:issue2) { create(:issue, author: user2, project: project, created_at: 2.days.ago) }
let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: project, created_at: 1.day.ago) } let!(:issue3) { create(:issue, author: user2, assignees: [assignee, another_assignee], project: project, created_at: 1.day.ago) }
it 'returns issues with by assignee_username' do it 'returns issues with by assignee_username', :aggregate_failures do
get api("/issues", user), params: { assignee_username: [assignee.username], scope: 'all' } get api("/issues", user), params: { assignee_username: [assignee.username], scope: 'all' }
expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id]) expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
expect_paginated_array_response([confidential_issue.id, issue3.id]) expect_paginated_array_response([confidential_issue.id, issue3.id])
end end
it 'returns issues by assignee_username as string' do it 'returns issues by assignee_username as string', :aggregate_failures do
get api("/issues", user), params: { assignee_username: assignee.username, scope: 'all' } get api("/issues", user), params: { assignee_username: assignee.username, scope: 'all' }
expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id]) expect(issue3.reload.assignees.pluck(:id)).to match_array([assignee.id, another_assignee.id])
expect_paginated_array_response([confidential_issue.id, issue3.id]) expect_paginated_array_response([confidential_issue.id, issue3.id])
end end
it 'returns error when multiple assignees are passed' do it 'returns error when multiple assignees are passed', :aggregate_failures do
get api("/issues", user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' } get api("/issues", user), params: { assignee_username: [assignee.username, another_assignee.username], scope: 'all' }
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response["error"]).to include("allows one value, but found 2") expect(json_response["error"]).to include("allows one value, but found 2")
end end
it 'returns error when assignee_username and assignee_id are passed together' do it 'returns error when assignee_username and assignee_id are passed together', :aggregate_failures do
get api("/issues", user), params: { assignee_username: [assignee.username], assignee_id: another_assignee.id, scope: 'all' } get api("/issues", user), params: { assignee_username: [assignee.username], assignee_id: another_assignee.id, scope: 'all' }
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
@ -1088,7 +1088,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
describe 'GET /projects/:id/issues/:issue_iid' do describe 'GET /projects/:id/issues/:issue_iid' do
it 'exposes full reference path' do it 'exposes full reference path', :aggregate_failures do
get api("/projects/#{project.id}/issues/#{issue.iid}", user) get api("/projects/#{project.id}/issues/#{issue.iid}", user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -1106,7 +1106,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'user does not have permission to view new issue' do context 'user does not have permission to view new issue' do
it 'does not return the issue as closed_as_duplicate_of' do it 'does not return the issue as closed_as_duplicate_of', :aggregate_failures do
get api("/projects/#{project.id}/issues/#{issue_closed_as_dup.iid}", user) get api("/projects/#{project.id}/issues/#{issue_closed_as_dup.iid}", user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -1119,7 +1119,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
new_issue.project.add_guest(user) new_issue.project.add_guest(user)
end end
it 'returns the issue as closed_as_duplicate_of' do it 'returns the issue as closed_as_duplicate_of', :aggregate_failures do
get api("/projects/#{project.id}/issues/#{issue_closed_as_dup.iid}", user) get api("/projects/#{project.id}/issues/#{issue_closed_as_dup.iid}", user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -1131,7 +1131,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
describe "POST /projects/:id/issues" do describe "POST /projects/:id/issues" do
it 'creates a new project issue' do it 'creates a new project issue', :aggregate_failures do
post api("/projects/#{project.id}/issues", user), params: { title: 'new issue' } post api("/projects/#{project.id}/issues", user), params: { title: 'new issue' }
expect(response).to have_gitlab_http_status(:created) expect(response).to have_gitlab_http_status(:created)
@ -1140,7 +1140,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'when confidential is null' do context 'when confidential is null' do
it 'responds with 400 error' do it 'responds with 400 error', :aggregate_failures do
post api("/projects/#{project.id}/issues", user), params: { title: 'issue', confidential: nil } post api("/projects/#{project.id}/issues", user), params: { title: 'issue', confidential: nil }
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
@ -1155,7 +1155,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
end end
it 'returns and error message and status code from the service' do it 'returns and error message and status code from the service', :aggregate_failures do
post api("/projects/#{project.id}/issues", user), params: { title: 'new issue' } post api("/projects/#{project.id}/issues", user), params: { title: 'new issue' }
expect(response).to have_gitlab_http_status(:forbidden) expect(response).to have_gitlab_http_status(:forbidden)
@ -1177,15 +1177,15 @@ RSpec.describe API::Issues, feature_category: :team_planning do
travel_to fixed_time travel_to fixed_time
end end
it 'allows admins to set the timestamp' do it 'allows admins to set the timestamp', :aggregate_failures do
put api("/projects/#{project.id}/issues/#{issue.iid}", admin), params: { labels: 'label1', updated_at: updated_at } put api("/projects/#{project.id}/issues/#{issue.iid}", admin, admin_mode: true), params: { labels: 'label1', updated_at: updated_at }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(Time.parse(json_response['updated_at'])).to be_like_time(updated_at) expect(Time.parse(json_response['updated_at'])).to be_like_time(updated_at)
expect(ResourceLabelEvent.last.created_at).to be_like_time(updated_at) expect(ResourceLabelEvent.last.created_at).to be_like_time(updated_at)
end end
it 'does not allow other users to set the timestamp' do it 'does not allow other users to set the timestamp', :aggregate_failures do
reporter = create(:user) reporter = create(:user)
project.add_developer(reporter) project.add_developer(reporter)
@ -1268,7 +1268,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'with valid params' do context 'with valid params' do
it 'reorders issues and returns a successful 200 response' do it 'reorders issues and returns a successful 200 response', :aggregate_failures do
put api("/projects/#{project.id}/issues/#{issue1.iid}/reorder", user), params: { move_after_id: issue2.id, move_before_id: issue3.id } put api("/projects/#{project.id}/issues/#{issue1.iid}/reorder", user), params: { move_after_id: issue2.id, move_before_id: issue3.id }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -1295,7 +1295,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
let(:other_project) { create(:project, group: group) } let(:other_project) { create(:project, group: group) }
let(:other_issue) { create(:issue, project: other_project, relative_position: 80) } let(:other_issue) { create(:issue, project: other_project, relative_position: 80) }
it 'reorders issues and returns a successful 200 response' do it 'reorders issues and returns a successful 200 response', :aggregate_failures do
put api("/projects/#{other_project.id}/issues/#{other_issue.iid}/reorder", user), params: { move_after_id: issue2.id, move_before_id: issue3.id } put api("/projects/#{other_project.id}/issues/#{other_issue.iid}/reorder", user), params: { move_after_id: issue2.id, move_before_id: issue3.id }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)

View File

@ -75,7 +75,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
describe 'POST /projects/:id/issues' do describe 'POST /projects/:id/issues' do
context 'support for deprecated assignee_id' do context 'support for deprecated assignee_id' do
it 'creates a new project issue' do it 'creates a new project issue', :aggregate_failures do
post api("/projects/#{project.id}/issues", user), post api("/projects/#{project.id}/issues", user),
params: { title: 'new issue', assignee_id: user2.id } params: { title: 'new issue', assignee_id: user2.id }
@ -85,7 +85,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(json_response['assignees'].first['name']).to eq(user2.name) expect(json_response['assignees'].first['name']).to eq(user2.name)
end end
it 'creates a new project issue when assignee_id is empty' do it 'creates a new project issue when assignee_id is empty', :aggregate_failures do
post api("/projects/#{project.id}/issues", user), post api("/projects/#{project.id}/issues", user),
params: { title: 'new issue', assignee_id: '' } params: { title: 'new issue', assignee_id: '' }
@ -96,7 +96,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'single assignee restrictions' do context 'single assignee restrictions' do
it 'creates a new project issue with no more than one assignee' do it 'creates a new project issue with no more than one assignee', :aggregate_failures do
post api("/projects/#{project.id}/issues", user), post api("/projects/#{project.id}/issues", user),
params: { title: 'new issue', assignee_ids: [user2.id, guest.id] } params: { title: 'new issue', assignee_ids: [user2.id, guest.id] }
@ -122,8 +122,8 @@ RSpec.describe API::Issues, feature_category: :team_planning do
context 'an internal ID is provided' do context 'an internal ID is provided' do
context 'by an admin' do context 'by an admin' do
it 'sets the internal ID on the new issue' do it 'sets the internal ID on the new issue', :aggregate_failures do
post api("/projects/#{project.id}/issues", admin), post api("/projects/#{project.id}/issues", admin, admin_mode: true),
params: { title: 'new issue', iid: 9001 } params: { title: 'new issue', iid: 9001 }
expect(response).to have_gitlab_http_status(:created) expect(response).to have_gitlab_http_status(:created)
@ -132,7 +132,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'by an owner' do context 'by an owner' do
it 'sets the internal ID on the new issue' do it 'sets the internal ID on the new issue', :aggregate_failures do
post api("/projects/#{project.id}/issues", user), post api("/projects/#{project.id}/issues", user),
params: { title: 'new issue', iid: 9001 } params: { title: 'new issue', iid: 9001 }
@ -145,7 +145,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:group_project) { create(:project, :public, namespace: group) } let(:group_project) { create(:project, :public, namespace: group) }
it 'sets the internal ID on the new issue' do it 'sets the internal ID on the new issue', :aggregate_failures do
group.add_owner(user2) group.add_owner(user2)
post api("/projects/#{group_project.id}/issues", user2), post api("/projects/#{group_project.id}/issues", user2),
params: { title: 'new issue', iid: 9001 } params: { title: 'new issue', iid: 9001 }
@ -156,7 +156,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'by another user' do context 'by another user' do
it 'ignores the given internal ID' do it 'ignores the given internal ID', :aggregate_failures do
post api("/projects/#{project.id}/issues", user2), post api("/projects/#{project.id}/issues", user2),
params: { title: 'new issue', iid: 9001 } params: { title: 'new issue', iid: 9001 }
@ -166,8 +166,8 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'when an issue with the same IID exists on database' do context 'when an issue with the same IID exists on database' do
it 'returns 409' do it 'returns 409', :aggregate_failures do
post api("/projects/#{project.id}/issues", admin), post api("/projects/#{project.id}/issues", admin, admin_mode: true),
params: { title: 'new issue', iid: issue.iid } params: { title: 'new issue', iid: issue.iid }
expect(response).to have_gitlab_http_status(:conflict) expect(response).to have_gitlab_http_status(:conflict)
@ -176,7 +176,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
end end
it 'creates a new project issue' do it 'creates a new project issue', :aggregate_failures do
post api("/projects/#{project.id}/issues", user), post api("/projects/#{project.id}/issues", user),
params: { title: 'new issue', labels: 'label, label2', weight: 3, assignee_ids: [user2.id] } params: { title: 'new issue', labels: 'label, label2', weight: 3, assignee_ids: [user2.id] }
@ -189,7 +189,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(json_response['assignees'].first['name']).to eq(user2.name) expect(json_response['assignees'].first['name']).to eq(user2.name)
end end
it 'creates a new project issue with labels param as array' do it 'creates a new project issue with labels param as array', :aggregate_failures do
post api("/projects/#{project.id}/issues", user), post api("/projects/#{project.id}/issues", user),
params: { title: 'new issue', labels: %w(label label2), weight: 3, assignee_ids: [user2.id] } params: { title: 'new issue', labels: %w(label label2), weight: 3, assignee_ids: [user2.id] }
@ -202,7 +202,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(json_response['assignees'].first['name']).to eq(user2.name) expect(json_response['assignees'].first['name']).to eq(user2.name)
end end
it 'creates a new confidential project issue' do it 'creates a new confidential project issue', :aggregate_failures do
post api("/projects/#{project.id}/issues", user), post api("/projects/#{project.id}/issues", user),
params: { title: 'new issue', confidential: true } params: { title: 'new issue', confidential: true }
@ -211,7 +211,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(json_response['confidential']).to be_truthy expect(json_response['confidential']).to be_truthy
end end
it 'creates a new confidential project issue with a different param' do it 'creates a new confidential project issue with a different param', :aggregate_failures do
post api("/projects/#{project.id}/issues", user), post api("/projects/#{project.id}/issues", user),
params: { title: 'new issue', confidential: 'y' } params: { title: 'new issue', confidential: 'y' }
@ -220,7 +220,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(json_response['confidential']).to be_truthy expect(json_response['confidential']).to be_truthy
end end
it 'creates a public issue when confidential param is false' do it 'creates a public issue when confidential param is false', :aggregate_failures do
post api("/projects/#{project.id}/issues", user), post api("/projects/#{project.id}/issues", user),
params: { title: 'new issue', confidential: false } params: { title: 'new issue', confidential: false }
@ -229,7 +229,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(json_response['confidential']).to be_falsy expect(json_response['confidential']).to be_falsy
end end
it 'creates a public issue when confidential param is invalid' do it 'creates a public issue when confidential param is invalid', :aggregate_failures do
post api("/projects/#{project.id}/issues", user), post api("/projects/#{project.id}/issues", user),
params: { title: 'new issue', confidential: 'foo' } params: { title: 'new issue', confidential: 'foo' }
@ -242,7 +242,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
end end
it 'allows special label names' do it 'allows special label names', :aggregate_failures do
post api("/projects/#{project.id}/issues", user), post api("/projects/#{project.id}/issues", user),
params: { params: {
title: 'new issue', title: 'new issue',
@ -256,7 +256,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(json_response['labels']).to include '&' expect(json_response['labels']).to include '&'
end end
it 'allows special label names with labels param as array' do it 'allows special label names with labels param as array', :aggregate_failures do
post api("/projects/#{project.id}/issues", user), post api("/projects/#{project.id}/issues", user),
params: { params: {
title: 'new issue', title: 'new issue',
@ -270,7 +270,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(json_response['labels']).to include '&' expect(json_response['labels']).to include '&'
end end
it 'returns 400 if title is too long' do it 'returns 400 if title is too long', :aggregate_failures do
post api("/projects/#{project.id}/issues", user), post api("/projects/#{project.id}/issues", user),
params: { title: 'g' * 256 } params: { title: 'g' * 256 }
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
@ -313,7 +313,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'with due date' do context 'with due date' do
it 'creates a new project issue' do it 'creates a new project issue', :aggregate_failures do
due_date = 2.weeks.from_now.strftime('%Y-%m-%d') due_date = 2.weeks.from_now.strftime('%Y-%m-%d')
post api("/projects/#{project.id}/issues", user), post api("/projects/#{project.id}/issues", user),
@ -336,8 +336,8 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'by an admin' do context 'by an admin' do
it 'sets the creation time on the new issue' do it 'sets the creation time on the new issue', :aggregate_failures do
post api("/projects/#{project.id}/issues", admin), params: params post api("/projects/#{project.id}/issues", admin, admin_mode: true), params: params
expect(response).to have_gitlab_http_status(:created) expect(response).to have_gitlab_http_status(:created)
expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time) expect(Time.parse(json_response['created_at'])).to be_like_time(creation_time)
@ -346,7 +346,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'by a project owner' do context 'by a project owner' do
it 'sets the creation time on the new issue' do it 'sets the creation time on the new issue', :aggregate_failures do
post api("/projects/#{project.id}/issues", user), params: params post api("/projects/#{project.id}/issues", user), params: params
expect(response).to have_gitlab_http_status(:created) expect(response).to have_gitlab_http_status(:created)
@ -356,7 +356,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'by a group owner' do context 'by a group owner' do
it 'sets the creation time on the new issue' do it 'sets the creation time on the new issue', :aggregate_failures do
group = create(:group) group = create(:group)
group_project = create(:project, :public, namespace: group) group_project = create(:project, :public, namespace: group)
group.add_owner(user2) group.add_owner(user2)
@ -370,7 +370,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'by another user' do context 'by another user' do
it 'ignores the given creation time' do it 'ignores the given creation time', :aggregate_failures do
project.add_developer(user2) project.add_developer(user2)
post api("/projects/#{project.id}/issues", user2), params: params post api("/projects/#{project.id}/issues", user2), params: params
@ -397,7 +397,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'when request exceeds the rate limit' do context 'when request exceeds the rate limit' do
it 'prevents users from creating more issues' do it 'prevents users from creating more issues', :aggregate_failures do
allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(true) allow(::Gitlab::ApplicationRateLimiter).to receive(:throttled?).and_return(true)
post api("/projects/#{project.id}/issues", user), post api("/projects/#{project.id}/issues", user),
@ -437,7 +437,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect { post_issue }.not_to change(Issue, :count) expect { post_issue }.not_to change(Issue, :count)
end end
it 'returns correct status and message' do it 'returns correct status and message', :aggregate_failures do
post_issue post_issue
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
@ -476,7 +476,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
let!(:target_project) { create(:project, creator_id: user.id, namespace: user.namespace) } let!(:target_project) { create(:project, creator_id: user.id, namespace: user.namespace) }
let!(:target_project2) { create(:project, creator_id: non_member.id, namespace: non_member.namespace) } let!(:target_project2) { create(:project, creator_id: non_member.id, namespace: non_member.namespace) }
it 'moves an issue' do it 'moves an issue', :aggregate_failures do
post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
params: { to_project_id: target_project.id } params: { to_project_id: target_project.id }
@ -485,7 +485,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'when source and target projects are the same' do context 'when source and target projects are the same' do
it 'returns 400 when trying to move an issue' do it 'returns 400 when trying to move an issue', :aggregate_failures do
post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
params: { to_project_id: project.id } params: { to_project_id: project.id }
@ -495,7 +495,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'when the user does not have the permission to move issues' do context 'when the user does not have the permission to move issues' do
it 'returns 400 when trying to move an issue' do it 'returns 400 when trying to move an issue', :aggregate_failures do
post api("/projects/#{project.id}/issues/#{issue.iid}/move", user), post api("/projects/#{project.id}/issues/#{issue.iid}/move", user),
params: { to_project_id: target_project2.id } params: { to_project_id: target_project2.id }
@ -504,8 +504,8 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
end end
it 'moves the issue to another namespace if I am admin' do it 'moves the issue to another namespace if I am admin', :aggregate_failures do
post api("/projects/#{project.id}/issues/#{issue.iid}/move", admin), post api("/projects/#{project.id}/issues/#{issue.iid}/move", admin, admin_mode: true),
params: { to_project_id: target_project2.id } params: { to_project_id: target_project2.id }
expect(response).to have_gitlab_http_status(:created) expect(response).to have_gitlab_http_status(:created)
@ -513,7 +513,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'when using the issue ID instead of iid' do context 'when using the issue ID instead of iid' do
it 'returns 404 when trying to move an issue', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341520' do it 'returns 404 when trying to move an issue', :aggregate_failures, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341520' do
post api("/projects/#{project.id}/issues/#{issue.id}/move", user), post api("/projects/#{project.id}/issues/#{issue.id}/move", user),
params: { to_project_id: target_project.id } params: { to_project_id: target_project.id }
@ -523,7 +523,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'when issue does not exist' do context 'when issue does not exist' do
it 'returns 404 when trying to move an issue' do it 'returns 404 when trying to move an issue', :aggregate_failures do
post api("/projects/#{project.id}/issues/123/move", user), post api("/projects/#{project.id}/issues/123/move", user),
params: { to_project_id: target_project.id } params: { to_project_id: target_project.id }
@ -533,7 +533,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'when source project does not exist' do context 'when source project does not exist' do
it 'returns 404 when trying to move an issue' do it 'returns 404 when trying to move an issue', :aggregate_failures do
post api("/projects/0/issues/#{issue.iid}/move", user), post api("/projects/0/issues/#{issue.iid}/move", user),
params: { to_project_id: target_project.id } params: { to_project_id: target_project.id }
@ -562,7 +562,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
context 'when user can admin the issue' do context 'when user can admin the issue' do
context 'when the user can admin the target project' do context 'when the user can admin the target project' do
it 'clones the issue' do it 'clones the issue', :aggregate_failures do
expect do expect do
post_clone_issue(user, issue, valid_target_project) post_clone_issue(user, issue, valid_target_project)
end.to change { valid_target_project.issues.count }.by(1) end.to change { valid_target_project.issues.count }.by(1)
@ -577,7 +577,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'when target project is the same source project' do context 'when target project is the same source project' do
it 'clones the issue' do it 'clones the issue', :aggregate_failures do
expect do expect do
post_clone_issue(user, issue, issue.project) post_clone_issue(user, issue, issue.project)
end.to change { issue.reset.project.issues.count }.by(1) end.to change { issue.reset.project.issues.count }.by(1)
@ -595,7 +595,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'when the user does not have the permission to clone issues' do context 'when the user does not have the permission to clone issues' do
it 'returns 400' do it 'returns 400', :aggregate_failures do
post api("/projects/#{project.id}/issues/#{issue.iid}/clone", user), post api("/projects/#{project.id}/issues/#{issue.iid}/clone", user),
params: { to_project_id: invalid_target_project.id } params: { to_project_id: invalid_target_project.id }
@ -605,7 +605,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'when using the issue ID instead of iid' do context 'when using the issue ID instead of iid' do
it 'returns 404', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341520' do it 'returns 404', :aggregate_failures, quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341520' do
post api("/projects/#{project.id}/issues/#{issue.id}/clone", user), post api("/projects/#{project.id}/issues/#{issue.id}/clone", user),
params: { to_project_id: valid_target_project.id } params: { to_project_id: valid_target_project.id }
@ -615,7 +615,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'when issue does not exist' do context 'when issue does not exist' do
it 'returns 404' do it 'returns 404', :aggregate_failures do
post api("/projects/#{project.id}/issues/12300/clone", user), post api("/projects/#{project.id}/issues/12300/clone", user),
params: { to_project_id: valid_target_project.id } params: { to_project_id: valid_target_project.id }
@ -625,7 +625,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'when source project does not exist' do context 'when source project does not exist' do
it 'returns 404' do it 'returns 404', :aggregate_failures do
post api("/projects/0/issues/#{issue.iid}/clone", user), post api("/projects/0/issues/#{issue.iid}/clone", user),
params: { to_project_id: valid_target_project.id } params: { to_project_id: valid_target_project.id }
@ -635,7 +635,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
context 'when target project does not exist' do context 'when target project does not exist' do
it 'returns 404' do it 'returns 404', :aggregate_failures do
post api("/projects/#{project.id}/issues/#{issue.iid}/clone", user), post api("/projects/#{project.id}/issues/#{issue.iid}/clone", user),
params: { to_project_id: 0 } params: { to_project_id: 0 }
@ -644,7 +644,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
end end
it 'clones the issue with notes when with_notes is true' do it 'clones the issue with notes when with_notes is true', :aggregate_failures do
expect do expect do
post api("/projects/#{project.id}/issues/#{issue.iid}/clone", user), post api("/projects/#{project.id}/issues/#{issue.iid}/clone", user),
params: { to_project_id: valid_target_project.id, with_notes: true } params: { to_project_id: valid_target_project.id, with_notes: true }
@ -661,7 +661,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
describe 'POST :id/issues/:issue_iid/subscribe' do describe 'POST :id/issues/:issue_iid/subscribe' do
it 'subscribes to an issue' do it 'subscribes to an issue', :aggregate_failures do
post api("/projects/#{project.id}/issues/#{issue.iid}/subscribe", user2) post api("/projects/#{project.id}/issues/#{issue.iid}/subscribe", user2)
expect(response).to have_gitlab_http_status(:created) expect(response).to have_gitlab_http_status(:created)
@ -694,7 +694,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
describe 'POST :id/issues/:issue_id/unsubscribe' do describe 'POST :id/issues/:issue_id/unsubscribe' do
it 'unsubscribes from an issue' do it 'unsubscribes from an issue', :aggregate_failures do
post api("/projects/#{project.id}/issues/#{issue.iid}/unsubscribe", user) post api("/projects/#{project.id}/issues/#{issue.iid}/unsubscribe", user)
expect(response).to have_gitlab_http_status(:created) expect(response).to have_gitlab_http_status(:created)

View File

@ -80,7 +80,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
describe 'PUT /projects/:id/issues/:issue_iid to update only title' do describe 'PUT /projects/:id/issues/:issue_iid to update only title' do
it 'updates a project issue' do it 'updates a project issue', :aggregate_failures do
put api_for_user, params: { title: updated_title } put api_for_user, params: { title: updated_title }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -109,7 +109,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
it 'allows special label names with labels param as array' do it 'allows special label names with labels param as array', :aggregate_failures do
put api_for_user, put api_for_user,
params: { params: {
title: updated_title, title: updated_title,
@ -135,42 +135,42 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(response).to have_gitlab_http_status(:forbidden) expect(response).to have_gitlab_http_status(:forbidden)
end end
it 'updates a confidential issue for project members' do it 'updates a confidential issue for project members', :aggregate_failures do
put api(confidential_issue_path, user), params: { title: updated_title } put api(confidential_issue_path, user), params: { title: updated_title }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['title']).to eq(updated_title) expect(json_response['title']).to eq(updated_title)
end end
it 'updates a confidential issue for author' do it 'updates a confidential issue for author', :aggregate_failures do
put api(confidential_issue_path, author), params: { title: updated_title } put api(confidential_issue_path, author), params: { title: updated_title }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['title']).to eq(updated_title) expect(json_response['title']).to eq(updated_title)
end end
it 'updates a confidential issue for admin' do it 'updates a confidential issue for admin', :aggregate_failures do
put api(confidential_issue_path, admin), params: { title: updated_title } put api(confidential_issue_path, admin, admin_mode: true), params: { title: updated_title }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['title']).to eq(updated_title) expect(json_response['title']).to eq(updated_title)
end end
it 'sets an issue to confidential' do it 'sets an issue to confidential', :aggregate_failures do
put api_for_user, params: { confidential: true } put api_for_user, params: { confidential: true }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['confidential']).to be_truthy expect(json_response['confidential']).to be_truthy
end end
it 'makes a confidential issue public' do it 'makes a confidential issue public', :aggregate_failures do
put api(confidential_issue_path, user), params: { confidential: false } put api(confidential_issue_path, user), params: { confidential: false }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['confidential']).to be_falsy expect(json_response['confidential']).to be_falsy
end end
it 'does not update a confidential issue with wrong confidential flag' do it 'does not update a confidential issue with wrong confidential flag', :aggregate_failures do
put api(confidential_issue_path, user), params: { confidential: 'foo' } put api(confidential_issue_path, user), params: { confidential: 'foo' }
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
@ -209,7 +209,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect { update_issue }.not_to change { issue.reload.title } expect { update_issue }.not_to change { issue.reload.title }
end end
it 'returns correct status and message' do it 'returns correct status and message', :aggregate_failures do
update_issue update_issue
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
@ -246,14 +246,14 @@ RSpec.describe API::Issues, feature_category: :team_planning do
describe 'PUT /projects/:id/issues/:issue_iid to update assignee' do describe 'PUT /projects/:id/issues/:issue_iid to update assignee' do
context 'support for deprecated assignee_id' do context 'support for deprecated assignee_id' do
it 'removes assignee' do it 'removes assignee', :aggregate_failures do
put api_for_user, params: { assignee_id: 0 } put api_for_user, params: { assignee_id: 0 }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['assignee']).to be_nil expect(json_response['assignee']).to be_nil
end end
it 'updates an issue with new assignee' do it 'updates an issue with new assignee', :aggregate_failures do
put api_for_user, params: { assignee_id: user2.id } put api_for_user, params: { assignee_id: user2.id }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -261,21 +261,21 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
end end
it 'removes assignee' do it 'removes assignee', :aggregate_failures do
put api_for_user, params: { assignee_ids: [0] } put api_for_user, params: { assignee_ids: [0] }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['assignees']).to be_empty expect(json_response['assignees']).to be_empty
end end
it 'updates an issue with new assignee' do it 'updates an issue with new assignee', :aggregate_failures do
put api_for_user, params: { assignee_ids: [user2.id] } put api_for_user, params: { assignee_ids: [user2.id] }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['assignees'].first['name']).to eq(user2.name) expect(json_response['assignees'].first['name']).to eq(user2.name)
end end
context 'single assignee restrictions' do context 'single assignee restrictions', :aggregate_failures do
it 'updates an issue with several assignees but only one has been applied' do it 'updates an issue with several assignees but only one has been applied' do
put api_for_user, params: { assignee_ids: [user2.id, guest.id] } put api_for_user, params: { assignee_ids: [user2.id, guest.id] }
@ -289,7 +289,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
let!(:label) { create(:label, title: 'dummy', project: project) } let!(:label) { create(:label, title: 'dummy', project: project) }
let!(:label_link) { create(:label_link, label: label, target: issue) } let!(:label_link) { create(:label_link, label: label, target: issue) }
it 'adds relevant labels' do it 'adds relevant labels', :aggregate_failures do
put api_for_user, params: { add_labels: '1, 2' } put api_for_user, params: { add_labels: '1, 2' }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -300,14 +300,14 @@ RSpec.describe API::Issues, feature_category: :team_planning do
let!(:label2) { create(:label, title: 'a-label', project: project) } let!(:label2) { create(:label, title: 'a-label', project: project) }
let!(:label_link2) { create(:label_link, label: label2, target: issue) } let!(:label_link2) { create(:label_link, label: label2, target: issue) }
it 'removes relevant labels' do it 'removes relevant labels', :aggregate_failures do
put api_for_user, params: { remove_labels: label2.title } put api_for_user, params: { remove_labels: label2.title }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['labels']).to eq([label.title]) expect(json_response['labels']).to eq([label.title])
end end
it 'removes all labels' do it 'removes all labels', :aggregate_failures do
put api_for_user, params: { remove_labels: "#{label.title}, #{label2.title}" } put api_for_user, params: { remove_labels: "#{label.title}, #{label2.title}" }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -315,14 +315,14 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
end end
it 'does not update labels if not present' do it 'does not update labels if not present', :aggregate_failures do
put api_for_user, params: { title: updated_title } put api_for_user, params: { title: updated_title }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['labels']).to eq([label.title]) expect(json_response['labels']).to eq([label.title])
end end
it 'removes all labels and touches the record' do it 'removes all labels and touches the record', :aggregate_failures do
travel_to(2.minutes.from_now) do travel_to(2.minutes.from_now) do
put api_for_user, params: { labels: '' } put api_for_user, params: { labels: '' }
end end
@ -332,7 +332,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(json_response['updated_at']).to be > Time.current expect(json_response['updated_at']).to be > Time.current
end end
it 'removes all labels and touches the record with labels param as array' do it 'removes all labels and touches the record with labels param as array', :aggregate_failures do
travel_to(2.minutes.from_now) do travel_to(2.minutes.from_now) do
put api_for_user, params: { labels: [''] } put api_for_user, params: { labels: [''] }
end end
@ -342,7 +342,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(json_response['updated_at']).to be > Time.current expect(json_response['updated_at']).to be > Time.current
end end
it 'updates labels and touches the record' do it 'updates labels and touches the record', :aggregate_failures do
travel_to(2.minutes.from_now) do travel_to(2.minutes.from_now) do
put api_for_user, params: { labels: 'foo,bar' } put api_for_user, params: { labels: 'foo,bar' }
end end
@ -352,7 +352,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(json_response['updated_at']).to be > Time.current expect(json_response['updated_at']).to be > Time.current
end end
it 'updates labels and touches the record with labels param as array' do it 'updates labels and touches the record with labels param as array', :aggregate_failures do
travel_to(2.minutes.from_now) do travel_to(2.minutes.from_now) do
put api_for_user, params: { labels: %w(foo bar) } put api_for_user, params: { labels: %w(foo bar) }
end end
@ -363,21 +363,21 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(json_response['updated_at']).to be > Time.current expect(json_response['updated_at']).to be > Time.current
end end
it 'allows special label names' do it 'allows special label names', :aggregate_failures do
put api_for_user, params: { labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&' } put api_for_user, params: { labels: 'label:foo, label-bar,label_bar,label/bar,label?bar,label&bar,?,&' }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['labels']).to contain_exactly('label:foo', 'label-bar', 'label_bar', 'label/bar', 'label?bar', 'label&bar', '?', '&') expect(json_response['labels']).to contain_exactly('label:foo', 'label-bar', 'label_bar', 'label/bar', 'label?bar', 'label&bar', '?', '&')
end end
it 'allows special label names with labels param as array' do it 'allows special label names with labels param as array', :aggregate_failures do
put api_for_user, params: { labels: ['label:foo', 'label-bar', 'label_bar', 'label/bar,label?bar,label&bar,?,&'] } put api_for_user, params: { labels: ['label:foo', 'label-bar', 'label_bar', 'label/bar,label?bar,label&bar,?,&'] }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['labels']).to contain_exactly('label:foo', 'label-bar', 'label_bar', 'label/bar', 'label?bar', 'label&bar', '?', '&') expect(json_response['labels']).to contain_exactly('label:foo', 'label-bar', 'label_bar', 'label/bar', 'label?bar', 'label&bar', '?', '&')
end end
it 'returns 400 if title is too long' do it 'returns 400 if title is too long', :aggregate_failures do
put api_for_user, params: { title: 'g' * 256 } put api_for_user, params: { title: 'g' * 256 }
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
@ -386,7 +386,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
describe 'PUT /projects/:id/issues/:issue_iid to update state and label' do describe 'PUT /projects/:id/issues/:issue_iid to update state and label' do
it 'updates a project issue' do it 'updates a project issue', :aggregate_failures do
put api_for_user, params: { labels: 'label2', state_event: 'close' } put api_for_user, params: { labels: 'label2', state_event: 'close' }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -394,7 +394,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(json_response['state']).to eq 'closed' expect(json_response['state']).to eq 'closed'
end end
it 'reopens a project isssue' do it 'reopens a project isssue', :aggregate_failures do
put api(issue_path, user), params: { state_event: 'reopen' } put api(issue_path, user), params: { state_event: 'reopen' }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -404,7 +404,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
describe 'PUT /projects/:id/issues/:issue_iid to update updated_at param' do describe 'PUT /projects/:id/issues/:issue_iid to update updated_at param' do
context 'when reporter makes request' do context 'when reporter makes request' do
it 'accepts the update date to be set' do it 'accepts the update date to be set', :aggregate_failures do
update_time = 2.weeks.ago update_time = 2.weeks.ago
put api_for_user, params: { title: 'some new title', updated_at: update_time } put api_for_user, params: { title: 'some new title', updated_at: update_time }
@ -436,7 +436,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
end end
it 'accepts the update date to be set' do it 'accepts the update date to be set', :aggregate_failures do
update_time = 2.weeks.ago update_time = 2.weeks.ago
put api_for_owner, params: { title: 'some new title', updated_at: update_time } put api_for_owner, params: { title: 'some new title', updated_at: update_time }
@ -448,7 +448,7 @@ RSpec.describe API::Issues, feature_category: :team_planning do
end end
describe 'PUT /projects/:id/issues/:issue_iid to update due date' do describe 'PUT /projects/:id/issues/:issue_iid to update due date' do
it 'creates a new project issue' do it 'creates a new project issue', :aggregate_failures do
due_date = 2.weeks.from_now.strftime('%Y-%m-%d') due_date = 2.weeks.from_now.strftime('%Y-%m-%d')
put api_for_user, params: { due_date: due_date } put api_for_user, params: { due_date: due_date }

View File

@ -35,39 +35,39 @@ RSpec.describe "Internal Project Pages Access", feature_category: :pages do
describe "GET /projects/:id/pages_access" do describe "GET /projects/:id/pages_access" do
context 'access depends on the level' do context 'access depends on the level' do
where(:pages_access_level, :with_user, :expected_result) do where(:pages_access_level, :with_user, :admin_mode, :expected_result) do
ProjectFeature::DISABLED | "admin" | 403 ProjectFeature::DISABLED | "admin" | true | 403
ProjectFeature::DISABLED | "owner" | 403 ProjectFeature::DISABLED | "owner" | false | 403
ProjectFeature::DISABLED | "master" | 403 ProjectFeature::DISABLED | "master" | false | 403
ProjectFeature::DISABLED | "developer" | 403 ProjectFeature::DISABLED | "developer" | false | 403
ProjectFeature::DISABLED | "reporter" | 403 ProjectFeature::DISABLED | "reporter" | false | 403
ProjectFeature::DISABLED | "guest" | 403 ProjectFeature::DISABLED | "guest" | false | 403
ProjectFeature::DISABLED | "user" | 403 ProjectFeature::DISABLED | "user" | false | 403
ProjectFeature::DISABLED | nil | 404 ProjectFeature::DISABLED | nil | false | 404
ProjectFeature::PUBLIC | "admin" | 200 ProjectFeature::PUBLIC | "admin" | false | 200
ProjectFeature::PUBLIC | "owner" | 200 ProjectFeature::PUBLIC | "owner" | false | 200
ProjectFeature::PUBLIC | "master" | 200 ProjectFeature::PUBLIC | "master" | false | 200
ProjectFeature::PUBLIC | "developer" | 200 ProjectFeature::PUBLIC | "developer" | false | 200
ProjectFeature::PUBLIC | "reporter" | 200 ProjectFeature::PUBLIC | "reporter" | false | 200
ProjectFeature::PUBLIC | "guest" | 200 ProjectFeature::PUBLIC | "guest" | false | 200
ProjectFeature::PUBLIC | "user" | 200 ProjectFeature::PUBLIC | "user" | false | 200
ProjectFeature::PUBLIC | nil | 404 ProjectFeature::PUBLIC | nil | false | 404
ProjectFeature::ENABLED | "admin" | 200 ProjectFeature::ENABLED | "admin" | false | 200
ProjectFeature::ENABLED | "owner" | 200 ProjectFeature::ENABLED | "owner" | false | 200
ProjectFeature::ENABLED | "master" | 200 ProjectFeature::ENABLED | "master" | false | 200
ProjectFeature::ENABLED | "developer" | 200 ProjectFeature::ENABLED | "developer" | false | 200
ProjectFeature::ENABLED | "reporter" | 200 ProjectFeature::ENABLED | "reporter" | false | 200
ProjectFeature::ENABLED | "guest" | 200 ProjectFeature::ENABLED | "guest" | false | 200
ProjectFeature::ENABLED | "user" | 200 ProjectFeature::ENABLED | "user" | false | 200
ProjectFeature::ENABLED | nil | 404 ProjectFeature::ENABLED | nil | false | 404
ProjectFeature::PRIVATE | "admin" | 200 ProjectFeature::PRIVATE | "admin" | true | 200
ProjectFeature::PRIVATE | "owner" | 200 ProjectFeature::PRIVATE | "owner" | false | 200
ProjectFeature::PRIVATE | "master" | 200 ProjectFeature::PRIVATE | "master" | false | 200
ProjectFeature::PRIVATE | "developer" | 200 ProjectFeature::PRIVATE | "developer" | false | 200
ProjectFeature::PRIVATE | "reporter" | 200 ProjectFeature::PRIVATE | "reporter" | false | 200
ProjectFeature::PRIVATE | "guest" | 200 ProjectFeature::PRIVATE | "guest" | false | 200
ProjectFeature::PRIVATE | "user" | 403 ProjectFeature::PRIVATE | "user" | false | 403
ProjectFeature::PRIVATE | nil | 404 ProjectFeature::PRIVATE | nil | false | 404
end end
with_them do with_them do
@ -77,7 +77,7 @@ RSpec.describe "Internal Project Pages Access", feature_category: :pages do
it "correct return value" do it "correct return value" do
if !with_user.nil? if !with_user.nil?
user = public_send(with_user) user = public_send(with_user)
get api("/projects/#{project.id}/pages_access", user) get api("/projects/#{project.id}/pages_access", user, admin_mode: admin_mode)
else else
get api("/projects/#{project.id}/pages_access") get api("/projects/#{project.id}/pages_access")
end end

View File

@ -3,7 +3,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe API::Pages, feature_category: :pages do RSpec.describe API::Pages, feature_category: :pages do
let_it_be(:project) { create(:project, path: 'my.project', pages_https_only: false) } let_it_be_with_reload(:project) { create(:project, path: 'my.project', pages_https_only: false) }
let_it_be(:admin) { create(:admin) } let_it_be(:admin) { create(:admin) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
@ -19,7 +19,7 @@ RSpec.describe API::Pages, feature_category: :pages do
end end
it_behaves_like '404 response' do it_behaves_like '404 response' do
let(:request) { delete api("/projects/#{project.id}/pages", admin) } let(:request) { delete api("/projects/#{project.id}/pages", admin, admin_mode: true) }
end end
end end
@ -30,13 +30,13 @@ RSpec.describe API::Pages, feature_category: :pages do
context 'when Pages are deployed' do context 'when Pages are deployed' do
it 'returns 204' do it 'returns 204' do
delete api("/projects/#{project.id}/pages", admin) delete api("/projects/#{project.id}/pages", admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:no_content) expect(response).to have_gitlab_http_status(:no_content)
end end
it 'removes the pages' do it 'removes the pages' do
delete api("/projects/#{project.id}/pages", admin) delete api("/projects/#{project.id}/pages", admin, admin_mode: true)
expect(project.reload.pages_metadatum.deployed?).to be(false) expect(project.reload.pages_metadatum.deployed?).to be(false)
end end
@ -48,7 +48,7 @@ RSpec.describe API::Pages, feature_category: :pages do
end end
it 'returns 204' do it 'returns 204' do
delete api("/projects/#{project.id}/pages", admin) delete api("/projects/#{project.id}/pages", admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:no_content) expect(response).to have_gitlab_http_status(:no_content)
end end
@ -58,7 +58,7 @@ RSpec.describe API::Pages, feature_category: :pages do
it 'returns 404' do it 'returns 404' do
id = -1 id = -1
delete api("/projects/#{id}/pages", admin) delete api("/projects/#{id}/pages", admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end

View File

@ -35,39 +35,39 @@ RSpec.describe "Private Project Pages Access", feature_category: :pages do
describe "GET /projects/:id/pages_access" do describe "GET /projects/:id/pages_access" do
context 'access depends on the level' do context 'access depends on the level' do
where(:pages_access_level, :with_user, :expected_result) do where(:pages_access_level, :with_user, :admin_mode, :expected_result) do
ProjectFeature::DISABLED | "admin" | 403 ProjectFeature::DISABLED | "admin" | true | 403
ProjectFeature::DISABLED | "owner" | 403 ProjectFeature::DISABLED | "owner" | false | 403
ProjectFeature::DISABLED | "master" | 403 ProjectFeature::DISABLED | "master" | false | 403
ProjectFeature::DISABLED | "developer" | 403 ProjectFeature::DISABLED | "developer" | false | 403
ProjectFeature::DISABLED | "reporter" | 403 ProjectFeature::DISABLED | "reporter" | false | 403
ProjectFeature::DISABLED | "guest" | 403 ProjectFeature::DISABLED | "guest" | false | 403
ProjectFeature::DISABLED | "user" | 404 ProjectFeature::DISABLED | "user" | false | 404
ProjectFeature::DISABLED | nil | 404 ProjectFeature::DISABLED | nil | false | 404
ProjectFeature::PUBLIC | "admin" | 200 ProjectFeature::PUBLIC | "admin" | true | 200
ProjectFeature::PUBLIC | "owner" | 200 ProjectFeature::PUBLIC | "owner" | false | 200
ProjectFeature::PUBLIC | "master" | 200 ProjectFeature::PUBLIC | "master" | false | 200
ProjectFeature::PUBLIC | "developer" | 200 ProjectFeature::PUBLIC | "developer" | false | 200
ProjectFeature::PUBLIC | "reporter" | 200 ProjectFeature::PUBLIC | "reporter" | false | 200
ProjectFeature::PUBLIC | "guest" | 200 ProjectFeature::PUBLIC | "guest" | false | 200
ProjectFeature::PUBLIC | "user" | 404 ProjectFeature::PUBLIC | "user" | false | 404
ProjectFeature::PUBLIC | nil | 404 ProjectFeature::PUBLIC | nil | false | 404
ProjectFeature::ENABLED | "admin" | 200 ProjectFeature::ENABLED | "admin" | true | 200
ProjectFeature::ENABLED | "owner" | 200 ProjectFeature::ENABLED | "owner" | false | 200
ProjectFeature::ENABLED | "master" | 200 ProjectFeature::ENABLED | "master" | false | 200
ProjectFeature::ENABLED | "developer" | 200 ProjectFeature::ENABLED | "developer" | false | 200
ProjectFeature::ENABLED | "reporter" | 200 ProjectFeature::ENABLED | "reporter" | false | 200
ProjectFeature::ENABLED | "guest" | 200 ProjectFeature::ENABLED | "guest" | false | 200
ProjectFeature::ENABLED | "user" | 404 ProjectFeature::ENABLED | "user" | false | 404
ProjectFeature::ENABLED | nil | 404 ProjectFeature::ENABLED | nil | false | 404
ProjectFeature::PRIVATE | "admin" | 200 ProjectFeature::PRIVATE | "admin" | true | 200
ProjectFeature::PRIVATE | "owner" | 200 ProjectFeature::PRIVATE | "owner" | false | 200
ProjectFeature::PRIVATE | "master" | 200 ProjectFeature::PRIVATE | "master" | false | 200
ProjectFeature::PRIVATE | "developer" | 200 ProjectFeature::PRIVATE | "developer" | false | 200
ProjectFeature::PRIVATE | "reporter" | 200 ProjectFeature::PRIVATE | "reporter" | false | 200
ProjectFeature::PRIVATE | "guest" | 200 ProjectFeature::PRIVATE | "guest" | false | 200
ProjectFeature::PRIVATE | "user" | 404 ProjectFeature::PRIVATE | "user" | false | 404
ProjectFeature::PRIVATE | nil | 404 ProjectFeature::PRIVATE | nil | false | 404
end end
with_them do with_them do
@ -77,7 +77,7 @@ RSpec.describe "Private Project Pages Access", feature_category: :pages do
it "correct return value" do it "correct return value" do
if !with_user.nil? if !with_user.nil?
user = public_send(with_user) user = public_send(with_user)
get api("/projects/#{project.id}/pages_access", user) get api("/projects/#{project.id}/pages_access", user, admin_mode: admin_mode)
else else
get api("/projects/#{project.id}/pages_access") get api("/projects/#{project.id}/pages_access")
end end

View File

@ -35,39 +35,39 @@ RSpec.describe "Public Project Pages Access", feature_category: :pages do
describe "GET /projects/:id/pages_access" do describe "GET /projects/:id/pages_access" do
context 'access depends on the level' do context 'access depends on the level' do
where(:pages_access_level, :with_user, :expected_result) do where(:pages_access_level, :with_user, :admin_mode, :expected_result) do
ProjectFeature::DISABLED | "admin" | 403 ProjectFeature::DISABLED | "admin" | false | 403
ProjectFeature::DISABLED | "owner" | 403 ProjectFeature::DISABLED | "owner" | false | 403
ProjectFeature::DISABLED | "master" | 403 ProjectFeature::DISABLED | "master" | false | 403
ProjectFeature::DISABLED | "developer" | 403 ProjectFeature::DISABLED | "developer" | false | 403
ProjectFeature::DISABLED | "reporter" | 403 ProjectFeature::DISABLED | "reporter" | false | 403
ProjectFeature::DISABLED | "guest" | 403 ProjectFeature::DISABLED | "guest" | false | 403
ProjectFeature::DISABLED | "user" | 403 ProjectFeature::DISABLED | "user" | false | 403
ProjectFeature::DISABLED | nil | 403 ProjectFeature::DISABLED | nil | false | 403
ProjectFeature::PUBLIC | "admin" | 200 ProjectFeature::PUBLIC | "admin" | false | 200
ProjectFeature::PUBLIC | "owner" | 200 ProjectFeature::PUBLIC | "owner" | false | 200
ProjectFeature::PUBLIC | "master" | 200 ProjectFeature::PUBLIC | "master" | false | 200
ProjectFeature::PUBLIC | "developer" | 200 ProjectFeature::PUBLIC | "developer" | false | 200
ProjectFeature::PUBLIC | "reporter" | 200 ProjectFeature::PUBLIC | "reporter" | false | 200
ProjectFeature::PUBLIC | "guest" | 200 ProjectFeature::PUBLIC | "guest" | false | 200
ProjectFeature::PUBLIC | "user" | 200 ProjectFeature::PUBLIC | "user" | false | 200
ProjectFeature::PUBLIC | nil | 200 ProjectFeature::PUBLIC | nil | false | 200
ProjectFeature::ENABLED | "admin" | 200 ProjectFeature::ENABLED | "admin" | false | 200
ProjectFeature::ENABLED | "owner" | 200 ProjectFeature::ENABLED | "owner" | false | 200
ProjectFeature::ENABLED | "master" | 200 ProjectFeature::ENABLED | "master" | false | 200
ProjectFeature::ENABLED | "developer" | 200 ProjectFeature::ENABLED | "developer" | false | 200
ProjectFeature::ENABLED | "reporter" | 200 ProjectFeature::ENABLED | "reporter" | false | 200
ProjectFeature::ENABLED | "guest" | 200 ProjectFeature::ENABLED | "guest" | false | 200
ProjectFeature::ENABLED | "user" | 200 ProjectFeature::ENABLED | "user" | false | 200
ProjectFeature::ENABLED | nil | 200 ProjectFeature::ENABLED | nil | false | 200
ProjectFeature::PRIVATE | "admin" | 200 ProjectFeature::PRIVATE | "admin" | true | 200
ProjectFeature::PRIVATE | "owner" | 200 ProjectFeature::PRIVATE | "owner" | false | 200
ProjectFeature::PRIVATE | "master" | 200 ProjectFeature::PRIVATE | "master" | false | 200
ProjectFeature::PRIVATE | "developer" | 200 ProjectFeature::PRIVATE | "developer" | false | 200
ProjectFeature::PRIVATE | "reporter" | 200 ProjectFeature::PRIVATE | "reporter" | false | 200
ProjectFeature::PRIVATE | "guest" | 200 ProjectFeature::PRIVATE | "guest" | false | 200
ProjectFeature::PRIVATE | "user" | 403 ProjectFeature::PRIVATE | "user" | false | 403
ProjectFeature::PRIVATE | nil | 403 ProjectFeature::PRIVATE | nil | false | 403
end end
with_them do with_them do
@ -77,7 +77,7 @@ RSpec.describe "Public Project Pages Access", feature_category: :pages do
it "correct return value" do it "correct return value" do
if !with_user.nil? if !with_user.nil?
user = public_send(with_user) user = public_send(with_user)
get api("/projects/#{project.id}/pages_access", user) get api("/projects/#{project.id}/pages_access", user, admin_mode: admin_mode)
else else
get api("/projects/#{project.id}/pages_access") get api("/projects/#{project.id}/pages_access")
end end

View File

@ -41,14 +41,14 @@ RSpec.describe API::PagesDomains, feature_category: :pages do
end end
it_behaves_like '404 response' do it_behaves_like '404 response' do
let(:request) { get api('/pages/domains', admin) } let(:request) { get api('/pages/domains', admin, admin_mode: true) }
end end
end end
context 'when pages is enabled' do context 'when pages is enabled' do
context 'when authenticated as an admin' do context 'when authenticated as an admin' do
it 'returns paginated all pages domains' do it 'returns paginated all pages domains', :aggregate_failures do
get api('/pages/domains', admin) get api('/pages/domains', admin, admin_mode: true)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/pages_domain_basics') expect(response).to match_response_schema('public_api/v4/pages_domain_basics')
@ -74,7 +74,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do
describe 'GET /projects/:project_id/pages/domains' do describe 'GET /projects/:project_id/pages/domains' do
shared_examples_for 'get pages domains' do shared_examples_for 'get pages domains' do
it 'returns paginated pages domains' do it 'returns paginated pages domains', :aggregate_failures do
get api(route, user) get api(route, user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -145,7 +145,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do
describe 'GET /projects/:project_id/pages/domains/:domain' do describe 'GET /projects/:project_id/pages/domains/:domain' do
shared_examples_for 'get pages domain' do shared_examples_for 'get pages domain' do
it 'returns pages domain' do it 'returns pages domain', :aggregate_failures do
get api(route_domain, user) get api(route_domain, user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -155,7 +155,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do
expect(json_response['certificate']).to be_nil expect(json_response['certificate']).to be_nil
end end
it 'returns pages domain with project path' do it 'returns pages domain with project path', :aggregate_failures do
get api(route_domain_path, user) get api(route_domain_path, user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -165,7 +165,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do
expect(json_response['certificate']).to be_nil expect(json_response['certificate']).to be_nil
end end
it 'returns pages domain with a certificate' do it 'returns pages domain with a certificate', :aggregate_failures do
get api(route_secure_domain, user) get api(route_secure_domain, user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -177,7 +177,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do
expect(json_response['auto_ssl_enabled']).to be false expect(json_response['auto_ssl_enabled']).to be false
end end
it 'returns pages domain with an expired certificate' do it 'returns pages domain with an expired certificate', :aggregate_failures do
get api(route_expired_domain, user) get api(route_expired_domain, user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -185,7 +185,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do
expect(json_response['certificate']['expired']).to be true expect(json_response['certificate']['expired']).to be true
end end
it 'returns pages domain with letsencrypt' do it 'returns pages domain with letsencrypt', :aggregate_failures do
get api(route_letsencrypt_domain, user) get api(route_letsencrypt_domain, user)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
@ -258,7 +258,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do
let(:params_secure) { pages_domain_secure_params.slice(:domain, :certificate, :key) } let(:params_secure) { pages_domain_secure_params.slice(:domain, :certificate, :key) }
shared_examples_for 'post pages domains' do shared_examples_for 'post pages domains' do
it 'creates a new pages domain' do it 'creates a new pages domain', :aggregate_failures do
expect { post api(route, user), params: params } expect { post api(route, user), params: params }
.to publish_event(PagesDomains::PagesDomainCreatedEvent) .to publish_event(PagesDomains::PagesDomainCreatedEvent)
.with( .with(
@ -279,7 +279,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do
expect(pages_domain.auto_ssl_enabled).to be false expect(pages_domain.auto_ssl_enabled).to be false
end end
it 'creates a new secure pages domain' do it 'creates a new secure pages domain', :aggregate_failures do
post api(route, user), params: params_secure post api(route, user), params: params_secure
pages_domain = PagesDomain.find_by(domain: json_response['domain']) pages_domain = PagesDomain.find_by(domain: json_response['domain'])
@ -291,7 +291,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do
expect(pages_domain.auto_ssl_enabled).to be false expect(pages_domain.auto_ssl_enabled).to be false
end end
it 'creates domain with letsencrypt enabled' do it 'creates domain with letsencrypt enabled', :aggregate_failures do
post api(route, user), params: pages_domain_with_letsencrypt_params post api(route, user), params: pages_domain_with_letsencrypt_params
pages_domain = PagesDomain.find_by(domain: json_response['domain']) pages_domain = PagesDomain.find_by(domain: json_response['domain'])
@ -301,7 +301,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do
expect(pages_domain.auto_ssl_enabled).to be true expect(pages_domain.auto_ssl_enabled).to be true
end end
it 'creates domain with letsencrypt enabled and provided certificate' do it 'creates domain with letsencrypt enabled and provided certificate', :aggregate_failures do
post api(route, user), params: params_secure.merge(auto_ssl_enabled: true) post api(route, user), params: params_secure.merge(auto_ssl_enabled: true)
pages_domain = PagesDomain.find_by(domain: json_response['domain']) pages_domain = PagesDomain.find_by(domain: json_response['domain'])
@ -376,7 +376,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do
let(:params_secure_nokey) { pages_domain_secure_params.slice(:certificate) } let(:params_secure_nokey) { pages_domain_secure_params.slice(:certificate) }
shared_examples_for 'put pages domain' do shared_examples_for 'put pages domain' do
it 'updates pages domain removing certificate' do it 'updates pages domain removing certificate', :aggregate_failures do
put api(route_secure_domain, user), params: { certificate: nil, key: nil } put api(route_secure_domain, user), params: { certificate: nil, key: nil }
pages_domain_secure.reload pages_domain_secure.reload
@ -399,7 +399,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do
) )
end end
it 'updates pages domain adding certificate' do it 'updates pages domain adding certificate', :aggregate_failures do
put api(route_domain, user), params: params_secure put api(route_domain, user), params: params_secure
pages_domain.reload pages_domain.reload
@ -409,7 +409,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do
expect(pages_domain.key).to eq(params_secure[:key]) expect(pages_domain.key).to eq(params_secure[:key])
end end
it 'updates pages domain adding certificate with letsencrypt' do it 'updates pages domain adding certificate with letsencrypt', :aggregate_failures do
put api(route_domain, user), params: params_secure.merge(auto_ssl_enabled: true) put api(route_domain, user), params: params_secure.merge(auto_ssl_enabled: true)
pages_domain.reload pages_domain.reload
@ -420,7 +420,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do
expect(pages_domain.auto_ssl_enabled).to be true expect(pages_domain.auto_ssl_enabled).to be true
end end
it 'updates pages domain enabling letsencrypt' do it 'updates pages domain enabling letsencrypt', :aggregate_failures do
put api(route_domain, user), params: { auto_ssl_enabled: true } put api(route_domain, user), params: { auto_ssl_enabled: true }
pages_domain.reload pages_domain.reload
@ -429,7 +429,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do
expect(pages_domain.auto_ssl_enabled).to be true expect(pages_domain.auto_ssl_enabled).to be true
end end
it 'updates pages domain disabling letsencrypt while preserving the certificate' do it 'updates pages domain disabling letsencrypt while preserving the certificate', :aggregate_failures do
put api(route_letsencrypt_domain, user), params: { auto_ssl_enabled: false } put api(route_letsencrypt_domain, user), params: { auto_ssl_enabled: false }
pages_domain_with_letsencrypt.reload pages_domain_with_letsencrypt.reload
@ -440,7 +440,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do
expect(pages_domain_with_letsencrypt.certificate).to be expect(pages_domain_with_letsencrypt.certificate).to be
end end
it 'updates pages domain with expired certificate' do it 'updates pages domain with expired certificate', :aggregate_failures do
put api(route_expired_domain, user), params: params_secure put api(route_expired_domain, user), params: params_secure
pages_domain_expired.reload pages_domain_expired.reload
@ -450,7 +450,7 @@ RSpec.describe API::PagesDomains, feature_category: :pages do
expect(pages_domain_expired.key).to eq(params_secure[:key]) expect(pages_domain_expired.key).to eq(params_secure[:key])
end end
it 'updates pages domain with expired certificate not updating key' do it 'updates pages domain with expired certificate not updating key', :aggregate_failures do
put api(route_secure_domain, user), params: params_secure_nokey put api(route_secure_domain, user), params: params_secure_nokey
pages_domain_secure.reload pages_domain_secure.reload

View File

@ -362,20 +362,10 @@ RSpec.configure do |config|
./spec/requests/api/deploy_tokens_spec.rb ./spec/requests/api/deploy_tokens_spec.rb
./spec/requests/api/freeze_periods_spec.rb ./spec/requests/api/freeze_periods_spec.rb
./spec/requests/api/groups_spec.rb ./spec/requests/api/groups_spec.rb
./spec/requests/api/issues/get_group_issues_spec.rb
./spec/requests/api/issues/get_project_issues_spec.rb
./spec/requests/api/issues/issues_spec.rb
./spec/requests/api/issues/post_projects_issues_spec.rb
./spec/requests/api/issues/put_projects_issues_spec.rb
./spec/requests/api/keys_spec.rb ./spec/requests/api/keys_spec.rb
./spec/requests/api/merge_requests_spec.rb ./spec/requests/api/merge_requests_spec.rb
./spec/requests/api/namespaces_spec.rb ./spec/requests/api/namespaces_spec.rb
./spec/requests/api/notes_spec.rb ./spec/requests/api/notes_spec.rb
./spec/requests/api/pages/internal_access_spec.rb
./spec/requests/api/pages/pages_spec.rb
./spec/requests/api/pages/private_access_spec.rb
./spec/requests/api/pages/public_access_spec.rb
./spec/requests/api/pages_domains_spec.rb
./spec/requests/api/personal_access_tokens/self_information_spec.rb ./spec/requests/api/personal_access_tokens/self_information_spec.rb
./spec/requests/api/personal_access_tokens_spec.rb ./spec/requests/api/personal_access_tokens_spec.rb
./spec/requests/api/project_export_spec.rb ./spec/requests/api/project_export_spec.rb

View File

@ -1237,10 +1237,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.24.0.tgz#bc8265919aa04b06cd08be91637471bad195936d" resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.24.0.tgz#bc8265919aa04b06cd08be91637471bad195936d"
integrity sha512-R4s5qJUFUIbPflknpw1aI/PchiNq65vY7LVsJZnQkY+vi+AgmsETdut/AdferbGWmeWMU0q2wuVu9phE8lDUgA== integrity sha512-R4s5qJUFUIbPflknpw1aI/PchiNq65vY7LVsJZnQkY+vi+AgmsETdut/AdferbGWmeWMU0q2wuVu9phE8lDUgA==
"@gitlab/ui@56.4.0": "@gitlab/ui@56.4.1":
version "56.4.0" version "56.4.1"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-56.4.0.tgz#35e2ee19fff9ff803f8737300d957b6a9f6ade1f" resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-56.4.1.tgz#bdf6284053b092608f6f0b70c6abeb36fd352aa2"
integrity sha512-L+0lf5jXrE34RruiibeDOb4wiziSsUhB+d3CHTQIjtm6qdqcwgYT03AoNTgc/E/YVLshEKywTJnnyakreecAlw== integrity sha512-WlEryaV/pwaJkHa+ioCcyB9slE+2RoPs15cmS+OtA9XsmWKqZTI1S00uZG9GcnA9hDKZM+HP2Apy7ku+myzLDQ==
dependencies: dependencies:
"@popperjs/core" "^2.11.2" "@popperjs/core" "^2.11.2"
bootstrap-vue "2.23.1" bootstrap-vue "2.23.1"