Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-02-20 12:12:47 +00:00
parent bd28d0fa02
commit c984b0faf4
61 changed files with 757 additions and 321 deletions

View File

@ -133,7 +133,6 @@ Gitlab/NamespacedClass:
- 'app/models/commit_status.rb'
- 'app/models/commit_user_mention.rb'
- 'app/models/compare.rb'
- 'app/models/concerns/uniquify.rb'
- 'app/models/container_expiration_policy.rb'
- 'app/models/container_repository.rb'
- 'app/models/context_commits_diff.rb'

View File

@ -400,7 +400,6 @@ Layout/SpaceInLambdaLiteral:
- 'spec/models/ability_spec.rb'
- 'spec/models/broadcast_message_spec.rb'
- 'spec/models/concerns/participable_spec.rb'
- 'spec/models/concerns/uniquify_spec.rb'
- 'spec/models/merge_request_spec.rb'
- 'spec/support/shared_examples/lib/cache_helpers_shared_examples.rb'
- 'spec/support/shared_examples/workers/batched_background_migration_worker_shared_examples.rb'

View File

@ -376,7 +376,6 @@ Lint/UnusedBlockArgument:
- 'spec/models/concerns/ci/partitionable/switch_spec.rb'
- 'spec/models/concerns/ci/partitionable_spec.rb'
- 'spec/models/concerns/each_batch_spec.rb'
- 'spec/models/concerns/uniquify_spec.rb'
- 'spec/models/container_repository_spec.rb'
- 'spec/models/network/graph_spec.rb'
- 'spec/models/packages/debian/file_metadatum_spec.rb'

View File

@ -5954,7 +5954,6 @@ RSpec/MissingFeatureCategory:
- 'spec/models/concerns/token_authenticatable_strategies/encryption_helper_spec.rb'
- 'spec/models/concerns/transactions_spec.rb'
- 'spec/models/concerns/triggerable_hooks_spec.rb'
- 'spec/models/concerns/uniquify_spec.rb'
- 'spec/models/concerns/usage_statistics_spec.rb'
- 'spec/models/concerns/vulnerability_finding_helpers_spec.rb'
- 'spec/models/concerns/vulnerability_finding_signature_helpers_spec.rb'

View File

