Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-07-21 12:10:03 +00:00
parent 265a7cecca
commit f44215bf40
55 changed files with 601 additions and 632 deletions

View File

@ -596,6 +596,18 @@ RSpec/HaveGitlabHttpStatus:
- 'spec/**/*'
- 'ee/spec/**/*'
RSpec/ContextWording:
Prefixes:
- when
- with
- without
- for
- and
- on
- in
- as
- if
Style/MultilineWhenThen:
Enabled: false

File diff suppressed because it is too large Load Diff

View File

@ -94,7 +94,6 @@ export const LFS_STORAGE = 'lfs';
*/
export const LEGACY_FILE_TYPES = [
'gemfile',
'gemspec',
'composer_json',
'podfile',
'podspec',

View File

@ -147,3 +147,4 @@ export const HLJS_COMMENT_SELECTOR = 'hljs-comment';
export const HLJS_ON_AFTER_HIGHLIGHT = 'after:highlight';
export const NPM_URL = 'https://npmjs.com/package';
export const GEM_URL = 'https://rubygems.org/gems';

View File

@ -1,7 +1,9 @@
import packageJsonLinker from './utils/package_json_linker';
import gemspecLinker from './utils/gemspec_linker';
const DEPENDENCY_LINKERS = {
package_json: packageJsonLinker,
gemspec: gemspecLinker,
};
/**

View File

@ -12,4 +12,5 @@ export const createLink = (href, innerText) => {
return link.outerHTML;
};
export const generateHLJSOpenTag = (type) => `<span class="hljs-${escape(type)}">&quot;`;
export const generateHLJSOpenTag = (type, delimiter = '&quot;') =>
`<span class="hljs-${escape(type)}">${delimiter}`;

View File

@ -0,0 +1,39 @@
import { joinPaths } from '~/lib/utils/url_utility';
import { GEM_URL } from '../../constants';
import { createLink, generateHLJSOpenTag } from './dependency_linker_util';
const methodRegex = '.*add_dependency.*|.*add_runtime_dependency.*|.*add_development_dependency.*';
const openTagRegex = generateHLJSOpenTag('string', '(&.*;)');
const closeTagRegex = '&.*</span>';
const DEPENDENCY_REGEX = new RegExp(
/*
* Detects gemspec dependencies inside of content that is highlighted by Highlight.js
* Example: s.add_dependency(<span class="hljs-string">&#x27;rugged&#x27;</span>, <span class="hljs-string">&#x27;~&gt; 0.24.0&#x27;</span>)
*
* Group 1 (method) : s.add_dependency(
* Group 2 (delimiter) : &#x27;
* Group 3 (packageName): rugged
* Group 4 (closeTag) : &#x27;</span>
* Group 5 (rest) : , <span class="hljs-string">&#x27;~&gt; 0.24.0&#x27;</span>)
*/
`(${methodRegex})${openTagRegex}(.*)(${closeTagRegex})(.*${closeTagRegex})`,
'gm',
);
const handleReplace = (method, delimiter, packageName, closeTag, rest) => {
// eslint-disable-next-line @gitlab/require-i18n-strings
const openTag = generateHLJSOpenTag('string linked', delimiter);
const href = joinPaths(GEM_URL, packageName);
const packageLink = createLink(href, packageName);
return `${method}${openTag}${packageLink}${closeTag}${rest}`;
};
export default (result) => {
return result.value.replace(
DEPENDENCY_REGEX,
(_, method, delimiter, packageName, closeTag, rest) =>
handleReplace(method, delimiter, packageName, closeTag, rest),
);
};

View File

