diff options
Diffstat (limited to 'spec/requests/api/graphql/mutations')
19 files changed, 768 insertions, 173 deletions
diff --git a/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb b/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb index 316b0f3755d..808dcefb84d 100644 --- a/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb +++ b/spec/requests/api/graphql/mutations/admin/sidekiq_queues/delete_jobs_spec.rb @@ -26,7 +26,7 @@ RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues, feature_cate context 'when the user is an admin' do let(:current_user) { admin } - context 'valid request' do + context 'when valid request' do around do |example| Sidekiq::Queue.new(queue).clear Sidekiq::Testing.disable!(&example) @@ -40,7 +40,7 @@ RSpec.describe 'Deleting Sidekiq jobs', :clean_gitlab_redis_queues, feature_cate 'args' => args, 'meta.user' => user.username ) - raise 'Not enqueued!' if Sidekiq::Queue.new(queue).size.zero? + raise 'Not enqueued!' if Sidekiq::Queue.new(queue).size.zero? # rubocop:disable Style/ZeroLengthPredicate -- Sidekiq::Queue doesn't implement #blank? or #empty? end it 'returns info about the deleted jobs' do diff --git a/spec/requests/api/graphql/mutations/branch_rules/update_spec.rb b/spec/requests/api/graphql/mutations/branch_rules/update_spec.rb new file mode 100644 index 00000000000..14874bdfaa8 --- /dev/null +++ b/spec/requests/api/graphql/mutations/branch_rules/update_spec.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'BranchRuleUpdate', feature_category: :source_code_management do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :public) } + let_it_be(:user) { create(:user) } + let!(:branch_rule_1) { create(:protected_branch, project: project, name: name_1) } + let!(:branch_rule_2) { create(:protected_branch, project: project, name: name_2) } + let(:name_1) { "name_1" } + let(:name_2) { "name_2" } + let(:new_name) { "new name" } + let(:id) { branch_rule_1.to_global_id } + let(:project_path) { project.full_path } + let(:name) { new_name } + let(:params) do + { + id: id, + project_path: project_path, + name: name + } + end + + let(:mutation) { graphql_mutation(:branch_rule_update, params) } + + subject(:post_mutation) { post_graphql_mutation(mutation, current_user: user) } + + def mutation_response + graphql_mutation_response(:branch_rule_update) + end + + context 'when the user does not have permission' do + before_all do + project.add_developer(user) + end + + it 'does not update the branch rule' do + expect { post_mutation }.not_to change { branch_rule_1 } + end + end + + context 'when the user can update a branch rules' do + let(:current_user) { user } + + before_all do + project.add_maintainer(user) + end + + it 'updates the protected branch' do + post_mutation + + expect(branch_rule_1.reload.name).to eq(new_name) + end + + it 'returns the updated branch rule' do + post_mutation + + expect(mutation_response).to have_key('branchRule') + expect(mutation_response['branchRule']['name']).to eq(new_name) + expect(mutation_response['errors']).to be_empty + end + + context 'when name already exists for the project' do + let(:params) do + { + id: id, + project_path: project_path, + name: name_2 + } + end + + it 'returns an error' do + post_mutation + + expect(mutation_response['errors'].first).to eq('Name has already been taken') + end + end + + context 'when the protected branch cannot be found' do + let(:id) { "gid://gitlab/ProtectedBranch/#{non_existing_record_id}" } + + it_behaves_like 'a mutation that returns top-level errors', + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + end + + context 'when the project cannot be found' do + let(:project_path) { 'not a project path' } + + it_behaves_like 'a mutation that returns top-level errors', + errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR] + end + end +end diff --git a/spec/requests/api/graphql/mutations/ci/catalog/resources/destroy_spec.rb b/spec/requests/api/graphql/mutations/ci/catalog/resources/destroy_spec.rb new file mode 100644 index 00000000000..3b278f973b7 --- /dev/null +++ b/spec/requests/api/graphql/mutations/ci/catalog/resources/destroy_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'CatalogResourceDestroy', feature_category: :pipeline_composition do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be(:project) { create(:project, :catalog_resource_with_components) } + let_it_be(:catalog_resource) { create(:ci_catalog_resource, project: project) } + + let(:mutation) do + variables = { + project_path: project.full_path + } + graphql_mutation(:catalog_resources_destroy, variables, + <<-QL.strip_heredoc + errors + QL + ) + end + + context 'when unauthorized' do + it_behaves_like 'a mutation that returns a top-level access error' + end + + context 'when authorized' do + before do + catalog_resource.project.add_owner(current_user) + end + + it 'destroys the catalog resource' do + expect(project.catalog_resource).to eq(catalog_resource) + + post_graphql_mutation(mutation, current_user: current_user) + + expect(project.reload.catalog_resource).to be_nil + expect_graphql_errors_to_be_empty + end + end +end diff --git a/spec/requests/api/graphql/mutations/ci/catalog/unpublish_spec.rb b/spec/requests/api/graphql/mutations/ci/catalog/unpublish_spec.rb deleted file mode 100644 index 07465777263..00000000000 --- a/spec/requests/api/graphql/mutations/ci/catalog/unpublish_spec.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'CatalogResourceUnpublish', feature_category: :pipeline_composition do - include GraphqlHelpers - - let_it_be(:current_user) { create(:user) } - let_it_be_with_reload(:resource) { create(:ci_catalog_resource) } - - let(:mutation) do - graphql_mutation( - :catalog_resource_unpublish, - id: resource.to_gid.to_s - ) - end - - subject(:post_query) { post_graphql_mutation(mutation, current_user: current_user) } - - context 'when unauthorized' do - it_behaves_like 'a mutation that returns a top-level access error' - end - - context 'when authorized' do - before_all do - resource.project.add_owner(current_user) - end - - context 'when the catalog resource is in published state' do - it 'updates the state to draft' do - resource.update!(state: :published) - expect(resource.state).to eq('published') - - post_query - - expect(resource.reload.state).to eq('draft') - expect_graphql_errors_to_be_empty - end - end - - context 'when the catalog resource is already in draft state' do - it 'leaves the state as draft' do - expect(resource.state).to eq('draft') - - post_query - - expect(resource.reload.state).to eq('draft') - expect_graphql_errors_to_be_empty - end - end - end -end diff --git a/spec/requests/api/graphql/mutations/ci/runner/create_spec.rb b/spec/requests/api/graphql/mutations/ci/runner/create_spec.rb index b697b9f73b7..567ef12df2b 100644 --- a/spec/requests/api/graphql/mutations/ci/runner/create_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/runner/create_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'RunnerCreate', feature_category: :runner_fleet do +RSpec.describe 'RunnerCreate', feature_category: :fleet_visibility do include GraphqlHelpers let_it_be(:user) { create(:user) } diff --git a/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb b/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb index 752242c3ab3..ef752448966 100644 --- a/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'RunnersRegistrationTokenReset', feature_category: :runner_fleet do +RSpec.describe 'RunnersRegistrationTokenReset', feature_category: :fleet_visibility do include GraphqlHelpers let(:mutation) { graphql_mutation(:runners_registration_token_reset, input) } diff --git a/spec/requests/api/graphql/mutations/container_registry/protection/rule/create_spec.rb b/spec/requests/api/graphql/mutations/container_registry/protection/rule/create_spec.rb index 0c708c3dc41..71b8c99c1c0 100644 --- a/spec/requests/api/graphql/mutations/container_registry/protection/rule/create_spec.rb +++ b/spec/requests/api/graphql/mutations/container_registry/protection/rule/create_spec.rb @@ -15,7 +15,7 @@ RSpec.describe 'Creating the container registry protection rule', :aggregate_fai let(:kwargs) do { project_path: project.full_path, - container_path_pattern: container_registry_protection_rule_attributes.container_path_pattern, + repository_path_pattern: container_registry_protection_rule_attributes.repository_path_pattern, push_protected_up_to_access_level: 'MAINTAINER', delete_protected_up_to_access_level: 'MAINTAINER' } @@ -26,7 +26,7 @@ RSpec.describe 'Creating the container registry protection rule', :aggregate_fai <<~QUERY containerRegistryProtectionRule { id - containerPathPattern + repositoryPathPattern } clientMutationId errors @@ -48,7 +48,7 @@ RSpec.describe 'Creating the container registry protection rule', :aggregate_fai 'errors' => be_blank, 'containerRegistryProtectionRule' => { 'id' => be_present, - 'containerPathPattern' => kwargs[:container_path_pattern] + 'repositoryPathPattern' => kwargs[:repository_path_pattern] } ) end @@ -57,7 +57,7 @@ RSpec.describe 'Creating the container registry protection rule', :aggregate_fai expect { subject }.to change { ::ContainerRegistry::Protection::Rule.count }.by(1) expect(::ContainerRegistry::Protection::Rule.where(project: project, - container_path_pattern: kwargs[:container_path_pattern])).to exist + repository_path_pattern: kwargs[:repository_path_pattern])).to exist end end @@ -84,9 +84,9 @@ RSpec.describe 'Creating the container registry protection rule', :aggregate_fai } end - context 'with invalid input field `containerPathPattern`' do + context 'with invalid input field `repositoryPathPattern`' do let(:kwargs) do - super().merge(container_path_pattern: '') + super().merge(repository_path_pattern: '') end it_behaves_like 'an erroneous response' @@ -95,7 +95,7 @@ RSpec.describe 'Creating the container registry protection rule', :aggregate_fai it { subject.tap do - expect(mutation_response['errors']).to eq ["Container path pattern can't be blank"] + expect(mutation_response['errors']).to eq ["Repository path pattern can't be blank"] end } end @@ -108,9 +108,9 @@ RSpec.describe 'Creating the container registry protection rule', :aggregate_fai context 'when container name pattern is slightly different' do let(:kwargs) do - # The field `container_path_pattern` is unique; this is why we change the value in a minimum way + # The field `repository_path_pattern` is unique; this is why we change the value in a minimum way super().merge( - container_path_pattern: "#{existing_container_registry_protection_rule.container_path_pattern}-unique" + repository_path_pattern: "#{existing_container_registry_protection_rule.repository_path_pattern}-unique" ) end @@ -121,9 +121,9 @@ RSpec.describe 'Creating the container registry protection rule', :aggregate_fai end end - context 'when field `container_path_pattern` is taken' do + context 'when field `repository_path_pattern` is taken' do let(:kwargs) do - super().merge(container_path_pattern: existing_container_registry_protection_rule.container_path_pattern, + super().merge(repository_path_pattern: existing_container_registry_protection_rule.repository_path_pattern, push_protected_up_to_access_level: 'MAINTAINER') end @@ -134,12 +134,12 @@ RSpec.describe 'Creating the container registry protection rule', :aggregate_fai it 'returns without error' do subject - expect(mutation_response['errors']).to eq ['Container path pattern has already been taken'] + expect(mutation_response['errors']).to eq ['Repository path pattern has already been taken'] end it 'does not create new container protection rules' do expect(::ContainerRegistry::Protection::Rule.where(project: project, - container_path_pattern: kwargs[:container_path_pattern], + repository_path_pattern: kwargs[:repository_path_pattern], push_protected_up_to_access_level: Gitlab::Access::MAINTAINER)).not_to exist end end diff --git a/spec/requests/api/graphql/mutations/container_registry/protection/rule/delete_spec.rb b/spec/requests/api/graphql/mutations/container_registry/protection/rule/delete_spec.rb new file mode 100644 index 00000000000..dd661c302ff --- /dev/null +++ b/spec/requests/api/graphql/mutations/container_registry/protection/rule/delete_spec.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Deleting a container registry protection rule', :aggregate_failures, feature_category: :container_registry do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :repository) } + let_it_be_with_refind(:container_protection_rule) do + create(:container_registry_protection_rule, project: project) + end + + let_it_be(:current_user) { create(:user, maintainer_projects: [project]) } + + let(:mutation) { graphql_mutation(:delete_container_registry_protection_rule, input) } + let(:mutation_response) { graphql_mutation_response(:delete_container_registry_protection_rule) } + let(:input) { { id: container_protection_rule.to_global_id } } + + subject(:post_graphql_mutation_delete_container_registry_protection_rule) do + post_graphql_mutation(mutation, current_user: current_user) + end + + shared_examples 'an erroneous response' do + it { post_graphql_mutation_delete_container_registry_protection_rule.tap { expect(mutation_response).to be_blank } } + + it do + expect { post_graphql_mutation_delete_container_registry_protection_rule } + .not_to change { ::ContainerRegistry::Protection::Rule.count } + end + end + + it_behaves_like 'a working GraphQL mutation' + + it 'responds with deleted container registry protection rule' do + expect { post_graphql_mutation_delete_container_registry_protection_rule } + .to change { ::ContainerRegistry::Protection::Rule.count }.from(1).to(0) + + expect_graphql_errors_to_be_empty + + expect(mutation_response).to include( + 'errors' => be_blank, + 'containerRegistryProtectionRule' => { + 'id' => container_protection_rule.to_global_id.to_s, + 'repositoryPathPattern' => container_protection_rule.repository_path_pattern, + 'deleteProtectedUpToAccessLevel' => container_protection_rule.delete_protected_up_to_access_level.upcase, + 'pushProtectedUpToAccessLevel' => container_protection_rule.push_protected_up_to_access_level.upcase + } + ) + end + + context 'with existing container registry protection rule belonging to other project' do + let_it_be(:container_protection_rule) do + create(:container_registry_protection_rule, repository_path_pattern: 'protection_rule_other_project') + end + + it_behaves_like 'an erroneous response' + + it { is_expected.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } } + end + + context 'with deleted container registry protection rule' do + let!(:container_protection_rule) do + create(:container_registry_protection_rule, project: project, + repository_path_pattern: 'protection_rule_deleted').destroy! + end + + it_behaves_like 'an erroneous response' + + it { is_expected.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } } + end + + context 'when current_user does not have permission' do + let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } + let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } } + let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } } + let_it_be(:anonymous) { create(:user) } + + where(:current_user) do + [ref(:developer), ref(:reporter), ref(:guest), ref(:anonymous)] + end + + with_them do + it_behaves_like 'an erroneous response' + + it { is_expected.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } } + end + end + + context "when feature flag ':container_registry_protected_containers' disabled" do + before do + stub_feature_flags(container_registry_protected_containers: false) + end + + it_behaves_like 'an erroneous response' + + it do + post_graphql_mutation_delete_container_registry_protection_rule + + expect_graphql_errors_to_include(/'container_registry_protected_containers' feature flag is disabled/) + end + end +end diff --git a/spec/requests/api/graphql/mutations/container_registry/protection/rule/update_spec.rb b/spec/requests/api/graphql/mutations/container_registry/protection/rule/update_spec.rb new file mode 100644 index 00000000000..cd2c8b9f0a2 --- /dev/null +++ b/spec/requests/api/graphql/mutations/container_registry/protection/rule/update_spec.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Updating the container registry protection rule', :aggregate_failures, feature_category: :container_registry do + include GraphqlHelpers + + let_it_be(:project) { create(:project) } + let_it_be_with_reload(:container_registry_protection_rule) do + create(:container_registry_protection_rule, project: project, push_protected_up_to_access_level: :developer) + end + + let_it_be(:current_user) { create(:user, maintainer_projects: [project]) } + + let(:container_registry_protection_rule_attributes) do + build_stubbed(:container_registry_protection_rule, project: project) + end + + let(:mutation) do + graphql_mutation(:update_container_registry_protection_rule, input, + <<~QUERY + containerRegistryProtectionRule { + repositoryPathPattern + deleteProtectedUpToAccessLevel + pushProtectedUpToAccessLevel + } + clientMutationId + errors + QUERY + ) + end + + let(:input) do + { + id: container_registry_protection_rule.to_global_id, + repository_path_pattern: "#{container_registry_protection_rule.repository_path_pattern}-updated", + delete_protected_up_to_access_level: 'OWNER', + push_protected_up_to_access_level: 'MAINTAINER' + } + end + + let(:mutation_response) { graphql_mutation_response(:update_container_registry_protection_rule) } + + subject { post_graphql_mutation(mutation, current_user: current_user) } + + shared_examples 'a successful response' do + it { subject.tap { expect_graphql_errors_to_be_empty } } + + it 'returns the updated container registry protection rule' do + subject + + expect(mutation_response).to include( + 'containerRegistryProtectionRule' => { + 'repositoryPathPattern' => input[:repository_path_pattern], + 'deleteProtectedUpToAccessLevel' => input[:delete_protected_up_to_access_level], + 'pushProtectedUpToAccessLevel' => input[:push_protected_up_to_access_level] + } + ) + end + + it do + subject.tap do + expect(container_registry_protection_rule.reload).to have_attributes( + repository_path_pattern: input[:repository_path_pattern], + push_protected_up_to_access_level: input[:push_protected_up_to_access_level].downcase + ) + end + end + end + + shared_examples 'an erroneous reponse' do + it { subject.tap { expect(mutation_response).to be_blank } } + it { expect { subject }.not_to change { container_registry_protection_rule.reload.updated_at } } + end + + it_behaves_like 'a successful response' + + context 'with other existing container registry protection rule with same repository_path_pattern' do + let_it_be_with_reload(:other_existing_container_registry_protection_rule) do + create(:container_registry_protection_rule, project: project, + repository_path_pattern: "#{container_registry_protection_rule.repository_path_pattern}-other") + end + + let(:input) do + super().merge(repository_path_pattern: other_existing_container_registry_protection_rule.repository_path_pattern) + end + + it { is_expected.tap { expect_graphql_errors_to_be_empty } } + + it 'returns a blank container registry protection rule' do + is_expected.tap { expect(mutation_response['containerRegistryProtectionRule']).to be_blank } + end + + it 'includes error message in response' do + is_expected.tap { expect(mutation_response['errors']).to eq ['Repository path pattern has already been taken'] } + end + end + + context 'with invalid input param `pushProtectedUpToAccessLevel`' do + let(:input) { super().merge(push_protected_up_to_access_level: nil) } + + it_behaves_like 'an erroneous reponse' + + it { is_expected.tap { expect_graphql_errors_to_include(/pushProtectedUpToAccessLevel can't be blank/) } } + end + + context 'with invalid input param `repositoryPathPattern`' do + let(:input) { super().merge(repository_path_pattern: '') } + + it_behaves_like 'an erroneous reponse' + + it { is_expected.tap { expect_graphql_errors_to_include(/repositoryPathPattern can't be blank/) } } + end + + context 'when current_user does not have permission' do + let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } + let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } } + let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } } + let_it_be(:anonymous) { create(:user) } + + where(:current_user) do + [ref(:developer), ref(:reporter), ref(:guest), ref(:anonymous)] + end + + with_them do + it { is_expected.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } } + end + end + + context "when feature flag ':container_registry_protected_containers' disabled" do + before do + stub_feature_flags(container_registry_protected_containers: false) + end + + it_behaves_like 'an erroneous reponse' + + it 'returns error of disabled feature flag' do + is_expected.tap do + expect_graphql_errors_to_include(/'container_registry_protected_containers' feature flag is disabled/) + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb index cb7bac771b3..1bd239ecd87 100644 --- a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb +++ b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb @@ -127,7 +127,7 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled, featur context 'when passing append as true' do let(:mode) { Types::MutationOperationModeEnum.enum[:append] } let(:input) { { assignee_usernames: [assignee2.username], operation_mode: mode } } - let(:db_query_limit) { 23 } + let(:db_query_limit) { 25 } before do # In CE, APPEND is a NOOP as you can't have multiple assignees @@ -147,7 +147,7 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled, featur end context 'when passing remove as true' do - let(:db_query_limit) { 31 } + let(:db_query_limit) { 33 } let(:mode) { Types::MutationOperationModeEnum.enum[:remove] } let(:input) { { assignee_usernames: [assignee.username], operation_mode: mode } } let(:expected_result) { [] } diff --git a/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb b/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb index 738dc3078e7..05c1a2d96d9 100644 --- a/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb +++ b/spec/requests/api/graphql/mutations/namespace/package_settings/update_spec.rb @@ -22,7 +22,8 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis npm_package_requests_forwarding: true, lock_npm_package_requests_forwarding: true, pypi_package_requests_forwarding: true, - lock_pypi_package_requests_forwarding: true + lock_pypi_package_requests_forwarding: true, + nuget_symbol_server_enabled: true } end @@ -42,6 +43,7 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis lockNpmPackageRequestsForwarding pypiPackageRequestsForwarding lockPypiPackageRequestsForwarding + nugetSymbolServerEnabled } errors QL @@ -70,6 +72,7 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis expect(package_settings_response['lockPypiPackageRequestsForwarding']).to eq(params[:lock_pypi_package_requests_forwarding]) expect(package_settings_response['npmPackageRequestsForwarding']).to eq(params[:npm_package_requests_forwarding]) expect(package_settings_response['lockNpmPackageRequestsForwarding']).to eq(params[:lock_npm_package_requests_forwarding]) + expect(package_settings_response['nugetSymbolServerEnabled']).to eq(params[:nuget_symbol_server_enabled]) end end @@ -111,7 +114,8 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis npm_package_requests_forwarding: nil, lock_npm_package_requests_forwarding: false, pypi_package_requests_forwarding: nil, - lock_pypi_package_requests_forwarding: false + lock_pypi_package_requests_forwarding: false, + nuget_symbol_server_enabled: false }, to: { maven_duplicates_allowed: false, maven_duplicate_exception_regex: 'foo-.*', @@ -124,7 +128,8 @@ RSpec.describe 'Updating the package settings', feature_category: :package_regis npm_package_requests_forwarding: true, lock_npm_package_requests_forwarding: true, pypi_package_requests_forwarding: true, - lock_pypi_package_requests_forwarding: true + lock_pypi_package_requests_forwarding: true, + nuget_symbol_server_enabled: true } it_behaves_like 'returning a success' diff --git a/spec/requests/api/graphql/mutations/organizations/create_spec.rb b/spec/requests/api/graphql/mutations/organizations/create_spec.rb index ac6b04104ba..8ab80685822 100644 --- a/spec/requests/api/graphql/mutations/organizations/create_spec.rb +++ b/spec/requests/api/graphql/mutations/organizations/create_spec.rb @@ -4,20 +4,24 @@ require 'spec_helper' RSpec.describe Mutations::Organizations::Create, feature_category: :cell do include GraphqlHelpers + include WorkhorseHelpers let_it_be(:user) { create(:user) } let(:mutation) { graphql_mutation(:organization_create, params) } let(:name) { 'Name' } let(:path) { 'path' } + let(:description) { nil } + let(:avatar) { fixture_file_upload("spec/fixtures/dk.png") } let(:params) do { name: name, - path: path + path: path, + avatar: avatar } end - subject(:create_organization) { post_graphql_mutation(mutation, current_user: current_user) } + subject(:create_organization) { post_graphql_mutation_with_uploads(mutation, current_user: current_user) } it { expect(described_class).to require_graphql_authorizations(:create_organization) } @@ -27,6 +31,7 @@ RSpec.describe Mutations::Organizations::Create, feature_category: :cell do context 'when the user does not have permission' do let(:current_user) { nil } + let(:avatar) { nil } it_behaves_like 'a mutation that returns a top-level access error' @@ -48,17 +53,35 @@ RSpec.describe Mutations::Organizations::Create, feature_category: :cell do end end - it 'creates an organization' do - expect { create_organization }.to change { Organizations::Organization.count }.by(1) + shared_examples 'creating an organization' do + it 'creates an organization' do + expect { create_organization }.to change { Organizations::Organization.count }.by(1) + end + + it 'returns the new organization' do + create_organization + + expect(graphql_data_at(:organization_create, :organization)).to match a_hash_including( + 'name' => name, + 'path' => path, + 'description' => description + ) + end end - it 'returns the new organization' do - create_organization + context 'with description' do + let(:description) { 'Organization description' } + let(:params) do + { + name: name, + path: path, + description: description + } + end - expect(graphql_data_at(:organization_create, :organization)).to match a_hash_including( - 'name' => name, - 'path' => path - ) + include_examples 'creating an organization' end + + include_examples 'creating an organization' end end diff --git a/spec/requests/api/graphql/mutations/organizations/update_spec.rb b/spec/requests/api/graphql/mutations/organizations/update_spec.rb new file mode 100644 index 00000000000..4e819c280d0 --- /dev/null +++ b/spec/requests/api/graphql/mutations/organizations/update_spec.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Organizations::Update, feature_category: :cell do + include GraphqlHelpers + include WorkhorseHelpers + + let_it_be(:user) { create(:user) } + let_it_be_with_reload(:organization) do + create(:organization) { |org| create(:organization_user, organization: org, user: user) } + end + + let(:mutation) { graphql_mutation(:organization_update, params) } + let(:name) { 'Name' } + let(:path) { 'path' } + let(:description) { 'org-description' } + let(:avatar) { nil } + let(:params) do + { + id: organization.to_global_id.to_s, + name: name, + path: path, + description: description, + avatar: avatar + } + end + + subject(:update_organization) { post_graphql_mutation_with_uploads(mutation, current_user: current_user) } + + it { expect(described_class).to require_graphql_authorizations(:admin_organization) } + + def mutation_response + graphql_mutation_response(:organization_update) + end + + context 'when the user does not have permission' do + let(:current_user) { nil } + + it_behaves_like 'a mutation that returns a top-level access error' + + it 'does not update the organization' do + initial_name = organization.name + initial_path = organization.path + + update_organization + organization.reset + + expect(organization.name).to eq(initial_name) + expect(organization.path).to eq(initial_path) + end + end + + context 'when the user has permission' do + let(:current_user) { user } + + context 'when the params are invalid' do + let(:name) { '' } + + it 'returns the validation error' do + update_organization + + expect(mutation_response).to include('errors' => ["Name can't be blank"]) + end + end + + context 'when single attribute is update' do + using RSpec::Parameterized::TableSyntax + + where(attribute: %w[name path description]) + + with_them do + let(:value) { "new-#{attribute}" } + let(:attribute_hash) { { attribute => value } } + let(:params) { { id: organization.to_global_id.to_s }.merge(attribute_hash) } + + it 'updates the given field' do + update_organization + + expect(graphql_data_at(:organization_update, :organization)).to match a_hash_including(attribute_hash) + expect(mutation_response['errors']).to be_empty + end + end + end + + it 'returns the updated organization' do + update_organization + + expect(graphql_data_at(:organization_update, :organization)).to match a_hash_including( + 'name' => name, + 'path' => path, + 'description' => description + ) + expect(mutation_response['errors']).to be_empty + end + + context 'with a new avatar' do + let(:filename) { 'spec/fixtures/dk.png' } + let(:avatar) { fixture_file_upload(filename) } + + it 'returns the updated organization' do + update_organization + + expect( + graphql_data_at(:organization_update, :organization) + ).to( + match( + a_hash_including( + 'name' => name, + 'path' => path, + 'description' => description + ) + ) + ) + expect(File.basename(organization.reload.avatar.file.file)).to eq(File.basename(filename)) + expect(mutation_response['errors']).to be_empty + end + end + end +end diff --git a/spec/requests/api/graphql/mutations/packages/bulk_destroy_spec.rb b/spec/requests/api/graphql/mutations/packages/bulk_destroy_spec.rb index d0980a2b43d..084958be1fb 100644 --- a/spec/requests/api/graphql/mutations/packages/bulk_destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/packages/bulk_destroy_spec.rb @@ -38,6 +38,26 @@ RSpec.describe 'Destroying multiple packages', feature_category: :package_regist end it_behaves_like 'returning response status', :success + + context 'when npm package' do + let_it_be_with_reload(:packages1) { create_list(:npm_package, 3, project: project1, name: 'test-package-1') } + let_it_be_with_reload(:packages2) { create_list(:npm_package, 2, project: project2, name: 'test-package-2') } + + it 'enqueues the worker to sync a metadata cache' do + arguments = [] + + expect(Packages::Npm::CreateMetadataCacheWorker) + .to receive(:bulk_perform_async_with_contexts).and_wrap_original do |original_method, *args| + packages = args.first + arguments = packages.map(&args.second[:arguments_proc]).uniq + original_method.call(*args) + end + + mutation_request + + expect(arguments).to contain_exactly([project1.id, 'test-package-1'], [project2.id, 'test-package-2']) + end + end end shared_examples 'denying the mutation request' do diff --git a/spec/requests/api/graphql/mutations/packages/destroy_spec.rb b/spec/requests/api/graphql/mutations/packages/destroy_spec.rb index 86167e7116f..6e0e5bd8aae 100644 --- a/spec/requests/api/graphql/mutations/packages/destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/packages/destroy_spec.rb @@ -35,6 +35,17 @@ RSpec.describe 'Destroying a package', feature_category: :package_registry do .to change { ::Packages::Package.pending_destruction.count }.by(1) end + context 'when npm package' do + let_it_be_with_reload(:package) { create(:npm_package) } + + it 'enqueues the worker to sync a metadata cache' do + expect(Packages::Npm::CreateMetadataCacheWorker) + .to receive(:perform_async).with(project.id, package.name) + + mutation_request + end + end + it_behaves_like 'returning response status', :success end diff --git a/spec/requests/api/graphql/mutations/packages/protection/rule/delete_spec.rb b/spec/requests/api/graphql/mutations/packages/protection/rule/delete_spec.rb index 1d94d520674..6c300f8ce57 100644 --- a/spec/requests/api/graphql/mutations/packages/protection/rule/delete_spec.rb +++ b/spec/requests/api/graphql/mutations/packages/protection/rule/delete_spec.rb @@ -15,7 +15,7 @@ RSpec.describe 'Deleting a package protection rule', :aggregate_failures, featur subject { post_graphql_mutation(mutation, current_user: current_user) } - shared_examples 'an erroneous reponse' do + shared_examples 'an erroneous response' do it { subject.tap { expect(mutation_response).to be_blank } } it { expect { subject }.not_to change { ::Packages::Protection::Rule.count } } end @@ -44,7 +44,7 @@ RSpec.describe 'Deleting a package protection rule', :aggregate_failures, featur create(:package_protection_rule, package_name_pattern: 'protection_rule_other_project') end - it_behaves_like 'an erroneous reponse' + it_behaves_like 'an erroneous response' it { subject.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } } end @@ -54,7 +54,7 @@ RSpec.describe 'Deleting a package protection rule', :aggregate_failures, featur create(:package_protection_rule, project: project, package_name_pattern: 'protection_rule_deleted').destroy! end - it_behaves_like 'an erroneous reponse' + it_behaves_like 'an erroneous response' it { subject.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } } end @@ -70,7 +70,7 @@ RSpec.describe 'Deleting a package protection rule', :aggregate_failures, featur end with_them do - it_behaves_like 'an erroneous reponse' + it_behaves_like 'an erroneous response' it { subject.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } } end @@ -81,7 +81,7 @@ RSpec.describe 'Deleting a package protection rule', :aggregate_failures, featur stub_feature_flags(packages_protected_packages: false) end - it_behaves_like 'an erroneous reponse' + it_behaves_like 'an erroneous response' it { subject.tap { expect_graphql_errors_to_include(/'packages_protected_packages' feature flag is disabled/) } } end diff --git a/spec/requests/api/graphql/mutations/packages/protection/rule/update_spec.rb b/spec/requests/api/graphql/mutations/packages/protection/rule/update_spec.rb new file mode 100644 index 00000000000..efc919062d6 --- /dev/null +++ b/spec/requests/api/graphql/mutations/packages/protection/rule/update_spec.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Updating the packages protection rule', :aggregate_failures, feature_category: :package_registry do + include GraphqlHelpers + + let_it_be(:project) { create(:project) } + let_it_be_with_reload(:package_protection_rule) do + create(:package_protection_rule, project: project, push_protected_up_to_access_level: :developer) + end + + let_it_be(:current_user) { create(:user, maintainer_projects: [project]) } + + let(:package_protection_rule_attributes) { build_stubbed(:package_protection_rule, project: project) } + + let(:mutation) do + graphql_mutation(:update_packages_protection_rule, input, + <<~QUERY + packageProtectionRule { + packageNamePattern + pushProtectedUpToAccessLevel + } + clientMutationId + errors + QUERY + ) + end + + let(:input) do + { + id: package_protection_rule.to_global_id, + package_name_pattern: "#{package_protection_rule.package_name_pattern}-updated", + push_protected_up_to_access_level: 'MAINTAINER' + } + end + + let(:mutation_response) { graphql_mutation_response(:update_packages_protection_rule) } + + subject { post_graphql_mutation(mutation, current_user: current_user) } + + shared_examples 'a successful response' do + it { subject.tap { expect_graphql_errors_to_be_empty } } + + it 'returns the updated package protection rule' do + subject + + expect(mutation_response).to include( + 'packageProtectionRule' => { + 'packageNamePattern' => input[:package_name_pattern], + 'pushProtectedUpToAccessLevel' => input[:push_protected_up_to_access_level] + } + ) + end + + it do + subject.tap do + expect(package_protection_rule.reload).to have_attributes( + package_name_pattern: input[:package_name_pattern], + push_protected_up_to_access_level: input[:push_protected_up_to_access_level].downcase + ) + end + end + end + + shared_examples 'an erroneous response' do + it { subject.tap { expect(mutation_response).to be_blank } } + it { expect { subject }.not_to change { package_protection_rule.reload.updated_at } } + end + + it_behaves_like 'a successful response' + + context 'with other existing package protection rule with same package_name_pattern' do + let_it_be_with_reload(:other_existing_package_protection_rule) do + create(:package_protection_rule, project: project, + package_name_pattern: "#{package_protection_rule.package_name_pattern}-other") + end + + let(:input) { super().merge(package_name_pattern: other_existing_package_protection_rule.package_name_pattern) } + + it { is_expected.tap { expect_graphql_errors_to_be_empty } } + + it 'returns a blank package protection rule' do + is_expected.tap { expect(mutation_response['packageProtectionRule']).to be_blank } + end + + it 'includes error message in response' do + is_expected.tap { expect(mutation_response['errors']).to eq ['Package name pattern has already been taken'] } + end + end + + context 'with invalid input param `pushProtectedUpToAccessLevel`' do + let(:input) { super().merge(push_protected_up_to_access_level: nil) } + + it_behaves_like 'an erroneous response' + + it { is_expected.tap { expect_graphql_errors_to_include(/pushProtectedUpToAccessLevel can't be blank/) } } + end + + context 'with invalid input param `packageNamePattern`' do + let(:input) { super().merge(package_name_pattern: '') } + + it_behaves_like 'an erroneous response' + + it { is_expected.tap { expect_graphql_errors_to_include(/packageNamePattern can't be blank/) } } + end + + context 'when current_user does not have permission' do + let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } + let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } } + let_it_be(:guest) { create(:user).tap { |u| project.add_guest(u) } } + let_it_be(:anonymous) { create(:user) } + + where(:current_user) do + [ref(:developer), ref(:reporter), ref(:guest), ref(:anonymous)] + end + + with_them do + it { is_expected.tap { expect_graphql_errors_to_include(/you don't have permission to perform this action/) } } + end + end + + context "when feature flag ':packages_protected_packages' disabled" do + before do + stub_feature_flags(packages_protected_packages: false) + end + + it_behaves_like 'an erroneous response' + + it 'returns error of disabled feature flag' do + is_expected.tap { expect_graphql_errors_to_include(/'packages_protected_packages' feature flag is disabled/) } + end + end +end diff --git a/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb b/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb index 65b8083c74f..b1cd3259eeb 100644 --- a/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb +++ b/spec/requests/api/graphql/mutations/user_preferences/update_spec.rb @@ -12,7 +12,8 @@ RSpec.describe Mutations::UserPreferences::Update, feature_category: :user_profi let(:input) do { 'issuesSort' => sort_value, - 'visibilityPipelineIdType' => 'IID' + 'visibilityPipelineIdType' => 'IID', + 'useWebIdeExtensionMarketplace' => true } end @@ -26,19 +27,26 @@ RSpec.describe Mutations::UserPreferences::Update, feature_category: :user_profi expect(response).to have_gitlab_http_status(:success) expect(mutation_response['userPreferences']['issuesSort']).to eq(sort_value) expect(mutation_response['userPreferences']['visibilityPipelineIdType']).to eq('IID') + expect(mutation_response['userPreferences']['useWebIdeExtensionMarketplace']).to eq(true) expect(current_user.user_preference.persisted?).to eq(true) expect(current_user.user_preference.issues_sort).to eq(Types::IssueSortEnum.values[sort_value].value.to_s) expect(current_user.user_preference.visibility_pipeline_id_type).to eq('iid') + expect(current_user.user_preference.use_web_ide_extension_marketplace).to eq(true) end end context 'when user has existing preference' do - before do - current_user.create_user_preference!( + let(:init_user_preference) do + { issues_sort: Types::IssueSortEnum.values['TITLE_DESC'].value, - visibility_pipeline_id_type: 'id' - ) + visibility_pipeline_id_type: 'id', + use_web_ide_extension_marketplace: true + } + end + + before do + current_user.create_user_preference!(init_user_preference) end it 'updates the existing value' do @@ -53,5 +61,29 @@ RSpec.describe Mutations::UserPreferences::Update, feature_category: :user_profi expect(current_user.user_preference.issues_sort).to eq(Types::IssueSortEnum.values[sort_value].value.to_s) expect(current_user.user_preference.visibility_pipeline_id_type).to eq('iid') end + + context 'when input has nil attributes' do + let(:input) do + { + 'issuesSort' => nil, + 'visibilityPipelineIdType' => nil, + 'useWebIdeExtensionMarketplace' => nil + } + end + + it 'updates only nullable attributes' do + post_graphql_mutation(mutation, current_user: current_user) + + current_user.user_preference.reload + + expect(current_user.user_preference).to have_attributes({ + # These are nullable and are exepcted to change + issues_sort: nil, + # These should not have changed + visibility_pipeline_id_type: init_user_preference[:visibility_pipeline_id_type], + use_web_ide_extension_marketplace: init_user_preference[:use_web_ide_extension_marketplace] + }) + end + end end end diff --git a/spec/requests/api/graphql/mutations/work_items/delete_task_spec.rb b/spec/requests/api/graphql/mutations/work_items/delete_task_spec.rb deleted file mode 100644 index b1828de046f..00000000000 --- a/spec/requests/api/graphql/mutations/work_items/delete_task_spec.rb +++ /dev/null @@ -1,79 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe "Delete a task in a work item's description", feature_category: :team_planning do - include GraphqlHelpers - - let_it_be(:project) { create(:project) } - let_it_be(:developer) { create(:user).tap { |user| project.add_developer(user) } } - let_it_be(:task) { create(:work_item, :task, project: project, author: developer) } - let_it_be(:work_item, refind: true) do - create(:work_item, project: project, description: "- [ ] #{task.to_reference}+", lock_version: 3) - end - - before_all do - create(:issue_link, source_id: work_item.id, target_id: task.id) - end - - let(:lock_version) { work_item.lock_version } - let(:input) do - { - 'id' => work_item.to_global_id.to_s, - 'lockVersion' => lock_version, - 'taskData' => { - 'id' => task.to_global_id.to_s, - 'lineNumberStart' => 1, - 'lineNumberEnd' => 1 - } - } - end - - let(:mutation) { graphql_mutation(:workItemDeleteTask, input) } - let(:mutation_response) { graphql_mutation_response(:work_item_delete_task) } - - context 'the user is not allowed to update a work item' do - let(:current_user) { create(:user) } - - it_behaves_like 'a mutation that returns a top-level access error' - end - - context 'when user can update the description but not delete the task' do - let(:current_user) { create(:user).tap { |u| project.add_developer(u) } } - - it_behaves_like 'a mutation that returns a top-level access error' - end - - context 'when user has permissions to remove a task' do - let(:current_user) { developer } - - it 'removes the task from the work item' do - expect do - post_graphql_mutation(mutation, current_user: current_user) - work_item.reload - end.to change(WorkItem, :count).by(-1).and( - change(IssueLink, :count).by(-1) - ).and( - change(work_item, :description).from("- [ ] #{task.to_reference}+").to("- [ ] #{task.title}") - ) - - expect(response).to have_gitlab_http_status(:success) - expect(mutation_response['workItem']).to include('id' => work_item.to_global_id.to_s) - end - - context 'when removing the task fails' do - let(:lock_version) { 2 } - - it 'makes no changes to the DB and returns an error message' do - expect do - post_graphql_mutation(mutation, current_user: current_user) - work_item.reload - end.to not_change(WorkItem, :count).and( - not_change(work_item, :description) - ) - - expect(mutation_response['errors']).to contain_exactly('Stale work item. Check lock version') - end - end - end -end |