@ -37,7 +37,7 @@ export default {
watch: {
filterParams: {
handler() {
if (this.list.id && !this.list.collapsed) {
if (!this.isApolloBoard && this.list.id && !this.list.collapsed) {
this.fetchItemsForList({ listId: this.list.id });
}
},
@ -46,7 +46,7 @@ export default {
},
'list.id': {
handler(id) {
if (id) {
if (!this.isApolloBoard && id) {
this.fetchItemsForList({ listId: this.list.id });
}
},

View File

@ -57,6 +57,9 @@ export default {
isProjectBoard: {
default: false,
},
isApolloBoard: {
default: false,
},
},
props: {
canAdminBoard: {
@ -213,7 +216,11 @@ export default {
} else {
try {
const board = await this.createOrUpdateBoard();
this.setBoard(board);
if (this.isApolloBoard) {
this.$emit('addBoard', board);
} else {
this.setBoard(board);
}
this.cancel();
const param = getParameterByName('group_by')
@ -278,7 +285,7 @@ export default {
@hide.prevent
>
<gl-alert
v-if="error"
v-if="!isApolloBoard && error"
class="gl-mb-3"
variant="danger"
:dismissible="true"

View File

@ -8,6 +8,7 @@ import {
GlDropdownItem,
GlModalDirective,
} from '@gitlab/ui';
import { produce } from 'immer';
import { throttle } from 'lodash';
import { mapActions, mapState } from 'vuex';
@ -89,6 +90,9 @@ export default {
parentType() {
return this.boardType;
},
boardQuery() {
return this.isGroupBoard ? groupBoardsQuery : projectBoardsQuery;
},
loading() {
return this.loadingRecentBoards || this.loadingBoards;
},
@ -155,9 +159,6 @@ export default {
name: node.name,
}));
},
boardQuery() {
return this.isGroupBoard ? groupBoardsQuery : projectBoardsQuery;
},
recentBoardsQuery() {
return this.isGroupBoard ? groupRecentBoardsQuery : projectRecentBoardsQuery;
},
@ -191,6 +192,29 @@ export default {
},
});
},
addBoard(board) {
const { defaultClient: store } = this.$apollo.provider.clients;
const sourceData = store.readQuery({
query: this.boardQuery,
variables: { fullPath: this.fullPath },
});
const newData = produce(sourceData, (draftState) => {
draftState[this.parentType].boards.edges = [
...draftState[this.parentType].boards.edges,
{ node: board },
];
});
store.writeQuery({
query: this.boardQuery,
variables: { fullPath: this.fullPath },
data: newData,
});
this.$emit('switchBoard', board.id);
},
isScrolledUp() {
const { content } = this.$refs;
@ -226,14 +250,12 @@ export default {
boardType: this.boardType,
});
},
fullBoardId(boardId) {
return fullBoardId(boardId);
},
async switchBoard(boardId, e) {
if (isMetaKey(e)) {
window.open(`${this.boardBaseUrl}/${boardId}`, '_blank');
} else if (this.isApolloBoard) {
this.$emit('switchBoard', this.fullBoardId(boardId));
this.$emit('switchBoard', fullBoardId(boardId));
updateHistory({ url: `${this.boardBaseUrl}/${boardId}` });
} else {
this.unsetActiveId();
this.fetchCurrentBoard(boardId);
@ -357,6 +379,7 @@ export default {
:weights="weights"
:current-board="boardToUse"
:current-page="currentPage"
@addBoard="addBoard"
@cancel="cancel"
/>
</span>

View File

@ -10,10 +10,7 @@ import Tracking from '~/tracking';
new UsernameValidator(); // eslint-disable-line no-new
new LengthValidator(); // eslint-disable-line no-new
new NoEmojiValidator(); // eslint-disable-line no-new
if (gon.features.trialEmailValidation) {
new EmailFormatValidator(); // eslint-disable-line no-new
}
new EmailFormatValidator(); // eslint-disable-line no-new
trackNewRegistrations();

View File

@ -70,7 +70,7 @@ Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
const $expandIcon = $('.js-sidebar-expand');
const $toggleContainer = $('.js-sidebar-toggle-container');
const isExpanded = $toggleContainer.data('is-expanded');
const tooltipLabel = isExpanded ? __('Expand sidebar') : __('Collapse sidebar');
const tooltipLabel = isExpanded ? __('Collapse sidebar') : __('Expand sidebar');
e.preventDefault();
if (isExpanded) {

View File

@ -26,6 +26,10 @@ class Admin::RunnersController < Admin::ApplicationController
render_404 unless Feature.enabled?(:create_runner_workflow, current_user)
end
def register
render_404 unless Feature.enabled?(:create_runner_workflow, current_user) && runner.registration_available?
end
def update
if Ci::Runners::UpdateRunnerService.new(@runner).execute(runner_params).success?
respond_to do |format|

View File

@ -5,7 +5,7 @@ class Admin::SpamLogsController < Admin::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def index
@spam_logs = SpamLog.includes(:user).order(id: :desc).page(params[:page])
@spam_logs = SpamLog.includes(:user).order(id: :desc).page(params[:page]).without_count
end
# rubocop: enable CodeReuse/ActiveRecord

View File

@ -25,7 +25,6 @@ class RegistrationsController < Devise::RegistrationsController
before_action only: [:new] do
push_frontend_feature_flag(:gitlab_gtm_datalayer, type: :ops)
push_frontend_feature_flag(:trial_email_validation, type: :development)
end
feature_category :authentication_and_authorization

View File

@ -14,9 +14,6 @@ module Types
JOB_COUNT_LIMIT = 1000
# Only allow ephemeral_authentication_token to be visible for a short while
RUNNER_EPHEMERAL_TOKEN_AVAILABILITY_TIME = 3.hours
alias_method :runner, :object
field :access_level, ::Types::Ci::RunnerAccessLevelEnum, null: false,
@ -39,7 +36,7 @@ module Types
field :edit_admin_url, GraphQL::Types::String, null: true,
description: 'Admin form URL of the runner. Only available for administrators.'
field :ephemeral_authentication_token, GraphQL::Types::String, null: true,
description: 'Ephemeral authentication token used for runner machine registration.',
description: 'Ephemeral authentication token used for runner machine registration. Only available for the creator of the runner for a limited time during registration.',
authorize: :read_ephemeral_token,
alpha: { milestone: '15.9' }
field :executor_name, GraphQL::Types::String, null: true,
@ -84,6 +81,8 @@ module Types
null: true,
resolver: ::Resolvers::Ci::RunnerProjectsResolver,
description: 'Find projects the runner is associated with. For project runners only.'
field :register_admin_url, GraphQL::Types::String, null: true,
description: 'URL of the temporary registration page of the runner. Only available before the runner is registered. Only available for administrators.'
field :revision, GraphQL::Types::String, null: true,
description: 'Revision of the runner.'
field :run_untagged, GraphQL::Types::Boolean, null: false,
@ -141,12 +140,14 @@ module Types
Gitlab::Routing.url_helpers.edit_admin_runner_url(runner) if can_admin_runners?
end
def ephemeral_authentication_token
return unless runner.authenticated_user_registration_type?
return unless runner.created_at > RUNNER_EPHEMERAL_TOKEN_AVAILABILITY_TIME.ago
return if runner.runner_machines.any?
def register_admin_url
return unless can_admin_runners? && runner.registration_available?
runner.token
Gitlab::Routing.url_helpers.register_admin_runner_url(runner)
end
def ephemeral_authentication_token
runner.token if runner.registration_available?
end
def project_count

View File

@ -54,6 +54,9 @@ module Ci
# The `STALE_TIMEOUT` constant defines the how far past the last contact or creation date a runner will be considered stale
STALE_TIMEOUT = 3.months
# Only allow authentication token to be visible for a short while
REGISTRATION_AVAILABILITY_TIME = 1.hour
AVAILABLE_TYPES_LEGACY = %w[specific shared].freeze
AVAILABLE_TYPES = runner_types.keys.freeze
AVAILABLE_STATUSES = %w[active paused online offline never_contacted stale].freeze # TODO: Remove in %16.0: active, paused. Relevant issue: https://gitlab.com/gitlab-org/gitlab/-/issues/344648
@ -499,6 +502,12 @@ module Ci
RunnerMachine.safe_find_or_create_by!(runner_id: id, system_xid: system_xid.to_s, &blk) # rubocop: disable Performance/ActiveRecordSubtransactionMethods
end
def registration_available?
authenticated_user_registration_type? &&
created_at > REGISTRATION_AVAILABILITY_TIME.ago &&
!runner_machines.any?
end
private
scope :with_upgrade_status, ->(upgrade_status) do

View File

@ -28,7 +28,7 @@ module HasUniqueInternalUsers
existing_user = uncached { scope.first }
return existing_user if existing_user.present?
uniquify = Uniquify.new
uniquify = Gitlab::Utils::Uniquify.new
username = uniquify.string(username) { |s| User.find_by_username(s) }

View File

@ -1,40 +0,0 @@
# frozen_string_literal: true
# Uniquify
#
# Return a version of the given 'base' string that is unique
# by appending a counter to it. Uniqueness is determined by
# repeated calls to the passed block.
#
# You can pass an initial value for the counter, if not given
# counting starts from 1.
#
# If `base` is a function/proc, we expect that calling it with a
# candidate counter returns a string to test/return.
class Uniquify
def initialize(counter = nil)
@counter = counter
end
def string(base)
@base = base
increment_counter! while yield(base_string)
base_string
end
private
def base_string
if @base.respond_to?(:call)
@base.call(@counter)
else
"#{@base}#{@counter}"
end
end
def increment_counter!
@counter ||= 0
@counter += 1
end
end

View File

@ -463,7 +463,7 @@ class Issue < ApplicationRecord
"#{to_branch_name}-#{suffix}"
end
Uniquify.new(start_counting_from).string(branch_name_generator) do |suggested_branch_name|
Gitlab::Utils::Uniquify.new(start_counting_from).string(branch_name_generator) do |suggested_branch_name|
project.repository.branch_exists?(suggested_branch_name)
end
end

View File

@ -244,7 +244,7 @@ class Namespace < ApplicationRecord
def clean_path(path, limited_to: Namespace.all)
slug = Gitlab::Slug::Path.new(path).generate
path = Namespaces::RandomizedSuffixPath.new(slug)
Uniquify.new.string(path) { |s| limited_to.find_by_path_or_name(s) }
Gitlab::Utils::Uniquify.new.string(path) { |s| limited_to.find_by_path_or_name(s) }
end
def clean_name(value)

View File

@ -85,7 +85,7 @@ module ResourceAccessTokens
end
def uniquify
Uniquify.new
Gitlab::Utils::Uniquify.new
end
def create_personal_access_token(user)

View File

@ -0,0 +1,4 @@
- add_to_breadcrumbs _('Runners'), admin_runners_path
- breadcrumb_title s_('Runners|Register')
- page_title s_('Runners|Register'), "##{@runner.id} (#{@runner.short_sha})"

View File

@ -17,6 +17,6 @@
%th= _('Primary Action')
%th
= render @spam_logs
= paginate @spam_logs, theme: 'gitlab'
= paginate_collection @spam_logs
- else
%h4= _('There are no Spam Logs')

View File

@ -1,8 +0,0 @@
---
name: trial_email_validation
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/92762
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/368999
milestone: '15.3'
type: development
group: group::acquisition
default_enabled: false

View File

@ -168,6 +168,7 @@ namespace :admin do
resources :runners, only: [:index, :new, :show, :edit, :update, :destroy] do
member do
get :register
post :resume
post :pause
end

View File

@ -33,9 +33,6 @@ For more information, see [Bitmask Searches in LDAP](https://ctovswild.com/2009/
<!-- vale gitlab.Spelling = YES -->
The user is set to an `ldap_blocked` state in GitLab if the previous conditions
fail. This means the user cannot sign in or push or pull code.
The process also updates the following user information:
- Name. Because of a [sync issue](https://gitlab.com/gitlab-org/gitlab/-/issues/342598), `name` is not synchronized if
@ -44,6 +41,26 @@ The process also updates the following user information:
- SSH public keys if `sync_ssh_keys` is set.
- Kerberos identity if Kerberos is enabled.
### Blocked users
A user is blocked if either the:
- [Access check fails](#user-sync) and that user is set to an `ldap_blocked` state in GitLab.
- LDAP server is not available when that user signs in.
If a user is blocked, that user cannot sign in or push or pull code.
A blocked user is unblocked when they sign in with LDAP if all of the following are true:
- All the access check conditions are true.
- The LDAP server is available when the user signs in.
**All users** are blocked if the LDAP server is unavailable when an LDAP user synchronization is run.
NOTE:
If all users are blocked due to the LDAP server not being available when an LDAP user synchronization is run,
a subsequent LDAP user synchronization does not automatically unblock those users.
### Adjust LDAP user sync schedule
By default, GitLab runs a worker once per day at 01:30 a.m. server time to

View File

@ -11544,7 +11544,7 @@ CI/CD variables for a project.
| <a id="cirunnercreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of creation of this runner. |
| <a id="cirunnerdescription"></a>`description` | [`String`](#string) | Description of the runner. |
| <a id="cirunnereditadminurl"></a>`editAdminUrl` | [`String`](#string) | Admin form URL of the runner. Only available for administrators. |
| <a id="cirunnerephemeralauthenticationtoken"></a>`ephemeralAuthenticationToken` **{warning-solid}** | [`String`](#string) | **Introduced** in 15.9. This feature is in Alpha. It can be changed or removed at any time. Ephemeral authentication token used for runner machine registration. |
| <a id="cirunnerephemeralauthenticationtoken"></a>`ephemeralAuthenticationToken` **{warning-solid}** | [`String`](#string) | **Introduced** in 15.9. This feature is in Alpha. It can be changed or removed at any time. Ephemeral authentication token used for runner machine registration. Only available for the creator of the runner for a limited time during registration. |
| <a id="cirunnerexecutorname"></a>`executorName` | [`String`](#string) | Executor last advertised by the runner. |
| <a id="cirunnergroups"></a>`groups` | [`GroupConnection`](#groupconnection) | Groups the runner is associated with. For group runners only. (see [Connections](#connections)) |
| <a id="cirunnerid"></a>`id` | [`CiRunnerID!`](#cirunnerid) | ID of the runner. |
@ -11561,6 +11561,7 @@ CI/CD variables for a project.
| <a id="cirunnerprivateprojectsminutescostfactor"></a>`privateProjectsMinutesCostFactor` | [`Float`](#float) | Private projects' "minutes cost factor" associated with the runner (GitLab.com only). |
| <a id="cirunnerprojectcount"></a>`projectCount` | [`Int`](#int) | Number of projects that the runner is associated with. |
| <a id="cirunnerpublicprojectsminutescostfactor"></a>`publicProjectsMinutesCostFactor` | [`Float`](#float) | Public projects' "minutes cost factor" associated with the runner (GitLab.com only). |
| <a id="cirunnerregisteradminurl"></a>`registerAdminUrl` | [`String`](#string) | URL of the temporary registration page of the runner. Only available before the runner is registered. Only available for administrators. |
| <a id="cirunnerrevision"></a>`revision` | [`String`](#string) | Revision of the runner. |
| <a id="cirunnerrununtagged"></a>`runUntagged` | [`Boolean!`](#boolean) | Indicates the runner is able to run untagged jobs. |
| <a id="cirunnerrunnertype"></a>`runnerType` | [`CiRunnerType!`](#cirunnertype) | Type of the runner. |

View File

@ -201,3 +201,13 @@ doesn't exist, GitLab returns `The requested URL returned error: 400`.
For example, you might accidentally use `main` for the branch name in a project that
uses a different branch name for its default branch.
Another possible cause for this error is a rule that prevents creation of the pipelines when `CI_PIPELINE_SOURCE` value is `trigger`, such as:
```yaml
rules:
- if: $CI_PIPELINE_SOURCE == "trigger"
when: never
```
Review your [`workflow:rules`](../yaml/index.md#workflowrules) to ensure a pipeline can be created when `CI_PIPELINE_SOURCE` value is `trigger`.

View File

@ -852,6 +852,31 @@ In the example above, the `is_admin?` method is overwritten when passing it to t
Working with archive files like `zip`, `tar`, `jar`, `war`, `cpio`, `apk`, `rar` and `7z` presents an area where potentially critical security vulnerabilities can sneak into an application.
### Utilities for safely working with archive files
There are common utilities that can be used to securely work with archive files.
#### Ruby
| Archive type | Utility |
|--------------|-------------|
| `zip` | `SafeZip` |
#### `SafeZip`
SafeZip provides a safe interface to extract specific directories or files within a `zip` archive through the `SafeZip::Extract` class.
Example:
```ruby
Dir.mktmpdir do |tmp_dir|
SafeZip::Extract.new(zip_file_path).extract(files: ['index.html', 'app/index.js'], to: tmp_dir)
SafeZip::Extract.new(zip_file_path).extract(directories: ['src/', 'test/'], to: tmp_dir)
rescue SafeZip::Extract::EntrySizeError
raise Error, "Path `#{file_path}` has invalid size in the zip!"
end
```
### Zip Slip
In 2018, the security company Snyk [released a blog post](https://security.snyk.io/research/zip-slip-vulnerability) describing research into a widespread and critical vulnerability present in many libraries and applications which allows an attacker to overwrite arbitrary files on the server file system which, in many cases, can be leveraged to achieve remote code execution. The vulnerability was dubbed Zip Slip.

View File

@ -70,8 +70,9 @@ The following information is displayed:
A GitLab SaaS subscription uses a concurrent (_seat_) model. You pay for a
subscription according to the maximum number of users assigned to the top-level group or its children during the billing period. You can
add and remove users during the subscription period, as long as the total users
at any given time doesn't exceed the subscription count.
add and remove users during the subscription period without incurring additional charges, as long as the total users
at any given time doesn't exceed the subscription count. If the total users exceeds your subscription count, you will incur an overage
which must be paid at your next [reconciliation](../quarterly_reconciliation.md).
A top-level group can be [changed](../../user/group/manage.md#change-a-groups-path) like any other group.

View File

@ -185,6 +185,13 @@ License.current.license_id
License.current.data
```
#### Interaction with licenses that start in the future
```ruby
# Future license data follows the same format as current license data it just uses a different modifier for the License prefix
License.future_dated
```
#### Check if a project feature is available on the instance
Features listed in [`features.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/models/gitlab_subscriptions/features.rb).

View File

@ -65,9 +65,10 @@ module BulkImports
namespace_children_names = namespace.children.pluck(:name) # rubocop: disable CodeReuse/ActiveRecord
if namespace_children_names.include?(data['name'])
data['name'] = Uniquify.new(1).string(-> (counter) { "#{data['name']}(#{counter})" }) do |base|
namespace_children_names.include?(base)
end
data['name'] =
Gitlab::Utils::Uniquify.new(1).string(-> (counter) { "#{data['name']}(#{counter})" }) do |base|
namespace_children_names.include?(base)
end
end
end

View File

@ -233,7 +233,7 @@ module Gitlab
email ||= auth_hash.email
valid_username = ::Namespace.clean_path(username)
valid_username = Uniquify.new.string(valid_username) { |s| !NamespacePathValidator.valid_path?(s) }
valid_username = Gitlab::Utils::Uniquify.new.string(valid_username) { |s| !NamespacePathValidator.valid_path?(s) }
{
name: name.strip.presence || valid_username,

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module Header
##
# Input parameter used for interpolation with the CI configuration.
class Input < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
attributes :default, prefix: :input
validations do
validates :config, type: Hash, allowed_keys: [:default]
validates :key, alphanumeric: true
validates :input_default, alphanumeric: true, allow_nil: true
end
end
end
end
end
end

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module Header
##
# This class represents the root entry of the GitLab CI configuration header.
#
# A header is the first document in a multi-doc YAML that contains metadata
# and specifications about the GitLab CI configuration (the second document).
#
# The header is optional. A CI configuration can also be represented with a
# YAML containing a single document.
class Root < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Configurable
ALLOWED_KEYS = %i[spec].freeze
validations do
validates :config, type: Hash, allowed_keys: ALLOWED_KEYS
end
entry :spec, Header::Spec,
description: 'Specifications of the CI configuration.',
inherit: false,
default: {}
def inputs_value
spec_entry.inputs_value
end
end
end
end
end
end

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module Header
class Spec < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Configurable
ALLOWED_KEYS = %i[inputs].freeze
validations do
validates :config, type: Hash, allowed_keys: ALLOWED_KEYS
end
entry :inputs, ::Gitlab::Config::Entry::ComposableHash,
description: 'Allowed input parameters used for interpolation.',
inherit: false,
metadata: { composable_class: ::Gitlab::Ci::Config::Header::Input }
end
end
end
end
end

View File

@ -0,0 +1,45 @@
# frozen_string_literal: true
# Uniquify
#
# Return a version of the given 'base' string that is unique
# by appending a counter to it. Uniqueness is determined by
# repeated calls to the passed block.
#
# You can pass an initial value for the counter, if not given
# counting starts from 1.
#
# If `base` is a function/proc, we expect that calling it with a
# candidate counter returns a string to test/return.
module Gitlab
module Utils
class Uniquify
def initialize(counter = nil)
@counter = counter
end
def string(base)
@base = base
increment_counter! while yield(base_string)
base_string
end
private
def base_string
if @base.respond_to?(:call)
@base.call(@counter)
else
"#{@base}#{@counter}"
end
end
def increment_counter!
@counter ||= 0
@counter += 1
end
end
end
end

View File

@ -1,6 +1,13 @@
# frozen_string_literal: true
module SafeZip
# SafeZip::Extract provides a safe interface
# to extract specific directories or files within a `zip` archive.
#
# @example Extract directories to destination
# SafeZip::Extract.new(archive_file).extract(directories: ['app/', 'test/'], to: destination_path)
# @example Extract files to destination
# SafeZip::Extract.new(archive_file).extract(files: ['index.html', 'app/index.js'], to: destination_path)
class Extract
Error = Class.new(StandardError)
PermissionDeniedError = Class.new(Error)
@ -17,6 +24,20 @@ module SafeZip
@archive_path = archive_file
end
# extract given files or directories from the archive into the destination path
#
# @param [Hash] opts the options for extraction.
# @option opts [Array<String] :files list of files to be extracted
# @option opts [Array<String] :directories list of directories to be extracted
# @option opts [String] :to destination path
#
# @raise [PermissionDeniedError]
# @raise [SymlinkSourceDoesNotExistError]
# @raise [UnsupportedEntryError]
# @raise [EntrySizeError]
# @raise [AlreadyExistsError]
# @raise [NoMatchingError]
# @raise [ExtractError]
def extract(opts = {})
params = SafeZip::ExtractParams.new(**opts)

View File

@ -37138,6 +37138,9 @@ msgstr ""
msgid "Runners|Recommended"
msgstr ""
msgid "Runners|Register"
msgstr ""
msgid "Runners|Register a group runner"
msgstr ""

View File

@ -35,7 +35,7 @@ module QA
@config = nil
@run_untagged = nil
@name = "qa-runner-#{SecureRandom.hex(4)}"
@image = 'registry.gitlab.com/gitlab-org/gitlab-runner:alpine'
@image = 'registry.gitlab.com/gitlab-org/gitlab-runner:alpine-v15.8.3'
@executor = :shell
@executor_image = 'registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine-ruby-2.7'
end

View File

@ -16,7 +16,7 @@ module QA
MSG
def initialize(name)
@image = 'gitlab/gitlab-runner:alpine'
@image = 'gitlab/gitlab-runner:alpine-v15.8.3'
@name = name || "qa-runner-#{SecureRandom.hex(4)}"
@run_untagged = true
@executor = :shell

View File

@ -19,7 +19,7 @@ module QA
resource.project = project
resource.name = runner_name
resource.tags = [runner_name]
resource.image = 'gitlab/gitlab-runner:alpine'
resource.image = 'gitlab/gitlab-runner:alpine-v15.8.3'
end
end

28
scripts/api/base.rb Normal file
View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
require 'gitlab'
require_relative 'default_options'
class Base
def initialize(options)
@project = options.fetch(:project)
# If api_token is nil, it's set to '' to allow unauthenticated requests (for forks).
api_token = options.fetch(:api_token, '')
warn "No API token given." if api_token.empty?
@client = Gitlab.client(
endpoint: options.fetch(:endpoint, API::DEFAULT_OPTIONS[:endpoint]),
private_token: api_token
)
end
def execute
raise NotImplementedError
end
private
attr_reader :project, :client
end

View File

@ -1,19 +1,13 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'gitlab'
require 'optparse'
require_relative 'default_options'
require_relative 'base'
class CancelPipeline
class CancelPipeline < Base
def initialize(options)
@project = options.delete(:project)
super
@pipeline_id = options.delete(:pipeline_id)
@client = Gitlab.client(
endpoint: options.delete(:endpoint) || API::DEFAULT_OPTIONS[:endpoint],
private_token: options.delete(:api_token)
)
end
def execute
@ -22,7 +16,7 @@ class CancelPipeline
private
attr_reader :project, :pipeline_id, :client
attr_reader :pipeline_id
end
if $PROGRAM_NAME == __FILE__

View File

@ -1,22 +1,11 @@
# frozen_string_literal: true
require 'gitlab'
require_relative 'default_options'
require_relative 'base'
class CommitMergeRequests
class CommitMergeRequests < Base
def initialize(options)
@project = options.fetch(:project)
super
@sha = options.fetch(:sha)
# If api_token is nil, it's set to '' to allow unauthenticated requests (for forks).
api_token = options.fetch(:api_token, '')
warn "No API token given." if api_token.empty?
@client = Gitlab.client(
endpoint: options.fetch(:endpoint, API::DEFAULT_OPTIONS[:endpoint]),
private_token: api_token
)
end
def execute
@ -25,5 +14,5 @@ class CommitMergeRequests
private
attr_reader :project, :sha, :client
attr_reader :sha
end

View File

@ -1,29 +1,9 @@
# frozen_string_literal: true
require 'gitlab'
require_relative 'default_options'
class CreateIssue
def initialize(options)
@project = options.fetch(:project)
# Force the token to be a string so that if api_token is nil, it's set to '',
# allowing unauthenticated requests (for forks).
api_token = options.delete(:api_token).to_s
warn "No API token given." if api_token.empty?
@client = Gitlab.client(
endpoint: options.delete(:endpoint) || API::DEFAULT_OPTIONS[:endpoint],
private_token: api_token
)
end
require_relative 'base'
class CreateIssue < Base
def execute(issue_data)
client.create_issue(project, issue_data.delete(:title), issue_data)
end
private
attr_reader :project, :client
end

View File

@ -1,32 +1,12 @@
# frozen_string_literal: true
require 'gitlab'
require_relative 'default_options'
class CreateIssueDiscussion
def initialize(options)
@project = options.fetch(:project)
# Force the token to be a string so that if api_token is nil, it's set to '',
# allowing unauthenticated requests (for forks).
api_token = options.delete(:api_token).to_s
warn "No API token given." if api_token.empty?
@client = Gitlab.client(
endpoint: options.delete(:endpoint) || API::DEFAULT_OPTIONS[:endpoint],
private_token: api_token
)
end
require_relative 'base'
class CreateIssueDiscussion < Base
def execute(discussion_data)
client.post(
"/projects/#{client.url_encode project}/issues/#{discussion_data.delete(:issue_iid)}/discussions",
body: discussion_data
)
end
private
attr_reader :project, :client
end

View File

@ -1,29 +1,9 @@
# frozen_string_literal: true
require 'gitlab'
require_relative 'default_options'
class FindIssues
def initialize(options)
@project = options.fetch(:project)
# Force the token to be a string so that if api_token is nil, it's set to '',
# allowing unauthenticated requests (for forks).
api_token = options.delete(:api_token).to_s
warn "No API token given." if api_token.empty?
@client = Gitlab.client(
endpoint: options.delete(:endpoint) || API::DEFAULT_OPTIONS[:endpoint],
private_token: api_token
)
end
require_relative 'base'
class FindIssues < Base
def execute(search_data)
client.issues(project, search_data)
end
private
attr_reader :project, :client
end

View File

@ -1,11 +1,10 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'gitlab'
require 'optparse'
require_relative 'default_options'
require_relative 'base'
class JobFinder
class JobFinder < Base
DEFAULT_OPTIONS = API::DEFAULT_OPTIONS.merge(
pipeline_query: {}.freeze,
job_query: {}.freeze
@ -13,22 +12,12 @@ class JobFinder
MAX_PIPELINES_TO_ITERATE = 20
def initialize(options)
@project = options.delete(:project)
super
@pipeline_query = options.delete(:pipeline_query) || DEFAULT_OPTIONS[:pipeline_query]
@job_query = options.delete(:job_query) || DEFAULT_OPTIONS[:job_query]
@pipeline_id = options.delete(:pipeline_id)
@job_name = options.delete(:job_name)
@artifact_path = options.delete(:artifact_path)
# Force the token to be a string so that if api_token is nil, it's set to '', allowing unauthenticated requests (for forks).
api_token = options.delete(:api_token).to_s
warn "No API token given." if api_token.empty?
@client = Gitlab.client(
endpoint: options.delete(:endpoint) || DEFAULT_OPTIONS[:endpoint],
private_token: api_token
)
end
def execute
@ -37,7 +26,7 @@ class JobFinder
private
attr_reader :project, :pipeline_query, :job_query, :pipeline_id, :job_name, :artifact_path, :client
attr_reader :pipeline_query, :job_query, :pipeline_id, :job_name, :artifact_path
def find_job_with_artifact
return if artifact_path.nil?

View File

@ -1,25 +1,12 @@
# frozen_string_literal: true
require 'gitlab'
require_relative 'base'
require_relative 'default_options'
class PipelineFailedJobs
class PipelineFailedJobs < Base
def initialize(options)
@project = options.delete(:project)
super
@pipeline_id = options.delete(:pipeline_id)
@exclude_allowed_to_fail_jobs = options.delete(:exclude_allowed_to_fail_jobs)
# Force the token to be a string so that if api_token is nil, it's set to '',
# allowing unauthenticated requests (for forks).
api_token = options.delete(:api_token).to_s
warn "No API token given." if api_token.empty?
@client = Gitlab.client(
endpoint: options.delete(:endpoint) || API::DEFAULT_OPTIONS[:endpoint],
private_token: api_token
)
end
def execute
@ -43,5 +30,5 @@ class PipelineFailedJobs
private
attr_reader :project, :pipeline_id, :exclude_allowed_to_fail_jobs, :client
attr_reader :pipeline_id, :exclude_allowed_to_fail_jobs
end

View File

@ -61,6 +61,51 @@ RSpec.describe Admin::RunnersController, feature_category: :runner_fleet do
end
end
describe '#register' do
subject(:register) { get :register, params: { id: new_runner.id } }
context 'when create_runner_workflow is enabled' do
before do
stub_feature_flags(create_runner_workflow: true)
end
context 'when runner can be registered after creation' do
let_it_be(:new_runner) { create(:ci_runner, registration_type: :authenticated_user) }
it 'renders a :register template' do
register
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:register)
end
end
context 'when runner cannot be registered after creation' do
let_it_be(:new_runner) { runner }
it 'returns :not_found' do
register
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'when create_runner_workflow is disabled' do
let_it_be(:new_runner) { create(:ci_runner, registration_type: :authenticated_user) }
before do
stub_feature_flags(create_runner_workflow: false)
end
it 'returns :not_found' do
register
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe '#edit' do
render_views

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Admin::SpamLogsController do
RSpec.describe Admin::SpamLogsController, feature_category: :instance_resiliency do
let(:admin) { create(:admin) }
let(:user) { create(:user) }
let!(:first_spam) { create(:spam_log, user: user) }
@ -13,9 +13,10 @@ RSpec.describe Admin::SpamLogsController do
end
describe '#index' do
it 'lists all spam logs' do
it 'lists paginated spam logs' do
get :index
expect(assigns(:spam_logs)).to be_kind_of(Kaminari::PaginatableWithoutCount)
expect(response).to have_gitlab_http_status(:ok)
end
end

View File

@ -59,18 +59,14 @@ describe('BoardForm', () => {
},
});
const createComponent = (props, data) => {
const createComponent = (props, provide) => {
wrapper = shallowMountExtended(BoardForm, {
propsData: { ...defaultProps, ...props },
data() {
return {
...data,
};
},
provide: {
boardBaseUrl: 'root',
isGroupBoard: true,
isProjectBoard: false,
...provide,
},
mocks: {
$apollo: {
@ -209,6 +205,30 @@ describe('BoardForm', () => {
expect(setBoardMock).not.toHaveBeenCalled();
expect(setErrorMock).toHaveBeenCalled();
});
describe('when Apollo boards FF is on', () => {
it('calls a correct GraphQL mutation and emits addBoard event', async () => {
createComponent(
{ canAdminBoard: true, currentPage: formType.new },
{ isApolloBoard: true },
);
fillForm();
await waitForPromises();
expect(mutate).toHaveBeenCalledWith({
mutation: createBoardMutation,
variables: {
input: expect.objectContaining({
name: 'test',
}),
},
});
await waitForPromises();
expect(wrapper.emitted('addBoard')).toHaveLength(1);
});
});
});
});

View File

@ -2,7 +2,6 @@ import { GlSprintf, GlModal, GlFormGroup, GlFormCheckbox, GlLoadingIcon } from '
import { shallowMount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status';
@ -87,24 +86,23 @@ describe('CustomNotificationsModal', () => {
describe('checkbox items', () => {
beforeEach(async () => {
const endpointUrl = '/api/v4/notification_settings';
mockAxios
.onGet(endpointUrl)
.reply(HTTP_STATUS_OK, mockNotificationSettingsResponses.default);
wrapper = createComponent();
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
events: [
{ id: 'new_release', enabled: true, name: 'New release', loading: false },
{ id: 'new_note', enabled: false, name: 'New note', loading: true },
],
});
wrapper.findComponent(GlModal).vm.$emit('show');
await nextTick();
await waitForPromises();
});
it.each`
index | eventId | eventName | enabled | loading
${0} | ${'new_release'} | ${'New release'} | ${true} | ${false}
${1} | ${'new_note'} | ${'New note'} | ${false} | ${true}
${1} | ${'new_note'} | ${'New note'} | ${false} | ${false}
`(
'renders a checkbox for "$eventName" with checked=$enabled',
async ({ index, eventName, enabled, loading }) => {
@ -214,16 +212,9 @@ describe('CustomNotificationsModal', () => {
wrapper = createComponent({ injectedProperties });
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
events: [
{ id: 'new_release', enabled: true, name: 'New release', loading: false },
{ id: 'new_note', enabled: false, name: 'New note', loading: false },
],
});
wrapper.findComponent(GlModal).vm.$emit('show');
await nextTick();
await waitForPromises();
findCheckboxAt(1).vm.$emit('change', true);
@ -241,19 +232,18 @@ describe('CustomNotificationsModal', () => {
);
it('shows a toast message when the request fails', async () => {
mockAxios.onPut('/api/v4/notification_settings').reply(HTTP_STATUS_NOT_FOUND, {});
const endpointUrl = '/api/v4/notification_settings';
mockAxios
.onGet(endpointUrl)
.reply(HTTP_STATUS_OK, mockNotificationSettingsResponses.default);
mockAxios.onPut(endpointUrl).reply(HTTP_STATUS_NOT_FOUND, {});
wrapper = createComponent();
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
// eslint-disable-next-line no-restricted-syntax
wrapper.setData({
events: [
{ id: 'new_release', enabled: true, name: 'New release', loading: false },
{ id: 'new_note', enabled: false, name: 'New note', loading: false },
],
});
wrapper.findComponent(GlModal).vm.$emit('show');
await nextTick();
await waitForPromises();
findCheckboxAt(1).vm.$emit('change', true);

View File

@ -6,6 +6,9 @@ import FileIcon from '~/vue_shared/components/file_icon.vue';
import FileRow from '~/vue_shared/components/file_row.vue';
import FileHeader from '~/vue_shared/components/file_row_header.vue';
const scrollIntoViewMock = jest.fn();
HTMLElement.prototype.scrollIntoView = scrollIntoViewMock;
describe('File row component', () => {
let wrapper;
@ -72,11 +75,10 @@ describe('File row component', () => {
},
level: 0,
});
jest.spyOn(wrapper.vm, '$emit');
wrapper.element.click();
expect(wrapper.vm.$emit).toHaveBeenCalledWith('toggleTreeOpen', fileName);
expect(wrapper.emitted('toggleTreeOpen')[0][0]).toEqual(fileName);
});
it('calls scrollIntoView if made active', () => {
@ -89,14 +91,12 @@ describe('File row component', () => {
level: 0,
});
jest.spyOn(wrapper.vm, 'scrollIntoView');
wrapper.setProps({
file: { ...wrapper.props('file'), active: true },
});
return nextTick().then(() => {
expect(wrapper.vm.scrollIntoView).toHaveBeenCalled();
expect(scrollIntoViewMock).toHaveBeenCalled();
});
});

View File

@ -11,9 +11,9 @@ RSpec.describe GitlabSchema.types['CiRunner'], feature_category: :runner do
expected_fields = %w[
id description created_at contacted_at maximum_timeout access_level active paused status
version short_sha revision locked run_untagged ip_address runner_type tag_list
project_count job_count admin_url edit_admin_url user_permissions executor_name architecture_name platform_name
maintenance_note maintenance_note_html groups projects jobs token_expires_at owner_project job_execution_status
ephemeral_authentication_token
project_count job_count admin_url edit_admin_url register_admin_url user_permissions executor_name
architecture_name platform_name maintenance_note maintenance_note_html groups projects jobs token_expires_at
owner_project job_execution_status ephemeral_authentication_token
]
expect(described_class).to include_graphql_fields(*expected_fields)

View File

@ -0,0 +1,70 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Header::Input, feature_category: :pipeline_authoring do
let(:factory) do
Gitlab::Config::Entry::Factory
.new(described_class)
.value(input_hash)
.with(key: input_name)
end
let(:input_name) { 'foo' }
subject(:config) { factory.create!.tap(&:compose!) }
shared_examples 'a valid input' do
let(:expected_hash) { input_hash }
it 'passes validations' do
expect(config).to be_valid
expect(config.errors).to be_empty
end
it 'returns the value' do
expect(config.value).to eq(expected_hash)
end
end
shared_examples 'an invalid input' do
let(:expected_hash) { input_hash }
it 'fails validations' do
expect(config).not_to be_valid
expect(config.errors).to eq(expected_errors)
end
it 'returns the value' do
expect(config.value).to eq(expected_hash)
end
end
context 'when has a default value' do
let(:input_hash) { { default: 'bar' } }
it_behaves_like 'a valid input'
end
context 'when is a required required input' do
let(:input_hash) { nil }
it_behaves_like 'a valid input'
end
context 'when contains unknown keywords' do
let(:input_hash) { { test: 123 } }
let(:expected_errors) { ['foo config contains unknown keys: test'] }
it_behaves_like 'an invalid input'
end
context 'when has invalid name' do
let(:input_name) { [123] }
let(:input_hash) { {} }
let(:expected_errors) { ['123 key must be an alphanumeric string'] }
it_behaves_like 'an invalid input'
end
end

View File

@ -0,0 +1,133 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Header::Root, feature_category: :pipeline_authoring do
let(:factory) { Gitlab::Config::Entry::Factory.new(described_class).value(header_hash) }
subject(:config) { factory.create!.tap(&:compose!) }
shared_examples 'a valid header' do
let(:expected_hash) { header_hash }
it 'passes validations' do
expect(config).to be_valid
expect(config.errors).to be_empty
end
it 'returns the value' do
expect(config.value).to eq(expected_hash)
end
end
shared_examples 'an invalid header' do
let(:expected_hash) { header_hash }
it 'fails validations' do
expect(config).not_to be_valid
expect(config.errors).to eq(expected_errors)
end
it 'returns the value' do
expect(config.value).to eq(expected_hash)
end
end
context 'when header contains default and required values for inputs' do
let(:header_hash) do
{
spec: {
inputs: {
test: {},
foo: {
default: 'bar'
}
}
}
}
end
it_behaves_like 'a valid header'
end
context 'when header contains minimal data' do
let(:header_hash) do
{
spec: {
inputs: nil
}
}
end
it_behaves_like 'a valid header' do
let(:expected_hash) { { spec: {} } }
end
end
context 'when header contains required inputs' do
let(:header_hash) do
{
spec: {
inputs: { foo: nil }
}
}
end
it_behaves_like 'a valid header' do
let(:expected_hash) do
{
spec: {
inputs: { foo: {} }
}
}
end
end
end
context 'when header contains unknown keywords' do
let(:header_hash) { { test: 123 } }
let(:expected_errors) { ['root config contains unknown keys: test'] }
it_behaves_like 'an invalid header'
end
context 'when header input entry has an unknown key' do
let(:header_hash) do
{
spec: {
inputs: {
foo: {
bad: 'value'
}
}
}
}
end
let(:expected_errors) { ['spec:inputs:foo config contains unknown keys: bad'] }
it_behaves_like 'an invalid header'
end
describe '#inputs_value' do
let(:header_hash) do
{
spec: {
inputs: {
foo: nil,
bar: {
default: 'baz'
}
}
}
}
end
it 'returns the inputs' do
expect(config.inputs_value).to eq({
foo: {},
bar: { default: 'baz' }
})
end
end
end

View File

@ -0,0 +1,44 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Header::Spec, feature_category: :pipeline_authoring do
let(:factory) { Gitlab::Config::Entry::Factory.new(described_class).value(spec_hash) }
subject(:config) { factory.create!.tap(&:compose!) }
context 'when spec contains default values for inputs' do
let(:spec_hash) do
{
inputs: {
foo: {
default: 'bar'
}
}
}
end
it 'passes validations' do
expect(config).to be_valid
expect(config.errors).to be_empty
end
it 'returns the value' do
expect(config.value).to eq(spec_hash)
end
end
context 'when spec contains unknown keywords' do
let(:spec_hash) { { test: 123 } }
let(:expected_errors) { ['spec config contains unknown keys: test'] }
it 'fails validations' do
expect(config).not_to be_valid
expect(config.errors).to eq(expected_errors)
end
it 'returns the value' do
expect(config.value).to eq(spec_hash)
end
end
end

View File

@ -1,13 +1,13 @@
# frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper'
RSpec.describe Uniquify do
let(:uniquify) { described_class.new }
RSpec.describe Gitlab::Utils::Uniquify, feature_category: :shared do
subject(:uniquify) { described_class.new }
describe "#string" do
it 'returns the given string if it does not exist' do
result = uniquify.string('test_string') { |s| false }
result = uniquify.string('test_string') { |_s| false }
expect(result).to eq('test_string')
end
@ -34,7 +34,7 @@ RSpec.describe Uniquify do
end
it 'allows passing in a base function that defines the location of the counter' do
result = uniquify.string(-> (counter) { "test_#{counter}_string" }) do |s|
result = uniquify.string(->(counter) { "test_#{counter}_string" }) do |s|
s == 'test__string'
end

View File

@ -107,6 +107,8 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
),
project_count: nil,
admin_url: "http://localhost/admin/runners/#{runner.id}",
edit_admin_url: "http://localhost/admin/runners/#{runner.id}/edit",
register_admin_url: runner.registration_available? ? "http://localhost/admin/runners/#{runner.id}/register" : nil,
user_permissions: {
'readRunner' => true,
'updateRunner' => true,
@ -135,7 +137,7 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
runner_data = graphql_data_at(:runner)
expect(runner_data).not_to be_nil
expect(runner_data).to match a_graphql_entity_for(runner, admin_url: nil)
expect(runner_data).to match a_graphql_entity_for(runner, admin_url: nil, edit_admin_url: nil)
expect(runner_data['tagList']).to match_array runner.tag_list
end
end
@ -307,6 +309,24 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
it_behaves_like 'runner details fetch'
end
describe 'for registration type' do
context 'when registered with registration token' do
let(:runner) do
create(:ci_runner, registration_type: :registration_token)
end
it_behaves_like 'runner details fetch'
end
context 'when registered with authenticated user' do
let(:runner) do
create(:ci_runner, registration_type: :authenticated_user)
end
it_behaves_like 'runner details fetch'
end
end
describe 'for group runner request' do
let(:query) do
%(
@ -568,14 +588,14 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
end
end
context 'with request made by creator' do
context 'with request made by creator', :frozen_time do
let(:user) { creator }
context 'with runner created in UI' do
let(:registration_type) { :authenticated_user }
context 'with runner created in last 3 hours' do
let(:created_at) { (3.hours - 1.second).ago }
context 'with runner created in last hour' do
let(:created_at) { (Ci::Runner::REGISTRATION_AVAILABILITY_TIME - 1.second).ago }
context 'with no runner machine registed yet' do
it_behaves_like 'an ephemeral_authentication_token'
@ -589,13 +609,13 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
end
context 'with runner created almost too long ago' do
let(:created_at) { (3.hours - 1.second).ago }
let(:created_at) { (Ci::Runner::REGISTRATION_AVAILABILITY_TIME - 1.second).ago }
it_behaves_like 'an ephemeral_authentication_token'
end
context 'with runner created too long ago' do
let(:created_at) { 3.hours.ago }
let(:created_at) { Ci::Runner::REGISTRATION_AVAILABILITY_TIME.ago }
it_behaves_like 'a protected ephemeral_authentication_token'
end
@ -604,8 +624,8 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do
context 'with runner registered from command line' do
let(:registration_type) { :registration_token }
context 'with runner created in last 3 hours' do
let(:created_at) { (3.hours - 1.second).ago }
context 'with runner created in last 1 hour' do
let(:created_at) { (Ci::Runner::REGISTRATION_AVAILABILITY_TIME - 1.second).ago }
it_behaves_like 'a protected ephemeral_authentication_token'
end

View File

@ -7956,7 +7956,6 @@
- './spec/models/concerns/token_authenticatable_strategies/encryption_helper_spec.rb'
- './spec/models/concerns/transactions_spec.rb'
- './spec/models/concerns/triggerable_hooks_spec.rb'
- './spec/models/concerns/uniquify_spec.rb'
- './spec/models/concerns/usage_statistics_spec.rb'
- './spec/models/concerns/vulnerability_finding_helpers_spec.rb'
- './spec/models/concerns/vulnerability_finding_signature_helpers_spec.rb'

View File

@ -8,52 +8,31 @@ RSpec.shared_examples 'user email validation' do
'This email address does not look right, are you sure you typed it correctly?'
end
context 'with trial_email_validation flag enabled' do
it 'shows an error message until a correct email is entered' do
visit path
expect(page).to have_content(email_hint_message)
expect(page).not_to have_content(email_error_message)
expect(page).not_to have_content(email_warning_message)
it 'shows an error message until a correct email is entered' do
visit path
expect(page).to have_content(email_hint_message)
expect(page).not_to have_content(email_error_message)
expect(page).not_to have_content(email_warning_message)
fill_in 'new_user_email', with: 'foo@'
fill_in 'new_user_first_name', with: ''
fill_in 'new_user_email', with: 'foo@'
fill_in 'new_user_first_name', with: ''
expect(page).not_to have_content(email_hint_message)
expect(page).to have_content(email_error_message)
expect(page).not_to have_content(email_warning_message)
expect(page).not_to have_content(email_hint_message)
expect(page).to have_content(email_error_message)
expect(page).not_to have_content(email_warning_message)
fill_in 'new_user_email', with: 'foo@bar'
fill_in 'new_user_first_name', with: ''
fill_in 'new_user_email', with: 'foo@bar'
fill_in 'new_user_first_name', with: ''
expect(page).not_to have_content(email_hint_message)
expect(page).not_to have_content(email_error_message)
expect(page).to have_content(email_warning_message)
expect(page).not_to have_content(email_hint_message)
expect(page).not_to have_content(email_error_message)
expect(page).to have_content(email_warning_message)
fill_in 'new_user_email', with: 'foo@gitlab.com'
fill_in 'new_user_first_name', with: ''
fill_in 'new_user_email', with: 'foo@gitlab.com'
fill_in 'new_user_first_name', with: ''
expect(page).not_to have_content(email_hint_message)
expect(page).not_to have_content(email_error_message)
expect(page).not_to have_content(email_warning_message)
end
end
context 'when trial_email_validation flag disabled' do
before do
stub_feature_flags trial_email_validation: false
end
it 'does not show an error message' do
visit path
expect(page).to have_content(email_hint_message)
expect(page).not_to have_content(email_error_message)
expect(page).not_to have_content(email_warning_message)
fill_in 'new_user_email', with: 'foo@'
expect(page).to have_content(email_hint_message)
expect(page).not_to have_content(email_error_message)
expect(page).not_to have_content(email_warning_message)
end
expect(page).not_to have_content(email_hint_message)
expect(page).not_to have_content(email_error_message)
expect(page).not_to have_content(email_warning_message)
end
end