diff options
Diffstat (limited to 'spec/graphql')
63 files changed, 1050 insertions, 498 deletions
diff --git a/spec/graphql/features/feature_flag_spec.rb b/spec/graphql/features/feature_flag_spec.rb deleted file mode 100644 index b06718eb16a..00000000000 --- a/spec/graphql/features/feature_flag_spec.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'Graphql Field feature flags' do - include GraphqlHelpers - include Graphql::ResolverFactories - - let_it_be(:user) { create(:user) } - - let(:feature_flag) { 'test_feature' } - let(:test_object) { double(name: 'My name') } - let(:query_string) { '{ item { name } }' } - let(:result) { execute_query(query_type)['data'] } - - before do - skip_feature_flags_yaml_validation - end - - subject { result } - - describe 'Feature flagged field' do - let(:type) { type_factory } - - let(:query_type) do - query_factory do |query| - query.field :item, type, null: true, _deprecated_feature_flag: feature_flag, resolver: new_resolver(test_object) - end - end - - it 'checks YAML definition for default_enabled' do - # Exception is indicative of a check for YAML definition - expect { subject }.to raise_error(Feature::InvalidFeatureFlagError, /The feature flag YAML definition for '#{feature_flag}' does not exist/) - end - - context 'skipping YAML check' do - before do - skip_default_enabled_yaml_check - end - - it 'returns the value when feature is enabled' do - expect(subject['item']).to eq('name' => test_object.name) - end - - it 'returns nil when the feature is disabled' do - stub_feature_flags(feature_flag => false) - - expect(subject).to be_nil - end - end - end -end diff --git a/spec/graphql/mutations/boards/issues/issue_move_list_spec.rb b/spec/graphql/mutations/boards/issues/issue_move_list_spec.rb index 10aed8a1f00..8e9a567f614 100644 --- a/spec/graphql/mutations/boards/issues/issue_move_list_spec.rb +++ b/spec/graphql/mutations/boards/issues/issue_move_list_spec.rb @@ -55,7 +55,7 @@ RSpec.describe Mutations::Boards::Issues::IssueMoveList do let(:move_params) { {} } it 'generates an error' do - expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, 'At least one of the arguments fromListId, toListId, afterId or beforeId is required') do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, 'At least one of the arguments fromListId, toListId, positionInList, moveAfterId, or moveBeforeId is required') do subject end end @@ -71,6 +71,50 @@ RSpec.describe Mutations::Boards::Issues::IssueMoveList do end end + context 'when positionInList is given' do + let(:move_params) { { from_list_id: list1.id, to_list_id: list2.id, position_in_list: 0 } } + + context 'when fromListId and toListId are missing' do + let(:move_params) { { position_in_list: 0 } } + + it 'generates an error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, 'Both fromListId and toListId are required when positionInList is given') do + subject + end + end + end + + context 'when move_before_id is also given' do + let(:move_params) { { from_list_id: list1.id, to_list_id: list2.id, position_in_list: 0, move_before_id: 1 } } + + it 'generates an error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, 'positionInList is mutually exclusive with any of moveBeforeId or moveAfterId') do + subject + end + end + end + + context 'when move_after_id is also given' do + let(:move_params) { { from_list_id: list1.id, to_list_id: list2.id, position_in_list: 0, move_after_id: 1 } } + + it 'generates an error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, 'positionInList is mutually exclusive with any of moveBeforeId or moveAfterId') do + subject + end + end + end + + context 'when position_in_list is invalid' do + let(:move_params) { { from_list_id: list1.id, to_list_id: list2.id, position_in_list: -5 } } + + it 'generates an error' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, "positionInList must be >= 0 or #{Boards::Issues::MoveService::LIST_END_POSITION}") do + subject + end + end + end + end + context 'when user have access to resources' do it 'moves and repositions issue' do subject diff --git a/spec/graphql/mutations/ci/runner/update_spec.rb b/spec/graphql/mutations/ci/runner/update_spec.rb index b8efd4213fa..39fe2a53a68 100644 --- a/spec/graphql/mutations/ci/runner/update_spec.rb +++ b/spec/graphql/mutations/ci/runner/update_spec.rb @@ -6,10 +6,13 @@ RSpec.describe Mutations::Ci::Runner::Update do include GraphqlHelpers let_it_be(:user) { create(:user) } - let_it_be(:runner) { create(:ci_runner, active: true, locked: false, run_untagged: true) } + let_it_be(:project1) { create(:project) } + let_it_be(:runner) do + create(:ci_runner, :project, projects: [project1], active: true, locked: false, run_untagged: true) + end let(:current_ctx) { { current_user: user } } - let(:mutated_runner) { subject[:runner] } + let(:mutated_runner) { response[:runner] } let(:mutation_params) do { @@ -21,14 +24,14 @@ RSpec.describe Mutations::Ci::Runner::Update do specify { expect(described_class).to require_graphql_authorizations(:update_runner) } describe '#resolve' do - subject do + subject(:response) do sync(resolve(described_class, args: mutation_params, ctx: current_ctx)) end context 'when the user cannot admin the runner' do it 'generates an error' do expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable) do - subject + response end end end @@ -37,7 +40,7 @@ RSpec.describe Mutations::Ci::Runner::Update do let(:mutation_params) { {} } it 'raises an error' do - expect { subject }.to raise_error(ArgumentError, "Arguments must be provided: id") + expect { response }.to raise_error(ArgumentError, "Arguments must be provided: id") end end @@ -45,41 +48,150 @@ RSpec.describe Mutations::Ci::Runner::Update do let(:admin_user) { create(:user, :admin) } let(:current_ctx) { { current_user: admin_user } } - let(:mutation_params) do - { - id: runner.to_global_id, - description: 'updated description', - maintenance_note: 'updated maintenance note', - maximum_timeout: 900, - access_level: 'ref_protected', - active: false, - locked: true, - run_untagged: false, - tag_list: %w(tag1 tag2) - } - end - context 'with valid arguments' do + let(:mutation_params) do + { + id: runner.to_global_id, + description: 'updated description', + maintenance_note: 'updated maintenance note', + maximum_timeout: 900, + access_level: 'ref_protected', + active: false, + locked: true, + run_untagged: false, + tag_list: %w(tag1 tag2) + } + end + it 'updates runner with correct values' do expected_attributes = mutation_params.except(:id, :tag_list) - subject + response - expect(subject[:errors]).to be_empty - expect(subject[:runner]).to be_an_instance_of(Ci::Runner) - expect(subject[:runner]).to have_attributes(expected_attributes) - expect(subject[:runner].tag_list).to contain_exactly(*mutation_params[:tag_list]) + expect(response[:errors]).to be_empty + expect(response[:runner]).to be_an_instance_of(Ci::Runner) + expect(response[:runner]).to have_attributes(expected_attributes) + expect(response[:runner].tag_list).to contain_exactly(*mutation_params[:tag_list]) expect(runner.reload).to have_attributes(expected_attributes) expect(runner.tag_list).to contain_exactly(*mutation_params[:tag_list]) end end + context 'with associatedProjects argument' do + let_it_be(:project2) { create(:project) } + + context 'with id set to project runner' do + let(:mutation_params) do + { + id: runner.to_global_id, + description: 'updated description', + associated_projects: [project2.to_global_id.to_s] + } + end + + it 'updates runner attributes and project relationships', :aggregate_failures do + expect_next_instance_of( + ::Ci::Runners::SetRunnerAssociatedProjectsService, + { + runner: runner, + current_user: admin_user, + project_ids: [project2.id] + } + ) do |service| + expect(service).to receive(:execute).and_call_original + end + + expected_attributes = mutation_params.except(:id, :associated_projects) + + response + + expect(response[:errors]).to be_empty + expect(response[:runner]).to be_an_instance_of(Ci::Runner) + expect(response[:runner]).to have_attributes(expected_attributes) + expect(runner.reload).to have_attributes(expected_attributes) + expect(runner.projects).to match_array([project1, project2]) + end + + context 'with user not allowed to assign runner' do + before do + allow(admin_user).to receive(:can?).with(:assign_runner, runner).and_return(false) + end + + it 'does not update runner', :aggregate_failures do + expect_next_instance_of( + ::Ci::Runners::SetRunnerAssociatedProjectsService, + { + runner: runner, + current_user: admin_user, + project_ids: [project2.id] + } + ) do |service| + expect(service).to receive(:execute).and_call_original + end + + expected_attributes = mutation_params.except(:id, :associated_projects) + + response + + expect(response[:errors]).to match_array(['user not allowed to assign runner']) + expect(response[:runner]).to be_an_instance_of(Ci::Runner) + expect(response[:runner]).not_to have_attributes(expected_attributes) + expect(runner.reload).not_to have_attributes(expected_attributes) + expect(runner.projects).to match_array([project1]) + end + end + end + + context 'with id set to instance runner' do + let(:instance_runner) { create(:ci_runner, :instance) } + let(:mutation_params) do + { + id: instance_runner.to_global_id, + description: 'updated description', + associated_projects: [project2.to_global_id.to_s] + } + end + + it 'raises error', :aggregate_failures do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do + response + end + end + end + end + + context 'with non-existing project ID in associatedProjects argument' do + let(:mutation_params) do + { + id: runner.to_global_id, + associated_projects: ['gid://gitlab/Project/-1'] + } + end + + it 'does not change associated projects' do + expected_attributes = mutation_params.except(:id, :associated_projects) + + response + + expect(response[:errors]).to be_empty + expect(response[:runner]).to be_an_instance_of(Ci::Runner) + expect(response[:runner]).to have_attributes(expected_attributes) + expect(runner.reload).to have_attributes(expected_attributes) + expect(runner.projects).to match_array([project1]) + end + end + context 'with out-of-range maximum_timeout and missing tag_list' do - it 'returns a descriptive error' do - mutation_params[:maximum_timeout] = 100 - mutation_params.delete(:tag_list) + let(:mutation_params) do + { + id: runner.to_global_id, + maximum_timeout: 100, + run_untagged: false + } + end - expect(subject[:errors]).to contain_exactly( + it 'returns a descriptive error' do + expect(response[:errors]).to contain_exactly( 'Maximum timeout needs to be at least 10 minutes', 'Tags list can not be empty when runner is not allowed to pick untagged jobs' ) @@ -90,7 +202,7 @@ RSpec.describe Mutations::Ci::Runner::Update do it 'returns a descriptive error' do mutation_params[:maintenance_note] = '1' * 1025 - expect(subject[:errors]).to contain_exactly( + expect(response[:errors]).to contain_exactly( 'Maintenance note is too long (maximum is 1024 characters)' ) end diff --git a/spec/graphql/mutations/commits/create_spec.rb b/spec/graphql/mutations/commits/create_spec.rb index 9fc9c731b96..fd0c2c46b2e 100644 --- a/spec/graphql/mutations/commits/create_spec.rb +++ b/spec/graphql/mutations/commits/create_spec.rb @@ -9,6 +9,7 @@ RSpec.describe Mutations::Commits::Create do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project, :public, :repository) } + let_it_be(:group) { create(:group, :public) } let(:context) do GraphQL::Query::Context.new( @@ -39,174 +40,208 @@ RSpec.describe Mutations::Commits::Create do let(:mutated_commit) { subject[:commit] } - it 'raises an error if the resource is not accessible to the user' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end - - context 'when user does not have enough permissions' do - before do - project.add_guest(user) - end - + context 'when user is not a project member' do it 'raises an error' do expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) end end - context 'when user is a maintainer of a different project' do - before do - create(:project_empty_repo).add_maintainer(user) - end + context 'when user is a direct project member' do + context 'and user is a guest' do + before do + project.add_guest(user) + end - it 'raises an error' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end end - end - context 'when the user can create a commit' do - let(:deltas) { mutated_commit.raw_deltas } + context 'and user is a developer' do + let(:deltas) { mutated_commit.raw_deltas } - before_all do - project.add_developer(user) - end - - context 'when service successfully creates a new commit' do - it "returns the ETag path for the commit's pipeline" do - commit_pipeline_path = subject[:commit_pipeline_path] - expect(commit_pipeline_path).to match(%r(pipelines/sha/\w+)) + before_all do + project.add_developer(user) end - it 'returns the content of the commit' do - expect(subject[:content]).to eq(actions.pluck(:content)) - end + context 'when service successfully creates a new commit' do + it "returns the ETag path for the commit's pipeline" do + commit_pipeline_path = subject[:commit_pipeline_path] + expect(commit_pipeline_path).to match(%r(pipelines/sha/\w+)) + end - it 'returns a new commit' do - expect(mutated_commit).to have_attributes(message: message, project: project) - expect(subject[:errors]).to be_empty + it 'returns the content of the commit' do + expect(subject[:content]).to eq(actions.pluck(:content)) + end - expect_to_contain_deltas([ - a_hash_including(a_mode: '0', b_mode: '100644', new_file: true, new_path: file_path) - ]) + it 'returns a new commit' do + expect(mutated_commit).to have_attributes(message: message, project: project) + expect(subject[:errors]).to be_empty + + expect_to_contain_deltas([ + a_hash_including(a_mode: '0', b_mode: '100644', new_file: true, new_path: file_path) + ]) + end end - end - context 'when request has multiple actions' do - let(:actions) do - [ - { - action: 'create', - file_path: 'foo/foobar', - content: 'some content' - }, - { - action: 'delete', - file_path: 'README.md' - }, - { - action: 'move', - file_path: "LICENSE.md", - previous_path: "LICENSE", - content: "some content" - }, - { - action: 'update', - file_path: 'VERSION', - content: 'new content' - }, - { - action: 'chmod', - file_path: 'CHANGELOG', - execute_filemode: true - } - ] + context 'when request has multiple actions' do + let(:actions) do + [ + { + action: 'create', + file_path: 'foo/foobar', + content: 'some content' + }, + { + action: 'delete', + file_path: 'README.md' + }, + { + action: 'move', + file_path: "LICENSE.md", + previous_path: "LICENSE", + content: "some content" + }, + { + action: 'update', + file_path: 'VERSION', + content: 'new content' + }, + { + action: 'chmod', + file_path: 'CHANGELOG', + execute_filemode: true + } + ] + end + + it 'returns a new commit' do + expect(mutated_commit).to have_attributes(message: message, project: project) + expect(subject[:errors]).to be_empty + + expect_to_contain_deltas([ + a_hash_including(a_mode: '0', b_mode: '100644', new_path: 'foo/foobar'), + a_hash_including(deleted_file: true, new_path: 'README.md'), + a_hash_including(deleted_file: true, new_path: 'LICENSE'), + a_hash_including(new_file: true, new_path: 'LICENSE.md'), + a_hash_including(new_file: false, new_path: 'VERSION'), + a_hash_including(a_mode: '100644', b_mode: '100755', new_path: 'CHANGELOG') + ]) + end end - it 'returns a new commit' do - expect(mutated_commit).to have_attributes(message: message, project: project) - expect(subject[:errors]).to be_empty - - expect_to_contain_deltas([ - a_hash_including(a_mode: '0', b_mode: '100644', new_path: 'foo/foobar'), - a_hash_including(deleted_file: true, new_path: 'README.md'), - a_hash_including(deleted_file: true, new_path: 'LICENSE'), - a_hash_including(new_file: true, new_path: 'LICENSE.md'), - a_hash_including(new_file: false, new_path: 'VERSION'), - a_hash_including(a_mode: '100644', b_mode: '100755', new_path: 'CHANGELOG') - ]) + context 'when actions are not defined' do + let(:actions) { [] } + + it 'returns a new commit' do + expect(mutated_commit).to have_attributes(message: message, project: project) + expect(subject[:errors]).to be_empty + + expect_to_contain_deltas([]) + end end - end - context 'when actions are not defined' do - let(:actions) { [] } + context 'when branch does not exist' do + let(:branch) { 'unknown' } - it 'returns a new commit' do - expect(mutated_commit).to have_attributes(message: message, project: project) - expect(subject[:errors]).to be_empty + it 'returns errors' do + expect(mutated_commit).to be_nil + expect(subject[:errors]).to match_array(['You can only create or edit files when you are on a branch']) + end + end - expect_to_contain_deltas([]) + context 'when branch does not exist and a start branch is provided' do + let(:branch) { 'my-branch' } + let(:start_branch) { 'master' } + let(:actions) do + [ + { + action: 'create', + file_path: 'ANOTHER_FILE.md', + content: 'Bye' + } + ] + end + + it 'returns a new commit' do + expect(mutated_commit).to have_attributes(message: message, project: project) + expect(subject[:errors]).to be_empty + expect(subject[:content]).to eq(actions.pluck(:content)) + + expect_to_contain_deltas([ + a_hash_including(a_mode: '0', b_mode: '100644', new_file: true, new_path: 'ANOTHER_FILE.md') + ]) + end end - end - context 'when branch does not exist' do - let(:branch) { 'unknown' } + context 'when message is not set' do + let(:message) { nil } - it 'returns errors' do - expect(mutated_commit).to be_nil - expect(subject[:errors]).to eq(['You can only create or edit files when you are on a branch']) + it 'returns errors' do + expect(mutated_commit).to be_nil + expect(subject[:errors].to_s).to match(/3:UserCommitFiles: empty CommitMessage/) + end end - end - context 'when branch does not exist and a start branch is provided' do - let(:branch) { 'my-branch' } - let(:start_branch) { 'master' } - let(:actions) do - [ - { - action: 'create', - file_path: 'ANOTHER_FILE.md', - content: 'Bye' - } - ] + context 'when actions are incorrect' do + let(:actions) { [{ action: 'unknown', file_path: 'test.md', content: '' }] } + + it 'returns errors' do + expect(mutated_commit).to be_nil + expect(subject[:errors]).to match_array(['Unknown action \'unknown\'']) + end end - it 'returns a new commit' do - expect(mutated_commit).to have_attributes(message: message, project: project) - expect(subject[:errors]).to be_empty - expect(subject[:content]).to eq(actions.pluck(:content)) + context 'when branch is protected' do + before do + create(:protected_branch, project: project, name: branch) + end - expect_to_contain_deltas([ - a_hash_including(a_mode: '0', b_mode: '100644', new_file: true, new_path: 'ANOTHER_FILE.md') - ]) + it 'returns errors' do + expect(mutated_commit).to be_nil + expect(subject[:errors]).to match_array(['You are not allowed to push into this branch']) + end end end + end - context 'when message is not set' do - let(:message) { nil } + context 'when user is an inherited member from the group' do + context 'when project is public with private repository' do + let(:project) { create(:project, :public, :repository, :repository_private, group: group) } - it 'returns errors' do - expect(mutated_commit).to be_nil - expect(subject[:errors].to_s).to match(/3:UserCommitFiles: empty CommitMessage/) + context 'and user is a guest' do + before do + group.add_guest(user) + end + + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end end end - context 'when actions are incorrect' do - let(:actions) { [{ action: 'unknown', file_path: 'test.md', content: '' }] } + context 'when project is private' do + let(:project) { create(:project, :private, :repository, group: group) } + + context 'and user is a guest' do + before do + group.add_guest(user) + end - it 'returns errors' do - expect(mutated_commit).to be_nil - expect(subject[:errors]).to eq(['Unknown action \'unknown\'']) + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end end end + end - context 'when branch is protected' do - before do - create(:protected_branch, project: project, name: branch) - end + context 'when user is a maintainer of a different project' do + before do + create(:project_empty_repo).add_maintainer(user) + end - it 'returns errors' do - expect(mutated_commit).to be_nil - expect(subject[:errors]).to eq(['You are not allowed to push into this branch']) - end + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) end end end diff --git a/spec/graphql/mutations/environments/canary_ingress/update_spec.rb b/spec/graphql/mutations/environments/canary_ingress/update_spec.rb index b93fb36a8ff..2476431816e 100644 --- a/spec/graphql/mutations/environments/canary_ingress/update_spec.rb +++ b/spec/graphql/mutations/environments/canary_ingress/update_spec.rb @@ -43,11 +43,12 @@ RSpec.describe Mutations::Environments::CanaryIngress::Update do end it 'returns notice about feature removal' do - expect(subject[:errors]).to match_array([ - 'This endpoint was deactivated as part of the certificate-based' \ - 'kubernetes integration removal. See Epic:' \ - 'https://gitlab.com/groups/gitlab-org/configure/-/epics/8' - ]) + expect(subject[:errors]).to match_array( + [ + 'This endpoint was deactivated as part of the certificate-based' \ + 'kubernetes integration removal. See Epic:' \ + 'https://gitlab.com/groups/gitlab-org/configure/-/epics/8' + ]) end end end diff --git a/spec/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb b/spec/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb index 4541f8af7d3..56514c985ff 100644 --- a/spec/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb +++ b/spec/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe Mutations::IncidentManagement::TimelineEvent::PromoteFromNote do + include NotesHelper + let_it_be(:current_user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:incident) { create(:incident, project: project) } @@ -23,7 +25,7 @@ RSpec.describe Mutations::IncidentManagement::TimelineEvent::PromoteFromNote do let(:expected_timeline_event) do instance_double( 'IncidentManagement::TimelineEvent', - note: comment.note, + note: "@#{comment.author.username} [commented](#{noteable_note_url(comment)}): '#{comment.note}'", occurred_at: comment.created_at.to_s, incident: incident, author: current_user, diff --git a/spec/graphql/mutations/merge_requests/create_spec.rb b/spec/graphql/mutations/merge_requests/create_spec.rb index e1edb60e4ff..6e593a5f4be 100644 --- a/spec/graphql/mutations/merge_requests/create_spec.rb +++ b/spec/graphql/mutations/merge_requests/create_spec.rb @@ -7,8 +7,7 @@ RSpec.describe Mutations::MergeRequests::Create do subject(:mutation) { described_class.new(object: nil, context: context, field: nil) } - let_it_be(:project) { create(:project, :public, :repository) } - let_it_be(:user) { create(:user) } + let(:user) { create(:user) } let(:context) do GraphQL::Query::Context.new( @@ -38,62 +37,106 @@ RSpec.describe Mutations::MergeRequests::Create do let(:mutated_merge_request) { subject[:merge_request] } - it 'raises an error if the resource is not accessible to the user' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + shared_examples 'resource not available' do + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end end - context 'when user does not have enough permissions to create a merge request' do - before do - project.add_guest(user) - end + context 'when user is not a project member' do + let_it_be(:project) { create(:project, :public, :repository) } - it 'raises an error if the resource is not accessible to the user' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end + it_behaves_like 'resource not available' end - context 'when the user can create a merge request' do - before_all do - project.add_developer(user) - end + context 'when user is a direct project member' do + let_it_be(:project) { create(:project, :public, :repository) } - it 'creates a new merge request' do - expect { mutated_merge_request }.to change(MergeRequest, :count).by(1) - end + context 'and user is a guest' do + before do + project.add_guest(user) + end - it 'returns a new merge request' do - expect(mutated_merge_request.title).to eq(title) - expect(subject[:errors]).to be_empty + it_behaves_like 'resource not available' end - context 'when optional description field is set' do - let(:description) { 'content' } + context 'and user is a developer' do + before do + project.add_developer(user) + end + + it 'creates a new merge request' do + expect { mutated_merge_request }.to change(MergeRequest, :count).by(1) + end - it 'returns a new merge request with a description' do - expect(mutated_merge_request.description).to eq(description) + it 'returns a new merge request' do + expect(mutated_merge_request.title).to eq(title) expect(subject[:errors]).to be_empty end - end - context 'when optional labels field is set' do - let(:labels) { %w[label-1 label-2] } + context 'when optional description field is set' do + let(:description) { 'content' } - it 'returns a new merge request with labels' do - expect(mutated_merge_request.labels.map(&:title)).to eq(labels) - expect(subject[:errors]).to be_empty + it 'returns a new merge request with a description' do + expect(mutated_merge_request.description).to eq(description) + expect(subject[:errors]).to be_empty + end + end + + context 'when optional labels field is set' do + let(:labels) { %w[label-1 label-2] } + + it 'returns a new merge request with labels' do + expect(mutated_merge_request.labels.map(&:title)).to eq(labels) + expect(subject[:errors]).to be_empty + end + end + + context 'when service cannot create a merge request' do + let(:title) { nil } + + it 'does not create a new merge request' do + expect { mutated_merge_request }.not_to change(MergeRequest, :count) + end + + it 'returns errors' do + expect(mutated_merge_request).to be_nil + expect(subject[:errors]).to match_array(['Title can\'t be blank']) + end end end + end - context 'when service cannot create a merge request' do - let(:title) { nil } + context 'when user is an inherited member from the group' do + let_it_be(:group) { create(:group, :public) } - it 'does not create a new merge request' do - expect { mutated_merge_request }.not_to change(MergeRequest, :count) + context 'when project is public with private merge requests' do + let_it_be(:project) do + create(:project, + :public, + :repository, + group: group, + merge_requests_access_level: ProjectFeature::DISABLED) end - it 'returns errors' do - expect(mutated_merge_request).to be_nil - expect(subject[:errors]).to eq(['Title can\'t be blank']) + context 'and user is a guest' do + before do + group.add_guest(user) + end + + it_behaves_like 'resource not available' + end + end + + context 'when project is private' do + let_it_be(:project) { create(:project, :private, :repository, group: group) } + + context 'and user is a guest' do + before do + group.add_guest(user) + end + + it_behaves_like 'resource not available' end end end diff --git a/spec/graphql/mutations/releases/create_spec.rb b/spec/graphql/mutations/releases/create_spec.rb index b6b9449aa39..e7c25a20bad 100644 --- a/spec/graphql/mutations/releases/create_spec.rb +++ b/spec/graphql/mutations/releases/create_spec.rb @@ -135,7 +135,7 @@ RSpec.describe Mutations::Releases::Create do it 'has an access error' do subject - expect(resolve).to include(errors: ['Access Denied']) + expect(resolve).to include(errors: ['You are not allowed to create this tag as it is protected.']) end end end diff --git a/spec/graphql/mutations/releases/update_spec.rb b/spec/graphql/mutations/releases/update_spec.rb index 15b10ea0648..0cf10e03fb1 100644 --- a/spec/graphql/mutations/releases/update_spec.rb +++ b/spec/graphql/mutations/releases/update_spec.rb @@ -18,8 +18,8 @@ RSpec.describe Mutations::Releases::Update do let_it_be(:release) do create(:release, project: project, tag: tag, name: name, - description: description, released_at: released_at, - created_at: created_at, milestones: [milestone_12_3, milestone_12_4]) + description: description, released_at: released_at, + created_at: created_at, milestones: [milestone_12_3, milestone_12_4]) end let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) } diff --git a/spec/graphql/resolvers/board_lists_resolver_spec.rb b/spec/graphql/resolvers/board_lists_resolver_spec.rb index c882ad7c818..2fb7c5c4717 100644 --- a/spec/graphql/resolvers/board_lists_resolver_spec.rb +++ b/spec/graphql/resolvers/board_lists_resolver_spec.rb @@ -101,7 +101,7 @@ RSpec.describe Resolvers::BoardListsResolver do def resolve_board_lists(args: {}, current_user: user) resolve(described_class, obj: board, args: args, ctx: { current_user: current_user }, - arg_style: :internal + arg_style: :internal ) end end diff --git a/spec/graphql/resolvers/ci/config_resolver_spec.rb b/spec/graphql/resolvers/ci/config_resolver_spec.rb index dc030b1313b..692bdf58784 100644 --- a/spec/graphql/resolvers/ci/config_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/config_resolver_spec.rb @@ -17,7 +17,7 @@ RSpec.describe Resolvers::Ci::ConfigResolver do subject(:response) do resolve(described_class, args: { project_path: project.full_path, content: content, sha: sha }, - ctx: { current_user: user }) + ctx: { current_user: user }) end shared_examples 'a valid config file' do diff --git a/spec/graphql/resolvers/ci/group_runners_resolver_spec.rb b/spec/graphql/resolvers/ci/group_runners_resolver_spec.rb index f99f48f5b07..57b2fcbea63 100644 --- a/spec/graphql/resolvers/ci/group_runners_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/group_runners_resolver_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Resolvers::Ci::GroupRunnersResolver do describe '#resolve' do subject do resolve(described_class, obj: obj, ctx: { current_user: user }, args: args, - arg_style: :internal) + arg_style: :internal) end include_context 'runners resolver setup' diff --git a/spec/graphql/resolvers/ci/job_token_scope_resolver_spec.rb b/spec/graphql/resolvers/ci/job_token_scope_resolver_spec.rb index ac7cef20df4..1bfd6fbf6b9 100644 --- a/spec/graphql/resolvers/ci/job_token_scope_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/job_token_scope_resolver_spec.rb @@ -20,10 +20,10 @@ RSpec.describe Resolvers::Ci::JobTokenScopeResolver do project.add_member(current_user, :maintainer) end - it 'returns nil when scope is not enabled' do + it 'returns the same project in the allow list of projects for the Ci Job Token when scope is not enabled' do allow(project).to receive(:ci_job_token_scope_enabled?).and_return(false) - expect(resolve_scope).to eq(nil) + expect(resolve_scope.all_projects).to contain_exactly(project) end it 'returns the same project in the allow list of projects for the Ci Job Token' do @@ -43,8 +43,8 @@ RSpec.describe Resolvers::Ci::JobTokenScopeResolver do project.update!(ci_job_token_scope_enabled: false) end - it 'returns nil' do - expect(resolve_scope).to be_nil + it 'resolves projects' do + expect(resolve_scope.all_projects).to contain_exactly(project) end end end diff --git a/spec/graphql/resolvers/ci/jobs_resolver_spec.rb b/spec/graphql/resolvers/ci/jobs_resolver_spec.rb index 6c228861ddf..80a70938dc4 100644 --- a/spec/graphql/resolvers/ci/jobs_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/jobs_resolver_spec.rb @@ -38,7 +38,7 @@ RSpec.describe Resolvers::Ci::JobsResolver do ::Types::Security::ReportTypeEnum.values['DAST'].value ] jobs = resolve(described_class, obj: pipeline, args: { security_report_types: report_types }, - arg_style: :internal) + arg_style: :internal) expect(jobs).to contain_exactly( have_attributes(name: 'DAST job'), diff --git a/spec/graphql/resolvers/ci/runner_projects_resolver_spec.rb b/spec/graphql/resolvers/ci/runner_projects_resolver_spec.rb new file mode 100644 index 00000000000..952c7337d65 --- /dev/null +++ b/spec/graphql/resolvers/ci/runner_projects_resolver_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Ci::RunnerProjectsResolver do + include GraphqlHelpers + + let_it_be(:project1) { create(:project, description: 'Project1.1') } + let_it_be(:project2) { create(:project, description: 'Project1.2') } + let_it_be(:project3) { create(:project, description: 'Project2.1') } + let_it_be(:runner) { create(:ci_runner, :project, projects: [project1, project2, project3]) } + + let(:args) { {} } + + subject { resolve_projects(args) } + + describe '#resolve' do + context 'with authorized user', :enable_admin_mode do + let(:current_user) { create(:user, :admin) } + + context 'with search argument' do + let(:args) { { search: 'Project1.' } } + + it 'returns a lazy value with projects containing the specified prefix' do + expect(subject).to be_a(GraphQL::Execution::Lazy) + expect(subject.value).to contain_exactly(project1, project2) + end + end + + context 'with supported arguments' do + let(:args) { { membership: true, search_namespaces: true, topics: %w[xyz] } } + + it 'creates ProjectsFinder with expected arguments' do + expect(ProjectsFinder).to receive(:new).with( + a_hash_including( + params: a_hash_including( + non_public: true, + search_namespaces: true, + topic: %w[xyz] + ) + ) + ).and_call_original + + expect(subject).to be_a(GraphQL::Execution::Lazy) + subject.value + end + end + + context 'without arguments' do + it 'returns a lazy value with all projects' do + expect(subject).to be_a(GraphQL::Execution::Lazy) + expect(subject.value).to contain_exactly(project1, project2, project3) + end + end + end + + context 'with unauthorized user' do + let(:current_user) { create(:user) } + + it { is_expected.to be_nil } + end + end + + private + + def resolve_projects(args = {}, context = { current_user: current_user }) + resolve(described_class, obj: runner, args: args, ctx: context) + end +end diff --git a/spec/graphql/resolvers/ci/runners_resolver_spec.rb b/spec/graphql/resolvers/ci/runners_resolver_spec.rb index 8586d359336..4038192a68a 100644 --- a/spec/graphql/resolvers/ci/runners_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/runners_resolver_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Resolvers::Ci::RunnersResolver do subject do resolve(described_class, obj: obj, ctx: { current_user: user }, args: args, - arg_style: :internal) + arg_style: :internal) end include_context 'runners resolver setup' diff --git a/spec/graphql/resolvers/ci/test_suite_resolver_spec.rb b/spec/graphql/resolvers/ci/test_suite_resolver_spec.rb index 606c6eb03a3..4083e77a38f 100644 --- a/spec/graphql/resolvers/ci/test_suite_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/test_suite_resolver_spec.rb @@ -32,11 +32,12 @@ RSpec.describe Resolvers::Ci::TestSuiteResolver do # Each test failure in this pipeline has a matching failure in the default branch recent_failures = test_suite[:test_cases].map { |tc| tc[:recent_failures] } - expect(recent_failures).to eq([ - { count: 1, base_branch: 'master' }, - { count: 1, base_branch: 'master' }, - { count: 1, base_branch: 'master' } - ]) + expect(recent_failures).to eq( + [ + { count: 1, base_branch: 'master' }, + { count: 1, base_branch: 'master' }, + { count: 1, base_branch: 'master' } + ]) end end diff --git a/spec/graphql/resolvers/container_repositories_resolver_spec.rb b/spec/graphql/resolvers/container_repositories_resolver_spec.rb index ed922259903..8cbb366f873 100644 --- a/spec/graphql/resolvers/container_repositories_resolver_spec.rb +++ b/spec/graphql/resolvers/container_repositories_resolver_spec.rb @@ -17,7 +17,7 @@ RSpec.describe Resolvers::ContainerRepositoriesResolver do subject do resolve(described_class, ctx: { current_user: user }, args: args, obj: object, - arg_style: :internal) + arg_style: :internal) end shared_examples 'returning container repositories' do diff --git a/spec/graphql/resolvers/container_repository_tags_resolver_spec.rb b/spec/graphql/resolvers/container_repository_tags_resolver_spec.rb index 9747f663759..3ed3fe76267 100644 --- a/spec/graphql/resolvers/container_repository_tags_resolver_spec.rb +++ b/spec/graphql/resolvers/container_repository_tags_resolver_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Resolvers::ContainerRepositoryTagsResolver do describe '#resolve' do let(:resolver) do resolve(described_class, ctx: { current_user: user }, obj: repository, args: args, - arg_style: :internal) + arg_style: :internal) end before do diff --git a/spec/graphql/resolvers/crm/organization_state_counts_resolver_spec.rb b/spec/graphql/resolvers/crm/organization_state_counts_resolver_spec.rb new file mode 100644 index 00000000000..c6ad4beeee0 --- /dev/null +++ b/spec/graphql/resolvers/crm/organization_state_counts_resolver_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Crm::OrganizationStateCountsResolver do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group, :crm_enabled) } + + before_all do + create(:organization, group: group, name: "ABC Corp") + create(:organization, group: group, name: "123 Corp", state: 'inactive') + create_list(:organization, 3, group: group) + create_list(:organization, 2, group: group, state: 'inactive') + end + + describe '#resolve' do + context 'with unauthorized user' do + it 'does not raise an error and returns nil' do + expect { resolve_counts(group) }.not_to raise_error + expect(resolve_counts(group)).to be_nil + end + end + + context 'with authorized user' do + before do + group.add_reporter(user) + end + + context 'without parent' do + it 'returns nil' do + expect(resolve_counts(nil)).to be_nil + end + end + + context 'with a group' do + context 'when no filter is provided' do + it 'returns the count of all organizations' do + counts = resolve_counts(group) + expect(counts['active']).to eq(4) + expect(counts['inactive']).to eq(3) + end + end + + context 'when search term is provided' do + it 'returns the correct counts' do + counts = resolve_counts(group, { search: "Corp" }) + + expect(counts['active']).to eq(1) + expect(counts['inactive']).to eq(1) + end + end + end + end + end + + def resolve_counts(parent, args = {}, context = { current_user: user }) + resolve(described_class, obj: parent, args: args, ctx: context) + end +end diff --git a/spec/graphql/resolvers/crm/organizations_resolver_spec.rb b/spec/graphql/resolvers/crm/organizations_resolver_spec.rb index 323f134ffc3..d5980bf3c41 100644 --- a/spec/graphql/resolvers/crm/organizations_resolver_spec.rb +++ b/spec/graphql/resolvers/crm/organizations_resolver_spec.rb @@ -55,11 +55,23 @@ RSpec.describe Resolvers::Crm::OrganizationsResolver do end context 'when no filter is provided' do - it 'returns all the organizations in the correct order' do + it 'returns all the organizations in the default order' do expect(resolve_organizations(group)).to eq([organization_a, organization_b]) end end + context 'when a sort is provided' do + it 'returns all the organizations in the correct order' do + expect(resolve_organizations(group, { sort: 'NAME_DESC' })).to eq([organization_b, organization_a]) + end + end + + context 'when filtering for all states' do + it 'returns all the organizations' do + expect(resolve_organizations(group, { state: 'all' })).to contain_exactly(organization_a, organization_b) + end + end + context 'when search term is provided' do it 'returns the correct organizations' do expect(resolve_organizations(group, { search: "def" })).to contain_exactly(organization_b) diff --git a/spec/graphql/resolvers/deployment_resolver_spec.rb b/spec/graphql/resolvers/deployment_resolver_spec.rb new file mode 100644 index 00000000000..9231edefddc --- /dev/null +++ b/spec/graphql/resolvers/deployment_resolver_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::DeploymentResolver do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :repository, :private) } + let_it_be(:environment) { create(:environment, project: project) } + let_it_be(:deployment) { create(:deployment, :created, environment: environment, project: project) } + let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } + + let(:current_user) { developer } + + describe '#resolve' do + it 'finds the deployment' do + expect(resolve_deployments(iid: deployment.iid)).to contain_exactly(deployment) + end + + it 'does not find the deployment if the IID does not match' do + expect(resolve_deployments(iid: non_existing_record_id)).to be_empty + end + end + + def resolve_deployments(args = {}, context = { current_user: current_user }) + resolve(described_class, obj: project, args: args, ctx: context) + end +end diff --git a/spec/graphql/resolvers/deployments_resolver_spec.rb b/spec/graphql/resolvers/deployments_resolver_spec.rb new file mode 100644 index 00000000000..4e5564aad0b --- /dev/null +++ b/spec/graphql/resolvers/deployments_resolver_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::DeploymentsResolver do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :repository, :private) } + let_it_be(:environment) { create(:environment, project: project) } + let_it_be(:deployment) { create(:deployment, :created, environment: environment, project: project) } + let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } + + let(:current_user) { developer } + + describe '#resolve' do + it 'finds the deployment' do + expect(resolve_deployments).to contain_exactly(deployment) + end + + it 'finds the deployment when status matches' do + expect(resolve_deployments(statuses: [:created])).to contain_exactly(deployment) + end + + it 'does not find the deployment when status does not match' do + expect(resolve_deployments(statuses: [:success])).to be_empty + end + + it 'transforms order_by for finder' do + expect(DeploymentsFinder) + .to receive(:new) + .with(environment: environment.id, status: ['success'], order_by: 'finished_at', sort: 'asc') + .and_call_original + + resolve_deployments(statuses: [:success], order_by: { finished_at: :asc }) + end + end + + def resolve_deployments(args = {}, context = { current_user: current_user }) + resolve(described_class, obj: environment, args: args, ctx: context) + end +end diff --git a/spec/graphql/resolvers/design_management/versions_resolver_spec.rb b/spec/graphql/resolvers/design_management/versions_resolver_spec.rb index 3a2ed445484..eb39e5bafc5 100644 --- a/spec/graphql/resolvers/design_management/versions_resolver_spec.rb +++ b/spec/graphql/resolvers/design_management/versions_resolver_spec.rb @@ -82,7 +82,7 @@ RSpec.describe Resolvers::DesignManagement::VersionsResolver do let(:params) do { earlier_or_equal_to_sha: first_version.sha, - earlier_or_equal_to_id: global_id_of(first_version) + earlier_or_equal_to_id: global_id_of(first_version) } end @@ -95,7 +95,7 @@ RSpec.describe Resolvers::DesignManagement::VersionsResolver do let(:params) do { earlier_or_equal_to_sha: first_version.sha, - earlier_or_equal_to_id: global_id_of(other_version) + earlier_or_equal_to_id: global_id_of(other_version) } end diff --git a/spec/graphql/resolvers/environments/last_deployment_resolver_spec.rb b/spec/graphql/resolvers/environments/last_deployment_resolver_spec.rb new file mode 100644 index 00000000000..95a1a06730d --- /dev/null +++ b/spec/graphql/resolvers/environments/last_deployment_resolver_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Environments::LastDeploymentResolver do + include GraphqlHelpers + include Gitlab::Graphql::Laziness + + let_it_be(:project) { create(:project, :repository, :private) } + let_it_be(:environment) { create(:environment, project: project) } + let_it_be(:deployment) { create(:deployment, :created, environment: environment, project: project) } + let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } + + let(:current_user) { developer } + + describe '#resolve' do + it 'finds the deployment when status matches' do + expect(resolve_last_deployment(status: :created)).to eq(deployment) + end + + it 'does not find the deployment when status does not match' do + expect(resolve_last_deployment(status: :success)).to be_nil + end + + it 'raises an error when status is not specified' do + expect { resolve_last_deployment }.to raise_error(ArgumentError) + end + + it 'raises an error when status is not supported' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, + '"skipped" status is not supported.') do + resolve_last_deployment(status: :skipped) + end + end + end + + def resolve_last_deployment(args = {}, context = { current_user: current_user }) + force(resolve(described_class, obj: environment, ctx: context, args: args)) + end +end diff --git a/spec/graphql/resolvers/group_members_resolver_spec.rb b/spec/graphql/resolvers/group_members_resolver_spec.rb index bd0b4870062..d860b87875e 100644 --- a/spec/graphql/resolvers/group_members_resolver_spec.rb +++ b/spec/graphql/resolvers/group_members_resolver_spec.rb @@ -2,9 +2,11 @@ require 'spec_helper' -RSpec.describe Resolvers::GroupMembersResolver do +RSpec.describe 'Resolvers::GroupMembersResolver' do include GraphqlHelpers + let(:described_class) { Resolvers::GroupMembersResolver } + specify do expect(described_class).to have_nullable_graphql_type(Types::GroupMemberType.connection_type) end diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index 89e45810033..a74b2a3f18c 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -311,49 +311,15 @@ RSpec.describe Resolvers::IssuesResolver do end context 'when searching issues' do - it 'returns correct issues' do - expect(resolve_issues(search: 'foo')).to contain_exactly(issue2) - end - - it 'uses project search optimization' do - expected_arguments = a_hash_including( - search: 'foo', - attempt_project_search_optimizations: true - ) - expect(IssuesFinder).to receive(:new).with(anything, expected_arguments).and_call_original - - resolve_issues(search: 'foo') - end - - context 'with anonymous user' do - let_it_be(:public_project) { create(:project, :public) } - let_it_be(:public_issue) { create(:issue, project: public_project, title: 'Test issue') } - - context 'with disable_anonymous_search enabled' do - before do - stub_feature_flags(disable_anonymous_search: true) - end - - it 'generates an error' do - error_message = "User must be authenticated to include the `search` argument." - - expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, error_message) do - resolve(described_class, obj: public_project, args: { search: 'test' }, ctx: { current_user: nil }) - end - end - end - - context 'with disable_anonymous_search disabled' do - before do - stub_feature_flags(disable_anonymous_search: false) - end - - it 'returns correct issues' do - expect( - resolve(described_class, obj: public_project, args: { search: 'test' }, ctx: { current_user: nil }) - ).to contain_exactly(public_issue) - end - end + it_behaves_like 'graphql query for searching issuables' do + let_it_be(:parent) { project } + let_it_be(:issuable1) { create(:issue, project: project, title: 'first created') } + let_it_be(:issuable2) { create(:issue, project: project, title: 'second created', description: 'text 1') } + let_it_be(:issuable3) { create(:issue, project: project, title: 'third', description: 'text 2') } + let_it_be(:issuable4) { create(:issue, project: project) } + + let_it_be(:finder_class) { IssuesFinder } + let_it_be(:optimization_param) { :attempt_project_search_optimizations } end end diff --git a/spec/graphql/resolvers/project_members_resolver_spec.rb b/spec/graphql/resolvers/project_members_resolver_spec.rb index 2f4145b3215..c38cb3d157b 100644 --- a/spec/graphql/resolvers/project_members_resolver_spec.rb +++ b/spec/graphql/resolvers/project_members_resolver_spec.rb @@ -2,9 +2,11 @@ require 'spec_helper' -RSpec.describe Resolvers::ProjectMembersResolver do +RSpec.describe 'Resolvers::ProjectMembersResolver' do include GraphqlHelpers + let(:described_class) { Resolvers::ProjectMembersResolver } + it_behaves_like 'querying members with a group' do let_it_be(:project) { create(:project, group: group_1) } let_it_be(:resource_member) { create(:project_member, user: user_1, project: project) } diff --git a/spec/graphql/resolvers/work_items_resolver_spec.rb b/spec/graphql/resolvers/work_items_resolver_spec.rb index 29eac0ab46e..d89ccc7f806 100644 --- a/spec/graphql/resolvers/work_items_resolver_spec.rb +++ b/spec/graphql/resolvers/work_items_resolver_spec.rb @@ -19,13 +19,13 @@ RSpec.describe Resolvers::WorkItemsResolver do let_it_be(:item2) do create(:work_item, project: project, state: :closed, title: 'foo', - created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: + created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: 1.hour.ago) end let_it_be(:item3) do create(:work_item, project: other_project, state: :closed, title: 'foo', - created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: + created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: 1.hour.ago) end @@ -52,49 +52,15 @@ RSpec.describe Resolvers::WorkItemsResolver do end context 'when searching items' do - it 'returns correct items' do - expect(resolve_items(search: 'foo')).to contain_exactly(item2) - end - - it 'uses project search optimization' do - expected_arguments = a_hash_including( - search: 'foo', - attempt_project_search_optimizations: true - ) - expect(::WorkItems::WorkItemsFinder).to receive(:new).with(anything, expected_arguments).and_call_original - - resolve_items(search: 'foo') - end - - context 'with anonymous user' do - let_it_be(:public_project) { create(:project, :public) } - let_it_be(:public_item) { create(:work_item, project: public_project, title: 'Test item') } - - context 'with disable_anonymous_search enabled' do - before do - stub_feature_flags(disable_anonymous_search: true) - end - - it 'generates an error' do - error_message = "User must be authenticated to include the `search` argument." - - expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, error_message) do - resolve(described_class, obj: public_project, args: { search: 'test' }, ctx: { current_user: nil }) - end - end - end - - context 'with disable_anonymous_search disabled' do - before do - stub_feature_flags(disable_anonymous_search: false) - end - - it 'returns correct items' do - expect( - resolve(described_class, obj: public_project, args: { search: 'test' }, ctx: { current_user: nil }) - ).to contain_exactly(public_item) - end - end + it_behaves_like 'graphql query for searching issuables' do + let_it_be(:parent) { project } + let_it_be(:issuable1) { create(:work_item, project: project, title: 'first created') } + let_it_be(:issuable2) { create(:work_item, project: project, title: 'second created', description: 'text 1') } + let_it_be(:issuable3) { create(:work_item, project: project, title: 'third', description: 'text 2') } + let_it_be(:issuable4) { create(:work_item, project: project) } + + let_it_be(:finder_class) { ::WorkItems::WorkItemsFinder } + let_it_be(:optimization_param) { :attempt_project_search_optimizations } end end diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb index b85716e4d21..9f8a8717efb 100644 --- a/spec/graphql/types/base_field_spec.rb +++ b/spec/graphql/types/base_field_spec.rb @@ -205,39 +205,6 @@ RSpec.describe Types::BaseField do end end end - - describe '#visible?' do - context 'and has a feature_flag' do - let(:flag) { :test_feature } - let(:field) { described_class.new(name: 'test', type: GraphQL::Types::String, _deprecated_feature_flag: flag, null: false) } - let(:context) { {} } - - before do - skip_feature_flags_yaml_validation - end - - it 'checks YAML definition for default_enabled' do - # Exception is indicative of a check for YAML definition - expect { field.visible?(context) }.to raise_error(Feature::InvalidFeatureFlagError, /The feature flag YAML definition for '#{flag}' does not exist/) - end - - context 'skipping YAML check' do - before do - skip_default_enabled_yaml_check - end - - it 'returns false if the feature is not enabled' do - stub_feature_flags(flag => false) - - expect(field.visible?(context)).to eq(false) - end - - it 'returns true if the feature is enabled' do - expect(field.visible?(context)).to eq(true) - end - end - end - end end describe '#resolve' do @@ -251,77 +218,11 @@ RSpec.describe Types::BaseField do end end - describe '#description' do - context 'feature flag given' do - let(:field) { described_class.new(name: 'test', type: GraphQL::Types::String, _deprecated_feature_flag: flag, null: false, description: 'Test description.') } - let(:flag) { :test_flag } - - it 'prepends the description' do - expect(field.description).to start_with 'Test description. Available only when feature flag `test_flag` is enabled.' - end - - context 'falsey feature_flag values' do - using RSpec::Parameterized::TableSyntax - - where(:flag, :feature_value, :default_enabled) do - '' | false | false - '' | true | false - nil | false | true - nil | true | false - end - - with_them do - it 'returns the correct description' do - expect(field.description).to eq('Test description.') - end - end - end - - context 'with different default_enabled values' do - using RSpec::Parameterized::TableSyntax - - where(:feature_value, :default_enabled, :expected_description) do - disabled_ff_description = "Test description. Available only when feature flag `test_flag` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice." - enabled_ff_description = "Test description. Available only when feature flag `test_flag` is enabled. This flag is enabled by default." - - false | false | disabled_ff_description - true | false | disabled_ff_description - false | true | enabled_ff_description - true | true | enabled_ff_description - end - - with_them do - before do - stub_feature_flags("#{flag}": feature_value) - - allow(Feature::Definition).to receive(:has_definition?).with(flag).and_return(true) - allow(Feature::Definition).to receive(:default_enabled?).and_return(default_enabled) - end - - it 'returns the correct availability in the description' do - expect(field.description).to eq expected_description - end - end - end - end - end - include_examples 'Gitlab-style deprecations' do def subject(args = {}) base_args = { name: 'test', type: GraphQL::Types::String, null: true } described_class.new(**base_args.merge(args)) end - - it 'interacts well with the `_deprecated_feature_flag` property' do - field = subject( - deprecated: { milestone: '1.10', reason: 'Deprecation reason' }, - description: 'Field description.', - _deprecated_feature_flag: 'foo_flag' - ) - - expect(field.description).to start_with('Field description. Available only when feature flag `foo_flag` is enabled.') - expect(field.description).to end_with('Deprecated in 1.10: Deprecation reason.') - end end end diff --git a/spec/graphql/types/branch_protections/merge_access_level_type_spec.rb b/spec/graphql/types/branch_protections/merge_access_level_type_spec.rb new file mode 100644 index 00000000000..8cc1005d97e --- /dev/null +++ b/spec/graphql/types/branch_protections/merge_access_level_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['MergeAccessLevel'] do + subject { described_class } + + let(:fields) { %i[access_level access_level_description] } + + specify { is_expected.to require_graphql_authorizations(:read_protected_branch) } + + specify { is_expected.to have_graphql_fields(fields).at_least } +end diff --git a/spec/graphql/types/branch_protections/push_access_level_type_spec.rb b/spec/graphql/types/branch_protections/push_access_level_type_spec.rb new file mode 100644 index 00000000000..c78c0bda74c --- /dev/null +++ b/spec/graphql/types/branch_protections/push_access_level_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['PushAccessLevel'] do + subject { described_class } + + let(:fields) { %i[access_level access_level_description] } + + specify { is_expected.to require_graphql_authorizations(:read_protected_branch) } + + specify { is_expected.to have_graphql_fields(fields).at_least } +end diff --git a/spec/graphql/types/branch_rule_type_spec.rb b/spec/graphql/types/branch_rule_type_spec.rb new file mode 100644 index 00000000000..277901f00bf --- /dev/null +++ b/spec/graphql/types/branch_rule_type_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['BranchRule'] do + include GraphqlHelpers + + subject { described_class } + + let(:fields) do + %i[ + name + branch_protection + created_at + updated_at + ] + end + + specify { is_expected.to require_graphql_authorizations(:read_protected_branch) } + + specify { is_expected.to have_graphql_fields(fields) } +end diff --git a/spec/graphql/types/branch_rules/branch_protection_type_spec.rb b/spec/graphql/types/branch_rules/branch_protection_type_spec.rb new file mode 100644 index 00000000000..bbc92fd8fef --- /dev/null +++ b/spec/graphql/types/branch_rules/branch_protection_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['BranchProtection'] do + subject { described_class } + + let(:fields) { %i[merge_access_levels push_access_levels allow_force_push] } + + specify { is_expected.to require_graphql_authorizations(:read_protected_branch) } + + specify { is_expected.to have_graphql_fields(fields).at_least } +end diff --git a/spec/graphql/types/ci/config_variable_type_spec.rb b/spec/graphql/types/ci/config_variable_type_spec.rb new file mode 100644 index 00000000000..2b0937a7858 --- /dev/null +++ b/spec/graphql/types/ci/config_variable_type_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['CiConfigVariable'] do + specify { expect(described_class).to have_graphql_fields(:key, :description, :value).at_least } +end diff --git a/spec/graphql/types/ci/group_variable_connection_type_spec.rb b/spec/graphql/types/ci/group_variable_connection_type_spec.rb new file mode 100644 index 00000000000..4a1fd490506 --- /dev/null +++ b/spec/graphql/types/ci/group_variable_connection_type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['CiGroupVariableConnection'] do + it 'has the expected fields' do + expected_fields = %i[limit page_info edges nodes] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/ci/instance_variable_type_spec.rb b/spec/graphql/types/ci/instance_variable_type_spec.rb index cf4aaed31f1..c77a4ac1dc4 100644 --- a/spec/graphql/types/ci/instance_variable_type_spec.rb +++ b/spec/graphql/types/ci/instance_variable_type_spec.rb @@ -5,5 +5,5 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['CiInstanceVariable'] do specify { expect(described_class.interfaces).to contain_exactly(Types::Ci::VariableInterface) } - specify { expect(described_class).to have_graphql_fields(:masked, :protected).at_least } + specify { expect(described_class).to have_graphql_fields(:environment_scope, :masked, :protected).at_least } end diff --git a/spec/graphql/types/ci/job_artifact_type_spec.rb b/spec/graphql/types/ci/job_artifact_type_spec.rb index 58b5f9cfcb7..3e054faf0c9 100644 --- a/spec/graphql/types/ci/job_artifact_type_spec.rb +++ b/spec/graphql/types/ci/job_artifact_type_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['CiJobArtifact'] do it 'has the correct fields' do - expected_fields = [:download_path, :file_type, :name] + expected_fields = [:id, :download_path, :file_type, :name, :size, :expire_at] expect(described_class).to have_graphql_fields(*expected_fields) end diff --git a/spec/graphql/types/ci/job_token_scope_type_spec.rb b/spec/graphql/types/ci/job_token_scope_type_spec.rb index 457d46b6896..18f4d762d1e 100644 --- a/spec/graphql/types/ci/job_token_scope_type_spec.rb +++ b/spec/graphql/types/ci/job_token_scope_type_spec.rb @@ -69,8 +69,8 @@ RSpec.describe GitlabSchema.types['CiJobTokenScopeType'] do expect(subject['errors']).to be_nil end - it 'returns nil' do - expect(subject['data']['project']['ciJobTokenScope']).to be_nil + it 'returns readable projects in scope' do + expect(returned_project_paths).to contain_exactly(project.path) end end end diff --git a/spec/graphql/types/ci/job_type_spec.rb b/spec/graphql/types/ci/job_type_spec.rb index bc9e64282bc..b3dee082d1f 100644 --- a/spec/graphql/types/ci/job_type_spec.rb +++ b/spec/graphql/types/ci/job_type_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe Types::Ci::JobType do + include GraphqlHelpers + specify { expect(described_class.graphql_name).to eq('CiJob') } specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Ci::Job) } @@ -45,8 +47,21 @@ RSpec.describe Types::Ci::JobType do tags triggered userPermissions + webPath ] expect(described_class).to have_graphql_fields(*expected_fields) end + + describe '#web_path' do + subject { resolve_field(:web_path, build, current_user: user, object_type: described_class) } + + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:build) { create(:ci_build, project: project, user: user) } + + it 'returns the web path of the job' do + is_expected.to eq("/#{project.full_path}/-/jobs/#{build.id}") + end + end end diff --git a/spec/graphql/types/ci/manual_variable_type_spec.rb b/spec/graphql/types/ci/manual_variable_type_spec.rb index 2884c818a52..21d36b7dfc0 100644 --- a/spec/graphql/types/ci/manual_variable_type_spec.rb +++ b/spec/graphql/types/ci/manual_variable_type_spec.rb @@ -4,4 +4,6 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['CiManualVariable'] do specify { expect(described_class.interfaces).to contain_exactly(Types::Ci::VariableInterface) } + + specify { expect(described_class).to have_graphql_fields(:environment_scope).at_least } end diff --git a/spec/graphql/types/ci/project_variable_connection_type_spec.rb b/spec/graphql/types/ci/project_variable_connection_type_spec.rb new file mode 100644 index 00000000000..97c3a207f7f --- /dev/null +++ b/spec/graphql/types/ci/project_variable_connection_type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['CiProjectVariableConnection'] do + it 'has the expected fields' do + expected_fields = %i[limit page_info edges nodes] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/ci/runner_architecture_type_spec.rb b/spec/graphql/types/ci/runner_architecture_type_spec.rb index 527adef8cf9..60709acfd53 100644 --- a/spec/graphql/types/ci/runner_architecture_type_spec.rb +++ b/spec/graphql/types/ci/runner_architecture_type_spec.rb @@ -7,8 +7,8 @@ RSpec.describe Types::Ci::RunnerArchitectureType do it 'exposes the expected fields' do expected_fields = %i[ - name - download_location + name + download_location ] expect(described_class).to have_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/ci/runner_platform_type_spec.rb b/spec/graphql/types/ci/runner_platform_type_spec.rb index 66b83f607d0..29b8e885183 100644 --- a/spec/graphql/types/ci/runner_platform_type_spec.rb +++ b/spec/graphql/types/ci/runner_platform_type_spec.rb @@ -7,9 +7,9 @@ RSpec.describe Types::Ci::RunnerPlatformType do it 'exposes the expected fields' do expected_fields = %i[ - name - human_readable_name - architectures + name + human_readable_name + architectures ] expect(described_class).to have_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/ci/variable_interface_spec.rb b/spec/graphql/types/ci/variable_interface_spec.rb index 8cef0ac2a14..328c5305a44 100644 --- a/spec/graphql/types/ci/variable_interface_spec.rb +++ b/spec/graphql/types/ci/variable_interface_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['CiVariable'] do specify do expect(described_class).to have_graphql_fields( - :id, :key, :value, :variable_type, :raw + :id, :key, :raw, :value, :variable_type ).at_least end end diff --git a/spec/graphql/types/clusters/agent_type_spec.rb b/spec/graphql/types/clusters/agent_type_spec.rb index 3f4faccf15d..bb1006c55c0 100644 --- a/spec/graphql/types/clusters/agent_type_spec.rb +++ b/spec/graphql/types/clusters/agent_type_spec.rb @@ -9,5 +9,5 @@ RSpec.describe GitlabSchema.types['ClusterAgent'] do it { expect(described_class).to require_graphql_authorizations(:read_cluster) } - it { expect(described_class).to have_graphql_fields(fields) } + it { expect(described_class).to include_graphql_fields(*fields) } end diff --git a/spec/graphql/types/customer_relations/organization_sort_enum_spec.rb b/spec/graphql/types/customer_relations/organization_sort_enum_spec.rb new file mode 100644 index 00000000000..7ff498f0097 --- /dev/null +++ b/spec/graphql/types/customer_relations/organization_sort_enum_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['OrganizationSort'] do + specify { expect(described_class.graphql_name).to eq('OrganizationSort') } + + it_behaves_like 'common sort values' + + it 'exposes all the contact sort values' do + expect(described_class.values.keys).to include( + *%w[ + NAME_ASC + NAME_DESC + DESCRIPTION_ASC + DESCRIPTION_DESC + DEFAULT_RATE_ASC + DEFAULT_RATE_DESC + ] + ) + end +end diff --git a/spec/graphql/types/customer_relations/organization_state_counts_type_spec.rb b/spec/graphql/types/customer_relations/organization_state_counts_type_spec.rb new file mode 100644 index 00000000000..a2c8dacb1a5 --- /dev/null +++ b/spec/graphql/types/customer_relations/organization_state_counts_type_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['OrganizationStateCounts'] do + include GraphqlHelpers + + let(:fields) do + %w[ + all + active + inactive + ] + end + + let(:object) do + { + 'inactive' => 3, + 'active' => 4 + } + end + + it { expect(described_class.graphql_name).to eq('OrganizationStateCounts') } + it { expect(described_class).to have_graphql_fields(fields) } + + describe '#all' do + it 'returns the sum of all counts' do + expect(resolve_field(:all, object)).to eq(7) + end + end +end diff --git a/spec/graphql/types/deployment_details_type_spec.rb b/spec/graphql/types/deployment_details_type_spec.rb new file mode 100644 index 00000000000..70fdc38019e --- /dev/null +++ b/spec/graphql/types/deployment_details_type_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['DeploymentDetails'] do + specify { expect(described_class.graphql_name).to eq('DeploymentDetails') } + + it 'has the expected fields' do + expected_fields = %w[ + id iid ref tag tags sha created_at updated_at finished_at status commit job triggerer + ] + + expect(described_class).to have_graphql_fields(*expected_fields) + end + + specify { expect(described_class).to require_graphql_authorizations(:read_deployment) } +end diff --git a/spec/graphql/types/deployment_type_spec.rb b/spec/graphql/types/deployment_type_spec.rb new file mode 100644 index 00000000000..bf4be0523c6 --- /dev/null +++ b/spec/graphql/types/deployment_type_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['Deployment'] do + specify { expect(described_class.graphql_name).to eq('Deployment') } + + it 'has the expected fields' do + expected_fields = %w[ + id iid ref tag sha created_at updated_at finished_at status commit job triggerer + ] + + expect(described_class).to have_graphql_fields(*expected_fields) + end + + specify { expect(described_class).to require_graphql_authorizations(:read_deployment) } +end diff --git a/spec/graphql/types/detployment_tag_type_spec.rb b/spec/graphql/types/detployment_tag_type_spec.rb new file mode 100644 index 00000000000..9a7a8db0970 --- /dev/null +++ b/spec/graphql/types/detployment_tag_type_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['DeploymentTag'] do + specify { expect(described_class.graphql_name).to eq('DeploymentTag') } + + it 'has the expected fields' do + expected_fields = %w[ + name path + ] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/environment_type_spec.rb b/spec/graphql/types/environment_type_spec.rb index 3671d35e8a5..ae58fe00af7 100644 --- a/spec/graphql/types/environment_type_spec.rb +++ b/spec/graphql/types/environment_type_spec.rb @@ -7,7 +7,8 @@ RSpec.describe GitlabSchema.types['Environment'] do it 'has the expected fields' do expected_fields = %w[ - name id state metrics_dashboard latest_opened_most_severe_alert path + name id state metrics_dashboard latest_opened_most_severe_alert path external_url deployments + slug createdAt updatedAt autoStopAt autoDeleteAt tier environmentType lastDeployment ] expect(described_class).to have_graphql_fields(*expected_fields) @@ -17,7 +18,7 @@ RSpec.describe GitlabSchema.types['Environment'] do context 'when there is an environment' do let_it_be(:project) { create(:project) } - let_it_be(:environment) { create(:environment, project: project) } + let_it_be(:environment) { create(:environment, project: project, external_url: 'https://gitlab.com') } let_it_be(:user) { create(:user) } subject { GitlabSchema.execute(query, context: { current_user: user }).as_json } @@ -29,6 +30,7 @@ RSpec.describe GitlabSchema.types['Environment'] do environment(name: "#{environment.name}") { name path + externalUrl state } } @@ -50,6 +52,10 @@ RSpec.describe GitlabSchema.types['Environment'] do ) end + it 'returns the external url of the environment' do + expect(subject['data']['project']['environment']['externalUrl']).to eq(environment.external_url) + end + context 'when query alert data for the environment' do let_it_be(:query) do %( diff --git a/spec/graphql/types/group_type_spec.rb b/spec/graphql/types/group_type_spec.rb index 72b3bb90194..0b65778ce90 100644 --- a/spec/graphql/types/group_type_spec.rb +++ b/spec/graphql/types/group_type_spec.rb @@ -24,8 +24,9 @@ RSpec.describe GitlabSchema.types['Group'] do dependency_proxy_blobs dependency_proxy_image_count dependency_proxy_blob_count dependency_proxy_total_size dependency_proxy_image_prefix dependency_proxy_image_ttl_policy - shared_runners_setting timelogs organizations contacts contact_state_counts - work_item_types recent_issue_boards ci_variables + shared_runners_setting timelogs organization_state_counts organizations + contact_state_counts contacts work_item_types + recent_issue_boards ci_variables ] expect(described_class).to include_graphql_fields(*expected_fields) @@ -62,6 +63,13 @@ RSpec.describe GitlabSchema.types['Group'] do it { is_expected.to have_graphql_resolver(Resolvers::Crm::ContactStateCountsResolver) } end + describe 'organization_state_counts field' do + subject { described_class.fields['organizationStateCounts'] } + + it { is_expected.to have_graphql_type(Types::CustomerRelations::OrganizationStateCountsType) } + it { is_expected.to have_graphql_resolver(Resolvers::Crm::OrganizationStateCountsResolver) } + end + it_behaves_like 'a GraphQL type with labels' do let(:labels_resolver_arguments) { [:search_term, :includeAncestorGroups, :includeDescendantGroups, :onlyGroupLabels] } end diff --git a/spec/graphql/types/merge_request_review_state_enum_spec.rb b/spec/graphql/types/merge_request_review_state_enum_spec.rb index 407a1ae3c1f..486e1c4f502 100644 --- a/spec/graphql/types/merge_request_review_state_enum_spec.rb +++ b/spec/graphql/types/merge_request_review_state_enum_spec.rb @@ -12,10 +12,6 @@ RSpec.describe GitlabSchema.types['MergeRequestReviewState'] do 'UNREVIEWED' => have_attributes( description: 'The merge request is unreviewed.', value: 'unreviewed' - ), - 'ATTENTION_REQUESTED' => have_attributes( - description: 'The merge request is attention_requested.', - value: 'attention_requested' ) ) end diff --git a/spec/graphql/types/metrics/dashboard_type_spec.rb b/spec/graphql/types/metrics/dashboard_type_spec.rb index 30dccc7c0be..114db87d5f1 100644 --- a/spec/graphql/types/metrics/dashboard_type_spec.rb +++ b/spec/graphql/types/metrics/dashboard_type_spec.rb @@ -7,8 +7,8 @@ RSpec.describe GitlabSchema.types['MetricsDashboard'] do it 'has the expected fields' do expected_fields = %w[ - path annotations schema_validation_warnings - ] + path annotations schema_validation_warnings + ] expect(described_class).to have_graphql_fields(*expected_fields) end diff --git a/spec/graphql/types/packages/composer/metadatum_type_spec.rb b/spec/graphql/types/packages/composer/metadatum_type_spec.rb index a950c10a41d..272d518cdd5 100644 --- a/spec/graphql/types/packages/composer/metadatum_type_spec.rb +++ b/spec/graphql/types/packages/composer/metadatum_type_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['ComposerMetadata'] do it 'includes composer metadatum fields' do expected_fields = %w[ - target_sha composer_json + target_sha composer_json ] expect(described_class).to include_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/packages/package_type_enum_spec.rb b/spec/graphql/types/packages/package_type_enum_spec.rb index 9d5a7716a61..fb93b1c8c8a 100644 --- a/spec/graphql/types/packages/package_type_enum_spec.rb +++ b/spec/graphql/types/packages/package_type_enum_spec.rb @@ -4,6 +4,6 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['PackageTypeEnum'] do it 'exposes all package types' do - expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN RUBYGEMS HELM TERRAFORM_MODULE]) + expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN RUBYGEMS HELM TERRAFORM_MODULE RPM]) end end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 5ff7653ce39..617cbdb07fe 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -4,7 +4,6 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['Project'] do include GraphqlHelpers - include Ci::TemplateHelpers specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Project) } @@ -37,7 +36,7 @@ RSpec.describe GitlabSchema.types['Project'] do cluster_agent cluster_agents agent_configurations ci_template timelogs merge_commit_template squash_commit_template work_item_types recent_issue_boards ci_config_path_or_default packages_cleanup_policy ci_variables - timelog_categories fork_targets + timelog_categories fork_targets branch_rules ci_config_variables ] expect(described_class).to include_graphql_fields(*expected_fields) @@ -509,6 +508,20 @@ RSpec.describe GitlabSchema.types['Project'] do it { is_expected.to have_graphql_resolver(Resolvers::Ci::JobTokenScopeResolver) } end + describe 'branch_rules field' do + subject { described_class.fields['branchRules'] } + + let(:br_resolver) { Resolvers::Projects::BranchRulesResolver } + + specify do + is_expected.to have_graphql_type( + Types::Projects::BranchRuleType.connection_type + ) + end + + specify { is_expected.to have_graphql_resolver(br_resolver) } + end + describe 'agent_configurations' do let_it_be(:project) { create(:project) } let_it_be(:user) { create(:user) } @@ -682,4 +695,54 @@ RSpec.describe GitlabSchema.types['Project'] do end end end + + describe 'branch_rules' do + let_it_be(:user) { create(:user) } + let_it_be(:project, reload: true) { create(:project, :public) } + let_it_be(:name) { 'feat/*' } + let_it_be(:protected_branch) do + create(:protected_branch, project: project, name: name) + end + + let(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + branchRules { + nodes { + name + } + } + } + } + ) + end + + let(:branch_rules_data) do + subject.dig('data', 'project', 'branchRules', 'nodes') + end + + subject { GitlabSchema.execute(query, context: { current_user: user }).as_json } + + context 'when a user can read protected branches' do + before do + project.add_maintainer(user) + end + + it 'is present and correct' do + expect(branch_rules_data.count).to eq(1) + expect(branch_rules_data.first['name']).to eq(name) + end + end + + context 'when a user cannot read protected branches' do + before do + project.add_guest(user) + end + + it 'is empty' do + expect(branch_rules_data.count).to eq(0) + end + end + end end diff --git a/spec/graphql/types/subscription_type_spec.rb b/spec/graphql/types/subscription_type_spec.rb index 9b043fa52cf..860cbbf0c15 100644 --- a/spec/graphql/types/subscription_type_spec.rb +++ b/spec/graphql/types/subscription_type_spec.rb @@ -10,8 +10,9 @@ RSpec.describe GitlabSchema.types['Subscription'] do issuable_title_updated issuable_labels_updated issuable_dates_updated + merge_request_reviewers_updated ] - expect(described_class).to have_graphql_fields(*expected_fields).only + expect(described_class).to include_graphql_fields(*expected_fields) end end diff --git a/spec/graphql/types/timelog_type_spec.rb b/spec/graphql/types/timelog_type_spec.rb index c897a25d10d..3a26ba89e04 100644 --- a/spec/graphql/types/timelog_type_spec.rb +++ b/spec/graphql/types/timelog_type_spec.rb @@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['Timelog'] do it { expect(described_class.graphql_name).to eq('Timelog') } it { expect(described_class).to have_graphql_fields(fields) } - it { expect(described_class).to require_graphql_authorizations(:read_issue) } + it { expect(described_class).to require_graphql_authorizations(:read_issuable) } it { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Timelog) } describe 'user field' do diff --git a/spec/graphql/types/user_merge_request_interaction_type_spec.rb b/spec/graphql/types/user_merge_request_interaction_type_spec.rb index 4782a1faf8d..3cd9750debb 100644 --- a/spec/graphql/types/user_merge_request_interaction_type_spec.rb +++ b/spec/graphql/types/user_merge_request_interaction_type_spec.rb @@ -76,11 +76,8 @@ RSpec.describe GitlabSchema.types['UserMergeRequestInteraction'] do context 'when the user has been asked to review the MR' do before do merge_request.reviewers << user - merge_request.find_reviewer(user).update!(state: :attention_requested) end - it { is_expected.to eq(Types::MergeRequestReviewStateEnum.values['ATTENTION_REQUESTED'].value) } - it 'implies not reviewed' do expect(resolve(:reviewed)).to be false end diff --git a/spec/graphql/types/work_item_type_spec.rb b/spec/graphql/types/work_item_type_spec.rb index c556424b0b4..11b02a88dbd 100644 --- a/spec/graphql/types/work_item_type_spec.rb +++ b/spec/graphql/types/work_item_type_spec.rb @@ -28,8 +28,6 @@ RSpec.describe GitlabSchema.types['WorkItem'] do closed_at ] - fields.each do |field_name| - expect(described_class).to have_graphql_fields(*fields) - end + expect(described_class).to have_graphql_fields(*fields) end end diff --git a/spec/graphql/types/work_items/widgets/description_type_spec.rb b/spec/graphql/types/work_items/widgets/description_type_spec.rb index 5ade1fe4aa2..aee388ce82a 100644 --- a/spec/graphql/types/work_items/widgets/description_type_spec.rb +++ b/spec/graphql/types/work_items/widgets/description_type_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe Types::WorkItems::Widgets::DescriptionType do it 'exposes the expected fields' do - expected_fields = %i[description description_html type] + expected_fields = %i[description description_html edited last_edited_at last_edited_by type] expect(described_class).to have_graphql_fields(*expected_fields) end |