@ -1,4 +1,4 @@
#import "./work_item.fragment.graphql"
#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
mutation createWorkItem($input: WorkItemCreateInput!) {
workItemCreate(input: $input) {

View File

@ -1,4 +1,4 @@
#import "./work_item.fragment.graphql"
#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
mutation workItemCreateFromTask($input: WorkItemCreateFromTaskInput!) {
workItemCreateFromTask(input: $input) {

View File

@ -1,4 +1,4 @@
#import "./work_item.fragment.graphql"
#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
mutation localUpdateWorkItem($input: LocalUpdateWorkItemInput) {
localUpdateWorkItem(input: $input) @client {

View File

@ -1,4 +1,4 @@
#import "./work_item.fragment.graphql"
#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
mutation workItemUpdate($input: WorkItemUpdateInput!) {
workItemUpdate(input: $input) {

View File

@ -1,4 +1,4 @@
#import "./work_item.fragment.graphql"
#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
mutation workItemUpdateTask($input: WorkItemUpdateTaskInput!) {
workItemUpdate: workItemUpdateTask(input: $input) {

View File

@ -1,4 +1,4 @@
#import "./work_item.fragment.graphql"
#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
mutation workItemUpdateWidgets($input: WorkItemUpdateWidgetsInput!) {
workItemUpdateWidgets(input: $input) {

View File

@ -28,10 +28,6 @@ fragment WorkItem on WorkItem {
}
}
}
... on WorkItemWidgetWeight {
type
weight
}
... on WorkItemWidgetHierarchy {
type
parent {
@ -40,10 +36,8 @@ fragment WorkItem on WorkItem {
title
}
children {
edges {
node {
id
}
nodes {
id
}
}
}

View File

@ -1,5 +1,5 @@
#import "~/graphql_shared/fragments/label.fragment.graphql"
#import "./work_item.fragment.graphql"
#import "ee_else_ce/work_items/graphql/work_item.fragment.graphql"
query workItem($id: WorkItemID!) {
workItem(id: $id) {

View File

@ -47,7 +47,14 @@ class Import::BulkImportsController < ApplicationController
end
def create
responses = create_params.map { |entry| ::BulkImports::CreateService.new(current_user, entry, credentials).execute }
responses = create_params.map do |entry|
if entry[:destination_name]
entry[:destination_slug] ||= entry[:destination_name]
entry.delete(:destination_name)
end
::BulkImports::CreateService.new(current_user, entry, credentials).execute
end
render json: responses.map { |response| { success: response.success?, id: response.payload[:id], message: response.message } }
end
@ -100,6 +107,7 @@ class Import::BulkImportsController < ApplicationController
source_type
source_full_path
destination_name
destination_slug
destination_namespace
]
end

View File

@ -200,7 +200,7 @@ module Types
if id
pipeline.statuses.id_in(id.model_id)
else
pipeline.statuses.by_name(name)
pipeline.latest_statuses.by_name(name)
end.take # rubocop: disable CodeReuse/ActiveRecord
end

View File

@ -55,6 +55,8 @@ class BulkImports::Entity < ApplicationRecord
scope :by_bulk_import_id, ->(bulk_import_id) { where(bulk_import_id: bulk_import_id)}
scope :order_by_created_at, -> (direction) { order(created_at: direction) }
alias_attribute :destination_slug, :destination_name
state_machine :status, initial: :created do
state :created, value: 0
state :started, value: 1

View File

@ -56,8 +56,10 @@ class Environment < ApplicationRecord
validates :external_url,
length: { maximum: 255 },
allow_nil: true,
addressable_url: true
allow_nil: true
validates :external_url, addressable_url: true, allow_nil: true, unless: :soft_validation_on_external_url_enabled?
validate :safe_external_url, if: :soft_validation_on_external_url_enabled?
delegate :manual_actions, :other_manual_actions, to: :last_deployment, allow_nil: true
delegate :auto_rollback_enabled?, to: :project
@ -493,6 +495,26 @@ class Environment < ApplicationRecord
private
def soft_validation_on_external_url_enabled?
::Feature.enabled?(:soft_validation_on_external_url, project)
end
# We deliberately avoid using AddressableUrlValidator to allow users to update their environments even if they have
# misconfigured `environment:url` keyword. The external URL is presented as a clickable link on UI and not consumed
# in GitLab internally, thus we sanitize the URL before the persistence to make sure the rendered link is XSS safe.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/337417
def safe_external_url
return unless self.external_url.present?
new_external_url = Addressable::URI.parse(self.external_url)
if Gitlab::Utils::SanitizeNodeLink::UNSAFE_PROTOCOLS.include?(new_external_url.normalized_scheme)
errors.add(:external_url, "#{new_external_url.normalized_scheme} scheme is not allowed")
end
rescue Addressable::URI::InvalidURIError
errors.add(:external_url, 'URI is invalid')
end
def rollout_status_available?
has_terminals?
end

View File

@ -7,7 +7,6 @@ class GroupMember < Member
SOURCE_TYPE = 'Namespace'
SOURCE_TYPE_FORMAT = /\ANamespace\z/.freeze
THRESHOLD_FOR_REFRESHING_AUTHORIZATIONS_VIA_PROJECTS = 1000
belongs_to :group, foreign_key: 'source_id'
alias_attribute :namespace_id, :source_id
@ -67,28 +66,8 @@ class GroupMember < Member
# its projects are also destroyed, so the removal of project_authorizations
# will happen behind the scenes via DB foreign keys anyway.
return if destroyed_by_association.present?
return unless user_id
return super if Feature.disabled?(:refresh_authorizations_via_affected_projects_on_group_membership, group)
# rubocop:disable CodeReuse/ServiceClass
projects_to_refresh = Groups::ProjectsRequiringAuthorizationsRefresh::OnDirectMembershipFinder.new(group).execute
threshold_exceeded = (projects_to_refresh.size > THRESHOLD_FOR_REFRESHING_AUTHORIZATIONS_VIA_PROJECTS)
# We want to try the new approach only if the number of affected projects are greater than the set threshold.
return super unless threshold_exceeded
AuthorizedProjectUpdate::ProjectAccessChangedService
.new(projects_to_refresh)
.execute(blocking: false)
# Until we compare the inconsistency rates of the new approach
# the old approach, we still run AuthorizedProjectsWorker
# but with some delay and lower urgency as a safety net.
UserProjectAccessChangedService
.new(user_id)
.execute(blocking: false, priority: UserProjectAccessChangedService::LOW_PRIORITY)
# rubocop:enable CodeReuse/ServiceClass
super
end
def send_invite

View File

@ -3,10 +3,6 @@
module Boards
class DestroyService < Boards::BaseService
def execute(board)
if boards.size == 1
return ServiceResponse.error(message: "The board could not be deleted, because the parent doesn't have any other boards.")
end
board.destroy!
ServiceResponse.success

View File

@ -64,7 +64,7 @@ module BulkImports
bulk_import: bulk_import,
source_type: entity[:source_type],
source_full_path: entity[:source_full_path],
destination_name: entity[:destination_name],
destination_slug: entity[:destination_slug],
destination_namespace: entity[:destination_namespace]
)
end

View File

@ -1,8 +1,8 @@
---
name: refresh_authorizations_via_affected_projects_on_group_membership
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87071
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/362204
milestone: '15.0'
name: soft_validation_on_external_url
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91970
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/367206
milestone: '15.2'
type: development
group: group::workspace
group: group::release
default_enabled: false

View File

@ -75,11 +75,12 @@ Email is processed correctly when a configured email address is present in one o
(sorted in the order they are checked):
- `To`
- `References`
- `Delivered-To`
- `Envelope-To` or `X-Envelope-To`
- `Received`
The `References` header is also accepted, however it is used specifically to relate email responses to existing discussion threads. It is not used for creating issues by email.
In GitLab 14.6 and later, [Service Desk](../user/project/service_desk.md)
also checks accepted headers.

View File

@ -27,7 +27,8 @@ POST /bulk_imports
| `entities` | Array | yes | List of entities to import. |
| `entities[source_type]` | String | yes | Source entity type (only `group_entity` is supported). |
| `entities[source_full_path]` | String | yes | Source full path of the entity to import. |
| `entities[destination_name]` | String | yes | Destination slug for the entity. |
| `entities[destination_name]` | String | yes | Deprecated: Use :destination_slug instead. Destination slug for the entity. |
| `entities[destination_slug]` | String | yes | Destination slug for the entity. |
| `entities[destination_namespace]` | String | no | Destination namespace for the entity. |
```shell
@ -41,7 +42,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitla
{
"source_full_path": "source/full/path",
"source_type": "group_entity",
"destination_name": "destination_slug",
"destination_slug": "destination_slug",
"destination_namespace": "destination/namespace/path"
}
]
@ -126,7 +127,7 @@ curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab
"bulk_import_id": 1,
"status": "finished",
"source_full_path": "source_group",
"destination_name": "destination_slug",
"destination_slug": "destination_slug",
"destination_namespace": "destination_path",
"parent_id": null,
"namespace_id": 1,
@ -140,7 +141,7 @@ curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab
"bulk_import_id": 2,
"status": "failed",
"source_full_path": "another_group",
"destination_name": "another_slug",
"destination_slug": "another_slug",
"destination_namespace": "another_namespace",
"parent_id": null,
"namespace_id": null,

View File

@ -12662,9 +12662,11 @@ Represents the Geo sync and verification state of a group wiki repository.
| <a id="groupwikirepositoryregistryid"></a>`id` | [`ID!`](#id) | ID of the GroupWikiRepositoryRegistry. |
| <a id="groupwikirepositoryregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the GroupWikiRepositoryRegistry. |
| <a id="groupwikirepositoryregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the GroupWikiRepositoryRegistry. |
| <a id="groupwikirepositoryregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the GroupWikiRepositoryRegistry should be resynced. |
| <a id="groupwikirepositoryregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the GroupWikiRepositoryRegistry is resynced. |
| <a id="groupwikirepositoryregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the GroupWikiRepositoryRegistry. |
| <a id="groupwikirepositoryregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the GroupWikiRepositoryRegistry. |
| <a id="groupwikirepositoryregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the GroupWikiRepositoryRegistry is reverified. |
| <a id="groupwikirepositoryregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the GroupWikiRepositoryRegistry. |
### `HelmFileMetadata`
@ -13111,9 +13113,11 @@ Represents the Geo replication and verification state of a job_artifact.
| <a id="jobartifactregistryid"></a>`id` | [`ID!`](#id) | ID of the JobArtifactRegistry. |
| <a id="jobartifactregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the JobArtifactRegistry. |
| <a id="jobartifactregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the JobArtifactRegistry. |
| <a id="jobartifactregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the JobArtifactRegistry should be resynced. |
| <a id="jobartifactregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the JobArtifactRegistry is resynced. |
| <a id="jobartifactregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the JobArtifactRegistry. |
| <a id="jobartifactregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the JobArtifactRegistry. |
| <a id="jobartifactregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the JobArtifactRegistry is reverified. |
| <a id="jobartifactregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the JobArtifactRegistry. |
### `JobPermissions`
@ -13163,9 +13167,11 @@ Represents the Geo sync and verification state of an LFS object.
| <a id="lfsobjectregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the LfsObjectRegistry. |
| <a id="lfsobjectregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the LfsObjectRegistry. |
| <a id="lfsobjectregistrylfsobjectid"></a>`lfsObjectId` | [`ID!`](#id) | ID of the LFS object. |
| <a id="lfsobjectregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the LfsObjectRegistry should be resynced. |
| <a id="lfsobjectregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the LfsObjectRegistry is resynced. |
| <a id="lfsobjectregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the LfsObjectRegistry. |
| <a id="lfsobjectregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the LfsObjectRegistry. |
| <a id="lfsobjectregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the LfsObjectRegistry is reverified. |
| <a id="lfsobjectregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the LfsObjectRegistry. |
### `LicenseHistoryEntry`
@ -13847,9 +13853,11 @@ Represents the Geo sync and verification state of a Merge Request diff.
| <a id="mergerequestdiffregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the MergeRequestDiffRegistry. |
| <a id="mergerequestdiffregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the MergeRequestDiffRegistry. |
| <a id="mergerequestdiffregistrymergerequestdiffid"></a>`mergeRequestDiffId` | [`ID!`](#id) | ID of the Merge Request diff. |
| <a id="mergerequestdiffregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the MergeRequestDiffRegistry should be resynced. |
| <a id="mergerequestdiffregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the MergeRequestDiffRegistry is resynced. |
| <a id="mergerequestdiffregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the MergeRequestDiffRegistry. |
| <a id="mergerequestdiffregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the MergeRequestDiffRegistry. |
| <a id="mergerequestdiffregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the MergeRequestDiffRegistry is reverified. |
| <a id="mergerequestdiffregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the MergeRequestDiffRegistry. |
### `MergeRequestParticipant`
@ -14794,9 +14802,11 @@ Represents the Geo sync and verification state of a package file.
| <a id="packagefileregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the PackageFileRegistry. |
| <a id="packagefileregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the PackageFileRegistry. |
| <a id="packagefileregistrypackagefileid"></a>`packageFileId` | [`ID!`](#id) | ID of the PackageFile. |
| <a id="packagefileregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the PackageFileRegistry should be resynced. |
| <a id="packagefileregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the PackageFileRegistry is resynced. |
| <a id="packagefileregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the PackageFileRegistry. |
| <a id="packagefileregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the PackageFileRegistry. |
| <a id="packagefileregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the PackageFileRegistry is reverified. |
| <a id="packagefileregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the PackageFileRegistry. |
### `PackageHelmDependencyType`
@ -14916,9 +14926,11 @@ Represents the Geo replication and verification state of a pages_deployment.
| <a id="pagesdeploymentregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the PagesDeploymentRegistry. |
| <a id="pagesdeploymentregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the PagesDeploymentRegistry. |
| <a id="pagesdeploymentregistrypagesdeploymentid"></a>`pagesDeploymentId` | [`ID!`](#id) | ID of the Pages Deployment. |
| <a id="pagesdeploymentregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the PagesDeploymentRegistry should be resynced. |
| <a id="pagesdeploymentregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the PagesDeploymentRegistry is resynced. |
| <a id="pagesdeploymentregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the PagesDeploymentRegistry. |
| <a id="pagesdeploymentregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the PagesDeploymentRegistry. |
| <a id="pagesdeploymentregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the PagesDeploymentRegistry is reverified. |
| <a id="pagesdeploymentregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the PagesDeploymentRegistry. |
### `PathLock`
@ -15085,9 +15097,11 @@ Represents the Geo sync and verification state of a pipeline artifact.
| <a id="pipelineartifactregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the PipelineArtifactRegistry. |
| <a id="pipelineartifactregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the PipelineArtifactRegistry. |
| <a id="pipelineartifactregistrypipelineartifactid"></a>`pipelineArtifactId` | [`ID!`](#id) | ID of the pipeline artifact. |
| <a id="pipelineartifactregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the PipelineArtifactRegistry should be resynced. |
| <a id="pipelineartifactregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the PipelineArtifactRegistry is resynced. |
| <a id="pipelineartifactregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the PipelineArtifactRegistry. |
| <a id="pipelineartifactregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the PipelineArtifactRegistry. |
| <a id="pipelineartifactregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the PipelineArtifactRegistry is reverified. |
| <a id="pipelineartifactregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the PipelineArtifactRegistry. |
### `PipelineCounts`
@ -17252,10 +17266,12 @@ Represents the Geo sync and verification state of a snippet repository.
| <a id="snippetrepositoryregistryid"></a>`id` | [`ID!`](#id) | ID of the SnippetRepositoryRegistry. |
| <a id="snippetrepositoryregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the SnippetRepositoryRegistry. |
| <a id="snippetrepositoryregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the SnippetRepositoryRegistry. |
| <a id="snippetrepositoryregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the SnippetRepositoryRegistry should be resynced. |
| <a id="snippetrepositoryregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the SnippetRepositoryRegistry is resynced. |
| <a id="snippetrepositoryregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the SnippetRepositoryRegistry. |
| <a id="snippetrepositoryregistrysnippetrepositoryid"></a>`snippetRepositoryId` | [`ID!`](#id) | ID of the Snippet Repository. |
| <a id="snippetrepositoryregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the SnippetRepositoryRegistry. |
| <a id="snippetrepositoryregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the SnippetRepositoryRegistry is reverified. |
| <a id="snippetrepositoryregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the SnippetRepositoryRegistry. |
### `StatusAction`
@ -17354,10 +17370,12 @@ Represents the Geo sync and verification state of a terraform state version.
| <a id="terraformstateversionregistryid"></a>`id` | [`ID!`](#id) | ID of the TerraformStateVersionRegistry. |
| <a id="terraformstateversionregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the TerraformStateVersionRegistry. |
| <a id="terraformstateversionregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the TerraformStateVersionRegistry. |
| <a id="terraformstateversionregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the TerraformStateVersionRegistry should be resynced. |
| <a id="terraformstateversionregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the TerraformStateVersionRegistry is resynced. |
| <a id="terraformstateversionregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the TerraformStateVersionRegistry. |
| <a id="terraformstateversionregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the TerraformStateVersionRegistry. |
| <a id="terraformstateversionregistryterraformstateversionid"></a>`terraformStateVersionId` | [`ID!`](#id) | ID of the terraform state version. |
| <a id="terraformstateversionregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the TerraformStateVersionRegistry is reverified. |
| <a id="terraformstateversionregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the TerraformStateVersionRegistry. |
### `TestCase`
@ -17618,9 +17636,11 @@ Represents the Geo replication and verification state of an upload.
| <a id="uploadregistryid"></a>`id` | [`ID!`](#id) | ID of the UploadRegistry. |
| <a id="uploadregistrylastsyncfailure"></a>`lastSyncFailure` | [`String`](#string) | Error message during sync of the UploadRegistry. |
| <a id="uploadregistrylastsyncedat"></a>`lastSyncedAt` | [`Time`](#time) | Timestamp of the most recent successful sync of the UploadRegistry. |
| <a id="uploadregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the UploadRegistry should be resynced. |
| <a id="uploadregistryretryat"></a>`retryAt` | [`Time`](#time) | Timestamp after which the UploadRegistry is resynced. |
| <a id="uploadregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the UploadRegistry. |
| <a id="uploadregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the UploadRegistry. |
| <a id="uploadregistryverificationretryat"></a>`verificationRetryAt` | [`Time`](#time) | Timestamp after which the UploadRegistry is reverified. |
| <a id="uploadregistryverifiedat"></a>`verifiedAt` | [`Time`](#time) | Timestamp of the most recent successful verification of the UploadRegistry. |
### `UsageTrendsMeasurement`

View File

@ -374,6 +374,8 @@ To retry or rollback a deployment:
### Environment URL
> [Fixed](https://gitlab.com/gitlab-org/gitlab/-/issues/337417) to persist arbitrary URLs in GitLab 15.2 [with a flag](../../administration/feature_flags.md) named `soft_validation_on_external_url`. Disabled by default.
The [environment URL](../yaml/index.md#environmenturl) is displayed in a few
places in GitLab:

View File

@ -47,11 +47,23 @@ module API
requires :source_type, type: String, desc: 'Source entity type (only `group_entity` is supported)',
values: %w[group_entity]
requires :source_full_path, type: String, desc: 'Source full path of the entity to import'
requires :destination_name, type: String, desc: 'Destination slug for the entity'
requires :destination_namespace, type: String, desc: 'Destination namespace for the entity'
optional :destination_slug, type: String, desc: 'Destination slug for the entity'
optional :destination_name, type: String,
desc: 'Deprecated: Use :destination_slug instead. Destination slug for the entity'
mutually_exclusive :destination_slug, :destination_name
at_least_one_of :destination_slug, :destination_name
end
end
post do
params[:entities].each do |entity|
if entity[:destination_name]
entity[:destination_slug] ||= entity[:destination_name]
entity.delete(:destination_name)
end
end
response = ::BulkImports::CreateService.new(
current_user,
params[:entities],

View File

@ -8,7 +8,8 @@ module API
expose :bulk_import_id
expose :status_name, as: :status
expose :source_full_path
expose :destination_name
expose :destination_name # deprecated
expose :destination_slug
expose :destination_namespace
expose :parent_id
expose :namespace_id

View File

@ -38,7 +38,7 @@ module BulkImports
end
def transform_path(import_entity, data)
data['path'] = import_entity.destination_name.parameterize
data['path'] = import_entity.destination_slug.parameterize
data
end

View File

@ -11,8 +11,8 @@ module BulkImports
entity = context.entity
visibility = data.delete('visibility')
project[:name] = entity.destination_name
project[:path] = entity.destination_name.parameterize
project[:name] = entity.destination_slug
project[:path] = entity.destination_slug.parameterize
project[:created_at] = data['created_at']
project[:import_type] = PROJECT_IMPORT_TYPE
project[:visibility_level] = Gitlab::VisibilityLevel.string_options[visibility] if visibility.present?

View File

@ -612,8 +612,8 @@ RSpec.describe Admin::UsersController do
end
context 'when the new password does not match the password confirmation' do
let(:password) { 'some_password' }
let(:password_confirmation) { 'not_same_as_password' }
let(:password) { User.random_password }
let(:password_confirmation) { User.random_password }
it 'shows the edit page again' do
update_password(user, password, password_confirmation)

View File

@ -245,11 +245,11 @@ RSpec.describe Import::BulkImportsController do
let(:bulk_import_params) do
[{ "source_type" => "group_entity",
"source_full_path" => "full_path",
"destination_name" => "destination_name",
"destination_slug" => "destination_name",
"destination_namespace" => "root" },
{ "source_type" => "group_entity2",
"source_full_path" => "full_path2",
"destination_name" => "destination_name2",
"destination_slug" => "destination_name2",
"destination_namespace" => "root" }]
end
@ -258,7 +258,7 @@ RSpec.describe Import::BulkImportsController do
session[:bulk_import_gitlab_url] = instance_url
end
it 'executes BulkImpors::CreateService' do
it 'executes BulkImports::CreateService' do
error_response = ServiceResponse.error(message: 'Record invalid', http_status: :unprocessable_entity)
expect_next_instance_of(
@ -276,6 +276,38 @@ RSpec.describe Import::BulkImportsController do
expect(json_response).to eq([{ "success" => true, "id" => bulk_import.id, "message" => nil },
{ "success" => false, "id" => nil, "message" => "Record invalid" }])
end
context 'when entity destination_name is specified' do
let(:bulk_import_params) do
[
{
"source_type" => "group_entity",
"source_full_path" => "full_path",
"destination_name" => "destination_name",
"destination_namespace" => "root"
}
]
end
it 'replaces destination_name with destination_slug and executes BulkImports::CreateService' do
entity = {
"source_type" => "group_entity",
"source_full_path" => "full_path",
"destination_slug" => "destination_name",
"destination_namespace" => "root"
}
expect_next_instance_of(
::BulkImports::CreateService, user, entity, { url: instance_url, access_token: pat }) do |service|
allow(service).to receive(:execute).and_return(ServiceResponse.success(payload: bulk_import))
end
post :create, params: { bulk_import: bulk_import_params }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to match_array([{ "success" => true, "id" => bulk_import.id, "message" => nil }])
end
end
end
end

View File

@ -3,16 +3,16 @@
require('spec_helper')
RSpec.describe ProfilesController, :request_store do
let(:password) { 'longsecret987!' }
let(:password) { User.random_password }
let(:user) { create(:user, password: password) }
describe 'POST update' do
it 'does not update password' do
sign_in(user)
new_password = User.random_password
expect do
post :update,
params: { user: { password: 'hello12345', password_confirmation: 'hello12345' } }
params: { user: { password: new_password, password_confirmation: new_password } }
end.not_to change { user.reload.encrypted_password }
expect(response).to have_gitlab_http_status(:found)

View File

@ -233,7 +233,7 @@ RSpec.describe Projects::EnvironmentsController do
end
context "when environment params are invalid" do
let(:params) { environment_params.merge(environment: { name: '/foo/', external_url: '/git.gitlab.com' }) }
let(:params) { environment_params.merge(environment: { external_url: 'javascript:alert("hello")' }) }
it 'returns bad request' do
subject

View File

@ -25,7 +25,7 @@ RSpec.describe RegistrationsController do
end
let_it_be(:base_user_params) do
{ first_name: 'first', last_name: 'last', username: 'new_username', email: 'new@user.com', password: 'Any_password' }
{ first_name: 'first', last_name: 'last', username: 'new_username', email: 'new@user.com', password: User.random_password }
end
let_it_be(:user_params) { { user: base_user_params } }
@ -222,7 +222,7 @@ RSpec.describe RegistrationsController do
context 'when the registration fails' do
let_it_be(:member) { create(:project_member, :invited) }
let_it_be(:missing_user_params) do
{ username: '', email: member.invite_email, password: 'Any_password' }
{ username: '', email: member.invite_email, password: User.random_password }
end
let_it_be(:user_params) { { user: missing_user_params } }
@ -535,7 +535,7 @@ RSpec.describe RegistrationsController do
end
it 'succeeds if password is confirmed' do
post :destroy, params: { password: '12345678' }
post :destroy, params: { password: user.password }
expect_success
end
@ -576,7 +576,7 @@ RSpec.describe RegistrationsController do
end
it 'fails' do
delete :destroy, params: { password: '12345678' }
delete :destroy, params: { password: user.password }
expect_failure(s_('Profiles|You must transfer ownership or delete groups you are an owner of before you can delete your account'))
end

View File

@ -19,7 +19,6 @@ RSpec.describe 'Merge request > User posts diff notes', :js do
project.add_developer(user)
sign_in(user)
stub_const('Gitlab::QueryLimiting::Transaction::THRESHOLD', 104)
end
context 'when hovering over a parallel view diff file' do

View File

@ -477,14 +477,14 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions do
end
context 'with invalid username and password' do
let(:user) { create(:user, password: 'not-the-default') }
let(:user) { create(:user) }
it 'blocks invalid login' do
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
.and increment(:user_password_invalid_counter)
gitlab_sign_in(user)
gitlab_sign_in(user, password: 'incorrect-password')
expect_single_session_with_short_ttl
expect(page).to have_content('Invalid login or password.')

View File

@ -1,8 +1,10 @@
import packageJsonLinker from '~/vue_shared/components/source_viewer/plugins/utils/package_json_linker';
import gemspecLinker from '~/vue_shared/components/source_viewer/plugins/utils/gemspec_linker';
import linkDependencies from '~/vue_shared/components/source_viewer/plugins/link_dependencies';
import { PACKAGE_JSON_FILE_TYPE, PACKAGE_JSON_CONTENT } from './mock_data';
import { PACKAGE_JSON_FILE_TYPE, PACKAGE_JSON_CONTENT, GEMSPEC_FILE_TYPE } from './mock_data';
jest.mock('~/vue_shared/components/source_viewer/plugins/utils/package_json_linker');
jest.mock('~/vue_shared/components/source_viewer/plugins/utils/gemspec_linker');
describe('Highlight.js plugin for linking dependencies', () => {
const hljsResultMock = { value: 'test' };
@ -11,4 +13,9 @@ describe('Highlight.js plugin for linking dependencies', () => {
linkDependencies(hljsResultMock, PACKAGE_JSON_FILE_TYPE, PACKAGE_JSON_CONTENT);
expect(packageJsonLinker).toHaveBeenCalled();
});
it('calls gemspecLinker for gemspec file types', () => {
linkDependencies(hljsResultMock, GEMSPEC_FILE_TYPE);
expect(gemspecLinker).toHaveBeenCalled();
});
});

View File

@ -1,2 +1,4 @@
export const PACKAGE_JSON_FILE_TYPE = 'package_json';
export const PACKAGE_JSON_CONTENT = '{ "dependencies": { "@babel/core": "^7.18.5" } }';
export const GEMSPEC_FILE_TYPE = 'gemspec';

View File

@ -0,0 +1,14 @@
import gemspecLinker from '~/vue_shared/components/source_viewer/plugins/utils/gemspec_linker';
describe('Highlight.js plugin for linking gemspec dependencies', () => {
it('mutates the input value by wrapping dependency names in anchors', () => {
const inputValue =
's.add_dependency(<span class="hljs-string">&#x27;rugged&#x27;</span>, <span class="hljs-string">&#x27;~&gt; 0.24.0&#x27;</span>)';
const outputValue =
's.add_dependency(<span class="hljs-string linked">&#x27;<a href="https://rubygems.org/gems/rugged" rel="nofollow noreferrer noopener">rugged</a>&#x27;</span>, <span class="hljs-string">&#x27;~&gt; 0.24.0&#x27;</span>)';
const hljsResultMock = { value: inputValue };
const output = gemspecLinker(hljsResultMock);
expect(output).toBe(outputValue);
});
});

View File

@ -59,11 +59,9 @@ export const workItemQueryResponse = {
title: 'Parent title',
},
children: {
edges: [
nodes: [
{
node: {
id: 'gid://gitlab/WorkItem/444',
},
id: 'gid://gitlab/WorkItem/444',
},
],
},
@ -96,9 +94,9 @@ export const updateWorkItemMutationResponse = {
widgets: [
{
children: {
edges: [
nodes: [
{
node: 'gid://gitlab/WorkItem/444',
id: 'gid://gitlab/WorkItem/444',
},
],
},
@ -161,11 +159,9 @@ export const workItemResponseFactory = ({
__typename: 'WorkItemWidgetHierarchy',
type: 'HIERARCHY',
children: {
edges: [
nodes: [
{
node: {
id: 'gid://gitlab/WorkItem/444',
},
id: 'gid://gitlab/WorkItem/444',
},
],
},

View File

@ -14,6 +14,7 @@ RSpec.describe API::Entities::BulkImports::Entity do
:status,
:source_full_path,
:destination_name,
:destination_slug,
:destination_namespace,
:parent_id,
:namespace_id,

View File

@ -13,7 +13,7 @@ RSpec.describe BulkImports::Groups::Transformers::GroupAttributesTransformer do
:bulk_import_entity,
bulk_import: bulk_import,
source_full_path: 'source/full/path',
destination_name: 'destination-name-path',
destination_slug: 'destination-slug-path',
destination_namespace: parent.full_path
)
end
@ -41,14 +41,14 @@ RSpec.describe BulkImports::Groups::Transformers::GroupAttributesTransformer do
'name' => 'Name',
'description' => 'Description',
'parent_id' => parent.id,
'path' => 'destination-name-path'
'path' => 'destination-slug-path'
})
end
it 'transforms path from destination_name' do
it 'transforms path from destination_slug' do
transformed_data = subject.transform(context, data)
expect(transformed_data['path']).to eq(entity.destination_name)
expect(transformed_data['path']).to eq(entity.destination_slug)
end
it 'removes full path' do

View File

@ -15,7 +15,7 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer
source_type: :project_entity,
bulk_import: bulk_import,
source_full_path: 'source/full/path',
destination_name: 'Destination Project Name',
destination_slug: 'Destination Project Name',
destination_namespace: destination_group.full_path
)
end
@ -32,12 +32,12 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer
subject(:transformed_data) { described_class.new.transform(context, data) }
it 'transforms name to destination name' do
expect(transformed_data[:name]).to eq(entity.destination_name)
it 'transforms name to destination slug' do
expect(transformed_data[:name]).to eq(entity.destination_slug)
end
it 'adds path as parameterized name' do
expect(transformed_data[:path]).to eq(entity.destination_name.parameterize)
expect(transformed_data[:path]).to eq(entity.destination_slug.parameterize)
end
it 'transforms visibility level' do
@ -65,7 +65,7 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer
source_type: :project_entity,
bulk_import: bulk_import,
source_full_path: 'source/full/path',
destination_name: 'Destination Project Name',
destination_slug: 'Destination Project Name',
destination_namespace: ''
)

View File

@ -42,6 +42,92 @@ RSpec.describe Environment, :use_clean_rails_memory_store_caching do
end
end
describe 'validate and sanitize external url' do
let_it_be_with_refind(:environment) { create(:environment) }
where(:source_external_url, :expected_error_message) do
nil | nil
'http://example.com' | nil
'example.com' | nil
'www.example.io' | nil
'http://$URL' | nil
'http://$(URL)' | nil
'custom://example.com' | nil
'1.1.1.1' | nil
'$BASE_URL/${CI_COMMIT_REF_NAME}' | nil
'$ENVIRONMENT_URL' | nil
'https://$SUB.$MAIN' | nil
'https://$SUB-$REGION.$MAIN' | nil
'https://example.com?param={()}' | nil
'http://XSS?x=<script>alert(1)</script>' | nil
'https://user:${VARIABLE}@example.io' | nil
'https://example.com/test?param={data}' | nil
'http://${URL}' | 'URI is invalid'
'https://${URL}.example/test' | 'URI is invalid'
'http://test${CI_MERGE_REQUEST_IID}.example.com' | 'URI is invalid'
'javascript:alert("hello")' | 'javascript scheme is not allowed'
end
with_them do
it 'sets an external URL or an error' do
environment.external_url = source_external_url
environment.valid?
if expected_error_message
expect(environment.errors[:external_url].first).to eq(expected_error_message)
else
expect(environment.errors[:external_url]).to be_empty,
"There were unexpected errors: #{environment.errors.full_messages}"
expect(environment.external_url).to eq(source_external_url)
end
end
end
context 'when soft_validation_on_external_url feature flag is disabled' do
before do
stub_feature_flags(soft_validation_on_external_url: false)
end
where(:source_external_url, :expected_error_message) do
nil | nil
'http://example.com' | nil
'example.com' | 'is blocked: Only allowed schemes are http, https'
'www.example.io' | 'is blocked: Only allowed schemes are http, https'
'http://$URL' | 'is blocked: Hostname or IP address invalid'
'http://$(URL)' | 'is blocked: Hostname or IP address invalid'
'custom://example.com' | 'is blocked: Only allowed schemes are http, https'
'1.1.1.1' | 'is blocked: Only allowed schemes are http, https'
'$BASE_URL/${CI_COMMIT_REF_NAME}' | 'is blocked: Only allowed schemes are http, https'
'$ENVIRONMENT_URL' | 'is blocked: Only allowed schemes are http, https'
'https://$SUB.$MAIN' | 'is blocked: Hostname or IP address invalid'
'https://$SUB-$REGION.$MAIN' | 'is blocked: Hostname or IP address invalid'
'https://example.com?param={()}' | nil
'http://XSS?x=<script>alert(1)</script>' | nil
'https://user:${VARIABLE}@example.io' | nil
'https://example.com/test?param={data}' | nil
'http://${URL}' | 'is blocked: URI is invalid'
'https://${URL}.example/test' | 'is blocked: URI is invalid'
'http://test${CI_MERGE_REQUEST_IID}.example.com' | 'is blocked: URI is invalid'
'javascript:alert("hello")' | 'is blocked: Only allowed schemes are http, https'
end
with_them do
it 'sets an external URL or an error' do
environment.external_url = source_external_url
environment.valid?
if expected_error_message
expect(environment.errors[:external_url].first).to eq(expected_error_message)
else
expect(environment.errors[:external_url]).to be_empty,
"There were unexpected errors: #{environment.errors.full_messages}"
expect(environment.external_url).to eq(source_external_url)
end
end
end
end
end
describe '.before_save' do
it 'ensures environment tier when a new object is created' do
environment = build(:environment, name: 'gprd', tier: nil)

View File

@ -165,13 +165,6 @@ RSpec.describe GroupMember do
let_it_be(:project_b) { create(:project, group: group) }
let_it_be(:project_c) { create(:project, group: group) }
let_it_be(:user) { create(:user) }
let_it_be(:affected_project_ids) { Project.id_in([project_a, project_b, project_c]).ids }
before do
stub_const(
"#{described_class.name}::THRESHOLD_FOR_REFRESHING_AUTHORIZATIONS_VIA_PROJECTS",
affected_project_ids.size - 1)
end
shared_examples_for 'calls UserProjectAccessChangedService to recalculate authorizations' do
it 'calls UserProjectAccessChangedService to recalculate authorizations' do
@ -183,41 +176,6 @@ RSpec.describe GroupMember do
end
end
shared_examples_for 'tries to update permissions via refreshing authorizations for the affected projects' do
context 'when the number of affected projects exceeds the set threshold' do
it 'updates permissions via refreshing authorizations for the affected projects asynchronously' do
expect_next_instance_of(
AuthorizedProjectUpdate::ProjectAccessChangedService, affected_project_ids
) do |service|
expect(service).to receive(:execute).with(blocking: false)
end
action
end
it 'calls AuthorizedProjectUpdate::UserRefreshFromReplicaWorker with a delay as a safety net' do
expect(AuthorizedProjectUpdate::UserRefreshFromReplicaWorker).to(
receive(:bulk_perform_in)
.with(1.hour,
[[user.id]],
batch_delay: 30.seconds, batch_size: 100)
)
action
end
end
context 'when the number of affected projects does not exceed the set threshold' do
before do
stub_const(
"#{described_class.name}::THRESHOLD_FOR_REFRESHING_AUTHORIZATIONS_VIA_PROJECTS",
affected_project_ids.size + 1)
end
it_behaves_like 'calls UserProjectAccessChangedService to recalculate authorizations'
end
end
context 'on create' do
let(:action) { group.add_member(user, Gitlab::Access::GUEST) }
let(:blocking) { true }
@ -228,15 +186,7 @@ RSpec.describe GroupMember do
.and change { user.can?(:guest_access, project_c) }.from(false).to(true)
end
it_behaves_like 'tries to update permissions via refreshing authorizations for the affected projects'
context 'when the feature flag `refresh_authorizations_via_affected_projects_on_group_membership` is disabled' do
before do
stub_feature_flags(refresh_authorizations_via_affected_projects_on_group_membership: false)
end
it_behaves_like 'calls UserProjectAccessChangedService to recalculate authorizations'
end
it_behaves_like 'calls UserProjectAccessChangedService to recalculate authorizations'
end
context 'on update' do
@ -253,15 +203,7 @@ RSpec.describe GroupMember do
.and change { user.can?(:developer_access, project_c) }.from(false).to(true)
end
it_behaves_like 'tries to update permissions via refreshing authorizations for the affected projects'
context 'when the feature flag `refresh_authorizations_via_affected_projects_on_group_membership` is disabled' do
before do
stub_feature_flags(refresh_authorizations_via_affected_projects_on_group_membership: false)
end
it_behaves_like 'calls UserProjectAccessChangedService to recalculate authorizations'
end
it_behaves_like 'calls UserProjectAccessChangedService to recalculate authorizations'
end
context 'on destroy' do
@ -278,15 +220,7 @@ RSpec.describe GroupMember do
.and change { user.can?(:guest_access, project_c) }.from(true).to(false)
end
it_behaves_like 'tries to update permissions via refreshing authorizations for the affected projects'
context 'when the feature flag `refresh_authorizations_via_affected_projects_on_group_membership` is disabled' do
before do
stub_feature_flags(refresh_authorizations_via_affected_projects_on_group_membership: false)
end
it_behaves_like 'calls UserProjectAccessChangedService to recalculate authorizations'
end
it_behaves_like 'calls UserProjectAccessChangedService to recalculate authorizations'
end
end
end

View File

@ -57,9 +57,11 @@ RSpec.describe API::Boards do
let(:url) { "/projects/#{board_parent.id}/boards/#{board.id}" }
it 'delete the issue board' do
delete api(url, user)
expect do
delete api(url, user)
expect(response).to have_gitlab_http_status(:no_content)
expect(response).to have_gitlab_http_status(:no_content)
end.to change {board_parent.boards.count}.by(-1)
end
end

View File

@ -53,23 +53,80 @@ RSpec.describe API::BulkImports do
end
end
it 'starts a new migration' do
post api('/bulk_imports', user), params: {
configuration: {
url: 'http://gitlab.example',
access_token: 'access_token'
},
entities: [
source_type: 'group_entity',
source_full_path: 'full_path',
destination_name: 'destination_slug',
destination_namespace: 'destination_namespace'
]
}
shared_examples 'starting a new migration' do
it 'starts a new migration' do
post api('/bulk_imports', user), params: {
configuration: {
url: 'http://gitlab.example',
access_token: 'access_token'
},
entities: [
{
source_type: 'group_entity',
source_full_path: 'full_path',
destination_namespace: 'destination_namespace'
}.merge(destination_param)
]
}
expect(response).to have_gitlab_http_status(:created)
expect(response).to have_gitlab_http_status(:created)
expect(json_response['status']).to eq('created')
expect(json_response['status']).to eq('created')
end
end
include_examples 'starting a new migration' do
let(:destination_param) { { destination_slug: 'destination_slug' } }
end
include_examples 'starting a new migration' do
let(:destination_param) { { destination_name: 'destination_name' } }
end
context 'when both destination_name & destination_slug are provided' do
it 'returns a mutually exclusive error' do
post api('/bulk_imports', user), params: {
configuration: {
url: 'http://gitlab.example',
access_token: 'access_token'
},
entities: [
{
source_type: 'group_entity',
source_full_path: 'full_path',
destination_name: 'destination_name',
destination_slug: 'destination_slug',
destination_namespace: 'destination_namespace'
}
]
}
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('entities[0][destination_slug], entities[0][destination_name] are mutually exclusive')
end
end
context 'when neither destination_name nor destination_slug is provided' do
it 'returns at_least_one_of error' do
post api('/bulk_imports', user), params: {
configuration: {
url: 'http://gitlab.example',
access_token: 'access_token'
},
entities: [
{
source_type: 'group_entity',
source_full_path: 'full_path',
destination_namespace: 'destination_namespace'
}
]
}
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['error']).to eq('entities[0][destination_slug], entities[0][destination_name] are missing, at least one parameter must be provided')
end
end
context 'when provided url is blocked' do
@ -82,7 +139,7 @@ RSpec.describe API::BulkImports do
entities: [
source_type: 'group_entity',
source_full_path: 'full_path',
destination_name: 'destination_slug',
destination_slug: 'destination_slug',
destination_namespace: 'destination_namespace'
]
}

View File

@ -166,6 +166,35 @@ RSpec.describe 'Query.project(fullPath).pipelines' do
end
end
describe '.job' do
let(:first_n) { var('Int') }
let(:query_path) do
[
[:project, { full_path: project.full_path }],
[:pipelines],
[:nodes],
[:job, { name: 'Job 1' }]
]
end
let(:query) do
wrap_fields(query_graphql_path(query_path, :status))
end
before_all do
pipeline = create(:ci_pipeline, project: project)
create(:ci_build, pipeline: pipeline, name: 'Job 1', status: :failed, retried: true)
create(:ci_build, pipeline: pipeline, name: 'Job 1', status: :success)
end
it 'fetches the latest job with the given name' do
post_graphql(query, current_user: user)
expect(graphql_data_at(*query_path.map(&:first))).to contain_exactly a_hash_including(
'status' => 'SUCCESS'
)
end
end
describe '.jobs' do
let(:first_n) { var('Int') }
let(:query_path) do

View File

@ -65,15 +65,8 @@ RSpec.describe Mutations::Boards::Destroy do
other_board.destroy!
end
it 'does not destroy the board' do
expect { subject }.not_to change { Board.count }.from(1)
end
it 'returns an error and not nil board' do
subject
expect(mutation_response['errors']).not_to be_empty
expect(mutation_response['board']).not_to be_nil
it 'does destroy the board' do
expect { subject }.to change { Board.count }.by(-1)
end
end
end

View File

@ -10,19 +10,19 @@ RSpec.describe BulkImports::CreateService do
{
source_type: 'group_entity',
source_full_path: 'full/path/to/group1',
destination_name: 'destination group 1',
destination_slug: 'destination group 1',
destination_namespace: 'full/path/to/destination1'
},
{
source_type: 'group_entity',
source_full_path: 'full/path/to/group2',
destination_name: 'destination group 2',
destination_slug: 'destination group 2',
destination_namespace: 'full/path/to/destination2'
},
{
source_type: 'project_entity',
source_full_path: 'full/path/to/project1',
destination_name: 'destination project 1',
destination_slug: 'destination project 1',
destination_namespace: 'full/path/to/destination1'
}
]

View File

@ -112,7 +112,7 @@ RSpec.describe Deployments::UpdateEnvironmentService do
end
context 'when external URL is invalid' do
let(:external_url) { 'google.com' }
let(:external_url) { 'javascript:alert("hello")' }
it 'fails to update the tier due to validation error' do
expect { subject.execute }.not_to change { environment.tier }
@ -123,7 +123,7 @@ RSpec.describe Deployments::UpdateEnvironmentService do
.with(an_instance_of(described_class::EnvironmentUpdateFailure),
project_id: project.id,
environment_id: environment.id,
reason: %q{External url is blocked: Only allowed schemes are http, https})
reason: %q{External url javascript scheme is not allowed})
.once
subject.execute

View File

@ -91,12 +91,12 @@ module LoginHelpers
# user - User instance to login with
# remember - Whether or not to check "Remember me" (default: false)
# two_factor_auth - If two-factor authentication is enabled (default: false)
# password - password to attempt to login with
# password - password to attempt to login with (default: user.password)
def gitlab_sign_in_with(user, remember: false, two_factor_auth: false, password: nil)
visit new_user_session_path
fill_in "user_login", with: user.email
fill_in "user_password", with: (password || "12345678")
fill_in "user_password", with: (password || user.password)
check 'user_remember_me' if remember
find('[data-testid="sign-in-button"]:enabled').click

View File

@ -20,10 +20,10 @@ RSpec.shared_examples 'board destroy service' do
end
context 'when there is only one board' do
it 'does not remove board' do
it 'does remove board' do
expect do
expect(service.execute(board)).to be_error
end.not_to change(boards, :count)
service.execute(board)
end.to change(boards, :count).by(-1)
end
end
end