Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-01-26 15:10:19 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-01-26 15:10:19 +0300
commita1c0b634f78f51389fd3ec390a1803afa3de49a2 (patch)
treef57fc3000a6e7f2bd271a63018427c8fb9c06c08 /spec
parentdb950c5706cdf47f7ccc2e02a4ffd691432ac5dc (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/db/schema_spec.rb6
-rw-r--r--spec/frontend/api_spec.js18
-rw-r--r--spec/frontend/jira_connect/subscriptions/api_spec.js2
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_spec.js74
-rw-r--r--spec/frontend/repository/utils/ref_switcher_utils_spec.js6
-rw-r--r--spec/graphql/resolvers/ci/variables_resolver_spec.rb70
-rw-r--r--spec/graphql/types/ci/variable_sort_enum_spec.rb12
-rw-r--r--spec/models/concerns/ci/has_variable_spec.rb34
-rw-r--r--spec/requests/api/graphql/ci/group_variables_spec.rb28
-rw-r--r--spec/requests/api/graphql/ci/instance_variables_spec.rb24
-rw-r--r--spec/requests/api/graphql/ci/project_variables_spec.rb28
-rw-r--r--spec/services/projects/container_repository/destroy_service_spec.rb143
-rw-r--r--spec/services/projects/destroy_service_spec.rb49
-rw-r--r--spec/support/shared_examples/requests/api/graphql/ci/sorted_paginated_variables_shared_examples.rb26
14 files changed, 421 insertions, 99 deletions
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index 9b839873fbb..327f162f57a 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -33,9 +33,9 @@ RSpec.describe 'Database schema', feature_category: :database do
chat_names: %w[chat_id team_id user_id integration_id],
chat_teams: %w[team_id],
ci_build_needs: %w[partition_id],
- ci_build_pending_states: %w[partition_id],
+ ci_build_pending_states: %w[partition_id build_id],
ci_build_report_results: %w[partition_id],
- ci_build_trace_chunks: %w[partition_id],
+ ci_build_trace_chunks: %w[partition_id build_id],
ci_build_trace_metadata: %w[partition_id],
ci_builds: %w[erased_by_id trigger_request_id partition_id],
ci_builds_runner_session: %w[partition_id build_id],
@@ -52,7 +52,7 @@ RSpec.describe 'Database schema', feature_category: :database do
ci_sources_pipelines: %w[partition_id source_partition_id],
ci_stages: %w[partition_id],
ci_trigger_requests: %w[commit_id],
- ci_unit_test_failures: %w[partition_id],
+ ci_unit_test_failures: %w[partition_id build_id],
cluster_providers_aws: %w[security_group_id vpc_id access_key_id],
cluster_providers_gcp: %w[gcp_project_id operation_id],
compliance_management_frameworks: %w[group_id],
diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js
index b9d68587fa5..6fd106502c4 100644
--- a/spec/frontend/api_spec.js
+++ b/spec/frontend/api_spec.js
@@ -206,7 +206,7 @@ describe('Api', () => {
expires_at: undefined,
};
- mock.onPost(expectedUrl).reply(200, {
+ mock.onPost(expectedUrl).reply(HTTP_STATUS_OK, {
status: 'success',
});
@@ -478,7 +478,7 @@ describe('Api', () => {
jest.spyOn(axios, 'post');
- mock.onPost(expectedUrl).reply(200, {
+ mock.onPost(expectedUrl).reply(HTTP_STATUS_OK, {
status: 'success',
});
@@ -494,7 +494,7 @@ describe('Api', () => {
const projectId = 1;
const options = { state: 'active' };
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/1/milestones`;
- mock.onGet(expectedUrl).reply(200, [
+ mock.onGet(expectedUrl).reply(HTTP_STATUS_OK, [
{
id: 1,
title: 'milestone1',
@@ -514,7 +514,7 @@ describe('Api', () => {
const projectId = 1;
const issueIid = 11;
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/1/issues/11/todo`;
- mock.onPost(expectedUrl).reply(200, {
+ mock.onPost(expectedUrl).reply(HTTP_STATUS_OK, {
id: 112,
project: {
id: 1,
@@ -541,7 +541,7 @@ describe('Api', () => {
expires_at: undefined,
};
- mock.onPost(expectedUrl).reply(200, {
+ mock.onPost(expectedUrl).reply(HTTP_STATUS_OK, {
status: 'success',
});
@@ -644,7 +644,7 @@ describe('Api', () => {
jest.spyOn(axios, 'post');
- mock.onPost(expectedUrl).reply(200, {
+ mock.onPost(expectedUrl).reply(HTTP_STATUS_OK, {
status: 'success',
});
@@ -958,7 +958,7 @@ describe('Api', () => {
jest.spyOn(axios, 'post');
- mock.onPost(expectedUrl).replyOnce(200, [
+ mock.onPost(expectedUrl).replyOnce(HTTP_STATUS_OK, [
{
id: 'abcdefghijklmnop',
short_id: 'abcdefg',
@@ -984,7 +984,9 @@ describe('Api', () => {
mock
.onGet(expectedUrl)
- .replyOnce(200, [{ id: 'abcdef', short_id: 'abcdefghi', title: 'Dummy commit title' }]);
+ .replyOnce(HTTP_STATUS_OK, [
+ { id: 'abcdef', short_id: 'abcdefghi', title: 'Dummy commit title' },
+ ]);
return Api.allContextCommits(projectPath, mergeRequestId).then(({ data }) => {
expect(data[0].title).toBe('Dummy commit title');
diff --git a/spec/frontend/jira_connect/subscriptions/api_spec.js b/spec/frontend/jira_connect/subscriptions/api_spec.js
index 21636017f10..e2a14a9102f 100644
--- a/spec/frontend/jira_connect/subscriptions/api_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/api_spec.js
@@ -104,9 +104,11 @@ describe('JiraConnect API', () => {
response = await makeRequest();
expect(axiosInstance.get).toHaveBeenCalledWith(mockGroupsPath, {
+ headers: {},
params: {
page: mockPage,
per_page: mockPerPage,
+ search: undefined,
},
});
expect(response.data).toEqual(mockResponse);
diff --git a/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_spec.js b/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_spec.js
index f1fc5e4d90b..97038a2a231 100644
--- a/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_spec.js
@@ -27,6 +27,7 @@ jest.mock('~/jira_connect/subscriptions/api', () => {
});
const mockGroupsPath = '/groups';
+const mockAccessToken = '123';
describe('GroupsList', () => {
let wrapper;
@@ -39,6 +40,9 @@ describe('GroupsList', () => {
provide: {
groupsPath: mockGroupsPath,
},
+ computed: {
+ accessToken: () => mockAccessToken,
+ },
...options,
}),
);
@@ -148,11 +152,15 @@ describe('GroupsList', () => {
});
it('calls `fetchGroups` with search term', () => {
- expect(fetchGroups).toHaveBeenLastCalledWith(mockGroupsPath, {
- page: 1,
- perPage: DEFAULT_GROUPS_PER_PAGE,
- search: mockSearchTeam,
- });
+ expect(fetchGroups).toHaveBeenLastCalledWith(
+ mockGroupsPath,
+ {
+ page: 1,
+ perPage: DEFAULT_GROUPS_PER_PAGE,
+ search: mockSearchTeam,
+ },
+ mockAccessToken,
+ );
});
it('disables GroupListItems', () => {
@@ -222,11 +230,15 @@ describe('GroupsList', () => {
findSearchBox().vm.$emit('input', newSearch);
if (shouldSearch) {
- expect(fetchGroups).toHaveBeenCalledWith(mockGroupsPath, {
- page: 1,
- perPage: DEFAULT_GROUPS_PER_PAGE,
- search: expectedSearchValue,
- });
+ expect(fetchGroups).toHaveBeenCalledWith(
+ mockGroupsPath,
+ {
+ page: 1,
+ perPage: DEFAULT_GROUPS_PER_PAGE,
+ search: expectedSearchValue,
+ },
+ mockAccessToken,
+ );
} else {
expect(fetchGroups).not.toHaveBeenCalled();
}
@@ -257,11 +269,15 @@ describe('GroupsList', () => {
});
it('should load results for page 2', () => {
- expect(fetchGroups).toHaveBeenLastCalledWith(mockGroupsPath, {
- page: 2,
- perPage: DEFAULT_GROUPS_PER_PAGE,
- search: '',
- });
+ expect(fetchGroups).toHaveBeenLastCalledWith(
+ mockGroupsPath,
+ {
+ page: 2,
+ perPage: DEFAULT_GROUPS_PER_PAGE,
+ search: '',
+ },
+ mockAccessToken,
+ );
});
it.each`
@@ -274,11 +290,15 @@ describe('GroupsList', () => {
const searchBox = findSearchBox();
searchBox.vm.$emit('input', searchTerm);
- expect(fetchGroups).toHaveBeenLastCalledWith(mockGroupsPath, {
- page: expectedPage,
- perPage: DEFAULT_GROUPS_PER_PAGE,
- search: expectedSearchTerm,
- });
+ expect(fetchGroups).toHaveBeenLastCalledWith(
+ mockGroupsPath,
+ {
+ page: expectedPage,
+ perPage: DEFAULT_GROUPS_PER_PAGE,
+ search: expectedSearchTerm,
+ },
+ mockAccessToken,
+ );
},
);
});
@@ -324,11 +344,15 @@ describe('GroupsList', () => {
const paginationEl = findPagination();
paginationEl.vm.$emit('input', 2);
- expect(fetchGroups).toHaveBeenLastCalledWith(mockGroupsPath, {
- page: 2,
- perPage: DEFAULT_GROUPS_PER_PAGE,
- search: '',
- });
+ expect(fetchGroups).toHaveBeenLastCalledWith(
+ mockGroupsPath,
+ {
+ page: 2,
+ perPage: DEFAULT_GROUPS_PER_PAGE,
+ search: '',
+ },
+ mockAccessToken,
+ );
});
});
});
diff --git a/spec/frontend/repository/utils/ref_switcher_utils_spec.js b/spec/frontend/repository/utils/ref_switcher_utils_spec.js
index 4d0250fffbf..7f708f13eaa 100644
--- a/spec/frontend/repository/utils/ref_switcher_utils_spec.js
+++ b/spec/frontend/repository/utils/ref_switcher_utils_spec.js
@@ -18,12 +18,14 @@ describe('generateRefDestinationPath', () => {
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/dir2/test.js#L123`}
`('generates the correct destination path for $currentPath', ({ currentPath, result }) => {
setWindowLocation(currentPath);
- expect(generateRefDestinationPath(projectRootPath, selectedRef)).toBe(result);
+ expect(generateRefDestinationPath(projectRootPath, currentRef, selectedRef)).toBe(result);
});
it('encodes the selected ref', () => {
const result = `${projectRootPath}/-/tree/${encodedRefWithSpecialCharMock}`;
- expect(generateRefDestinationPath(projectRootPath, refWithSpecialCharMock)).toBe(result);
+ expect(generateRefDestinationPath(projectRootPath, currentRef, refWithSpecialCharMock)).toBe(
+ result,
+ );
});
});
diff --git a/spec/graphql/resolvers/ci/variables_resolver_spec.rb b/spec/graphql/resolvers/ci/variables_resolver_spec.rb
new file mode 100644
index 00000000000..16b72e8cb7f
--- /dev/null
+++ b/spec/graphql/resolvers/ci/variables_resolver_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::Ci::VariablesResolver, feature_category: :pipeline_authoring do
+ include GraphqlHelpers
+
+ describe '#resolve' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:args) { {} }
+ let_it_be(:obj) { nil }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:project) { create(:project) }
+
+ let_it_be(:ci_instance_variables) do
+ [
+ create(:ci_instance_variable, key: 'a'),
+ create(:ci_instance_variable, key: 'b')
+ ]
+ end
+
+ let_it_be(:ci_group_variables) do
+ [
+ create(:ci_group_variable, group: group, key: 'a'),
+ create(:ci_group_variable, group: group, key: 'b')
+ ]
+ end
+
+ let_it_be(:ci_project_variables) do
+ [
+ create(:ci_variable, project: project, key: 'a'),
+ create(:ci_variable, project: project, key: 'b')
+ ]
+ end
+
+ subject(:resolve_variables) { resolve(described_class, obj: obj, ctx: { current_user: user }, args: args) }
+
+ context 'when parent object is nil' do
+ context 'when user is authorized', :enable_admin_mode do
+ let_it_be(:user) { create(:admin) }
+
+ it "returns the instance's variables" do
+ expect(resolve_variables.items.to_a).to match_array(ci_instance_variables)
+ end
+ end
+
+ context 'when user is not authorized' do
+ it "returns nil" do
+ expect(resolve_variables).to be_nil
+ end
+ end
+ end
+
+ context 'when parent object is a Group' do
+ let_it_be(:obj) { group }
+
+ it "returns the group's variables" do
+ expect(resolve_variables.items.to_a).to match_array(ci_group_variables)
+ end
+ end
+
+ context 'when parent object is a Project' do
+ let_it_be(:obj) { project }
+
+ it "returns the project's variables" do
+ expect(resolve_variables.items.to_a).to match_array(ci_project_variables)
+ end
+ end
+ end
+end
diff --git a/spec/graphql/types/ci/variable_sort_enum_spec.rb b/spec/graphql/types/ci/variable_sort_enum_spec.rb
new file mode 100644
index 00000000000..1702360a21f
--- /dev/null
+++ b/spec/graphql/types/ci/variable_sort_enum_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::Ci::VariableSortEnum, feature_category: :pipeline_authoring do
+ it 'exposes the available order methods' do
+ expect(described_class.values).to match(
+ 'KEY_ASC' => have_attributes(value: :key_asc),
+ 'KEY_DESC' => have_attributes(value: :key_desc)
+ )
+ end
+end
diff --git a/spec/models/concerns/ci/has_variable_spec.rb b/spec/models/concerns/ci/has_variable_spec.rb
index 861d8f3b974..d7d0cabd4ae 100644
--- a/spec/models/concerns/ci/has_variable_spec.rb
+++ b/spec/models/concerns/ci/has_variable_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Ci::HasVariable do
+RSpec.describe Ci::HasVariable, feature_category: :continuous_integration do
subject { build(:ci_variable) }
it { is_expected.to validate_presence_of(:key) }
@@ -113,4 +113,36 @@ RSpec.describe Ci::HasVariable do
end
end
end
+
+ describe '.order_by' do
+ let_it_be(:relation) { Ci::Variable.all }
+
+ it 'supports ordering by key ascending' do
+ expect(relation).to receive(:reorder).with({ key: :asc })
+
+ relation.order_by('key_asc')
+ end
+
+ it 'supports ordering by key descending' do
+ expect(relation).to receive(:reorder).with({ key: :desc })
+
+ relation.order_by('key_desc')
+ end
+
+ context 'when order method is unknown' do
+ it 'does not call reorder' do
+ expect(relation).not_to receive(:reorder)
+
+ relation.order_by('unknown')
+ end
+ end
+
+ context 'when order method is nil' do
+ it 'does not call reorder' do
+ expect(relation).not_to receive(:reorder)
+
+ relation.order_by(nil)
+ end
+ end
+ end
end
diff --git a/spec/requests/api/graphql/ci/group_variables_spec.rb b/spec/requests/api/graphql/ci/group_variables_spec.rb
index 51cbb4719f7..e45ddbb1585 100644
--- a/spec/requests/api/graphql/ci/group_variables_spec.rb
+++ b/spec/requests/api/graphql/ci/group_variables_spec.rb
@@ -72,4 +72,32 @@ RSpec.describe 'Query.group(fullPath).ciVariables', feature_category: :pipeline_
expect(graphql_data.dig('group', 'ciVariables')).to be_nil
end
end
+
+ describe 'sorting and pagination' do
+ let_it_be(:current_user) { user }
+ let_it_be(:data_path) { [:group, :ci_variables] }
+ let_it_be(:variables) do
+ [
+ create(:ci_group_variable, group: group, key: 'd'),
+ create(:ci_group_variable, group: group, key: 'a'),
+ create(:ci_group_variable, group: group, key: 'c'),
+ create(:ci_group_variable, group: group, key: 'e'),
+ create(:ci_group_variable, group: group, key: 'b')
+ ]
+ end
+
+ def pagination_query(params)
+ graphql_query_for(
+ :group,
+ { fullPath: group.full_path },
+ query_graphql_field('ciVariables', params, "#{page_info} nodes { id }")
+ )
+ end
+
+ before do
+ group.add_owner(current_user)
+ end
+
+ it_behaves_like 'sorted paginated variables'
+ end
end
diff --git a/spec/requests/api/graphql/ci/instance_variables_spec.rb b/spec/requests/api/graphql/ci/instance_variables_spec.rb
index e0397e17923..5b65ae88426 100644
--- a/spec/requests/api/graphql/ci/instance_variables_spec.rb
+++ b/spec/requests/api/graphql/ci/instance_variables_spec.rb
@@ -69,4 +69,28 @@ RSpec.describe 'Query.ciVariables', feature_category: :pipeline_authoring do
expect(graphql_data.dig('ciVariables')).to be_nil
end
end
+
+ describe 'sorting and pagination' do
+ let_it_be(:current_user) { create(:admin) }
+ let_it_be(:data_path) { [:ci_variables] }
+ let_it_be(:variables) do
+ [
+ create(:ci_instance_variable, key: 'd'),
+ create(:ci_instance_variable, key: 'a'),
+ create(:ci_instance_variable, key: 'c'),
+ create(:ci_instance_variable, key: 'e'),
+ create(:ci_instance_variable, key: 'b')
+ ]
+ end
+
+ def pagination_query(params)
+ graphql_query_for(
+ :ci_variables,
+ params,
+ "#{page_info} nodes { id }"
+ )
+ end
+
+ it_behaves_like 'sorted paginated variables'
+ end
end
diff --git a/spec/requests/api/graphql/ci/project_variables_spec.rb b/spec/requests/api/graphql/ci/project_variables_spec.rb
index 0338b58a0ea..f9a32b42b4d 100644
--- a/spec/requests/api/graphql/ci/project_variables_spec.rb
+++ b/spec/requests/api/graphql/ci/project_variables_spec.rb
@@ -66,4 +66,32 @@ RSpec.describe 'Query.project(fullPath).ciVariables', feature_category: :pipelin
expect(graphql_data.dig('project', 'ciVariables')).to be_nil
end
end
+
+ describe 'sorting and pagination' do
+ let_it_be(:current_user) { user }
+ let_it_be(:data_path) { [:project, :ci_variables] }
+ let_it_be(:variables) do
+ [
+ create(:ci_variable, project: project, key: 'd'),
+ create(:ci_variable, project: project, key: 'a'),
+ create(:ci_variable, project: project, key: 'c'),
+ create(:ci_variable, project: project, key: 'e'),
+ create(:ci_variable, project: project, key: 'b')
+ ]
+ end
+
+ def pagination_query(params)
+ graphql_query_for(
+ :project,
+ { fullPath: project.full_path },
+ query_graphql_field('ciVariables', params, "#{page_info} nodes { id }")
+ )
+ end
+
+ before do
+ project.add_maintainer(current_user)
+ end
+
+ it_behaves_like 'sorted paginated variables'
+ end
end
diff --git a/spec/services/projects/container_repository/destroy_service_spec.rb b/spec/services/projects/container_repository/destroy_service_spec.rb
index 0ec0aecaa04..fed1d13daa5 100644
--- a/spec/services/projects/container_repository/destroy_service_spec.rb
+++ b/spec/services/projects/container_repository/destroy_service_spec.rb
@@ -5,86 +5,131 @@ require 'spec_helper'
RSpec.describe Projects::ContainerRepository::DestroyService do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :private) }
+ let_it_be(:params) { {} }
- subject { described_class.new(project, user) }
+ subject { described_class.new(project, user, params) }
before do
stub_container_registry_config(enabled: true)
end
- context 'when user does not have access to registry' do
- let!(:repository) { create(:container_repository, :root, project: project) }
+ shared_examples 'returning an error status with message' do |error_message|
+ it 'returns an error status' do
+ response = subject.execute(repository)
- it 'does not delete a repository' do
- expect { subject.execute(repository) }.not_to change { ContainerRepository.count }
+ expect(response).to include(status: :error, message: error_message)
end
end
- context 'when user has access to registry' do
+ shared_examples 'executing with permissions' do
+ let_it_be_with_refind(:repository) { create(:container_repository, :root, project: project) }
+
before do
- project.add_developer(user)
+ stub_container_registry_tags(repository: :any, tags: %w[latest stable])
end
- context 'when root container repository exists' do
- let!(:repository) { create(:container_repository, :root, project: project) }
+ it 'deletes the repository' do
+ expect_cleanup_tags_service_with(container_repository: repository, return_status: :success)
+ expect { subject.execute(repository) }.to change { ContainerRepository.count }.by(-1)
+ end
+
+ it 'sends disable_timeout = true as part of the params as default' do
+ expect_cleanup_tags_service_with(container_repository: repository, return_status: :success, disable_timeout: true)
+ expect { subject.execute(repository) }.to change { ContainerRepository.count }.by(-1)
+ end
+
+ it 'sends disable_timeout = false as part of the params if it is set to false' do
+ expect_cleanup_tags_service_with(container_repository: repository, return_status: :success, disable_timeout: false)
+ expect { subject.execute(repository, disable_timeout: false) }.to change { ContainerRepository.count }.by(-1)
+ end
+ context 'when deleting the tags fails' do
before do
- stub_container_registry_tags(repository: :any, tags: %w[latest stable])
+ expect_cleanup_tags_service_with(container_repository: repository, return_status: :error)
+ allow(Gitlab::AppLogger).to receive(:error).and_call_original
end
- it 'deletes the repository' do
- expect_cleanup_tags_service_with(container_repository: repository, return_status: :success)
- expect { subject.execute(repository) }.to change { ContainerRepository.count }.by(-1)
- end
+ it 'sets status as deleted_failed' do
+ subject.execute(repository)
- it 'sends disable_timeout = true as part of the params as default' do
- expect_cleanup_tags_service_with(container_repository: repository, return_status: :success, disable_timeout: true)
- expect { subject.execute(repository) }.to change { ContainerRepository.count }.by(-1)
+ expect(repository).to be_delete_failed
end
- it 'sends disable_timeout = false as part of the params if it is set to false' do
- expect_cleanup_tags_service_with(container_repository: repository, return_status: :success, disable_timeout: false)
- expect { subject.execute(repository, disable_timeout: false) }.to change { ContainerRepository.count }.by(-1)
- end
+ it 'logs the error' do
+ subject.execute(repository)
- context 'when deleting the tags fails' do
- it 'sets status as deleted_failed' do
- expect_cleanup_tags_service_with(container_repository: repository, return_status: :error)
- allow(Gitlab::AppLogger).to receive(:error).and_call_original
+ expect(Gitlab::AppLogger).to have_received(:error)
+ .with("Container repository with ID: #{repository.id} and path: #{repository.path} failed with message: error in deleting tags")
+ end
- subject.execute(repository)
+ it_behaves_like 'returning an error status with message', 'Deletion failed for container repository'
+ end
- expect(repository).to be_delete_failed
- expect(Gitlab::AppLogger).to have_received(:error)
- .with("Container repository with ID: #{repository.id} and path: #{repository.path} failed with message: error in deleting tags")
- end
+ context 'when destroying the repository fails' do
+ before do
+ expect_cleanup_tags_service_with(container_repository: repository, return_status: :success)
+ allow(repository).to receive(:destroy).and_return(false)
+ allow(repository.errors).to receive(:full_messages).and_return(['Error 1', 'Error 2'])
+ allow(Gitlab::AppLogger).to receive(:error).and_call_original
end
- context 'when destroying the repository fails' do
- it 'sets status as deleted_failed' do
- expect_cleanup_tags_service_with(container_repository: repository, return_status: :success)
- allow(repository).to receive(:destroy).and_return(false)
- allow(repository.errors).to receive(:full_messages).and_return(['Error 1', 'Error 2'])
- allow(Gitlab::AppLogger).to receive(:error).and_call_original
+ it 'sets status as deleted_failed' do
+ subject.execute(repository)
+
+ expect(repository).to be_delete_failed
+ end
- subject.execute(repository)
+ it 'logs the error' do
+ subject.execute(repository)
- expect(repository).to be_delete_failed
- expect(Gitlab::AppLogger).to have_received(:error)
- .with("Container repository with ID: #{repository.id} and path: #{repository.path} failed with message: Error 1. Error 2")
- end
+ expect(Gitlab::AppLogger).to have_received(:error)
+ .with("Container repository with ID: #{repository.id} and path: #{repository.path} failed with message: Error 1. Error 2")
end
- def expect_cleanup_tags_service_with(container_repository:, return_status:, disable_timeout: true)
- delete_tags_service = instance_double(Projects::ContainerRepository::CleanupTagsService)
+ it_behaves_like 'returning an error status with message', 'Deletion failed for container repository'
+ end
+ end
+
+ context 'when user has access to registry' do
+ before do
+ project.add_developer(user)
+ end
- expect(Projects::ContainerRepository::CleanupTagsService).to receive(:new).with(
- container_repository: container_repository,
- params: described_class::CLEANUP_TAGS_SERVICE_PARAMS.merge('disable_timeout' => disable_timeout)
- ).and_return(delete_tags_service)
+ it_behaves_like 'executing with permissions'
+ end
- expect(delete_tags_service).to receive(:execute).and_return(status: return_status)
- end
+ context 'when user does not have access to registry' do
+ let_it_be(:repository) { create(:container_repository, :root, project: project) }
+
+ it 'does not delete a repository' do
+ expect { subject.execute(repository) }.not_to change { ContainerRepository.count }
end
+
+ it_behaves_like 'returning an error status with message', 'Unauthorized access'
+ end
+
+ context 'when called during project deletion' do
+ let(:user) { nil }
+ let(:params) { { skip_permission_check: true } }
+
+ it_behaves_like 'executing with permissions'
+ end
+
+ context 'when there is no user' do
+ let(:user) { nil }
+ let(:repository) { create(:container_repository, :root, project: project) }
+
+ it_behaves_like 'returning an error status with message', 'Unauthorized access'
+ end
+
+ def expect_cleanup_tags_service_with(container_repository:, return_status:, disable_timeout: true)
+ delete_tags_service = instance_double(Projects::ContainerRepository::CleanupTagsService)
+
+ expect(Projects::ContainerRepository::CleanupTagsService).to receive(:new).with(
+ container_repository: container_repository,
+ params: described_class::CLEANUP_TAGS_SERVICE_PARAMS.merge('disable_timeout' => disable_timeout)
+ ).and_return(delete_tags_service)
+
+ expect(delete_tags_service).to receive(:execute).and_return(status: return_status)
end
end
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 0ef0ecc1099..05f699ed357 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -343,18 +343,30 @@ RSpec.describe Projects::DestroyService, :aggregate_failures, :event_store_publi
end
context 'when image repository deletion succeeds' do
- it 'removes tags' do
- expect_any_instance_of(Projects::ContainerRepository::CleanupTagsService)
- .to receive(:execute).and_return({ status: :success })
+ it 'returns true' do
+ expect_next_instance_of(Projects::ContainerRepository::CleanupTagsService) do |instance|
+ expect(instance).to receive(:execute).and_return(status: :success)
+ end
- destroy_project(project, user)
+ expect(destroy_project(project, user)).to be true
+ end
+ end
+
+ context 'when image repository deletion raises an error' do
+ it 'returns false' do
+ expect_next_instance_of(Projects::ContainerRepository::CleanupTagsService) do |service|
+ expect(service).to receive(:execute).and_raise(RuntimeError)
+ end
+
+ expect(destroy_project(project, user)).to be false
end
end
context 'when image repository deletion fails' do
- it 'raises an exception' do
- expect_any_instance_of(Projects::ContainerRepository::CleanupTagsService)
- .to receive(:execute).and_raise(RuntimeError)
+ it 'returns false' do
+ expect_next_instance_of(Projects::ContainerRepository::DestroyService) do |service|
+ expect(service).to receive(:execute).and_return({ status: :error })
+ end
expect(destroy_project(project, user)).to be false
end
@@ -381,8 +393,9 @@ RSpec.describe Projects::DestroyService, :aggregate_failures, :event_store_publi
context 'when image repository tags deletion succeeds' do
it 'removes tags' do
- expect_any_instance_of(ContainerRepository)
- .to receive(:delete_tags!).and_return(true)
+ expect_next_instance_of(Projects::ContainerRepository::DestroyService) do |service|
+ expect(service).to receive(:execute).and_return({ status: :sucess })
+ end
destroy_project(project, user)
end
@@ -390,13 +403,27 @@ RSpec.describe Projects::DestroyService, :aggregate_failures, :event_store_publi
context 'when image repository tags deletion fails' do
it 'raises an exception' do
- expect_any_instance_of(ContainerRepository)
- .to receive(:delete_tags!).and_return(false)
+ expect_next_instance_of(Projects::ContainerRepository::DestroyService) do |service|
+ expect(service).to receive(:execute).and_return({ status: :error })
+ end
expect(destroy_project(project, user)).to be false
end
end
end
+
+ context 'when there are no tags for legacy root repository' do
+ before do
+ stub_container_registry_tags(repository: project.full_path,
+ tags: [])
+ end
+
+ it 'does not try to destroy the repository' do
+ expect(Projects::ContainerRepository::DestroyService).not_to receive(:new)
+
+ destroy_project(project, user)
+ end
+ end
end
context 'for a forked project with LFS objects' do
diff --git a/spec/support/shared_examples/requests/api/graphql/ci/sorted_paginated_variables_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/ci/sorted_paginated_variables_shared_examples.rb
new file mode 100644
index 00000000000..306310c9e9c
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/graphql/ci/sorted_paginated_variables_shared_examples.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+# Requires `current_user`, `data_path`, `variables`, and `pagination_query(params)` bindings
+RSpec.shared_examples 'sorted paginated variables' do
+ subject(:expected_ordered_variables) { ordered_variables.map { |var| var.to_global_id.to_s } }
+
+ context 'when sorted by key ascending' do
+ let(:ordered_variables) { variables.sort_by(&:key) }
+
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { :KEY_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { expected_ordered_variables }
+ end
+ end
+
+ context 'when sorted by key descending' do
+ let(:ordered_variables) { variables.sort_by(&:key).reverse }
+
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { :KEY_DESC }
+ let(:first_param) { 2 }
+ let(:all_records) { expected_ordered_variables }
+ end
+ end
+end