Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
eaeb21af27
commit
b24742b7ed
|
@ -1 +1 @@
|
|||
15.5.0
|
||||
15.5.1
|
||||
|
|
|
@ -137,6 +137,19 @@ class GitlabSchema < GraphQL::Schema
|
|||
gid
|
||||
end
|
||||
|
||||
# Parse an array of strings to an array of GlobalIDs, raising ArgumentError if there are problems
|
||||
# with it.
|
||||
# See #parse_gid
|
||||
#
|
||||
# ```
|
||||
# gids = GitlabSchema.parse_gids(my_array_of_strings, expected_type: ::Project)
|
||||
# project_ids = gids.map(&:model_id)
|
||||
# gids.all? { |gid| gid.model_class == ::Project }
|
||||
# ```
|
||||
def parse_gids(global_ids, ctx = {})
|
||||
global_ids.map { |gid| parse_gid(gid, ctx) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def max_query_complexity(ctx)
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module Packages
|
||||
class BulkDestroy < ::Mutations::BaseMutation
|
||||
graphql_name 'DestroyPackages'
|
||||
|
||||
MAX_PACKAGES = 20
|
||||
TOO_MANY_IDS_ERROR = "Cannot delete more than #{MAX_PACKAGES} packages"
|
||||
|
||||
argument :ids,
|
||||
[::Types::GlobalIDType[::Packages::Package]],
|
||||
required: true,
|
||||
description: "Global IDs of the Packages. Max #{MAX_PACKAGES}"
|
||||
|
||||
def resolve(ids:)
|
||||
raise_resource_not_available_error!(TOO_MANY_IDS_ERROR) if ids.size > MAX_PACKAGES
|
||||
|
||||
ids = GitlabSchema.parse_gids(ids, expected_type: ::Packages::Package)
|
||||
.map(&:model_id)
|
||||
|
||||
service = ::Packages::MarkPackagesForDestructionService.new(
|
||||
packages: packages_from(ids),
|
||||
current_user: current_user
|
||||
)
|
||||
result = service.execute
|
||||
|
||||
raise_resource_not_available_error! if result.reason == :unauthorized
|
||||
|
||||
errors = result.error? ? Array.wrap(result[:message]) : []
|
||||
|
||||
{ errors: errors }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def packages_from(ids)
|
||||
::Packages::Package.displayable
|
||||
.id_in(ids)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -25,7 +25,7 @@ module Mutations
|
|||
project = authorized_find!(project_path)
|
||||
raise_resource_not_available_error! "Cannot delete more than #{MAXIMUM_FILES} files" if ids.size > MAXIMUM_FILES
|
||||
|
||||
package_files = ::Packages::PackageFile.where(id: parse_gids(ids)) # rubocop:disable CodeReuse/ActiveRecord
|
||||
package_files = ::Packages::PackageFile.id_in(parse_gids(ids))
|
||||
|
||||
ensure_file_access!(project, package_files)
|
||||
|
||||
|
@ -47,7 +47,7 @@ module Mutations
|
|||
end
|
||||
|
||||
def parse_gids(gids)
|
||||
gids.map { |gid| GitlabSchema.parse_gid(gid, expected_type: ::Packages::PackageFile).model_id }
|
||||
GitlabSchema.parse_gids(gids, expected_type: ::Packages::PackageFile).map(&:model_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -138,6 +138,8 @@ module Types
|
|||
mount_mutation Mutations::UserCallouts::Create
|
||||
mount_mutation Mutations::UserPreferences::Update
|
||||
mount_mutation Mutations::Packages::Destroy
|
||||
mount_mutation Mutations::Packages::BulkDestroy,
|
||||
extensions: [::Gitlab::Graphql::Limit::FieldCallCount => { limit: 1 }]
|
||||
mount_mutation Mutations::Packages::DestroyFile
|
||||
mount_mutation Mutations::Packages::DestroyFiles
|
||||
mount_mutation Mutations::Packages::Cleanup::Policy::Update
|
||||
|
|
|
@ -124,6 +124,7 @@ class Packages::Package < ApplicationRecord
|
|||
scope :with_package_type, ->(package_type) { where(package_type: package_type) }
|
||||
scope :without_package_type, ->(package_type) { where.not(package_type: package_type) }
|
||||
scope :displayable, -> { with_status(DISPLAYABLE_STATUSES) }
|
||||
scope :including_project_full_path, -> { includes(project: :route) }
|
||||
scope :including_project_route, -> { includes(project: { namespace: :route }) }
|
||||
scope :including_tags, -> { includes(:tags) }
|
||||
scope :including_dependency_links, -> { includes(dependency_links: :dependency) }
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Packages
|
||||
class MarkPackagesForDestructionService
|
||||
include BaseServiceUtility
|
||||
|
||||
BATCH_SIZE = 20
|
||||
|
||||
UNAUTHORIZED_RESPONSE = ServiceResponse.error(
|
||||
message: "You don't have the permission to perform this action",
|
||||
reason: :unauthorized
|
||||
).freeze
|
||||
|
||||
ERROR_RESPONSE = ServiceResponse.error(
|
||||
message: 'Failed to mark the packages as pending destruction'
|
||||
).freeze
|
||||
|
||||
SUCCESS_RESPONSE = ServiceResponse.success(
|
||||
message: 'Packages were successfully marked as pending destruction'
|
||||
).freeze
|
||||
|
||||
# Initialize this service with the given packages and user.
|
||||
#
|
||||
# * `packages`: must be an ActiveRecord relationship.
|
||||
# * `current_user`: an User object. Could be nil.
|
||||
def initialize(packages:, current_user: nil)
|
||||
@packages = packages
|
||||
@current_user = current_user
|
||||
end
|
||||
|
||||
def execute(batch_size: BATCH_SIZE)
|
||||
no_access = false
|
||||
min_batch_size = [batch_size, BATCH_SIZE].min
|
||||
|
||||
@packages.each_batch(of: min_batch_size) do |batched_packages|
|
||||
loaded_packages = batched_packages.including_project_full_path.to_a
|
||||
|
||||
break no_access = true unless can_destroy_packages?(loaded_packages)
|
||||
|
||||
::Packages::Package.id_in(loaded_packages.map(&:id))
|
||||
.update_all(status: :pending_destruction)
|
||||
|
||||
sync_maven_metadata(loaded_packages)
|
||||
mark_package_files_for_destruction(loaded_packages)
|
||||
end
|
||||
|
||||
return UNAUTHORIZED_RESPONSE if no_access
|
||||
|
||||
SUCCESS_RESPONSE
|
||||
rescue StandardError
|
||||
ERROR_RESPONSE
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mark_package_files_for_destruction(packages)
|
||||
::Packages::MarkPackageFilesForDestructionWorker.bulk_perform_async_with_contexts(
|
||||
packages,
|
||||
arguments_proc: -> (package) { package.id },
|
||||
context_proc: -> (package) { { project: package.project, user: @current_user } }
|
||||
)
|
||||
end
|
||||
|
||||
def sync_maven_metadata(packages)
|
||||
maven_packages_with_version = packages.select { |pkg| pkg.maven? && pkg.version? }
|
||||
::Packages::Maven::Metadata::SyncWorker.bulk_perform_async_with_contexts(
|
||||
maven_packages_with_version,
|
||||
arguments_proc: -> (package) { [@current_user.id, package.project_id, package.name] },
|
||||
context_proc: -> (package) { { project: package.project, user: @current_user } }
|
||||
)
|
||||
end
|
||||
|
||||
def can_destroy_packages?(packages)
|
||||
packages.all? do |package|
|
||||
can?(@current_user, :destroy_package, package)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2426,6 +2426,24 @@ Input type: `DestroyPackageFilesInput`
|
|||
| <a id="mutationdestroypackagefilesclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationdestroypackagefileserrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
|
||||
### `Mutation.destroyPackages`
|
||||
|
||||
Input type: `DestroyPackagesInput`
|
||||
|
||||
#### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationdestroypackagesclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationdestroypackagesids"></a>`ids` | [`[PackagesPackageID!]!`](#packagespackageid) | Global IDs of the Packages. Max 20. |
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationdestroypackagesclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationdestroypackageserrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
|
||||
### `Mutation.destroySnippet`
|
||||
|
||||
Input type: `DestroySnippetInput`
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 21 KiB |
Binary file not shown.
Before Width: | Height: | Size: 55 KiB |
|
@ -463,8 +463,9 @@ Without the `config.extend_remember_period` flag, you would be forced to sign in
|
|||
- [Create users](account/create_accounts.md)
|
||||
- [Sign in to your GitLab account](../../topics/authentication/index.md)
|
||||
- [Change your password](user_passwords.md)
|
||||
- [Receive emails for sign-ins from unknown IP addresses or devices](unknown_sign_in_notification.md)
|
||||
- [Receive emails for attempted sign-ins using a wrong two-factor authentication code](wrong_two_factor_authentication_code_notification.md)
|
||||
- Receive emails for:
|
||||
- [Sign-ins from unknown IP addresses or devices](notifications.md#notifications-for-unknown-sign-ins)
|
||||
- [Attempted sign-ins using wrong two-factor authentication codes](notifications.md#notifications-for-attempted-sign-in-using-wrong-two-factor-authentication-codes)
|
||||
- Manage applications that can [use GitLab as an OAuth provider](../../integration/oauth_provider.md#introduction-to-oauth)
|
||||
- Manage [personal access tokens](personal_access_tokens.md) to access your account via API and authorized applications
|
||||
- Manage [SSH keys](../ssh.md) to access your account via SSH
|
||||
|
|
|
@ -286,6 +286,36 @@ By default, you don't receive notifications for issues, merge requests, or epics
|
|||
To always receive notifications on your own issues, merge requests, and so on, turn on
|
||||
[notifications about your own activity](#global-notification-settings).
|
||||
|
||||
## Notifications for unknown sign-ins
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/27211) in GitLab 13.0.
|
||||
|
||||
NOTE:
|
||||
This feature is enabled by default for self-managed instances. Administrators may disable this feature
|
||||
through the [Sign-in restrictions](../admin_area/settings/sign_in_restrictions.md#email-notification-for-unknown-sign-ins) section of the UI.
|
||||
The feature is always enabled on GitLab.com.
|
||||
|
||||
When a user successfully signs in from a previously unknown IP address or device,
|
||||
GitLab notifies the user by email. In this way, GitLab proactively alerts users of potentially
|
||||
malicious or unauthorized sign-ins.
|
||||
|
||||
GitLab uses several methods to identify a known sign-in. All methods must fail for a notification email to be sent.
|
||||
|
||||
- Last sign-in IP: The current sign-in IP address is checked against the last sign-in
|
||||
IP address.
|
||||
- Current active sessions: If the user has an existing active session from the
|
||||
same IP address. See [Active Sessions](active_sessions.md).
|
||||
- Cookie: After successful sign in, an encrypted cookie is stored in the browser.
|
||||
This cookie is set to expire 14 days after the last successful sign in.
|
||||
|
||||
## Notifications for attempted sign-in using wrong two-factor authentication codes
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/374740) in GitLab 15.5.
|
||||
|
||||
GitLab sends you an email notification if it detects an attempt to sign in to your account using a wrong two-factor
|
||||
authentication (2FA) code. This can help you detect that a bad actor gained access to your username and password, and is trying
|
||||
to brute force 2FA.
|
||||
|
||||
## Notifications on designs
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217095) in GitLab 13.6.
|
||||
|
|
|
@ -1,32 +1,11 @@
|
|||
---
|
||||
stage: Manage
|
||||
group: Authentication and Authorization
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
redirect_to: 'notifications.md'
|
||||
remove_date: '2023-01-15'
|
||||
---
|
||||
|
||||
# Email notification for unknown sign-ins **(FREE)**
|
||||
This document was moved to [another location](notifications.md).
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/27211) in GitLab 13.0.
|
||||
|
||||
NOTE:
|
||||
This feature is enabled by default for self-managed instances. Administrators may disable this feature
|
||||
through the [Sign-in restrictions](../admin_area/settings/sign_in_restrictions.md#email-notification-for-unknown-sign-ins) section of the UI.
|
||||
The feature is always enabled on GitLab.com.
|
||||
|
||||
When a user successfully signs in from a previously unknown IP address or device,
|
||||
GitLab notifies the user by email. In this way, GitLab proactively alerts users of potentially
|
||||
malicious or unauthorized sign-ins.
|
||||
|
||||
There are several methods used to identify a known sign-in. All methods must fail
|
||||
for a notification email to be sent.
|
||||
|
||||
- Last sign-in IP: The current sign-in IP address is checked against the last sign-in
|
||||
IP address.
|
||||
- Current active sessions: If the user has an existing active session from the
|
||||
same IP address. See [Active Sessions](active_sessions.md).
|
||||
- Cookie: After successful sign in, an encrypted cookie is stored in the browser.
|
||||
This cookie is set to expire 14 days after the last successful sign in.
|
||||
|
||||
## Example notification email
|
||||
|
||||
![Unknown sign in email](img/unknown_sign_in_email_v14_0.png)
|
||||
<!-- This redirect file can be deleted after <2023-01-15>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
---
|
||||
stage: Manage
|
||||
group: Authentication and Authorization
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# Email notification for attempted sign-in using wrong two-factor authentication code **(FREE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/374740) in GitLab 15.5.
|
||||
|
||||
GitLab sends you an email notification if it detects an attempt to sign in to your account using a wrong two-factor authentication code. This way, GitLab proactively alerts you of potentially malicious or unauthorized sign-ins, in case a bad actor gained access to your username and password, and is trying to bruteforce two-factor authentication.
|
||||
|
||||
## Example notification email
|
||||
|
||||
![Incorrect two-factor code email](img/wrong_two_factor_authentication_code_v15_5.png)
|
|
@ -24,3 +24,8 @@ before_script:
|
|||
mix:
|
||||
script:
|
||||
- mix test
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
script: echo "Define your deployment script!"
|
||||
environment: production
|
||||
|
|
|
@ -35,3 +35,8 @@ test:
|
|||
- $CI_PROJECT_DIR/coverage
|
||||
reports:
|
||||
junit: report.xml
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
script: echo "Define your deployment script!"
|
||||
environment: production
|
||||
|
|
|
@ -28,3 +28,8 @@ compile:
|
|||
artifacts:
|
||||
paths:
|
||||
- mybinaries
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
script: echo "Define your deployment script!"
|
||||
environment: production
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# You can copy and paste this template into a new `.gitlab-ci.yml` file.
|
||||
# You should not add this template to an existing `.gitlab-ci.yml` file by using the `include:` keyword.
|
||||
# To contribute improvements to CI/CD templates, please follow the Development guide at:
|
||||
# https://docs.gitlab.com/ee/development/cicd/templates.html
|
||||
# This specific template is located at:
|
||||
|
@ -39,3 +41,8 @@ test:
|
|||
paths:
|
||||
- build
|
||||
- .gradle
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
script: echo "Define your deployment script!"
|
||||
environment: production
|
||||
|
|
|
@ -46,3 +46,8 @@ before_script:
|
|||
build:
|
||||
script:
|
||||
- ./gradlew build
|
||||
|
||||
deploy:
|
||||
stage: deploy
|
||||
script: echo "Define your deployment script!"
|
||||
environment: production
|
||||
|
|
|
@ -232,11 +232,7 @@ RSpec.describe GitlabSchema do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.parse_gid' do
|
||||
let_it_be(:global_id) { 'gid://gitlab/TestOne/2147483647' }
|
||||
|
||||
subject(:parse_gid) { described_class.parse_gid(global_id) }
|
||||
|
||||
context 'for gid parsing' do
|
||||
before do
|
||||
test_base = Class.new
|
||||
test_one = Class.new(test_base)
|
||||
|
@ -249,65 +245,84 @@ RSpec.describe GitlabSchema do
|
|||
stub_const('TestThree', test_three)
|
||||
end
|
||||
|
||||
it 'parses the gid' do
|
||||
gid = parse_gid
|
||||
describe '.parse_gid' do
|
||||
let_it_be(:global_id) { 'gid://gitlab/TestOne/2147483647' }
|
||||
|
||||
expect(gid.model_id).to eq '2147483647'
|
||||
expect(gid.model_class).to eq TestOne
|
||||
end
|
||||
subject(:parse_gid) { described_class.parse_gid(global_id) }
|
||||
|
||||
context 'when gid is malformed' do
|
||||
let_it_be(:global_id) { 'malformed://gitlab/TestOne/2147483647' }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { parse_gid }
|
||||
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid GitLab ID.")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when using expected_type' do
|
||||
it 'accepts a single type' do
|
||||
gid = described_class.parse_gid(global_id, expected_type: TestOne)
|
||||
it 'parses the gid' do
|
||||
gid = parse_gid
|
||||
|
||||
expect(gid.model_id).to eq '2147483647'
|
||||
expect(gid.model_class).to eq TestOne
|
||||
end
|
||||
|
||||
it 'accepts an ancestor type' do
|
||||
gid = described_class.parse_gid(global_id, expected_type: TestBase)
|
||||
context 'when gid is malformed' do
|
||||
let_it_be(:global_id) { 'malformed://gitlab/TestOne/2147483647' }
|
||||
|
||||
expect(gid.model_class).to eq TestOne
|
||||
it 'raises an error' do
|
||||
expect { parse_gid }
|
||||
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid GitLab ID.")
|
||||
end
|
||||
end
|
||||
|
||||
it 'rejects an unknown type' do
|
||||
expect { described_class.parse_gid(global_id, expected_type: TestTwo) }
|
||||
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid ID for TestTwo.")
|
||||
context 'when using expected_type' do
|
||||
it 'accepts a single type' do
|
||||
gid = described_class.parse_gid(global_id, expected_type: TestOne)
|
||||
|
||||
expect(gid.model_class).to eq TestOne
|
||||
end
|
||||
|
||||
it 'accepts an ancestor type' do
|
||||
gid = described_class.parse_gid(global_id, expected_type: TestBase)
|
||||
|
||||
expect(gid.model_class).to eq TestOne
|
||||
end
|
||||
|
||||
it 'rejects an unknown type' do
|
||||
expect { described_class.parse_gid(global_id, expected_type: TestTwo) }
|
||||
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid ID for TestTwo.")
|
||||
end
|
||||
|
||||
context 'when expected_type is an array' do
|
||||
subject(:parse_gid) { described_class.parse_gid(global_id, expected_type: [TestOne, TestTwo]) }
|
||||
|
||||
context 'when global_id is of type TestOne' do
|
||||
it 'returns an object of an expected type' do
|
||||
expect(parse_gid.model_class).to eq TestOne
|
||||
end
|
||||
end
|
||||
|
||||
context 'when global_id is of type TestTwo' do
|
||||
let_it_be(:global_id) { 'gid://gitlab/TestTwo/2147483647' }
|
||||
|
||||
it 'returns an object of an expected type' do
|
||||
expect(parse_gid.model_class).to eq TestTwo
|
||||
end
|
||||
end
|
||||
|
||||
context 'when global_id is of type TestThree' do
|
||||
let_it_be(:global_id) { 'gid://gitlab/TestThree/2147483647' }
|
||||
|
||||
it 'rejects an unknown type' do
|
||||
expect { parse_gid }
|
||||
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid ID for TestOne, TestTwo.")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when expected_type is an array' do
|
||||
subject(:parse_gid) { described_class.parse_gid(global_id, expected_type: [TestOne, TestTwo]) }
|
||||
describe '.parse_gids' do
|
||||
let_it_be(:global_ids) { %w[gid://gitlab/TestOne/123 gid://gitlab/TestOne/456] }
|
||||
|
||||
context 'when global_id is of type TestOne' do
|
||||
it 'returns an object of an expected type' do
|
||||
expect(parse_gid.model_class).to eq TestOne
|
||||
end
|
||||
end
|
||||
subject(:parse_gids) { described_class.parse_gids(global_ids, expected_type: TestOne) }
|
||||
|
||||
context 'when global_id is of type TestTwo' do
|
||||
let_it_be(:global_id) { 'gid://gitlab/TestTwo/2147483647' }
|
||||
|
||||
it 'returns an object of an expected type' do
|
||||
expect(parse_gid.model_class).to eq TestTwo
|
||||
end
|
||||
end
|
||||
|
||||
context 'when global_id is of type TestThree' do
|
||||
let_it_be(:global_id) { 'gid://gitlab/TestThree/2147483647' }
|
||||
|
||||
it 'rejects an unknown type' do
|
||||
expect { parse_gid }
|
||||
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, "#{global_id} is not a valid ID for TestOne, TestTwo.")
|
||||
end
|
||||
end
|
||||
it 'parses the gids' do
|
||||
expect(described_class).to receive(:parse_gid).with('gid://gitlab/TestOne/123', expected_type: TestOne).and_call_original
|
||||
expect(described_class).to receive(:parse_gid).with('gid://gitlab/TestOne/456', expected_type: TestOne).and_call_original
|
||||
expect(parse_gids.map(&:model_id)).to eq %w[123 456]
|
||||
expect(parse_gids.map(&:model_class)).to match_array [TestOne, TestOne]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,29 +2,28 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
# Snippet visibility scenarios are included in more details in spec/support/snippet_visibility.rb
|
||||
# Snippet visibility scenarios are included in more details in spec/finders/snippets_finder_spec.rb
|
||||
RSpec.describe ProjectSnippetPolicy do
|
||||
let_it_be(:group) { create(:group, :public) }
|
||||
let_it_be(:regular_user) { create(:user) }
|
||||
let_it_be(:other_user) { create(:user) }
|
||||
let_it_be(:external_user) { create(:user, :external) }
|
||||
let_it_be(:project) { create(:project, :public) }
|
||||
|
||||
let(:snippet) { create(:project_snippet, snippet_visibility, project: project, author: author) }
|
||||
let(:author) { other_user }
|
||||
let(:author_permissions) do
|
||||
let_it_be(:author) { create(:user) }
|
||||
let_it_be(:author_permissions) do
|
||||
[
|
||||
:update_snippet,
|
||||
:admin_snippet
|
||||
]
|
||||
end
|
||||
|
||||
let(:snippet) { build(:project_snippet, snippet_visibility, project: project, author: author) }
|
||||
|
||||
subject { described_class.new(current_user, snippet) }
|
||||
|
||||
shared_examples 'regular user access rights' do
|
||||
shared_examples 'regular user member permissions' do
|
||||
context 'not snippet author' do
|
||||
context 'project team member (non guest)' do
|
||||
context 'member (guest)' do
|
||||
before do
|
||||
project.add_developer(current_user)
|
||||
membership_target.add_guest(current_user)
|
||||
end
|
||||
|
||||
it do
|
||||
|
@ -33,25 +32,35 @@ RSpec.describe ProjectSnippetPolicy do
|
|||
end
|
||||
end
|
||||
|
||||
context 'project team member (guest)' do
|
||||
context 'member (reporter)' do
|
||||
before do
|
||||
project.add_guest(current_user)
|
||||
membership_target.add_reporter(current_user)
|
||||
end
|
||||
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note)
|
||||
expect_disallowed(:admin_snippet)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'project team member (maintainer)' do
|
||||
context 'member (developer)' do
|
||||
before do
|
||||
project.add_maintainer(current_user)
|
||||
membership_target.add_developer(current_user)
|
||||
end
|
||||
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note)
|
||||
expect_allowed(*author_permissions)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'member (maintainer)' do
|
||||
before do
|
||||
membership_target.add_maintainer(current_user)
|
||||
end
|
||||
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note, *author_permissions)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -59,196 +68,263 @@ RSpec.describe ProjectSnippetPolicy do
|
|||
context 'snippet author' do
|
||||
let(:author) { current_user }
|
||||
|
||||
context 'project member (non guest)' do
|
||||
context 'member (guest)' do
|
||||
before do
|
||||
project.add_developer(current_user)
|
||||
membership_target.add_guest(current_user)
|
||||
end
|
||||
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note)
|
||||
expect_allowed(*author_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'project member (guest)' do
|
||||
before do
|
||||
project.add_guest(current_user)
|
||||
end
|
||||
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note)
|
||||
expect_allowed(:read_snippet, :create_note, :update_snippet)
|
||||
expect_disallowed(:admin_snippet)
|
||||
end
|
||||
end
|
||||
|
||||
context 'project team member (maintainer)' do
|
||||
context 'member (reporter)' do
|
||||
before do
|
||||
project.add_maintainer(current_user)
|
||||
membership_target.add_reporter(current_user)
|
||||
end
|
||||
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note)
|
||||
expect_allowed(*author_permissions)
|
||||
expect_allowed(:read_snippet, :create_note, *author_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'not a project member' do
|
||||
context 'member (developer)' do
|
||||
before do
|
||||
membership_target.add_developer(current_user)
|
||||
end
|
||||
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note)
|
||||
expect_disallowed(:admin_snippet)
|
||||
expect_allowed(:read_snippet, :create_note, *author_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'member (maintainer)' do
|
||||
before do
|
||||
membership_target.add_maintainer(current_user)
|
||||
end
|
||||
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note, *author_permissions)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'public snippet' do
|
||||
let(:snippet_visibility) { :public }
|
||||
shared_examples 'regular user non-member author permissions' do
|
||||
let(:author) { current_user }
|
||||
|
||||
context 'no user' do
|
||||
let(:current_user) { nil }
|
||||
|
||||
it do
|
||||
expect_allowed(:read_snippet)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'regular user' do
|
||||
let(:current_user) { regular_user }
|
||||
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
|
||||
it_behaves_like 'regular user access rights'
|
||||
end
|
||||
|
||||
context 'external user' do
|
||||
let(:current_user) { external_user }
|
||||
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
|
||||
context 'project team member' do
|
||||
before do
|
||||
project.add_developer(external_user)
|
||||
end
|
||||
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
end
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note, :update_snippet)
|
||||
expect_disallowed(:admin_snippet)
|
||||
end
|
||||
end
|
||||
|
||||
context 'internal snippet' do
|
||||
let(:snippet_visibility) { :internal }
|
||||
context 'when project is public' do
|
||||
let_it_be(:project) { create(:project, :public, group: group) }
|
||||
|
||||
context 'no user' do
|
||||
let(:current_user) { nil }
|
||||
context 'with public snippet' do
|
||||
let(:snippet_visibility) { :public }
|
||||
|
||||
it do
|
||||
expect_disallowed(:read_snippet)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
end
|
||||
context 'no user' do
|
||||
let(:current_user) { nil }
|
||||
|
||||
context 'regular user' do
|
||||
let(:current_user) { regular_user }
|
||||
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
|
||||
it_behaves_like 'regular user access rights'
|
||||
end
|
||||
|
||||
context 'external user' do
|
||||
let(:current_user) { external_user }
|
||||
|
||||
it do
|
||||
expect_disallowed(:read_snippet, :create_note)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
|
||||
context 'project team member' do
|
||||
before do
|
||||
project.add_developer(external_user)
|
||||
it do
|
||||
expect_allowed(:read_snippet)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'regular user' do
|
||||
let(:current_user) { regular_user }
|
||||
let(:membership_target) { project }
|
||||
|
||||
context 'when user is not a member' do
|
||||
context 'and is not the snippet author' do
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and is the snippet author' do
|
||||
it_behaves_like 'regular user non-member author permissions'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is a member' do
|
||||
it_behaves_like 'regular user member permissions'
|
||||
end
|
||||
end
|
||||
|
||||
context 'external user' do
|
||||
let(:current_user) { external_user }
|
||||
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'private snippet' do
|
||||
let(:snippet_visibility) { :private }
|
||||
context 'when user is a member' do
|
||||
before do
|
||||
project.add_developer(external_user)
|
||||
end
|
||||
|
||||
context 'no user' do
|
||||
let(:current_user) { nil }
|
||||
|
||||
it do
|
||||
expect_disallowed(:read_snippet)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'regular user' do
|
||||
let(:current_user) { regular_user }
|
||||
|
||||
it do
|
||||
expect_disallowed(:read_snippet, :create_note)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
|
||||
it_behaves_like 'regular user access rights'
|
||||
end
|
||||
|
||||
context 'external user' do
|
||||
let(:current_user) { external_user }
|
||||
|
||||
it do
|
||||
expect_disallowed(:read_snippet, :create_note)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
|
||||
context 'project team member' do
|
||||
before do
|
||||
project.add_developer(current_user)
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with internal snippet' do
|
||||
let(:snippet_visibility) { :internal }
|
||||
|
||||
context 'no user' do
|
||||
let(:current_user) { nil }
|
||||
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note)
|
||||
expect_disallowed(:read_snippet)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'admin user' do
|
||||
let(:snippet_visibility) { :private }
|
||||
let(:current_user) { create(:admin) }
|
||||
context 'regular user' do
|
||||
let(:current_user) { regular_user }
|
||||
let(:membership_target) { project }
|
||||
|
||||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note)
|
||||
expect_allowed(*author_permissions)
|
||||
context 'when user is not a member' do
|
||||
context 'and is not the snippet author' do
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and is the snippet author' do
|
||||
it_behaves_like 'regular user non-member author permissions'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is a member' do
|
||||
it_behaves_like 'regular user member permissions'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
context 'external user' do
|
||||
let(:current_user) { external_user }
|
||||
|
||||
it do
|
||||
expect_disallowed(:read_snippet, :create_note)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
|
||||
context 'when user is a member' do
|
||||
before do
|
||||
project.add_developer(external_user)
|
||||
end
|
||||
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with private snippet' do
|
||||
let(:snippet_visibility) { :private }
|
||||
|
||||
context 'no user' do
|
||||
let(:current_user) { nil }
|
||||
|
||||
it do
|
||||
expect_disallowed(:read_snippet)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'regular user' do
|
||||
let(:current_user) { regular_user }
|
||||
let(:membership_target) { project }
|
||||
|
||||
context 'when user is not a member' do
|
||||
context 'and is not the snippet author' do
|
||||
it do
|
||||
expect_disallowed(:read_snippet, :create_note)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and is the snippet author' do
|
||||
it_behaves_like 'regular user non-member author permissions'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is a member' do
|
||||
it_behaves_like 'regular user member permissions'
|
||||
end
|
||||
end
|
||||
|
||||
context 'inherited user' do
|
||||
let(:current_user) { regular_user }
|
||||
let(:membership_target) { group }
|
||||
|
||||
it_behaves_like 'regular user member permissions'
|
||||
end
|
||||
|
||||
context 'external user' do
|
||||
let(:current_user) { external_user }
|
||||
|
||||
it do
|
||||
expect_disallowed(:read_snippet, :create_note)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
|
||||
context 'when user is a member' do
|
||||
before do
|
||||
project.add_developer(current_user)
|
||||
end
|
||||
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'admin user' do
|
||||
let(:snippet_visibility) { :private }
|
||||
let(:current_user) { create(:admin) }
|
||||
|
||||
context 'when admin mode is enabled', :enable_admin_mode do
|
||||
it do
|
||||
expect_allowed(:read_snippet, :create_note)
|
||||
expect_allowed(*author_permissions)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when admin mode is disabled' do
|
||||
it do
|
||||
expect_disallowed(:read_snippet, :create_note)
|
||||
expect_disallowed(*author_permissions)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project is private' do
|
||||
let_it_be(:project) { create(:project, :private, group: group) }
|
||||
|
||||
let(:snippet_visibility) { :private }
|
||||
|
||||
context 'inherited user' do
|
||||
let(:current_user) { regular_user }
|
||||
let(:membership_target) { group }
|
||||
|
||||
it_behaves_like 'regular user member permissions'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Destroying multiple packages' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:project1) { create(:project) }
|
||||
let_it_be(:project2) { create(:project) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be_with_reload(:packages1) { create_list(:package, 3, project: project1) }
|
||||
let_it_be_with_reload(:packages2) { create_list(:package, 2, project: project2) }
|
||||
|
||||
let(:ids) { packages1.append(packages2).flatten.map(&:to_global_id).map(&:to_s) }
|
||||
|
||||
let(:query) do
|
||||
<<~GQL
|
||||
errors
|
||||
GQL
|
||||
end
|
||||
|
||||
let(:params) do
|
||||
{
|
||||
ids: ids
|
||||
}
|
||||
end
|
||||
|
||||
let(:mutation) { graphql_mutation(:destroy_packages, params, query) }
|
||||
|
||||
describe 'post graphql mutation' do
|
||||
subject(:mutation_request) { post_graphql_mutation(mutation, current_user: user) }
|
||||
|
||||
shared_examples 'destroying the packages' do
|
||||
it 'marks the packages as pending destruction' do
|
||||
expect { mutation_request }.to change { ::Packages::Package.pending_destruction.count }.by(5)
|
||||
end
|
||||
|
||||
it_behaves_like 'returning response status', :success
|
||||
end
|
||||
|
||||
shared_examples 'denying the mutation request' do
|
||||
|response = ::Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR|
|
||||
it 'does not mark the packages as pending destruction' do
|
||||
expect { mutation_request }.not_to change { ::Packages::Package.pending_destruction.count }
|
||||
expect_graphql_errors_to_include(response)
|
||||
end
|
||||
|
||||
it_behaves_like 'returning response status', :success
|
||||
end
|
||||
|
||||
context 'with valid params' do
|
||||
where(:user_role, :shared_examples_name) do
|
||||
:maintainer | 'destroying the packages'
|
||||
:developer | 'denying the mutation request'
|
||||
:reporter | 'denying the mutation request'
|
||||
:guest | 'denying the mutation request'
|
||||
:not_in_project | 'denying the mutation request'
|
||||
end
|
||||
|
||||
with_them do
|
||||
before do
|
||||
unless user_role == :not_in_project
|
||||
project1.send("add_#{user_role}", user)
|
||||
project2.send("add_#{user_role}", user)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like params[:shared_examples_name]
|
||||
end
|
||||
|
||||
context 'for over the limit' do
|
||||
before do
|
||||
project1.add_maintainer(user)
|
||||
project2.add_maintainer(user)
|
||||
stub_const("Mutations::Packages::BulkDestroy::MAX_PACKAGES", 2)
|
||||
end
|
||||
|
||||
it_behaves_like 'denying the mutation request', ::Mutations::Packages::BulkDestroy::TOO_MANY_IDS_ERROR
|
||||
end
|
||||
|
||||
context 'with packages outside of the project' do
|
||||
before do
|
||||
project1.add_maintainer(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'denying the mutation request'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid params' do
|
||||
let(:ids) { 'foo' }
|
||||
|
||||
it_behaves_like 'denying the mutation request', 'invalid value for id'
|
||||
end
|
||||
|
||||
context 'with multi mutations' do
|
||||
let(:package1) { packages1.first }
|
||||
let(:package2) { packages2.first }
|
||||
let(:query) do
|
||||
<<~QUERY
|
||||
mutation {
|
||||
a: destroyPackages(input: { ids: ["#{package1.to_global_id}"]}) {
|
||||
errors
|
||||
}
|
||||
b: destroyPackages(input: { ids: ["#{package2.to_global_id}"]}) {
|
||||
errors
|
||||
}
|
||||
}
|
||||
QUERY
|
||||
end
|
||||
|
||||
subject(:mutation_request) { post_graphql(query, current_user: user) }
|
||||
|
||||
before do
|
||||
project1.add_maintainer(user)
|
||||
project2.add_maintainer(user)
|
||||
end
|
||||
|
||||
it 'executes the first mutation but not the second one' do
|
||||
expect { mutation_request }.to change { package1.reload.status }.from('default').to('pending_destruction')
|
||||
.and not_change { package2.reload.status }
|
||||
expect_graphql_errors_to_include('"destroyPackages" field can be requested only for 1 Mutation(s) at a time.')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,107 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::MarkPackagesForDestructionService, :sidekiq_inline do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be_with_reload(:packages) { create_list(:npm_package, 3, project: project) }
|
||||
|
||||
let(:user) { project.owner }
|
||||
|
||||
# The service only accepts ActiveRecord relationships and not arrays.
|
||||
let(:service) { described_class.new(packages: ::Packages::Package.id_in(package_ids), current_user: user) }
|
||||
let(:package_ids) { packages.map(&:id) }
|
||||
|
||||
describe '#execute' do
|
||||
subject { service.execute }
|
||||
|
||||
context 'when the user is authorized' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
context 'when it is successful' do
|
||||
it 'marks the packages as pending destruction' do
|
||||
expect(::Packages::Maven::Metadata::SyncService).not_to receive(:new)
|
||||
|
||||
expect { subject }.to change { ::Packages::Package.pending_destruction.count }.from(0).to(3)
|
||||
.and change { Packages::PackageFile.pending_destruction.count }.from(0).to(3)
|
||||
packages.each { |pkg| expect(pkg.reload).to be_pending_destruction }
|
||||
|
||||
expect(subject).to be_a(ServiceResponse)
|
||||
expect(subject).to be_success
|
||||
expect(subject.message).to eq('Packages were successfully marked as pending destruction')
|
||||
end
|
||||
|
||||
context 'with maven packages' do
|
||||
let_it_be_with_reload(:packages) { create_list(:maven_package, 3, project: project) }
|
||||
|
||||
it 'marks the packages as pending destruction' do
|
||||
expect(::Packages::Maven::Metadata::SyncService).to receive(:new).once.and_call_original
|
||||
|
||||
expect { subject }.to change { ::Packages::Package.pending_destruction.count }.from(0).to(3)
|
||||
.and change { Packages::PackageFile.pending_destruction.count }.from(0).to(9)
|
||||
packages.each { |pkg| expect(pkg.reload).to be_pending_destruction }
|
||||
|
||||
expect(subject).to be_a(ServiceResponse)
|
||||
expect(subject).to be_success
|
||||
expect(subject.message).to eq('Packages were successfully marked as pending destruction')
|
||||
end
|
||||
|
||||
context 'without version' do
|
||||
before do
|
||||
::Packages::Package.id_in(package_ids).update_all(version: nil)
|
||||
end
|
||||
|
||||
it 'marks the packages as pending destruction' do
|
||||
expect(::Packages::Maven::Metadata::SyncService).not_to receive(:new)
|
||||
|
||||
expect { subject }.to change { ::Packages::Package.pending_destruction.count }.from(0).to(3)
|
||||
.and change { Packages::PackageFile.pending_destruction.count }.from(0).to(9)
|
||||
packages.each { |pkg| expect(pkg.reload).to be_pending_destruction }
|
||||
|
||||
expect(subject).to be_a(ServiceResponse)
|
||||
expect(subject).to be_success
|
||||
expect(subject.message).to eq('Packages were successfully marked as pending destruction')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when it is not successful' do
|
||||
before do
|
||||
allow(service).to receive(:can_destroy_packages?).and_raise(StandardError, 'test')
|
||||
end
|
||||
|
||||
it 'returns an error ServiceResponse' do
|
||||
expect(::Packages::Maven::Metadata::SyncService).not_to receive(:new)
|
||||
|
||||
expect { subject }.to not_change { ::Packages::Package.pending_destruction.count }
|
||||
.and not_change { ::Packages::PackageFile.pending_destruction.count }
|
||||
|
||||
expect(subject).to be_a(ServiceResponse)
|
||||
expect(subject).to be_error
|
||||
expect(subject.message).to eq("Failed to mark the packages as pending destruction")
|
||||
expect(subject.status).to eq(:error)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user is not authorized' do
|
||||
let(:user) { nil }
|
||||
|
||||
it 'returns an error ServiceResponse' do
|
||||
expect(::Packages::Maven::Metadata::SyncService).not_to receive(:new)
|
||||
|
||||
expect { subject }.to not_change { ::Packages::Package.pending_destruction.count }
|
||||
.and not_change { ::Packages::PackageFile.pending_destruction.count }
|
||||
|
||||
expect(subject).to be_a(ServiceResponse)
|
||||
expect(subject).to be_error
|
||||
expect(subject.message).to eq("You don't have the permission to perform this action")
|
||||
expect(subject.status).to eq(:error)
|
||||
expect(subject.reason).to eq(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -115,9 +115,7 @@ RSpec.describe RepositoryForkWorker do
|
|||
|
||||
context 'project ID, storage and repo paths passed' do
|
||||
def perform!
|
||||
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
|
||||
subject.perform(forked_project.id, TestEnv.repos_path, project.disk_path)
|
||||
end
|
||||
subject.perform(forked_project.id, 'repos/path', project.disk_path)
|
||||
end
|
||||
|
||||
it_behaves_like 'RepositoryForkWorker performing'
|
||||
|
|
Loading…
Reference in New Issue