Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
9dab4d7b64
commit
edd183a633
|
@ -329,7 +329,6 @@ linters:
|
|||
- 'ee/app/views/errors/kerberos_denied.html.haml'
|
||||
- 'ee/app/views/groups/ee/_settings_nav.html.haml'
|
||||
- 'ee/app/views/groups/group_members/_ldap_sync.html.haml'
|
||||
- 'ee/app/views/groups/group_members/_sync_button.html.haml'
|
||||
- 'ee/app/views/groups/hooks/edit.html.haml'
|
||||
- 'ee/app/views/groups/ldap_group_links/index.html.haml'
|
||||
- 'ee/app/views/layouts/nav/ee/admin/_new_monitoring_sidebar.html.haml'
|
||||
|
@ -362,7 +361,6 @@ linters:
|
|||
- 'ee/app/views/projects/services/gitlab_slack_application/_help.html.haml'
|
||||
- 'ee/app/views/projects/services/gitlab_slack_application/_slack_integration_form.html.haml'
|
||||
- 'ee/app/views/projects/settings/slacks/edit.html.haml'
|
||||
- 'ee/app/views/shared/_mirror_update_button.html.haml'
|
||||
- 'ee/app/views/shared/epic/_search_bar.html.haml'
|
||||
- 'ee/app/views/shared/issuable/_approvals.html.haml'
|
||||
- 'ee/app/views/shared/issuable/_board_create_list_dropdown.html.haml'
|
||||
|
|
|
@ -186,31 +186,21 @@ RSpec/ExpectChange:
|
|||
# Offense count: 47
|
||||
RSpec/ExpectGitlabTracking:
|
||||
Exclude:
|
||||
- 'ee/spec/controllers/groups/analytics/coverage_reports_controller_spec.rb'
|
||||
- 'ee/spec/controllers/projects/settings/operations_controller_spec.rb'
|
||||
- 'ee/spec/controllers/registrations_controller_spec.rb'
|
||||
- 'ee/spec/requests/api/visual_review_discussions_spec.rb'
|
||||
- 'ee/spec/services/epics/issue_promote_service_spec.rb'
|
||||
- 'spec/controllers/groups/registry/repositories_controller_spec.rb'
|
||||
- 'spec/controllers/groups_controller_spec.rb'
|
||||
- 'spec/controllers/projects/registry/repositories_controller_spec.rb'
|
||||
- 'spec/controllers/projects/registry/tags_controller_spec.rb'
|
||||
- 'spec/controllers/projects/settings/operations_controller_spec.rb'
|
||||
- 'spec/controllers/registrations_controller_spec.rb'
|
||||
- 'spec/lib/api/helpers_spec.rb'
|
||||
- 'spec/lib/gitlab/experimentation_spec.rb'
|
||||
- 'spec/mailers/notify_spec.rb'
|
||||
- 'spec/models/project_services/prometheus_service_spec.rb'
|
||||
- 'spec/requests/api/project_container_repositories_spec.rb'
|
||||
- 'spec/services/clusters/applications/check_installation_progress_service_spec.rb'
|
||||
- 'spec/services/issues/zoom_link_service_spec.rb'
|
||||
- 'spec/support/helpers/snowplow_helpers.rb'
|
||||
- 'spec/support/shared_examples/controllers/trackable_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/requests/api/container_repositories_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/requests/api/discussions_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/requests/api/packages_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/requests/api/tracking_shared_examples.rb'
|
||||
- 'spec/support/snowplow.rb'
|
||||
|
||||
# Offense count: 751
|
||||
RSpec/ExpectInHook:
|
||||
|
|
|
@ -1 +1 @@
|
|||
19109e7090eb6dab2b8c0c168bbef76ceca79a31
|
||||
0ebfb705b79a8baecc1db46f31761f83f4e471f9
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script>
|
||||
import { GlForm, GlFormGroup, GlFormInput, GlFormTextarea } from '@gitlab/ui';
|
||||
import AccessorUtilities from '~/lib/utils/accessor';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -19,55 +18,25 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editable: {
|
||||
title: this.title,
|
||||
description: this.description,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
editableStorageKey() {
|
||||
return this.getId('local-storage', 'editable');
|
||||
},
|
||||
hasLocalStorage() {
|
||||
return AccessorUtilities.isLocalStorageAccessSafe();
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initCachedEditable();
|
||||
this.preSelect();
|
||||
},
|
||||
methods: {
|
||||
getId(type, key) {
|
||||
return `sse-merge-request-meta-${type}-${key}`;
|
||||
},
|
||||
initCachedEditable() {
|
||||
if (this.hasLocalStorage) {
|
||||
const cachedEditable = JSON.parse(localStorage.getItem(this.editableStorageKey));
|
||||
if (cachedEditable) {
|
||||
this.editable = cachedEditable;
|
||||
}
|
||||
}
|
||||
},
|
||||
preSelect() {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.title.$el.select();
|
||||
});
|
||||
},
|
||||
resetCachedEditable() {
|
||||
if (this.hasLocalStorage) {
|
||||
window.localStorage.removeItem(this.editableStorageKey);
|
||||
}
|
||||
},
|
||||
onUpdate() {
|
||||
const payload = { ...this.editable };
|
||||
onUpdate(field, value) {
|
||||
const payload = {
|
||||
title: this.title,
|
||||
description: this.description,
|
||||
[field]: value,
|
||||
};
|
||||
this.$emit('updateSettings', payload);
|
||||
|
||||
if (this.hasLocalStorage) {
|
||||
window.localStorage.setItem(this.editableStorageKey, JSON.stringify(payload));
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -83,9 +52,9 @@ export default {
|
|||
<gl-form-input
|
||||
:id="getId('control', 'title')"
|
||||
ref="title"
|
||||
v-model.lazy="editable.title"
|
||||
:value="title"
|
||||
type="text"
|
||||
@input="onUpdate"
|
||||
@input="onUpdate('title', $event)"
|
||||
/>
|
||||
</gl-form-group>
|
||||
|
||||
|
@ -96,8 +65,8 @@ export default {
|
|||
>
|
||||
<gl-form-textarea
|
||||
:id="getId('control', 'description')"
|
||||
v-model.lazy="editable.description"
|
||||
@input="onUpdate"
|
||||
:value="description"
|
||||
@input="onUpdate('description', $event)"
|
||||
/>
|
||||
</gl-form-group>
|
||||
</gl-form>
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
<script>
|
||||
import { GlModal } from '@gitlab/ui';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
|
||||
|
||||
import EditMetaControls from './edit_meta_controls.vue';
|
||||
|
||||
import { MR_META_LOCAL_STORAGE_KEY } from '../constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlModal,
|
||||
EditMetaControls,
|
||||
LocalStorageSync,
|
||||
},
|
||||
props: {
|
||||
sourcePath: {
|
||||
|
@ -17,6 +21,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
clearStorage: false,
|
||||
mergeRequestMeta: {
|
||||
title: sprintf(s__(`StaticSiteEditor|Update %{sourcePath} file`), {
|
||||
sourcePath: this.sourcePath,
|
||||
|
@ -51,7 +56,7 @@ export default {
|
|||
},
|
||||
onPrimary() {
|
||||
this.$emit('primary', this.mergeRequestMeta);
|
||||
this.$refs.editMetaControls.resetCachedEditable();
|
||||
this.clearStorage = true;
|
||||
},
|
||||
onSecondary() {
|
||||
this.hide();
|
||||
|
@ -60,6 +65,7 @@ export default {
|
|||
this.mergeRequestMeta = { ...mergeRequestMeta };
|
||||
},
|
||||
},
|
||||
storageKey: MR_META_LOCAL_STORAGE_KEY,
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -75,6 +81,12 @@ export default {
|
|||
@secondary="onSecondary"
|
||||
@hide="() => $emit('hide')"
|
||||
>
|
||||
<local-storage-sync
|
||||
v-model="mergeRequestMeta"
|
||||
:storage-key="$options.storageKey"
|
||||
:clear="clearStorage"
|
||||
as-json
|
||||
/>
|
||||
<edit-meta-controls
|
||||
ref="editMetaControls"
|
||||
:title="mergeRequestMeta.title"
|
||||
|
|
|
@ -21,3 +21,5 @@ export const TRACKING_ACTION_CREATE_MERGE_REQUEST = 'create_merge_request';
|
|||
export const TRACKING_ACTION_INITIALIZE_EDITOR = 'initialize_editor';
|
||||
|
||||
export const DEFAULT_IMAGE_UPLOAD_PATH = 'source/images/uploads/';
|
||||
|
||||
export const MR_META_LOCAL_STORAGE_KEY = 'sse-merge-request-meta-storage-key';
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
<script>
|
||||
import { isEmpty } from 'lodash';
|
||||
import { GlIcon, GlButton, GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import {
|
||||
GlIcon,
|
||||
GlButton,
|
||||
GlButtonGroup,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlSprintf,
|
||||
GlLink,
|
||||
GlTooltipDirective,
|
||||
} from '@gitlab/ui';
|
||||
import readyToMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/ready_to_merge';
|
||||
import simplePoll from '~/lib/utils/simple_poll';
|
||||
import { __ } from '~/locale';
|
||||
|
@ -36,6 +45,9 @@ export default {
|
|||
GlSprintf,
|
||||
GlLink,
|
||||
GlButton,
|
||||
GlButtonGroup,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
MergeTrainHelperText: () =>
|
||||
import('ee_component/vue_merge_request_widget/components/merge_train_helper_text.vue'),
|
||||
MergeImmediatelyConfirmationDialog: () =>
|
||||
|
@ -43,6 +55,9 @@ export default {
|
|||
'ee_component/vue_merge_request_widget/components/merge_immediately_confirmation_dialog.vue'
|
||||
),
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
mixins: [readyToMergeMixin],
|
||||
props: {
|
||||
mr: { type: Object, required: true },
|
||||
|
@ -283,7 +298,7 @@ export default {
|
|||
<status-icon :status="iconClass" />
|
||||
<div class="media-body">
|
||||
<div class="mr-widget-body-controls media space-children">
|
||||
<span class="btn-group">
|
||||
<gl-button-group>
|
||||
<gl-button
|
||||
size="medium"
|
||||
category="primary"
|
||||
|
@ -294,54 +309,33 @@ export default {
|
|||
@click="handleMergeButtonClick(isAutoMergeAvailable)"
|
||||
>{{ mergeButtonText }}</gl-button
|
||||
>
|
||||
<button
|
||||
<gl-dropdown
|
||||
v-if="shouldShowMergeImmediatelyDropdown"
|
||||
v-gl-tooltip.hover.focus="__('Select merge moment')"
|
||||
:disabled="isMergeButtonDisabled"
|
||||
type="button"
|
||||
class="btn btn-sm btn-info dropdown-toggle js-merge-moment"
|
||||
data-toggle="dropdown"
|
||||
variant="info"
|
||||
data-qa-selector="merge_moment_dropdown"
|
||||
:aria-label="__('Select merge moment')"
|
||||
toggle-class="btn-icon js-merge-moment"
|
||||
>
|
||||
<i class="fa fa-chevron-down" aria-hidden="true"></i>
|
||||
</button>
|
||||
<ul
|
||||
v-if="shouldShowMergeImmediatelyDropdown"
|
||||
class="dropdown-menu dropdown-menu-right"
|
||||
role="menu"
|
||||
>
|
||||
<li>
|
||||
<a
|
||||
class="auto_merge_enabled qa-merge-when-pipeline-succeeds-option"
|
||||
href="#"
|
||||
@click.prevent="handleMergeButtonClick(true)"
|
||||
>
|
||||
<span class="media">
|
||||
<gl-icon name="status_success" class="merge-opt-icon" aria-hidden="true" />
|
||||
<span class="media-body merge-opt-title">{{ autoMergeText }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<merge-immediately-confirmation-dialog
|
||||
ref="confirmationDialog"
|
||||
:docs-url="mr.mergeImmediatelyDocsPath"
|
||||
@mergeImmediately="onMergeImmediatelyConfirmation"
|
||||
/>
|
||||
<a
|
||||
class="accept-merge-request js-merge-immediately-button"
|
||||
data-qa-selector="merge_immediately_option"
|
||||
href="#"
|
||||
@click.prevent="handleMergeImmediatelyButtonClick"
|
||||
>
|
||||
<span class="media">
|
||||
<gl-icon name="status_warning" class="merge-opt-icon" aria-hidden="true" />
|
||||
<span class="media-body merge-opt-title">{{ __('Merge immediately') }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
<template #button-content>
|
||||
<gl-icon name="chevron-down" class="mr-0" />
|
||||
<span class="sr-only">{{ __('Select merge moment') }}</span>
|
||||
</template>
|
||||
<gl-dropdown-item
|
||||
icon-name="warning"
|
||||
button-class="accept-merge-request js-merge-immediately-button"
|
||||
data-qa-selector="merge_immediately_option"
|
||||
@click="handleMergeImmediatelyButtonClick"
|
||||
>
|
||||
{{ __('Merge immediately') }}
|
||||
</gl-dropdown-item>
|
||||
<merge-immediately-confirmation-dialog
|
||||
ref="confirmationDialog"
|
||||
:docs-url="mr.mergeImmediatelyDocsPath"
|
||||
@mergeImmediately="onMergeImmediatelyConfirmation"
|
||||
/>
|
||||
</gl-dropdown>
|
||||
</gl-button-group>
|
||||
<div class="media-body-wrap space-children">
|
||||
<template v-if="shouldShowMergeControls">
|
||||
<label v-if="mr.canRemoveSourceBranch">
|
||||
|
|
|
@ -22,11 +22,21 @@ export default {
|
|||
required: false,
|
||||
default: true,
|
||||
},
|
||||
clear: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.saveValue(this.serialize(newVal));
|
||||
},
|
||||
clear(newVal) {
|
||||
if (newVal) {
|
||||
localStorage.removeItem(this.storageKey);
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// On mount, trigger update if we actually have a localStorageValue
|
||||
|
|
|
@ -113,10 +113,6 @@
|
|||
content: '\f0da';
|
||||
}
|
||||
|
||||
.fa-refresh::before {
|
||||
content: '\f021';
|
||||
}
|
||||
|
||||
.fa-chevron-up::before {
|
||||
content: '\f077';
|
||||
}
|
||||
|
|
|
@ -56,3 +56,7 @@
|
|||
vertical-align: text-bottom;
|
||||
}
|
||||
}
|
||||
|
||||
.spin {
|
||||
animation: spinner-rotate 2s infinite linear;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class JwksController < ActionController::Base # rubocop:disable Rails/ApplicationController
|
||||
def index
|
||||
render json: { keys: keys }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def keys
|
||||
[
|
||||
# We keep openid_connect_signing_key so that we can seamlessly
|
||||
# replace it with ci_jwt_signing_key and remove it on the next release.
|
||||
# TODO: Remove openid_connect_signing_key in 13.7
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/221031
|
||||
Rails.application.secrets.openid_connect_signing_key,
|
||||
Gitlab::CurrentSettings.ci_jwt_signing_key
|
||||
].compact.map do |key_data|
|
||||
OpenSSL::PKey::RSA.new(key_data)
|
||||
.public_key
|
||||
.to_jwk
|
||||
.slice(:kty, :kid, :e, :n)
|
||||
.merge(use: 'sig', alg: 'RS256')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -66,6 +66,11 @@ class MergeRequestsFinder < IssuableFinder
|
|||
by_source_project_id(items)
|
||||
end
|
||||
|
||||
def filter_negated_items(items)
|
||||
items = super(items)
|
||||
by_negated_target_branch(items)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def by_commit(items)
|
||||
|
@ -98,6 +103,14 @@ class MergeRequestsFinder < IssuableFinder
|
|||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def by_negated_target_branch(items)
|
||||
return items unless not_params[:target_branch]
|
||||
|
||||
items.where.not(target_branch: not_params[:target_branch])
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def source_project_id
|
||||
@source_project_id ||= params[:source_project_id].presence
|
||||
end
|
||||
|
|
|
@ -74,6 +74,10 @@ module Types
|
|||
description: 'Time estimate of the issue'
|
||||
field :total_time_spent, GraphQL::INT_TYPE, null: false,
|
||||
description: 'Total time reported as spent on the issue'
|
||||
field :human_time_estimate, GraphQL::STRING_TYPE, null: true,
|
||||
description: 'Human-readable time estimate of the issue'
|
||||
field :human_total_time_spent, GraphQL::STRING_TYPE, null: true,
|
||||
description: 'Human-readable total time reported as spent on the issue'
|
||||
|
||||
field :closed_at, Types::TimeType, null: true,
|
||||
description: 'Timestamp of when the issue was closed'
|
||||
|
|
|
@ -20,7 +20,8 @@ module SearchHelper
|
|||
resources_results = [
|
||||
recent_items_autocomplete(term),
|
||||
groups_autocomplete(term),
|
||||
projects_autocomplete(term)
|
||||
projects_autocomplete(term),
|
||||
issue_autocomplete(term)
|
||||
].flatten
|
||||
|
||||
search_pattern = Regexp.new(Regexp.escape(term), "i")
|
||||
|
@ -183,6 +184,24 @@ module SearchHelper
|
|||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def issue_autocomplete(term)
|
||||
return [] unless @project.present? && current_user && term =~ /\A#{Issue.reference_prefix}\d+\z/
|
||||
|
||||
iid = term.sub(Issue.reference_prefix, '').to_i
|
||||
issue = @project.issues.find_by_iid(iid)
|
||||
return [] unless issue && Ability.allowed?(current_user, :read_issue, issue)
|
||||
|
||||
[
|
||||
{
|
||||
category: 'In this project',
|
||||
id: issue.id,
|
||||
label: search_result_sanitize("#{issue.title} (#{issue.to_reference})"),
|
||||
url: issue_path(issue),
|
||||
avatar_url: issue.project.avatar_url || ''
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
# Autocomplete results for the current user's projects
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def projects_autocomplete(term, limit = 5)
|
||||
|
|
|
@ -384,6 +384,9 @@ class ApplicationSetting < ApplicationRecord
|
|||
validates :raw_blob_request_limit,
|
||||
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
||||
|
||||
validates :ci_jwt_signing_key,
|
||||
rsa_key: true, allow_nil: true
|
||||
|
||||
attr_encrypted :asset_proxy_secret_key,
|
||||
mode: :per_attribute_iv,
|
||||
key: Settings.attr_encrypted_db_key_base_truncated,
|
||||
|
@ -409,6 +412,7 @@ class ApplicationSetting < ApplicationRecord
|
|||
attr_encrypted :recaptcha_site_key, encryption_options_base_truncated_aes_256_gcm
|
||||
attr_encrypted :slack_app_secret, encryption_options_base_truncated_aes_256_gcm
|
||||
attr_encrypted :slack_app_verification_token, encryption_options_base_truncated_aes_256_gcm
|
||||
attr_encrypted :ci_jwt_signing_key, encryption_options_base_truncated_aes_256_gcm
|
||||
|
||||
before_validation :ensure_uuid!
|
||||
|
||||
|
|
|
@ -1059,7 +1059,7 @@ module Ci
|
|||
|
||||
jwt = Gitlab::Ci::Jwt.for_build(self)
|
||||
variables.append(key: 'CI_JOB_JWT', value: jwt, public: false, masked: true)
|
||||
rescue OpenSSL::PKey::RSAError => e
|
||||
rescue OpenSSL::PKey::RSAError, Gitlab::Ci::Jwt::NoSigningKeyError => e
|
||||
Gitlab::ErrorTracking.track_exception(e)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -205,13 +205,8 @@ class CommitStatus < ApplicationRecord
|
|||
# 'rspec:linux: 1/10' => 'rspec:linux'
|
||||
common_name = name.to_s.gsub(%r{\d+[\s:\/\\]+\d+\s*}, '')
|
||||
|
||||
if ::Gitlab::Ci::Features.one_dimensional_matrix_enabled?
|
||||
# 'rspec:linux: [aws, max memory]' => 'rspec:linux', 'rspec:linux: [aws]' => 'rspec:linux'
|
||||
common_name.gsub!(%r{: \[.*\]\s*\z}, '')
|
||||
else
|
||||
# 'rspec:linux: [aws, max memory]' => 'rspec:linux', 'rspec:linux: [aws]' => 'rspec:linux: [aws]'
|
||||
common_name.gsub!(%r{: \[.*, .*\]\s*\z}, '')
|
||||
end
|
||||
# 'rspec:linux: [aws, max memory]' => 'rspec:linux', 'rspec:linux: [aws]' => 'rspec:linux'
|
||||
common_name.gsub!(%r{: \[.*\]\s*\z}, '')
|
||||
|
||||
common_name.strip!
|
||||
common_name
|
||||
|
|
|
@ -21,6 +21,7 @@ class GroupMember < Member
|
|||
scope :of_groups, ->(groups) { where(source_id: groups.select(:id)) }
|
||||
scope :of_ldap_type, -> { where(ldap: true) }
|
||||
scope :count_users_by_group_id, -> { group(:source_id).count }
|
||||
scope :with_user, -> (user) { where(user: user) }
|
||||
|
||||
after_create :update_two_factor_requirement, unless: :invite?
|
||||
after_destroy :update_two_factor_requirement, unless: :invite?
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# RsaKeyValidator
|
||||
#
|
||||
# Custom validator for RSA private keys.
|
||||
#
|
||||
# class Project < ActiveRecord::Base
|
||||
# validates :signing_key, rsa_key: true
|
||||
# end
|
||||
#
|
||||
class RsaKeyValidator < ActiveModel::EachValidator
|
||||
def validate_each(record, attribute, value)
|
||||
unless valid_rsa_key?(value)
|
||||
record.errors.add(attribute, "is not a valid RSA key")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def valid_rsa_key?(value)
|
||||
return false unless value
|
||||
|
||||
OpenSSL::PKey::RSA.new(value)
|
||||
rescue OpenSSL::PKey::RSAError
|
||||
false
|
||||
end
|
||||
end
|
|
@ -9,7 +9,7 @@
|
|||
= succeed ':' do
|
||||
= link_to note.author_name, user_url(note.author)
|
||||
- if discussion.nil?
|
||||
commented
|
||||
= link_to 'commented', target_url
|
||||
- else
|
||||
- if note.start_of_discussion?
|
||||
started a new
|
||||
|
|
|
@ -74,4 +74,4 @@
|
|||
- if mirror.ssh_key_auth?
|
||||
= clipboard_button(text: mirror.ssh_public_key, class: 'gl-button btn btn-default', title: _('Copy SSH public key'), qa_selector: 'copy_public_key_button')
|
||||
= render 'shared/remote_mirror_update_button', remote_mirror: mirror
|
||||
%button.js-delete-mirror.qa-delete-mirror.rspec-delete-mirror.btn.gl-button.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= sprite_icon('remove')
|
||||
%button.js-delete-mirror.qa-delete-mirror.rspec-delete-mirror.btn.btn-icon.gl-button.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= sprite_icon('remove')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
- if remote_mirror.update_in_progress?
|
||||
%button.btn.disabled{ type: 'button', data: { toggle: 'tooltip', container: 'body', qa_selector: 'updating_button' }, title: _('Updating') }
|
||||
= icon("refresh spin")
|
||||
%button.btn.btn-icon.gl-button.disabled{ type: 'button', data: { toggle: 'tooltip', container: 'body', qa_selector: 'updating_button' }, title: _('Updating') }
|
||||
= sprite_icon("retry", css_class: "spin")
|
||||
- elsif remote_mirror.enabled?
|
||||
= link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn qa-update-now-button rspec-update-now-button", data: { toggle: 'tooltip', container: 'body' }, title: _('Update now') do
|
||||
= icon("refresh")
|
||||
= link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn btn-icon gl-button qa-update-now-button rspec-update-now-button", data: { toggle: 'tooltip', container: 'body' }, title: _('Update now') do
|
||||
= sprite_icon("retry")
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add CI JWT signing key to application_setings
|
||||
merge_request: 43950
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Search Autocomplete add GFM support for issues
|
||||
merge_request: 44930
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Expose humanTimeEstimate and humanTotalTimeSpent via graphql
|
||||
merge_request: 45508
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix incorrect code in Load Performance Testing docs
|
||||
merge_request: 45877
|
||||
author:
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add link to the note on the email sent after adding a comment on an issue
|
||||
merge_request: 45511
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Replace fa-refresh icon with GitLab SVG
|
||||
merge_request: 45777
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fixed target branch not filtering
|
||||
merge_request: 45652
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add `position` column into security_findings table
|
||||
merge_request: 44815
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
name: ci_jwt_signing_key
|
||||
introduced_by_url:
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/258546
|
||||
type: development
|
||||
group: group::release management
|
||||
default_enabled: false
|
|
@ -1,7 +0,0 @@
|
|||
---
|
||||
name: one_dimensional_matrix
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42170
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/256062
|
||||
type: development
|
||||
group: group::pipeline authoring
|
||||
default_enabled: true
|
|
@ -175,9 +175,8 @@ Rails.application.routes.draw do
|
|||
resources :abuse_reports, only: [:new, :create]
|
||||
|
||||
# JWKS (JSON Web Key Set) endpoint
|
||||
# Used by third parties to verify CI_JOB_JWT, placeholder route
|
||||
# in case we decide to move away from doorkeeper-openid_connect
|
||||
get 'jwks' => 'doorkeeper/openid_connect/discovery#keys'
|
||||
# Used by third parties to verify CI_JOB_JWT
|
||||
get 'jwks' => 'jwks#index'
|
||||
|
||||
draw :snippets
|
||||
draw :profile
|
||||
|
|
|
@ -7,4 +7,7 @@ ApplicationSetting.create_from_defaults
|
|||
puts "Enable hashed storage for every new projects.".color(:green)
|
||||
ApplicationSetting.current_without_cache.update!(hashed_storage_enabled: true)
|
||||
|
||||
puts "Generate CI JWT signing key".color(:green)
|
||||
ApplicationSetting.current_without_cache.update!(ci_jwt_signing_key: OpenSSL::PKey::RSA.new(2048).to_pem)
|
||||
|
||||
print '.'
|
||||
|
|
|
@ -24,3 +24,7 @@ if ENV['GITLAB_PROMETHEUS_METRICS_ENABLED'].present?
|
|||
settings.prometheus_metrics_enabled = value
|
||||
save(settings, 'Prometheus metrics enabled flag')
|
||||
end
|
||||
|
||||
settings = Gitlab::CurrentSettings.current_application_settings
|
||||
settings.ci_jwt_signing_key = OpenSSL::PKey::RSA.new(2048).to_pem
|
||||
save(settings, 'CI JWT signing key')
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddCiJwtSigningKeyToApplicationSettings < ActiveRecord::Migration[6.0]
|
||||
DOWNTIME = false
|
||||
|
||||
# rubocop:disable Migration/AddLimitToTextColumns
|
||||
# limit is added in 20201001011937_add_text_limit_to_application_settings_encrypted_ci_jwt_signing_key_iv
|
||||
def change
|
||||
add_column :application_settings, :encrypted_ci_jwt_signing_key, :text
|
||||
add_column :application_settings, :encrypted_ci_jwt_signing_key_iv, :text
|
||||
end
|
||||
# rubocop:enable Migration/AddLimitToTextColumns
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddTextLimitToApplicationSettingsEncryptedCiJwtSigningKeyIv < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_text_limit :application_settings, :encrypted_ci_jwt_signing_key_iv, 255
|
||||
end
|
||||
|
||||
def down
|
||||
remove_text_limit :application_settings, :encrypted_ci_jwt_signing_key_iv
|
||||
end
|
||||
end
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class GenerateCiJwtSigningKey < ActiveRecord::Migration[6.0]
|
||||
DOWNTIME = false
|
||||
|
||||
class ApplicationSetting < ActiveRecord::Base
|
||||
self.table_name = 'application_settings'
|
||||
|
||||
attr_encrypted :ci_jwt_signing_key, {
|
||||
mode: :per_attribute_iv,
|
||||
key: Rails.application.secrets.db_key_base[0..31],
|
||||
algorithm: 'aes-256-gcm',
|
||||
encode: true
|
||||
}
|
||||
end
|
||||
|
||||
def up
|
||||
ApplicationSetting.reset_column_information
|
||||
|
||||
ApplicationSetting.find_each do |application_setting|
|
||||
application_setting.update(ci_jwt_signing_key: OpenSSL::PKey::RSA.new(2048).to_pem)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
ApplicationSetting.reset_column_information
|
||||
|
||||
ApplicationSetting.find_each do |application_setting|
|
||||
application_setting.update_columns(encrypted_ci_jwt_signing_key: nil, encrypted_ci_jwt_signing_key_iv: nil)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddPositionIntoSecurityFindings < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
add_column :security_findings, :position, :integer
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_column :security_findings, :position
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddUniqueIndexOnScanIdAndPositionOfSecurityFindings < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
INDEX_NAME = 'index_security_findings_on_scan_id_and_position'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_index :security_findings, [:scan_id, :position], unique: true, name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :security_findings, INDEX_NAME
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
b7b49ca4c021b7caa9f8612ad9b69d4ec6d79894db2e43266bfe26f2e0bffe08
|
|
@ -0,0 +1 @@
|
|||
8af89bb3e63bfca24cee8fdf6f0dd587fae7d81bfeaf6d427f84c7b37c9664ba
|
|
@ -0,0 +1 @@
|
|||
966f6e95189b551cba0ef548cb410911c0beee30d0a265ae21d90321ecbb2a00
|
|
@ -0,0 +1 @@
|
|||
d0ca8f0dbe0cf0fbbdd715867f3ae20862683433d919ee5cd942086d21f3b44d
|
|
@ -0,0 +1 @@
|
|||
f19ab0de07415e728849ef4e56804909a3a4a57ad8f55fe71a27bc43c535ac66
|
|
@ -9294,9 +9294,12 @@ CREATE TABLE application_settings (
|
|||
require_admin_approval_after_user_signup boolean DEFAULT false NOT NULL,
|
||||
help_page_documentation_base_url text,
|
||||
automatic_purchased_storage_allocation boolean DEFAULT false NOT NULL,
|
||||
encrypted_ci_jwt_signing_key text,
|
||||
encrypted_ci_jwt_signing_key_iv text,
|
||||
CONSTRAINT check_2dba05b802 CHECK ((char_length(gitpod_url) <= 255)),
|
||||
CONSTRAINT check_51700b31b5 CHECK ((char_length(default_branch_name) <= 255)),
|
||||
CONSTRAINT check_57123c9593 CHECK ((char_length(help_page_documentation_base_url) <= 255)),
|
||||
CONSTRAINT check_85a39b68ff CHECK ((char_length(encrypted_ci_jwt_signing_key_iv) <= 255)),
|
||||
CONSTRAINT check_9c6c447a13 CHECK ((char_length(maintenance_mode_message) <= 255)),
|
||||
CONSTRAINT check_d03919528d CHECK ((char_length(container_registry_vendor) <= 255)),
|
||||
CONSTRAINT check_d820146492 CHECK ((char_length(spam_check_endpoint_url) <= 255)),
|
||||
|
@ -15857,6 +15860,7 @@ CREATE TABLE security_findings (
|
|||
confidence smallint NOT NULL,
|
||||
project_fingerprint text NOT NULL,
|
||||
deduplicated boolean DEFAULT false NOT NULL,
|
||||
"position" integer,
|
||||
CONSTRAINT check_b9508c6df8 CHECK ((char_length(project_fingerprint) <= 40))
|
||||
);
|
||||
|
||||
|
@ -21528,6 +21532,8 @@ CREATE INDEX index_security_findings_on_project_fingerprint ON security_findings
|
|||
|
||||
CREATE INDEX index_security_findings_on_scan_id_and_deduplicated ON security_findings USING btree (scan_id, deduplicated);
|
||||
|
||||
CREATE UNIQUE INDEX index_security_findings_on_scan_id_and_position ON security_findings USING btree (scan_id, "position");
|
||||
|
||||
CREATE INDEX index_security_findings_on_scanner_id ON security_findings USING btree (scanner_id);
|
||||
|
||||
CREATE INDEX index_security_findings_on_severity ON security_findings USING btree (severity);
|
||||
|
|
|
@ -6807,6 +6807,16 @@ type EpicIssue implements CurrentUserTodos & Noteable {
|
|||
"""
|
||||
healthStatus: HealthStatus
|
||||
|
||||
"""
|
||||
Human-readable time estimate of the issue
|
||||
"""
|
||||
humanTimeEstimate: String
|
||||
|
||||
"""
|
||||
Human-readable total time reported as spent on the issue
|
||||
"""
|
||||
humanTotalTimeSpent: String
|
||||
|
||||
"""
|
||||
Global ID of the epic-issue relation
|
||||
"""
|
||||
|
@ -9020,6 +9030,16 @@ type Issue implements CurrentUserTodos & Noteable {
|
|||
"""
|
||||
healthStatus: HealthStatus
|
||||
|
||||
"""
|
||||
Human-readable time estimate of the issue
|
||||
"""
|
||||
humanTimeEstimate: String
|
||||
|
||||
"""
|
||||
Human-readable total time reported as spent on the issue
|
||||
"""
|
||||
humanTotalTimeSpent: String
|
||||
|
||||
"""
|
||||
ID of the issue
|
||||
"""
|
||||
|
|
|
@ -18716,6 +18716,34 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "humanTimeEstimate",
|
||||
"description": "Human-readable time estimate of the issue",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "humanTotalTimeSpent",
|
||||
"description": "Human-readable total time reported as spent on the issue",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"description": "Global ID of the epic-issue relation",
|
||||
|
@ -24526,6 +24554,34 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "humanTimeEstimate",
|
||||
"description": "Human-readable time estimate of the issue",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "humanTotalTimeSpent",
|
||||
"description": "Human-readable total time reported as spent on the issue",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
"description": "ID of the issue",
|
||||
|
|
|
@ -1081,6 +1081,8 @@ Relationship between an epic and an issue.
|
|||
| `epic` | Epic | Epic to which this issue belongs |
|
||||
| `epicIssueId` | ID! | ID of the epic-issue relation |
|
||||
| `healthStatus` | HealthStatus | Current health status. Returns null if `save_issuable_health_status` feature flag is disabled. |
|
||||
| `humanTimeEstimate` | String | Human-readable time estimate of the issue |
|
||||
| `humanTotalTimeSpent` | String | Human-readable total time reported as spent on the issue |
|
||||
| `id` | ID | Global ID of the epic-issue relation |
|
||||
| `iid` | ID! | Internal ID of the issue |
|
||||
| `iteration` | Iteration | Iteration of the issue |
|
||||
|
@ -1273,6 +1275,8 @@ Represents a recorded measurement (object count) for the Admins.
|
|||
| `dueDate` | Time | Due date of the issue |
|
||||
| `epic` | Epic | Epic to which this issue belongs |
|
||||
| `healthStatus` | HealthStatus | Current health status. Returns null if `save_issuable_health_status` feature flag is disabled. |
|
||||
| `humanTimeEstimate` | String | Human-readable time estimate of the issue |
|
||||
| `humanTotalTimeSpent` | String | Human-readable total time reported as spent on the issue |
|
||||
| `id` | ID! | ID of the issue |
|
||||
| `iid` | ID! | Internal ID of the issue |
|
||||
| `iteration` | Iteration | Iteration of the issue |
|
||||
|
|
|
@ -3574,9 +3574,6 @@ There can be from 2 to 50 jobs.
|
|||
|
||||
[In GitLab 13.5](https://gitlab.com/gitlab-org/gitlab/-/issues/26362) and later,
|
||||
you can have one-dimensional matrices with a single job.
|
||||
The ability to have one-dimensional matrices is [deployed behind a feature flag](../../user/feature_flags.md),
|
||||
enabled by default. It's enabled on GitLab.com. For self-managed GitLab instances,
|
||||
administrators can opt to disable it by [disabling the `one_dimensional_matrix:` feature flag](../../administration/feature_flags.md). **(CORE ONLY)**
|
||||
|
||||
Every job gets the same `CI_NODE_TOTAL` [environment variable](../variables/README.md#predefined-environment-variables) value, and a unique `CI_NODE_INDEX` value.
|
||||
|
||||
|
|
|
@ -156,7 +156,7 @@ The best approach is to capture the dynamic URL in a [`.env` file](https://docs.
|
|||
as a job artifact to be shared, then use a custom environment variable we've provided named `K6_DOCKER_OPTIONS`
|
||||
to configure the k6 Docker container to use the file. With this, k6 can then use any
|
||||
environment variables from the `.env` file in scripts using standard JavaScript,
|
||||
such as: ``http.get(`${__ENV.ENVIRONMENT_URL`})``.
|
||||
such as: ``http.get(`${__ENV.ENVIRONMENT_URL}`)``.
|
||||
|
||||
For example:
|
||||
|
||||
|
|
|
@ -211,6 +211,7 @@ You can also type in this search bar to see autocomplete suggestions for:
|
|||
- Recently viewed issues (try and type some word from the title of a recently viewed issue)
|
||||
- Recently viewed merge requests (try and type some word from the title of a recently viewed merge request)
|
||||
- Recently viewed epics (try and type some word from the title of a recently viewed epic)
|
||||
- [GitLab Flavored Markdown](../markdown.md#special-gitlab-references) (GFM) for issues within a project (try and type a GFM reference for an issue)
|
||||
|
||||
## Basic search
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ module Gitlab
|
|||
validations do
|
||||
validates :config, variables: { array_values: true }
|
||||
validates :config, length: {
|
||||
minimum: :minimum,
|
||||
minimum: 1,
|
||||
too_short: 'requires at least %{count} items'
|
||||
}
|
||||
end
|
||||
|
@ -28,10 +28,6 @@ module Gitlab
|
|||
.map { |key, value| [key.to_s, Array(value).map(&:to_s)] }
|
||||
.to_h
|
||||
end
|
||||
|
||||
def minimum
|
||||
::Gitlab::Ci::Features.one_dimensional_matrix_enabled? ? 1 : 2
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -59,10 +59,6 @@ module Gitlab
|
|||
::Feature.enabled?(:ci_trace_log_invalid_chunks, project, type: :ops, default_enabled: false)
|
||||
end
|
||||
|
||||
def self.one_dimensional_matrix_enabled?
|
||||
::Feature.enabled?(:one_dimensional_matrix, default_enabled: true)
|
||||
end
|
||||
|
||||
def self.manual_bridges_enabled?(project)
|
||||
::Feature.enabled?(:ci_manual_bridges, project, default_enabled: true)
|
||||
end
|
||||
|
|
|
@ -6,6 +6,8 @@ module Gitlab
|
|||
NOT_BEFORE_TIME = 5
|
||||
DEFAULT_EXPIRE_TIME = 60 * 5
|
||||
|
||||
NoSigningKeyError = Class.new(StandardError)
|
||||
|
||||
def self.for_build(build)
|
||||
self.new(build, ttl: build.metadata_timeout).encoded
|
||||
end
|
||||
|
@ -27,7 +29,7 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
attr_reader :build, :ttl, :key_data
|
||||
attr_reader :build, :ttl
|
||||
|
||||
def reserved_claims
|
||||
now = Time.now.to_i
|
||||
|
@ -60,7 +62,17 @@ module Gitlab
|
|||
end
|
||||
|
||||
def key
|
||||
@key ||= OpenSSL::PKey::RSA.new(Rails.application.secrets.openid_connect_signing_key)
|
||||
@key ||= begin
|
||||
key_data = if Feature.enabled?(:ci_jwt_signing_key, build.project)
|
||||
Gitlab::CurrentSettings.ci_jwt_signing_key
|
||||
else
|
||||
Rails.application.secrets.openid_connect_signing_key
|
||||
end
|
||||
|
||||
raise NoSigningKeyError unless key_data
|
||||
|
||||
OpenSSL::PKey::RSA.new(key_data)
|
||||
end
|
||||
end
|
||||
|
||||
def public_key
|
||||
|
|
|
@ -44,7 +44,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def unique_events(event_names:, start_date:, end_date:)
|
||||
events = events_for(Array(event_names))
|
||||
events = events_for(Array(event_names).map(&:to_s))
|
||||
|
||||
raise 'Events should be in same slot' unless events_in_same_slot?(events)
|
||||
raise 'Events should be in same category' unless events_in_same_category?(events)
|
||||
|
@ -141,7 +141,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def event_for(event_name)
|
||||
known_events.find { |event| event[:name] == event_name }
|
||||
known_events.find { |event| event[:name] == event_name.to_s }
|
||||
end
|
||||
|
||||
def events_for(event_names)
|
||||
|
|
|
@ -25558,6 +25558,9 @@ msgstr ""
|
|||
msgid "Successfully scheduled a pipeline to run. Go to the %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details."
|
||||
msgstr ""
|
||||
|
||||
msgid "Successfully synced %{synced_timeago}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Successfully unblocked"
|
||||
msgstr ""
|
||||
|
||||
|
@ -25693,12 +25696,18 @@ msgstr ""
|
|||
msgid "Sync information"
|
||||
msgstr ""
|
||||
|
||||
msgid "Sync now"
|
||||
msgstr ""
|
||||
|
||||
msgid "Synced"
|
||||
msgstr ""
|
||||
|
||||
msgid "Synchronization disabled"
|
||||
msgstr ""
|
||||
|
||||
msgid "Syncing…"
|
||||
msgstr ""
|
||||
|
||||
msgid "System"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ module QA
|
|||
element :merge_button
|
||||
element :fast_forward_message, 'Fast-forward merge without a merge commit' # rubocop:disable QA/ElementWithPattern
|
||||
element :merge_moment_dropdown
|
||||
element :merge_when_pipeline_succeeds_option
|
||||
element :merge_immediately_option
|
||||
end
|
||||
|
||||
|
|
|
@ -319,10 +319,10 @@ RSpec.describe GroupsController, factory_default: :keep do
|
|||
stub_experiment(onboarding_issues: false)
|
||||
end
|
||||
|
||||
it 'does not track anything' do
|
||||
expect(Gitlab::Tracking).not_to receive(:event)
|
||||
|
||||
it 'does not track anything', :snowplow do
|
||||
create_namespace
|
||||
|
||||
expect_no_snowplow_event
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -336,15 +336,15 @@ RSpec.describe GroupsController, factory_default: :keep do
|
|||
stub_experiment_for_user(onboarding_issues: false)
|
||||
end
|
||||
|
||||
it 'tracks the event with the "created_namespace" action with the "control_group" property' do
|
||||
expect(Gitlab::Tracking).to receive(:event).with(
|
||||
'Growth::Conversion::Experiment::OnboardingIssues',
|
||||
'created_namespace',
|
||||
it 'tracks the event with the "created_namespace" action with the "control_group" property', :snowplow do
|
||||
create_namespace
|
||||
|
||||
expect_snowplow_event(
|
||||
category: 'Growth::Conversion::Experiment::OnboardingIssues',
|
||||
action: 'created_namespace',
|
||||
label: anything,
|
||||
property: 'control_group'
|
||||
)
|
||||
|
||||
create_namespace
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -353,15 +353,15 @@ RSpec.describe GroupsController, factory_default: :keep do
|
|||
stub_experiment_for_user(onboarding_issues: true)
|
||||
end
|
||||
|
||||
it 'tracks the event with the "created_namespace" action with the "experimental_group" property' do
|
||||
expect(Gitlab::Tracking).to receive(:event).with(
|
||||
'Growth::Conversion::Experiment::OnboardingIssues',
|
||||
'created_namespace',
|
||||
it 'tracks the event with the "created_namespace" action with the "experimental_group" property', :snowplow do
|
||||
create_namespace
|
||||
|
||||
expect_snowplow_event(
|
||||
category: 'Growth::Conversion::Experiment::OnboardingIssues',
|
||||
action: 'created_namespace',
|
||||
label: anything,
|
||||
property: 'experimental_group'
|
||||
)
|
||||
|
||||
create_namespace
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe JwksController do
|
||||
describe 'GET #index' do
|
||||
let(:ci_jwt_signing_key) { OpenSSL::PKey::RSA.generate(1024) }
|
||||
let(:ci_jwk) { ci_jwt_signing_key.to_jwk }
|
||||
let(:oidc_jwk) { OpenSSL::PKey::RSA.new(Rails.application.secrets.openid_connect_signing_key).to_jwk }
|
||||
|
||||
before do
|
||||
stub_application_setting(ci_jwt_signing_key: ci_jwt_signing_key.to_s)
|
||||
end
|
||||
|
||||
it 'returns signing keys used to sign CI_JOB_JWT' do
|
||||
get :index
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
||||
ids = json_response['keys'].map { |jwk| jwk['kid'] }
|
||||
expect(ids).to contain_exactly(ci_jwk['kid'], oidc_jwk['kid'])
|
||||
end
|
||||
|
||||
it 'does not leak private key data' do
|
||||
get :index
|
||||
|
||||
aggregate_failures do
|
||||
json_response['keys'].each do |jwk|
|
||||
expect(jwk.keys).to contain_exactly('kty', 'kid', 'e', 'n', 'use', 'alg')
|
||||
expect(jwk['use']).to eq('sig')
|
||||
expect(jwk['alg']).to eq('RS256')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -62,4 +62,11 @@ RSpec.describe 'seed production settings' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'CI JWT signing key' do
|
||||
it 'writes valid RSA key to the database' do
|
||||
expect { load(settings_file) }.to change { settings.reload.ci_jwt_signing_key }.from(nil)
|
||||
expect { OpenSSL::PKey::RSA.new(settings.ci_jwt_signing_key) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,7 +34,7 @@ RSpec.describe 'Merge requests > User merges immediately', :js do
|
|||
find('.dropdown-toggle').click
|
||||
|
||||
Sidekiq::Testing.fake! do
|
||||
click_link 'Merge immediately'
|
||||
click_button 'Merge immediately'
|
||||
|
||||
expect(find('.accept-merge-request.btn-info')).to have_content('Merge in progress')
|
||||
|
||||
|
|
|
@ -93,19 +93,6 @@ RSpec.describe 'Merge request > User merges when pipeline succeeds', :js do
|
|||
it_behaves_like 'Merge when pipeline succeeds activator'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'enabling Merge when pipeline succeeds via dropdown' do
|
||||
it 'activates the Merge when pipeline succeeds feature' do
|
||||
wait_for_requests
|
||||
|
||||
find('.js-merge-moment').click
|
||||
click_link 'Merge when pipeline succeeds'
|
||||
|
||||
expect(page).to have_content "Set by #{user.name} to be merged automatically when the pipeline succeeds"
|
||||
expect(page).to have_content "The source branch will not be deleted"
|
||||
expect(page).to have_link "Cancel automatic merge"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when merge when pipeline succeeds is enabled' do
|
||||
|
|
|
@ -44,4 +44,14 @@ RSpec.describe 'Merge Requests > User filters by target branch', :js do
|
|||
expect(page).not_to have_content mr2.title
|
||||
end
|
||||
end
|
||||
|
||||
context 'filtering by target-branch:!=master' do
|
||||
it 'applies the filter' do
|
||||
input_filtered_search('target-branch:!=master')
|
||||
|
||||
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
|
||||
expect(page).not_to have_content mr1.title
|
||||
expect(page).to have_content mr2.title
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
|
||||
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
|
||||
import { GlFormInput, GlFormTextarea } from '@gitlab/ui';
|
||||
|
||||
import EditMetaControls from '~/static_site_editor/components/edit_meta_controls.vue';
|
||||
|
@ -8,8 +7,6 @@ import EditMetaControls from '~/static_site_editor/components/edit_meta_controls
|
|||
import { mergeRequestMeta } from '../mock_data';
|
||||
|
||||
describe('~/static_site_editor/components/edit_meta_controls.vue', () => {
|
||||
useLocalStorageSpy();
|
||||
|
||||
let wrapper;
|
||||
let mockSelect;
|
||||
let mockGlFormInputTitleInstance;
|
||||
|
@ -86,14 +83,5 @@ describe('~/static_site_editor/components/edit_meta_controls.vue', () => {
|
|||
|
||||
expect(wrapper.emitted('updateSettings')[0][0]).toMatchObject(newSettings);
|
||||
});
|
||||
|
||||
it('should remember the input changes', () => {
|
||||
findGlFormInputTitle().vm.$emit('input', newTitle);
|
||||
findGlFormTextAreaDescription().vm.$emit('input', newDescription);
|
||||
|
||||
const newSettings = { title: newTitle, description: newDescription };
|
||||
|
||||
expect(localStorage.setItem).toHaveBeenCalledWith(storageKey, JSON.stringify(newSettings));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
|
||||
import { GlModal } from '@gitlab/ui';
|
||||
|
||||
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
|
||||
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
|
||||
import EditMetaModal from '~/static_site_editor/components/edit_meta_modal.vue';
|
||||
import EditMetaControls from '~/static_site_editor/components/edit_meta_controls.vue';
|
||||
|
||||
import { MR_META_LOCAL_STORAGE_KEY } from '~/static_site_editor/constants';
|
||||
import { sourcePath, mergeRequestMeta } from '../mock_data';
|
||||
|
||||
describe('~/static_site_editor/components/edit_meta_modal.vue', () => {
|
||||
useLocalStorageSpy();
|
||||
|
||||
let wrapper;
|
||||
let resetCachedEditable;
|
||||
let mockEditMetaControlsInstance;
|
||||
|
@ -30,6 +32,11 @@ describe('~/static_site_editor/components/edit_meta_modal.vue', () => {
|
|||
|
||||
const findGlModal = () => wrapper.find(GlModal);
|
||||
const findEditMetaControls = () => wrapper.find(EditMetaControls);
|
||||
const findLocalStorageSync = () => wrapper.find(LocalStorageSync);
|
||||
|
||||
beforeEach(() => {
|
||||
localStorage.setItem(MR_META_LOCAL_STORAGE_KEY);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
buildWrapper();
|
||||
|
@ -43,6 +50,16 @@ describe('~/static_site_editor/components/edit_meta_modal.vue', () => {
|
|||
wrapper = null;
|
||||
});
|
||||
|
||||
it('initializes initial merge request meta with local storage data', async () => {
|
||||
const localStorageMeta = { title: 'stored title', description: 'stored description' };
|
||||
|
||||
findLocalStorageSync().vm.$emit('input', localStorageMeta);
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(findEditMetaControls().props()).toEqual(localStorageMeta);
|
||||
});
|
||||
|
||||
it('renders the modal', () => {
|
||||
expect(findGlModal().exists()).toBe(true);
|
||||
});
|
||||
|
@ -63,18 +80,32 @@ describe('~/static_site_editor/components/edit_meta_modal.vue', () => {
|
|||
expect(findEditMetaControls().props('description')).toBe(description);
|
||||
});
|
||||
|
||||
it('emits the primary event with mergeRequestMeta', () => {
|
||||
findGlModal().vm.$emit('primary', mergeRequestMeta);
|
||||
expect(wrapper.emitted('primary')).toEqual([[mergeRequestMeta]]);
|
||||
});
|
||||
describe('when save button is clicked', () => {
|
||||
beforeEach(() => {
|
||||
findGlModal().vm.$emit('primary', mergeRequestMeta);
|
||||
});
|
||||
|
||||
it('calls resetCachedEditable on EditMetaControls when primary emits', () => {
|
||||
findGlModal().vm.$emit('primary', mergeRequestMeta);
|
||||
expect(mockEditMetaControlsInstance.resetCachedEditable).toHaveBeenCalled();
|
||||
it('removes merge request meta from local storage', () => {
|
||||
expect(findLocalStorageSync().props().clear).toBe(true);
|
||||
});
|
||||
|
||||
it('emits the primary event with mergeRequestMeta', () => {
|
||||
expect(wrapper.emitted('primary')).toEqual([[mergeRequestMeta]]);
|
||||
});
|
||||
});
|
||||
|
||||
it('emits the hide event', () => {
|
||||
findGlModal().vm.$emit('hide');
|
||||
expect(wrapper.emitted('hide')).toEqual([[]]);
|
||||
});
|
||||
|
||||
it('stores merge request meta changes in local storage when changes happen', async () => {
|
||||
const newMeta = { title: 'new title', description: 'new description' };
|
||||
|
||||
findEditMetaControls().vm.$emit('updateSettings', newMeta);
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(findLocalStorageSync().props('value')).toEqual(newMeta);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -239,4 +239,30 @@ describe('Local Storage Sync', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('clears localStorage when clear property is true', async () => {
|
||||
const storageKey = 'key';
|
||||
const value = 'initial';
|
||||
|
||||
createComponent({
|
||||
props: {
|
||||
storageKey,
|
||||
},
|
||||
});
|
||||
wrapper.setProps({
|
||||
value,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(localStorage.getItem(storageKey)).toBe(value);
|
||||
|
||||
wrapper.setProps({
|
||||
clear: true,
|
||||
});
|
||||
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(localStorage.getItem(storageKey)).toBe(null);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@ RSpec.describe GitlabSchema.types['Issue'] do
|
|||
it 'has specific fields' do
|
||||
fields = %i[id iid title description state reference author assignees participants labels milestone due_date
|
||||
confidential discussion_locked upvotes downvotes user_notes_count web_path web_url relative_position
|
||||
subscribed time_estimate total_time_spent closed_at created_at updated_at task_completion_status
|
||||
subscribed time_estimate total_time_spent human_time_estimate human_total_time_spent closed_at created_at updated_at task_completion_status
|
||||
designs design_collection alert_management_alert severity current_user_todos]
|
||||
|
||||
fields.each do |field_name|
|
||||
|
|
|
@ -73,7 +73,7 @@ RSpec.describe SearchHelper do
|
|||
expect(result.keys).to match_array(%i[category id label url avatar_url])
|
||||
end
|
||||
|
||||
it 'includes the users recently viewed issues' do
|
||||
it 'includes the users recently viewed issues', :aggregate_failures do
|
||||
recent_issues = instance_double(::Gitlab::Search::RecentIssues)
|
||||
expect(::Gitlab::Search::RecentIssues).to receive(:new).with(user: user).and_return(recent_issues)
|
||||
project1 = create(:project, :with_avatar, namespace: user.namespace)
|
||||
|
@ -104,7 +104,7 @@ RSpec.describe SearchHelper do
|
|||
})
|
||||
end
|
||||
|
||||
it 'includes the users recently viewed merge requests' do
|
||||
it 'includes the users recently viewed merge requests', :aggregate_failures do
|
||||
recent_merge_requests = instance_double(::Gitlab::Search::RecentMergeRequests)
|
||||
expect(::Gitlab::Search::RecentMergeRequests).to receive(:new).with(user: user).and_return(recent_merge_requests)
|
||||
project1 = create(:project, :with_avatar, namespace: user.namespace)
|
||||
|
@ -145,10 +145,40 @@ RSpec.describe SearchHelper do
|
|||
@project = create(:project, :repository)
|
||||
end
|
||||
|
||||
it "includes project-specific sections" do
|
||||
it "includes project-specific sections", :aggregate_failures do
|
||||
expect(search_autocomplete_opts("Files").size).to eq(1)
|
||||
expect(search_autocomplete_opts("Commits").size).to eq(1)
|
||||
end
|
||||
|
||||
context 'when user does not have access to project' do
|
||||
it 'does not include issues by iid' do
|
||||
issue = create(:issue, project: @project)
|
||||
results = search_autocomplete_opts("\##{issue.iid}")
|
||||
|
||||
expect(results.count).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has project access' do
|
||||
before do
|
||||
@project = create(:project, :repository, namespace: user.namespace)
|
||||
end
|
||||
|
||||
it 'includes issues by iid', :aggregate_failures do
|
||||
issue = create(:issue, project: @project, title: 'test title')
|
||||
results = search_autocomplete_opts("\##{issue.iid}")
|
||||
|
||||
expect(results.count).to eq(1)
|
||||
|
||||
expect(results.first).to include({
|
||||
category: 'In this project',
|
||||
id: issue.id,
|
||||
label: 'test title (#1)',
|
||||
url: ::Gitlab::Routing.url_helpers.project_issue_path(issue.project, issue),
|
||||
avatar_url: '' # project has no avatar
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -46,17 +46,41 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Product::Matrix do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with one_dimensional_matrix feature flag enabled' do
|
||||
before do
|
||||
stub_feature_flags(one_dimensional_matrix: true)
|
||||
matrix.compose!
|
||||
context 'when entry config has only one variable with multiple values' do
|
||||
let(:config) do
|
||||
[
|
||||
{
|
||||
'VAR_1' => %w[build test]
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
context 'when entry config has only one variable with multiple values' do
|
||||
describe '#valid?' do
|
||||
it { is_expected.to be_valid }
|
||||
end
|
||||
|
||||
describe '#errors' do
|
||||
it 'returns no errors' do
|
||||
expect(matrix.errors)
|
||||
.to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe '#value' do
|
||||
before do
|
||||
matrix.compose!
|
||||
end
|
||||
|
||||
it 'returns the value without raising an error' do
|
||||
expect(matrix.value).to eq([{ 'VAR_1' => %w[build test] }])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when entry config has only one variable with one value' do
|
||||
let(:config) do
|
||||
[
|
||||
{
|
||||
'VAR_1' => %w[build test]
|
||||
'VAR_1' => %w[test]
|
||||
}
|
||||
]
|
||||
end
|
||||
|
@ -78,107 +102,7 @@ RSpec.describe ::Gitlab::Ci::Config::Entry::Product::Matrix do
|
|||
end
|
||||
|
||||
it 'returns the value without raising an error' do
|
||||
expect(matrix.value).to eq([{ 'VAR_1' => %w[build test] }])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when entry config has only one variable with one value' do
|
||||
let(:config) do
|
||||
[
|
||||
{
|
||||
'VAR_1' => %w[test]
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
describe '#valid?' do
|
||||
it { is_expected.to be_valid }
|
||||
end
|
||||
|
||||
describe '#errors' do
|
||||
it 'returns no errors' do
|
||||
expect(matrix.errors)
|
||||
.to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe '#value' do
|
||||
before do
|
||||
matrix.compose!
|
||||
end
|
||||
|
||||
it 'returns the value without raising an error' do
|
||||
expect(matrix.value).to eq([{ 'VAR_1' => %w[test] }])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with one_dimensional_matrix feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(one_dimensional_matrix: false)
|
||||
matrix.compose!
|
||||
end
|
||||
|
||||
context 'when entry config has only one variable with multiple values' do
|
||||
let(:config) do
|
||||
[
|
||||
{
|
||||
'VAR_1' => %w[build test]
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
describe '#valid?' do
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
|
||||
describe '#errors' do
|
||||
it 'returns error about too many jobs' do
|
||||
expect(matrix.errors)
|
||||
.to include('variables config requires at least 2 items')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#value' do
|
||||
before do
|
||||
matrix.compose!
|
||||
end
|
||||
|
||||
it 'returns the value without raising an error' do
|
||||
expect(matrix.value).to eq([{ 'VAR_1' => %w[build test] }])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when entry config has only one variable with one value' do
|
||||
let(:config) do
|
||||
[
|
||||
{
|
||||
'VAR_1' => %w[test]
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
describe '#valid?' do
|
||||
it { is_expected.not_to be_valid }
|
||||
end
|
||||
|
||||
describe '#errors' do
|
||||
it 'returns no errors' do
|
||||
expect(matrix.errors)
|
||||
.to include('variables config requires at least 2 items')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#value' do
|
||||
before do
|
||||
matrix.compose!
|
||||
end
|
||||
|
||||
it 'returns the value without raising an error' do
|
||||
expect(matrix.value).to eq([{ 'VAR_1' => %w[test] }])
|
||||
end
|
||||
expect(matrix.value).to eq([{ 'VAR_1' => %w[test] }])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# After Feature one_dimensional_matrix is removed, this can be changed back to fast_spec_helper
|
||||
require 'spec_helper'
|
||||
require 'fast_spec_helper'
|
||||
require_dependency 'active_model'
|
||||
|
||||
RSpec.describe Gitlab::Ci::Config::Entry::Product::Variables do
|
||||
|
@ -46,70 +45,18 @@ RSpec.describe Gitlab::Ci::Config::Entry::Product::Variables do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with one_dimensional_matrix feature flag enabled' do
|
||||
context 'with only one variable' do
|
||||
before do
|
||||
stub_feature_flags(one_dimensional_matrix: true)
|
||||
end
|
||||
let(:config) { { VAR: 'test' } }
|
||||
context 'with only one variable' do
|
||||
let(:config) { { VAR: 'test' } }
|
||||
|
||||
describe '#valid?' do
|
||||
it 'is valid' do
|
||||
expect(entry).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
describe '#errors' do
|
||||
it 'does not append errors' do
|
||||
expect(entry.errors).to be_empty
|
||||
end
|
||||
describe '#valid?' do
|
||||
it 'is valid' do
|
||||
expect(entry).to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with one_dimensional_matrix feature flag disabled' do
|
||||
context 'when entry value is not correct' do
|
||||
before do
|
||||
stub_feature_flags(one_dimensional_matrix: false)
|
||||
end
|
||||
shared_examples 'invalid variables' do |message|
|
||||
describe '#errors' do
|
||||
it 'saves errors' do
|
||||
expect(entry.errors).to include(message)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#valid?' do
|
||||
it 'is not valid' do
|
||||
expect(entry).not_to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with array' do
|
||||
let(:config) { [:VAR, 'test'] }
|
||||
|
||||
it_behaves_like 'invalid variables', /should be a hash of key value pairs/
|
||||
end
|
||||
|
||||
context 'with empty array' do
|
||||
let(:config) { { VAR: 'test', VAR2: [] } }
|
||||
|
||||
it_behaves_like 'invalid variables', /should be a hash of key value pairs/
|
||||
end
|
||||
|
||||
context 'with nested array' do
|
||||
let(:config) { { VAR: 'test', VAR2: [1, [2]] } }
|
||||
|
||||
it_behaves_like 'invalid variables', /should be a hash of key value pairs/
|
||||
end
|
||||
|
||||
context 'with one_dimensional_matrix feature flag disabled' do
|
||||
context 'with only one variable' do
|
||||
let(:config) { { VAR: 'test' } }
|
||||
|
||||
it_behaves_like 'invalid variables', /variables config requires at least 2 items/
|
||||
end
|
||||
describe '#errors' do
|
||||
it 'does not append errors' do
|
||||
expect(entry.errors).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -93,32 +93,65 @@ RSpec.describe Gitlab::Ci::Jwt do
|
|||
end
|
||||
|
||||
describe '.for_build' do
|
||||
let(:rsa_key) { OpenSSL::PKey::RSA.new(Rails.application.secrets.openid_connect_signing_key) }
|
||||
shared_examples 'generating JWT for build' do
|
||||
context 'when signing key is present' do
|
||||
let(:rsa_key) { OpenSSL::PKey::RSA.generate(1024) }
|
||||
let(:rsa_key_data) { rsa_key.to_s }
|
||||
|
||||
it 'generates JWT with key id' do
|
||||
_payload, headers = JWT.decode(jwt, rsa_key.public_key, true, { algorithm: 'RS256' })
|
||||
|
||||
expect(headers['kid']).to eq(rsa_key.public_key.to_jwk['kid'])
|
||||
end
|
||||
|
||||
it 'generates JWT for the given job with ttl equal to build timeout' do
|
||||
expect(build).to receive(:metadata_timeout).and_return(3_600)
|
||||
|
||||
payload, _headers = JWT.decode(jwt, rsa_key.public_key, true, { algorithm: 'RS256' })
|
||||
ttl = payload["exp"] - payload["iat"]
|
||||
|
||||
expect(ttl).to eq(3_600)
|
||||
end
|
||||
|
||||
it 'generates JWT for the given job with default ttl if build timeout is not set' do
|
||||
expect(build).to receive(:metadata_timeout).and_return(nil)
|
||||
|
||||
payload, _headers = JWT.decode(jwt, rsa_key.public_key, true, { algorithm: 'RS256' })
|
||||
ttl = payload["exp"] - payload["iat"]
|
||||
|
||||
expect(ttl).to eq(5.minutes.to_i)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when signing key is missing' do
|
||||
let(:rsa_key_data) { nil }
|
||||
|
||||
it 'raises NoSigningKeyError' do
|
||||
expect { jwt }.to raise_error described_class::NoSigningKeyError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
subject(:jwt) { described_class.for_build(build) }
|
||||
|
||||
it 'generates JWT with key id' do
|
||||
_payload, headers = JWT.decode(jwt, rsa_key.public_key, true, { algorithm: 'RS256' })
|
||||
context 'when ci_jwt_signing_key feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_jwt_signing_key: false)
|
||||
|
||||
expect(headers['kid']).to eq(rsa_key.public_key.to_jwk['kid'])
|
||||
allow(Rails.application.secrets).to receive(:openid_connect_signing_key).and_return(rsa_key_data)
|
||||
end
|
||||
|
||||
it_behaves_like 'generating JWT for build'
|
||||
end
|
||||
|
||||
it 'generates JWT for the given job with ttl equal to build timeout' do
|
||||
expect(build).to receive(:metadata_timeout).and_return(3_600)
|
||||
context 'when ci_jwt_signing_key feature flag is enabled' do
|
||||
before do
|
||||
stub_feature_flags(ci_jwt_signing_key: true)
|
||||
|
||||
payload, _headers = JWT.decode(jwt, rsa_key.public_key, true, { algorithm: 'RS256' })
|
||||
ttl = payload["exp"] - payload["iat"]
|
||||
stub_application_setting(ci_jwt_signing_key: rsa_key_data)
|
||||
end
|
||||
|
||||
expect(ttl).to eq(3_600)
|
||||
end
|
||||
|
||||
it 'generates JWT for the given job with default ttl if build timeout is not set' do
|
||||
expect(build).to receive(:metadata_timeout).and_return(nil)
|
||||
|
||||
payload, _headers = JWT.decode(jwt, rsa_key.public_key, true, { algorithm: 'RS256' })
|
||||
ttl = payload["exp"] - payload["iat"]
|
||||
|
||||
expect(ttl).to eq(5.minutes.to_i)
|
||||
it_behaves_like 'generating JWT for build'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -77,6 +77,12 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
|
|||
stub_application_setting(usage_ping_enabled: true)
|
||||
end
|
||||
|
||||
it 'tracks event when using symbol' do
|
||||
expect(Gitlab::Redis::HLL).to receive(:add)
|
||||
|
||||
described_class.track_event(entity1, :g_analytics_contribution)
|
||||
end
|
||||
|
||||
it "raise error if metrics don't have same aggregation" do
|
||||
expect { described_class.track_event(entity1, different_aggregation, Date.current) } .to raise_error(Gitlab::UsageDataCounters::HLLRedisCounter::UnknownAggregation)
|
||||
end
|
||||
|
@ -201,6 +207,10 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
|
|||
it { expect(described_class.unique_events(event_names: weekly_event, start_date: 4.weeks.ago, end_date: 3.weeks.ago)).to eq(1) }
|
||||
end
|
||||
|
||||
context 'when using symbol as parameter' do
|
||||
it { expect(described_class.unique_events(event_names: weekly_event.to_sym, start_date: 4.weeks.ago, end_date: 3.weeks.ago)).to eq(1) }
|
||||
end
|
||||
|
||||
context 'when using daily aggregation' do
|
||||
it { expect(described_class.unique_events(event_names: daily_event, start_date: 7.days.ago, end_date: Date.current)).to eq(2) }
|
||||
it { expect(described_class.unique_events(event_names: daily_event, start_date: 28.days.ago, end_date: Date.current)).to eq(3) }
|
||||
|
|
|
@ -619,6 +619,7 @@ RSpec.describe Notify do
|
|||
let(:mailer) do
|
||||
mailer = described_class.new
|
||||
mailer.instance_variable_set(:@note, mail_thread_note)
|
||||
mailer.instance_variable_set(:@target_url, "https://some.link")
|
||||
mailer
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
require Rails.root.join('db', 'migrate', '20201008013434_generate_ci_jwt_signing_key.rb')
|
||||
|
||||
RSpec.describe GenerateCiJwtSigningKey do
|
||||
let(:application_settings) do
|
||||
Class.new(ActiveRecord::Base) do
|
||||
self.table_name = 'application_settings'
|
||||
|
||||
attr_encrypted :ci_jwt_signing_key, {
|
||||
mode: :per_attribute_iv,
|
||||
key: Rails.application.secrets.db_key_base[0..31],
|
||||
algorithm: 'aes-256-gcm',
|
||||
encode: true
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
it 'generates JWT signing key' do
|
||||
application_settings.create!
|
||||
|
||||
reversible_migration do |migration|
|
||||
migration.before -> {
|
||||
settings = application_settings.first
|
||||
|
||||
expect(settings.ci_jwt_signing_key).to be_nil
|
||||
expect(settings.encrypted_ci_jwt_signing_key).to be_nil
|
||||
expect(settings.encrypted_ci_jwt_signing_key_iv).to be_nil
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
settings = application_settings.first
|
||||
|
||||
expect(settings.encrypted_ci_jwt_signing_key).to be_present
|
||||
expect(settings.encrypted_ci_jwt_signing_key_iv).to be_present
|
||||
expect { OpenSSL::PKey::RSA.new(settings.ci_jwt_signing_key) }.not_to raise_error
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -647,6 +647,23 @@ RSpec.describe ApplicationSetting do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#ci_jwt_signing_key' do
|
||||
it { is_expected.not_to allow_value('').for(:ci_jwt_signing_key) }
|
||||
it { is_expected.not_to allow_value('invalid RSA key').for(:ci_jwt_signing_key) }
|
||||
it { is_expected.to allow_value(nil).for(:ci_jwt_signing_key) }
|
||||
it { is_expected.to allow_value(OpenSSL::PKey::RSA.new(1024).to_pem).for(:ci_jwt_signing_key) }
|
||||
|
||||
it 'is encrypted' do
|
||||
subject.ci_jwt_signing_key = OpenSSL::PKey::RSA.new(1024).to_pem
|
||||
|
||||
aggregate_failures do
|
||||
expect(subject.encrypted_ci_jwt_signing_key).to be_present
|
||||
expect(subject.encrypted_ci_jwt_signing_key_iv).to be_present
|
||||
expect(subject.encrypted_ci_jwt_signing_key).not_to eq(subject.ci_jwt_signing_key)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'static objects external storage' do
|
||||
|
|
|
@ -2464,7 +2464,7 @@ RSpec.describe Ci::Build do
|
|||
end
|
||||
|
||||
before do
|
||||
allow(Gitlab::Ci::Jwt).to receive(:for_build).with(build).and_return('ci.job.jwt')
|
||||
allow(Gitlab::Ci::Jwt).to receive(:for_build).and_return('ci.job.jwt')
|
||||
build.set_token('my-token')
|
||||
build.yaml_variables = []
|
||||
end
|
||||
|
@ -2482,12 +2482,17 @@ RSpec.describe Ci::Build do
|
|||
end
|
||||
|
||||
context 'when CI_JOB_JWT generation fails' do
|
||||
it 'CI_JOB_JWT is not included' do
|
||||
expect(Gitlab::Ci::Jwt).to receive(:for_build).and_raise(OpenSSL::PKey::RSAError, 'Neither PUB key nor PRIV key: not enough data')
|
||||
expect(Gitlab::ErrorTracking).to receive(:track_exception)
|
||||
[
|
||||
OpenSSL::PKey::RSAError,
|
||||
Gitlab::Ci::Jwt::NoSigningKeyError
|
||||
].each do |reason_to_fail|
|
||||
it 'CI_JOB_JWT is not included' do
|
||||
expect(Gitlab::Ci::Jwt).to receive(:for_build).and_raise(reason_to_fail)
|
||||
expect(Gitlab::ErrorTracking).to receive(:track_exception)
|
||||
|
||||
expect { subject }.not_to raise_error
|
||||
expect(subject.pluck(:key)).not_to include('CI_JOB_JWT')
|
||||
expect { subject }.not_to raise_error
|
||||
expect(subject.pluck(:key)).not_to include('CI_JOB_JWT')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -493,104 +493,49 @@ RSpec.describe CommitStatus do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with the one_dimensional_matrix feature flag disabled' do
|
||||
describe '#group_name' do
|
||||
before do
|
||||
stub_feature_flags(one_dimensional_matrix: false)
|
||||
end
|
||||
describe '#group_name' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
let(:commit_status) do
|
||||
build(:commit_status, pipeline: pipeline, stage: 'test')
|
||||
end
|
||||
|
||||
subject { commit_status.group_name }
|
||||
|
||||
tests = {
|
||||
'rspec:windows' => 'rspec:windows',
|
||||
'rspec:windows 0' => 'rspec:windows 0',
|
||||
'rspec:windows 0 test' => 'rspec:windows 0 test',
|
||||
'rspec:windows 0 1' => 'rspec:windows',
|
||||
'rspec:windows 0 1 name' => 'rspec:windows name',
|
||||
'rspec:windows 0/1' => 'rspec:windows',
|
||||
'rspec:windows 0/1 name' => 'rspec:windows name',
|
||||
'rspec:windows 0:1' => 'rspec:windows',
|
||||
'rspec:windows 0:1 name' => 'rspec:windows name',
|
||||
'rspec:windows 10000 20000' => 'rspec:windows',
|
||||
'rspec:windows 0 : / 1' => 'rspec:windows',
|
||||
'rspec:windows 0 : / 1 name' => 'rspec:windows name',
|
||||
'0 1 name ruby' => 'name ruby',
|
||||
'0 :/ 1 name ruby' => 'name ruby',
|
||||
'rspec: [aws]' => 'rspec: [aws]',
|
||||
'rspec: [aws] 0/1' => 'rspec: [aws]',
|
||||
'rspec: [aws, max memory]' => 'rspec',
|
||||
'rspec:linux: [aws, max memory, data]' => 'rspec:linux',
|
||||
'rspec: [inception: [something, other thing], value]' => 'rspec',
|
||||
'rspec:windows 0/1: [name, other]' => 'rspec:windows',
|
||||
'rspec:windows: [name, other] 0/1' => 'rspec:windows',
|
||||
'rspec:windows: [name, 0/1] 0/1' => 'rspec:windows',
|
||||
'rspec:windows: [0/1, name]' => 'rspec:windows',
|
||||
'rspec:windows: [, ]' => 'rspec:windows',
|
||||
'rspec:windows: [name]' => 'rspec:windows: [name]',
|
||||
'rspec:windows: [name,other]' => 'rspec:windows: [name,other]'
|
||||
}
|
||||
|
||||
tests.each do |name, group_name|
|
||||
it "'#{name}' puts in '#{group_name}'" do
|
||||
commit_status.name = name
|
||||
|
||||
is_expected.to eq(group_name)
|
||||
end
|
||||
end
|
||||
let(:commit_status) do
|
||||
build(:commit_status, pipeline: pipeline, stage: 'test')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with one_dimensional_matrix feature flag enabled' do
|
||||
describe '#group_name' do
|
||||
before do
|
||||
stub_feature_flags(one_dimensional_matrix: true)
|
||||
end
|
||||
subject { commit_status.group_name }
|
||||
|
||||
let(:commit_status) do
|
||||
build(:commit_status, pipeline: pipeline, stage: 'test')
|
||||
end
|
||||
where(:name, :group_name) do
|
||||
'rspec:windows' | 'rspec:windows'
|
||||
'rspec:windows 0' | 'rspec:windows 0'
|
||||
'rspec:windows 0 test' | 'rspec:windows 0 test'
|
||||
'rspec:windows 0 1' | 'rspec:windows'
|
||||
'rspec:windows 0 1 name' | 'rspec:windows name'
|
||||
'rspec:windows 0/1' | 'rspec:windows'
|
||||
'rspec:windows 0/1 name' | 'rspec:windows name'
|
||||
'rspec:windows 0:1' | 'rspec:windows'
|
||||
'rspec:windows 0:1 name' | 'rspec:windows name'
|
||||
'rspec:windows 10000 20000' | 'rspec:windows'
|
||||
'rspec:windows 0 : / 1' | 'rspec:windows'
|
||||
'rspec:windows 0 : / 1 name' | 'rspec:windows name'
|
||||
'0 1 name ruby' | 'name ruby'
|
||||
'0 :/ 1 name ruby' | 'name ruby'
|
||||
'rspec: [aws]' | 'rspec'
|
||||
'rspec: [aws] 0/1' | 'rspec'
|
||||
'rspec: [aws, max memory]' | 'rspec'
|
||||
'rspec:linux: [aws, max memory, data]' | 'rspec:linux'
|
||||
'rspec: [inception: [something, other thing], value]' | 'rspec'
|
||||
'rspec:windows 0/1: [name, other]' | 'rspec:windows'
|
||||
'rspec:windows: [name, other] 0/1' | 'rspec:windows'
|
||||
'rspec:windows: [name, 0/1] 0/1' | 'rspec:windows'
|
||||
'rspec:windows: [0/1, name]' | 'rspec:windows'
|
||||
'rspec:windows: [, ]' | 'rspec:windows'
|
||||
'rspec:windows: [name]' | 'rspec:windows'
|
||||
'rspec:windows: [name,other]' | 'rspec:windows'
|
||||
end
|
||||
|
||||
subject { commit_status.group_name }
|
||||
with_them do
|
||||
it "#{params[:name]} puts in #{params[:group_name]}" do
|
||||
commit_status.name = name
|
||||
|
||||
tests = {
|
||||
'rspec:windows' => 'rspec:windows',
|
||||
'rspec:windows 0' => 'rspec:windows 0',
|
||||
'rspec:windows 0 test' => 'rspec:windows 0 test',
|
||||
'rspec:windows 0 1' => 'rspec:windows',
|
||||
'rspec:windows 0 1 name' => 'rspec:windows name',
|
||||
'rspec:windows 0/1' => 'rspec:windows',
|
||||
'rspec:windows 0/1 name' => 'rspec:windows name',
|
||||
'rspec:windows 0:1' => 'rspec:windows',
|
||||
'rspec:windows 0:1 name' => 'rspec:windows name',
|
||||
'rspec:windows 10000 20000' => 'rspec:windows',
|
||||
'rspec:windows 0 : / 1' => 'rspec:windows',
|
||||
'rspec:windows 0 : / 1 name' => 'rspec:windows name',
|
||||
'0 1 name ruby' => 'name ruby',
|
||||
'0 :/ 1 name ruby' => 'name ruby',
|
||||
'rspec: [aws]' => 'rspec',
|
||||
'rspec: [aws] 0/1' => 'rspec',
|
||||
'rspec: [aws, max memory]' => 'rspec',
|
||||
'rspec:linux: [aws, max memory, data]' => 'rspec:linux',
|
||||
'rspec: [inception: [something, other thing], value]' => 'rspec',
|
||||
'rspec:windows 0/1: [name, other]' => 'rspec:windows',
|
||||
'rspec:windows: [name, other] 0/1' => 'rspec:windows',
|
||||
'rspec:windows: [name, 0/1] 0/1' => 'rspec:windows',
|
||||
'rspec:windows: [0/1, name]' => 'rspec:windows',
|
||||
'rspec:windows: [, ]' => 'rspec:windows',
|
||||
'rspec:windows: [name]' => 'rspec:windows',
|
||||
'rspec:windows: [name,other]' => 'rspec:windows'
|
||||
}
|
||||
|
||||
tests.each do |name, group_name|
|
||||
it "'#{name}' puts in '#{group_name}'" do
|
||||
commit_status.name = name
|
||||
|
||||
is_expected.to eq(group_name)
|
||||
end
|
||||
is_expected.to eq(group_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,9 +4,10 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe GroupMember do
|
||||
context 'scopes' do
|
||||
let_it_be(:user_1) { create(:user) }
|
||||
let_it_be(:user_2) { create(:user) }
|
||||
|
||||
it 'counts users by group ID' do
|
||||
user_1 = create(:user)
|
||||
user_2 = create(:user)
|
||||
group_1 = create(:group)
|
||||
group_2 = create(:group)
|
||||
|
||||
|
@ -25,6 +26,15 @@ RSpec.describe GroupMember do
|
|||
expect(described_class.of_ldap_type).to eq([group_member])
|
||||
end
|
||||
end
|
||||
|
||||
describe '.with_user' do
|
||||
it 'returns requested user' do
|
||||
group_member = create(:group_member, user: user_2)
|
||||
create(:group_member, user: user_1)
|
||||
|
||||
expect(described_class.with_user(user_2)).to eq([group_member])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.access_level_roles' do
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
# oauth_discovery_keys GET /oauth/discovery/keys(.:format) doorkeeper/openid_connect/discovery#keys
|
||||
# jwks GET /-/jwks(.:format) doorkeeper/openid_connect/discovery#keys
|
||||
# oauth_discovery_provider GET /.well-known/openid-configuration(.:format) doorkeeper/openid_connect/discovery#provider
|
||||
# oauth_discovery_webfinger GET /.well-known/webfinger(.:format) doorkeeper/openid_connect/discovery#webfinger
|
||||
RSpec.describe Doorkeeper::OpenidConnect::DiscoveryController, 'routing' do
|
||||
|
@ -18,10 +17,6 @@ RSpec.describe Doorkeeper::OpenidConnect::DiscoveryController, 'routing' do
|
|||
it "to #keys" do
|
||||
expect(get('/oauth/discovery/keys')).to route_to('doorkeeper/openid_connect/discovery#keys')
|
||||
end
|
||||
|
||||
it "/-/jwks" do
|
||||
expect(get('/-/jwks')).to route_to('doorkeeper/openid_connect/discovery#keys')
|
||||
end
|
||||
end
|
||||
|
||||
# oauth_userinfo GET /oauth/userinfo(.:format) doorkeeper/openid_connect/userinfo#show
|
||||
|
|
|
@ -369,3 +369,10 @@ RSpec.describe RunnerSetupController, 'routing' do
|
|||
expect(get("/-/runner_setup/platforms")).to route_to('runner_setup#platforms')
|
||||
end
|
||||
end
|
||||
|
||||
# jwks GET /-/jwks(.:format) jwks#index
|
||||
RSpec.describe JwksController, "routing" do
|
||||
it "to #index" do
|
||||
expect(get('/-/jwks')).to route_to('jwks#index')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -161,10 +161,10 @@ RSpec.describe Clusters::Applications::CheckInstallationProgressService, '#execu
|
|||
expect(application.status_reason).to be_nil
|
||||
end
|
||||
|
||||
it 'tracks application install' do
|
||||
expect(Gitlab::Tracking).to receive(:event).with('cluster:applications', "cluster_application_helm_installed")
|
||||
|
||||
it 'tracks application install', :snowplow do
|
||||
service.execute
|
||||
|
||||
expect_snowplow_event(category: 'cluster:applications', action: 'cluster_application_helm_installed')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -46,10 +46,15 @@ RSpec.describe Issues::ZoomLinkService do
|
|||
expect(ZoomMeeting.canonical_meeting_url(issue)).to eq(zoom_link)
|
||||
end
|
||||
|
||||
it 'tracks the add event' do
|
||||
expect(Gitlab::Tracking).to receive(:event)
|
||||
.with('IncidentManagement::ZoomIntegration', 'add_zoom_meeting', label: 'Issue ID', value: issue.id)
|
||||
it 'tracks the add event', :snowplow do
|
||||
result
|
||||
|
||||
expect_snowplow_event(
|
||||
category: 'IncidentManagement::ZoomIntegration',
|
||||
action: 'add_zoom_meeting',
|
||||
label: 'Issue ID',
|
||||
value: issue.id
|
||||
)
|
||||
end
|
||||
|
||||
it 'creates a zoom_link_added notification' do
|
||||
|
@ -180,10 +185,15 @@ RSpec.describe Issues::ZoomLinkService do
|
|||
expect(ZoomMeeting.canonical_meeting_url(issue)).to eq(nil)
|
||||
end
|
||||
|
||||
it 'tracks the remove event' do
|
||||
expect(Gitlab::Tracking).to receive(:event)
|
||||
.with('IncidentManagement::ZoomIntegration', 'remove_zoom_meeting', label: 'Issue ID', value: issue.id)
|
||||
it 'tracks the remove event', :snowplow do
|
||||
result
|
||||
|
||||
expect_snowplow_event(
|
||||
category: 'IncidentManagement::ZoomIntegration',
|
||||
action: 'remove_zoom_meeting',
|
||||
label: 'Issue ID',
|
||||
value: issue.id
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -36,10 +36,10 @@ module SnowplowHelpers
|
|||
# would not pass any arguments when using **kwargs.
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/263430
|
||||
if kwargs.present?
|
||||
expect(Gitlab::Tracking).to have_received(:event)
|
||||
expect(Gitlab::Tracking).to have_received(:event) # rubocop:disable RSpec/ExpectGitlabTracking
|
||||
.with(category, action, **kwargs).at_least(:once)
|
||||
else
|
||||
expect(Gitlab::Tracking).to have_received(:event)
|
||||
expect(Gitlab::Tracking).to have_received(:event) # rubocop:disable RSpec/ExpectGitlabTracking
|
||||
.with(category, action).at_least(:once)
|
||||
end
|
||||
end
|
||||
|
@ -56,6 +56,6 @@ module SnowplowHelpers
|
|||
# end
|
||||
# end
|
||||
def expect_no_snowplow_event
|
||||
expect(Gitlab::Tracking).not_to have_received(:event)
|
||||
expect(Gitlab::Tracking).not_to have_received(:event) # rubocop:disable RSpec/ExpectGitlabTracking
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@ RSpec.configure do |config|
|
|||
|
||||
stub_application_setting(snowplow_enabled: true)
|
||||
|
||||
allow(Gitlab::Tracking).to receive(:event).and_call_original
|
||||
allow(Gitlab::Tracking).to receive(:event).and_call_original # rubocop:disable RSpec/ExpectGitlabTracking
|
||||
end
|
||||
|
||||
config.after(:each, :snowplow) do
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe RsaKeyValidator do
|
||||
let(:validatable) do
|
||||
Class.new do
|
||||
include ActiveModel::Validations
|
||||
|
||||
attr_accessor :signing_key
|
||||
|
||||
validates :signing_key, rsa_key: true
|
||||
|
||||
def initialize(signing_key)
|
||||
@signing_key = signing_key
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
subject(:validator) { described_class.new(attributes: [:signing_key]) }
|
||||
|
||||
it 'is not valid when invalid RSA key is provided' do
|
||||
record = validatable.new('invalid RSA key')
|
||||
|
||||
validator.validate(record)
|
||||
|
||||
aggregate_failures do
|
||||
expect(record).not_to be_valid
|
||||
expect(record.errors[:signing_key]).to include('is not a valid RSA key')
|
||||
end
|
||||
end
|
||||
|
||||
it 'is valid when valid RSA key is provided' do
|
||||
record = validatable.new(OpenSSL::PKey::RSA.new(1024).to_pem)
|
||||
|
||||
validator.validate(record)
|
||||
|
||||
expect(record).to be_valid
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue