diff --git a/doc/administration/settings/visibility_and_access_controls.md b/doc/administration/settings/visibility_and_access_controls.md index 0ad0f4c5dbd992a0f7c2d9f31df9eee90b0b216b..9d888d673e6daa6a951c2755a916600a597a9a57 100644 --- a/doc/administration/settings/visibility_and_access_controls.md +++ b/doc/administration/settings/visibility_and_access_controls.md @@ -98,6 +98,7 @@ To disable the restriction: - Enabled delayed deletion for projects by default [on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/393622) and [on GitLab Self-Managed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119606) in GitLab 16.0. - [Changed to default behavior for groups](https://gitlab.com/gitlab-org/gitlab/-/issues/389557) on the Premium and Ultimate tier in GitLab 16.0. - [Moved](https://gitlab.com/groups/gitlab-org/-/epics/17208) from GitLab Premium to GitLab Free in 18.0. +- [Instance setting](#immediate-deletion) to allow immediate deletion for groups or projects scheduled for deletion [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/205556) in GitLab 18.5. Enabled by default. Disabled on GitLab.com and Dedicated. {{< /history >}} @@ -124,8 +125,10 @@ To configure deletion protection for groups and projects: {{< history >}} -- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/561680) in GitLab 18.4 [with a flag](../../administration/feature_flags/_index.md) named `disallow_immediate_deletion`. Disabled by default. -- [Replaced](https://gitlab.com/gitlab-org/gitlab/-/issues/569453) in GitLab 18.5 by an instance setting to allow immediate deletion of groups and projects scheduled for deletion. [Controlled by a flag](../feature_flags/_index.md) named `allow_immediate_namespaces_deletion`. Feature flag is disabled by default. +- Instance setting to allow immediate deletion for groups or projects scheduled for deletion + [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/205556) in GitLab 18.5 + [with a flag](../feature_flags/_index.md) named `allow_immediate_namespaces_deletion`. + Enabled by default on self-managed, but disabled on GitLab.com and Dedicated. {{< /history >}} @@ -136,19 +139,20 @@ For more information, see the history. {{< /alert >}} -Immediately delete groups and projects scheduled for deletion. This action bypasses the configured retention period and deletes groups or projects immediately. +By default, immediate deletion is allowed for groups and projects marked for deletion. This allows users +to effectively bypass the configured retention period and delete groups or projects immediately. -To turn off immediate deletion for groups and projects: +This can be disabled, so that groups and projects are only deleted automatically after the configured retention period: 1. On the left sidebar, at the bottom, select **Admin**. 1. Select **Settings** > **General**. 1. Expand **Visibility and access controls**. -1. Under **Immediate deletion**, clear the checkbox. +1. Scroll to **Immediate deletion** and uncheck the checkbox. 1. Select **Save changes**. {{< alert type="note" >}} -Administrators can still delete groups and projects when this setting is turned off. +Administrators can always immediately delete groups and projects through the Admin pages. {{< /alert >}} diff --git a/doc/api/groups.md b/doc/api/groups.md index 5594bd323f55db1e1eaf06c4288a7dcc0db29f72..4219d6aa65a9d1540642189c562eebf4af7589a4 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -1372,7 +1372,11 @@ curl --request POST --header "PRIVATE-TOKEN: " \ {{< history >}} - Marking groups for deletion [available](https://gitlab.com/groups/gitlab-org/-/epics/17208) on Free tier in GitLab 18.0. -- `permanently_remove` was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/201957) in GitLab 18.4 [with a flag](../administration/feature_flags/_index.md) named `disallow_immediate_deletion`. +- Since GitLab 18.5, `permanently_remove` is [not permitted](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/205572) + when the immediate deletion + [instance setting](../administration/settings/visibility_and_access_controls.md#immediate-deletion) + is disabled (behind [a feature flag](../administration/feature_flags/_index.md) named `allow_immediate_namespaces_deletion`). + The setting is enabled by default on self-managed, but disabled on GitLab.com and Dedicated. {{< /history >}} @@ -1388,14 +1392,23 @@ Marks a group for deletion. Groups are deleted at the end of the retention perio This endpoint can also immediately delete a subgroup that was previously marked for deletion. +{{< alert type="warning" >}} + +On GitLab.com, after a group is deleted, its data is retained for 30 days, and immediate deletion is not available. +If you really need to delete a group immediately on GitLab.com, you can open a [support ticket](https://about.gitlab.com/support/). + +{{< /alert >}} + ```plaintext DELETE /groups/:id ``` Parameters: +| Attribute | Type | Required | Description | +|-----------|----------------|----------|-------------| | `id` | integer or string | yes | The ID or [URL-encoded path](rest/_index.md#namespaced-paths) of the group. | -| `permanently_remove` | boolean/string | no | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/201957) in GitLab 18.4. If `true`, immediately deletes a subgroup that is already marked for deletion. Cannot delete top-level groups. | +| `permanently_remove` | boolean/string | no | If `true`, immediately deletes a subgroup that is already marked for deletion. Cannot delete top-level groups. Disabled on GitLab.com and Dedicated. | | `full_path` | string | no | The full path to the subgroup. Used to confirm deletion of the subgroup. If `permanently_remove` is `true`, this attribute is required. To find the subgroup path, see the [group details](groups.md#get-a-single-group). | The response is `202 Accepted` if the user has authorization. diff --git a/doc/api/projects.md b/doc/api/projects.md index 2210fec19e444d936372ca3f3c5cb5a6c7919b01..2c55de22877e6408ffb836bcb6d8cab9a0a882e2 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -2183,21 +2183,30 @@ Example response: - Immediately deleting projects was [enabled on GitLab.com and GitLab Self-Managed](https://gitlab.com/gitlab-org/gitlab/-/issues/396500) in GitLab 15.11. - [Marking project for deletion was moved](https://gitlab.com/groups/gitlab-org/-/epics/17208) from GitLab Premium to GitLab Free in 18.0. -- `permanently_remove` was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/201957) in GitLab 18.4 [with a flag](../administration/feature_flags/_index.md) named `disallow_immediate_deletion`. +- Since GitLab 18.5, `permanently_remove` is [not permitted](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/205572) + when the immediate deletion + [instance setting](../administration/settings/visibility_and_access_controls.md#immediate-deletion) + is disabled (behind [a feature flag](../administration/feature_flags/_index.md) named `allow_immediate_namespaces_deletion`). + The setting is enabled by default on self-managed, but disabled on GitLab.com and Dedicated. {{< /history >}} -Delete a project. This endpoint: +Prerequisites: + +- You must be an administrator or have the Owner role for the project. + +Marks a project for deletion. Projects are deleted at the end of the retention period: + +- On GitLab.com, projects are retained for 30 days. +- On GitLab Self-Managed, the retention period is controlled by the + [instance settings](../administration/settings/visibility_and_access_controls.md#deletion-protection). -- Deletes a project including all associated resources, including issues and merge requests. -- Marks the project for deletion. On GitLab.com, by default, the deletion happens 30 days later. On GitLab Self-Managed, - the retention period depends on the [instance settings](../administration/settings/visibility_and_access_controls.md#deletion-protection). -- Deletes project immediately if the project is marked for deletion (GitLab 15.11 and later). +This endpoint can also immediately delete a project that was previously marked for deletion. {{< alert type="warning" >}} -The option to delete projects immediately from deletion protection settings in the **Admin** area was -[deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/389557) in GitLab 15.9 and removed in GitLab 16.0. +On GitLab.com, after a project is deleted, its data is retained for 30 days, and immediate deletion is not available. +If you really need to delete a project immediately on GitLab.com, you can open a [support ticket](https://about.gitlab.com/support/). {{< /alert >}} @@ -2211,7 +2220,7 @@ Supported attributes: |:---------------------|:------------------|:---------|:------------| | `id` | integer or string | Yes | The ID or [URL-encoded path of the project](rest/_index.md#namespaced-paths). | | `full_path` | string | no | Full path of project to use with `permanently_remove`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/396500) in GitLab 15.11 for Premium and Ultimate only and moved to GitLab Free in 18.0. To find the project path, use `path_with_namespace` from [get single project](projects.md#get-a-single-project). | -| `permanently_remove` | boolean/string | no | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/201957) in GitLab 18.4. Immediately deletes a project if it is marked for deletion. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/396500) in GitLab 15.11 for Premium and Ultimate only and moved to GitLab Free in 18.0. | +| `permanently_remove` | boolean/string | no | Immediately deletes a project if it is marked for deletion. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/396500) in GitLab 15.11 for Premium and Ultimate only and moved to GitLab Free in 18.0. Disabled on GitLab.com and Dedicated. | ### Restore a project marked for deletion diff --git a/lib/api/groups.rb b/lib/api/groups.rb index ff048e6ee8e54255808561bfac94d09c4f950624..b96df3c60e796018422c5db13e854c4459f7e5bb 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -173,8 +173,8 @@ def present_groups_with_pagination_strategies(params, groups) end def immediately_delete_subgroup_error(group) - if Feature.enabled?(:disallow_immediate_deletion, current_user) - '`permanently_remove` option is not available anymore (behind the :disallow_immediate_deletion feature flag).' + if !Gitlab::CurrentSettings.allow_immediate_namespaces_deletion_for_user?(current_user) + '`permanently_remove` option is not permitted on this instance.' elsif !group.subgroup? '`permanently_remove` option is only available for subgroups.' elsif !group.self_deletion_scheduled? diff --git a/lib/api/projects.rb b/lib/api/projects.rb index ca862b486b072f50bfc2837c8295c9da6ccb7ac3..6270eda286cc81fb12187ecadc3c33f0fad75932 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -86,9 +86,8 @@ def support_order_by_similarity!(attrs) end def immediately_delete_project_error(project) - # Admin frontend uses this endpoint to force-delete projects - if Feature.enabled?(:disallow_immediate_deletion, current_user) && !current_user.can_admin_all_resources? - '`permanently_remove` option is not available anymore (behind the :disallow_immediate_deletion feature flag).' + if !Gitlab::CurrentSettings.allow_immediate_namespaces_deletion_for_user?(current_user) + '`permanently_remove` option is not permitted on this instance.' elsif !project.self_deletion_scheduled? 'Project must be marked for deletion first.' elsif project.full_path != params[:full_path] diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index d1ef32ae5df9342f85dc7459dc82caedc31e586a..9690fe8a21906f4b01ed3d979772538f03acb12c 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -3346,27 +3346,30 @@ def make_upload_request describe "DELETE /groups/:id" do let(:group) { group1 } let(:user) { user1 } + let(:admin_mode) { false } let(:params) { {} } - subject { delete api("/groups/#{group.id}", user), params: params } + subject(:api_request) { delete api("/groups/#{group.id}", user, admin_mode: admin_mode), params: params } shared_examples_for 'immediately enqueues the job to delete the group' do it 'immediately enqueues the job to delete the group', :clean_gitlab_redis_queues do Sidekiq::Testing.fake! do - expect { subject }.to change(GroupDestroyWorker.jobs, :size).by(1) + expect { api_request }.to change(GroupDestroyWorker.jobs, :size).by(1) end expect(response).to have_gitlab_http_status(:accepted) end end - shared_examples_for 'does not immediately enqueues the job to delete the group' do |error_message| + shared_examples_for 'does not immediately enqueues the job to delete the group' do + let(:expected_http_status) { :bad_request } + it 'does not immediately enqueues the job to delete the group', :clean_gitlab_redis_queues do Sidekiq::Testing.fake! do - expect { subject }.not_to change(GroupDestroyWorker.jobs, :size) + expect { api_request }.not_to change(GroupDestroyWorker.jobs, :size) end - expect(response).to have_gitlab_http_status(:bad_request) + expect(response).to have_gitlab_http_status(expected_http_status) expect(json_response['message']).to eq(error_message) end end @@ -3374,7 +3377,7 @@ def make_upload_request shared_examples_for 'marks group for delayed deletion' do it 'marks group for delayed deletion', :clean_gitlab_redis_queues do Sidekiq::Testing.fake! do - expect { subject }.not_to change(GroupDestroyWorker.jobs, :size) + expect { api_request }.not_to change(GroupDestroyWorker.jobs, :size) end group.reload @@ -3412,64 +3415,92 @@ def make_upload_request let(:params) { { permanently_remove: true } } context 'if group is a subgroup' do - let(:subgroup) { create(:group, parent: group) } + let(:parent_group) { create(:group) } - subject { delete api("/groups/#{subgroup.id}", user), params: params } - - context 'forbidden by the :disallow_immediate_deletion feature flag' do - it_behaves_like 'does not immediately enqueues the job to delete the group', - '`permanently_remove` option is not available anymore (behind the :disallow_immediate_deletion feature flag).' + before do + group.update!(parent: parent_group) end - context 'when the :disallow_immediate_deletion feature flag is disabled' do + describe 'when the :allow_immediate_namespaces_deletion application setting is false' do before do - stub_feature_flags(disallow_immediate_deletion: false) + stub_application_setting(allow_immediate_namespaces_deletion: false) end - context 'when group is not marked for deletion' do - it_behaves_like 'does not immediately enqueues the job to delete the group', 'Group must be marked for deletion first.' + it_behaves_like 'does not immediately enqueues the job to delete the group' do + let(:error_message) { '`permanently_remove` option is not permitted on this instance.' } end - context 'when group is already marked for deletion' do - before do - create(:group_deletion_schedule, group: subgroup, marked_for_deletion_on: Date.current) - end + context 'when current user is an admin' do + let_it_be(:user) { admin } + let(:admin_mode) { true } + + context 'when group is already marked for deletion' do + let(:params) { { permanently_remove: true, full_path: group.full_path } } - context 'when full_path param is not passed' do - it_behaves_like 'does not immediately enqueues the job to delete the group', - '`full_path` is incorrect. You must enter the complete path for the subgroup.' + before do + create(:group_deletion_schedule, group: group, marked_for_deletion_on: Date.current) + group.add_owner(admin) + end + + it_behaves_like 'immediately enqueues the job to delete the group' + + context 'when admin_mode is false' do + let(:admin_mode) { false } + + it_behaves_like 'does not immediately enqueues the job to delete the group' do + let(:error_message) { '`permanently_remove` option is not permitted on this instance.' } + end + end end + end + end - context 'when full_path param is not equal to full_path' do - let(:params) { { permanently_remove: true, full_path: subgroup.path } } + context 'when group is not marked for deletion' do + it_behaves_like 'does not immediately enqueues the job to delete the group' do + let(:error_message) { 'Group must be marked for deletion first.' } + end + end - it_behaves_like 'does not immediately enqueues the job to delete the group', - '`full_path` is incorrect. You must enter the complete path for the subgroup.' + context 'when group is already marked for deletion' do + before do + create(:group_deletion_schedule, group: group, marked_for_deletion_on: Date.current) + end + + context 'when full_path param is not passed' do + it_behaves_like 'does not immediately enqueues the job to delete the group' do + let(:error_message) { '`full_path` is incorrect. You must enter the complete path for the subgroup.' } end + end - context 'when the full_path param is passed and it matches the full path of subgroup' do - let(:params) { { permanently_remove: true, full_path: subgroup.full_path } } + context 'when full_path param is not equal to full_path' do + let(:params) { { permanently_remove: true, full_path: group.path } } - it_behaves_like 'immediately enqueues the job to delete the group' + it_behaves_like 'does not immediately enqueues the job to delete the group' do + let(:error_message) { '`full_path` is incorrect. You must enter the complete path for the subgroup.' } end end - end - end - context 'if group is not a subgroup' do - subject { delete api("/groups/#{group.id}", user), params: params } + context 'when the full_path param is passed and it matches the full path of subgroup' do + let(:params) { { permanently_remove: true, full_path: group.full_path } } - context 'forbidden by the :disallow_immediate_deletion feature flag' do - it_behaves_like 'does not immediately enqueues the job to delete the group', - '`permanently_remove` option is not available anymore (behind the :disallow_immediate_deletion feature flag).' + it_behaves_like 'immediately enqueues the job to delete the group' + end end + end - context 'when the :disallow_immediate_deletion feature flag is disabled' do + context 'if group is not a subgroup' do + describe 'when the :allow_immediate_namespaces_deletion application setting is false' do before do - stub_feature_flags(disallow_immediate_deletion: false) + stub_application_setting(allow_immediate_namespaces_deletion: false) end - it_behaves_like 'does not immediately enqueues the job to delete the group', '`permanently_remove` option is only available for subgroups.' + it_behaves_like 'does not immediately enqueues the job to delete the group' do + let(:error_message) { '`permanently_remove` option is not permitted on this instance.' } + end + end + + it_behaves_like 'does not immediately enqueues the job to delete the group' do + let(:error_message) { '`permanently_remove` option is only available for subgroups.' } end end end @@ -3481,7 +3512,7 @@ def make_upload_request end it 'returns an error' do - subject + api_request expect(response).to have_gitlab_http_status(:bad_request) expect(json_response['message']).to eq('error') diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index a19ec31884c0ccd584d66a4a7fd655228e112f93..818a47ad05d0b6fb9d2e806633844819be424ef7 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -5566,6 +5566,10 @@ def request_with_second_scope describe 'DELETE /projects/:id' do let(:path) { "/projects/#{project.id}" } + let(:admin_mode) { false } + let(:params) { {} } + + subject(:api_request) { delete api(path, user, admin_mode: admin_mode), params: params } it_behaves_like 'DELETE request permissions for admin mode' do let(:success_status_code) { :accepted } @@ -5574,7 +5578,7 @@ def request_with_second_scope context 'when authenticated as user' do it 'removes project' do - delete api(path, user) + api_request expect(response).to have_gitlab_http_status(:accepted) expect(json_response['message']).to eql('202 Accepted') @@ -5604,39 +5608,30 @@ def request_with_second_scope end context 'when authenticated as admin' do - it 'removes any existing project' do - delete api("/projects/#{project.id}", admin, admin_mode: true) - - expect(response).to have_gitlab_http_status(:accepted) - expect(json_response['message']).to eql('202 Accepted') - end - - it 'does not remove a non existing project' do - delete api("/projects/#{non_existing_record_id}", admin, admin_mode: true) - expect(response).to have_gitlab_http_status(:not_found) - end - it_behaves_like '412 response' do let(:success_status) { 202 } - subject(:request) { api("/projects/#{project.id}", admin, admin_mode: true) } + subject(:request) { api(path, admin, admin_mode: true) } end end - shared_examples 'deletes project immediately' do |admin_mode = false| + shared_examples 'deletes project immediately' do it :aggregate_failures do expect(::Projects::DestroyService).to receive(:new).with(project, user, {}).and_call_original - delete api(path, user, admin_mode: admin_mode), params: params + api_request + expect(response).to have_gitlab_http_status(:accepted) end end - shared_examples 'immediately delete project error' do |admin_mode = false, expected_http_status = :bad_request| + shared_examples 'immediately delete project error' do + let(:expected_http_status) { :bad_request } + it :aggregate_failures do expect(::Projects::DestroyService).not_to receive(:new) expect(::Projects::MarkForDeletionService).not_to receive(:new) - delete api(path, user, admin_mode: admin_mode), params: params + api_request expect(response).to have_gitlab_http_status(expected_http_status) expect(Gitlab::Json.parse(response.body)['message']).to eq(error_message) @@ -5646,12 +5641,11 @@ def request_with_second_scope context 'for delayed deletion' do let_it_be(:group) { create(:group) } let_it_be_with_reload(:project) { create(:project, group: group, owners: user) } - let(:params) { {} } it 'marks the project for deletion' do expect(::Projects::MarkForDeletionService).to receive(:new).with(project, user, {}).and_call_original - delete api(path, user), params: params + api_request expect(response).to have_gitlab_http_status(:accepted) expect(project.reload.self_deletion_scheduled?).to be_truthy @@ -5678,7 +5672,7 @@ def request_with_second_scope message = 'Error' expect(::Projects::MarkForDeletionService).to receive_message_chain(:new, :execute).and_return({ status: :error, message: message }) - delete api("/projects/#{project.id}", user) + api_request expect(response).to have_gitlab_http_status(:bad_request) expect(json_response["message"]).to eq(message) @@ -5689,64 +5683,74 @@ def request_with_second_scope params.merge!(permanently_remove: true) end - context 'forbidden by the :disallow_immediate_deletion feature flag' do - let(:error_message) { '`permanently_remove` option is not available anymore (behind the :disallow_immediate_deletion feature flag).' } + describe 'when the :allow_immediate_namespaces_deletion application setting is false' do + before do + stub_application_setting(allow_immediate_namespaces_deletion: false) + end - it_behaves_like 'immediately delete project error' - end + it_behaves_like 'immediately delete project error' do + let(:error_message) { '`permanently_remove` option is not permitted on this instance.' } + end - context 'when current user is an admin' do - let_it_be(:user) { admin } + context 'when current user is an admin' do + let_it_be(:user) { admin } + let(:admin_mode) { true } - context 'when project is already marked for deletion' do - before do - project.update!(archived: true, marked_for_deletion_at: 1.day.ago, deleting_user: user) - params.merge!(full_path: project.full_path) - end + context 'when project is already marked for deletion' do + before do + project.update!(marked_for_deletion_at: 1.day.ago, deleting_user: admin) + params[:full_path] = project.full_path + project.add_owner(admin) + end - it_behaves_like 'deletes project immediately', true + it_behaves_like 'deletes project immediately' - context 'when admin_mode is false' do - let(:error_message) { '404 Project Not Found' } + context 'when admin_mode is false' do + let(:admin_mode) { false } - it_behaves_like 'immediately delete project error', false, :not_found + it_behaves_like 'immediately delete project error' do + let(:error_message) { '`permanently_remove` option is not permitted on this instance.' } + end + end end end end - context 'when the :disallow_immediate_deletion feature flag is disabled' do - before do - stub_feature_flags(disallow_immediate_deletion: false) - end + context 'when project is not marked for deletion' do + let(:error_message) { 'Project must be marked for deletion first.' } - context 'when project is not marked for deletion' do - let(:error_message) { 'Project must be marked for deletion first.' } + it_behaves_like 'immediately delete project error' + end - it_behaves_like 'immediately delete project error' + context 'when project is already marked for deletion' do + before do + project.update!(archived: true, marked_for_deletion_at: 1.day.ago, deleting_user: user) end - context 'when project is already marked for deletion' do + context 'with correct project full path' do before do - project.update!(archived: true, marked_for_deletion_at: 1.day.ago, deleting_user: user) + params.merge!(full_path: project.full_path) end - context 'with correct project full path' do + it_behaves_like 'deletes project immediately' + + context 'when the allow_immediate_namespaces_deletion FF is disabled' do before do - params.merge!(full_path: project.full_path) + stub_feature_flags(allow_immediate_namespaces_deletion: false) end it_behaves_like 'deletes project immediately' end + end - context 'with incorrect project full path' do - let(:error_message) { '`full_path` is incorrect. You must enter the complete path for the project.' } - - before do - params.merge!(full_path: "#{project.full_path}-wrong-path") - end + context 'with incorrect project full path' do + let(:error_message) { '`full_path` is incorrect. You must enter the complete path for the project.' } - it_behaves_like 'immediately delete project error' + before do + params.merge!(full_path: "#{project.full_path}-wrong-path") end + + it_behaves_like 'immediately delete project error' end end end