diff options
Diffstat (limited to 'spec/graphql')
40 files changed, 828 insertions, 157 deletions
diff --git a/spec/graphql/mutations/boards/lists/create_spec.rb b/spec/graphql/mutations/boards/lists/create_spec.rb index 894dd1f34b4..815064e7c58 100644 --- a/spec/graphql/mutations/boards/lists/create_spec.rb +++ b/spec/graphql/mutations/boards/lists/create_spec.rb @@ -3,84 +3,8 @@ require 'spec_helper' RSpec.describe Mutations::Boards::Lists::Create do - include GraphqlHelpers - let_it_be(:group) { create(:group, :private) } let_it_be(:board) { create(:board, group: group) } - let_it_be(:user) { create(:user) } - let_it_be(:guest) { create(:user) } - - let(:current_user) { user } - let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) } - let(:list_create_params) { {} } - - before_all do - group.add_reporter(user) - group.add_guest(guest) - end - - subject { mutation.resolve(board_id: board.to_global_id.to_s, **list_create_params) } - - describe '#ready?' do - it 'raises an error if required arguments are missing' do - expect { mutation.ready?(board_id: 'some id') } - .to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/) - end - - it 'raises an error if too many required arguments are specified' do - expect { mutation.ready?(board_id: 'some id', backlog: true, label_id: 'some label') } - .to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/) - end - end - - describe '#resolve' do - context 'with proper permissions' do - describe 'backlog list' do - let(:list_create_params) { { backlog: true } } - - it 'creates one and only one backlog' do - expect { subject }.to change { board.lists.backlog.count }.from(0).to(1) - expect(board.lists.backlog.first.list_type).to eq 'backlog' - - backlog_id = board.lists.backlog.first.id - - expect { subject }.not_to change { board.lists.backlog.count } - expect(board.lists.backlog.last.id).to eq backlog_id - end - end - - describe 'label list' do - let_it_be(:dev_label) do - create(:group_label, title: 'Development', color: '#FFAABB', group: group) - end - - let(:list_create_params) { { label_id: dev_label.to_global_id.to_s } } - - it 'creates a new issue board list for labels' do - expect { subject }.to change { board.lists.count }.from(1).to(2) - - new_list = subject[:list] - - expect(new_list.title).to eq dev_label.title - expect(new_list.position).to eq 0 - end - - context 'when label not found' do - let(:list_create_params) { { label_id: "gid://gitlab/Label/#{non_existing_record_id}" } } - - it 'returns an error' do - expect(subject[:errors]).to include 'Label not found' - end - end - end - end - - context 'without proper permissions' do - let(:current_user) { guest } - it 'raises an error' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end - end - end + it_behaves_like 'board lists create mutation' end diff --git a/spec/graphql/mutations/concerns/mutations/can_mutate_spammable_spec.rb b/spec/graphql/mutations/concerns/mutations/can_mutate_spammable_spec.rb new file mode 100644 index 00000000000..ee8db7a1f31 --- /dev/null +++ b/spec/graphql/mutations/concerns/mutations/can_mutate_spammable_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::CanMutateSpammable do + let(:mutation_class) do + Class.new(Mutations::BaseMutation) do + include Mutations::CanMutateSpammable + end + end + + let(:request) { double(:request) } + let(:query) { double(:query, schema: GitlabSchema) } + let(:context) { GraphQL::Query::Context.new(query: query, object: nil, values: { request: request }) } + + subject(:mutation) { mutation_class.new(object: nil, context: context, field: nil) } + + describe '#additional_spam_params' do + it 'returns additional spam-related params' do + expect(subject.send(:additional_spam_params)).to eq({ api: true, request: request }) + end + end + + describe '#with_spam_action_fields' do + let(:spam_log) { double(:spam_log, id: 1) } + let(:spammable) { double(:spammable, spam?: true, render_recaptcha?: true, spam_log: spam_log) } + + before do + allow(Gitlab::CurrentSettings).to receive(:recaptcha_site_key) { 'abc123' } + end + + it 'merges in spam action fields from spammable' do + result = subject.send(:with_spam_action_fields, spammable) do + { other_field: true } + end + expect(result) + .to eq({ + spam: true, + needs_captcha_response: true, + spam_log_id: 1, + captcha_site_key: 'abc123', + other_field: true + }) + end + end +end diff --git a/spec/graphql/mutations/merge_requests/update_spec.rb b/spec/graphql/mutations/merge_requests/update_spec.rb index 8acd2562ea8..206abaf34ce 100644 --- a/spec/graphql/mutations/merge_requests/update_spec.rb +++ b/spec/graphql/mutations/merge_requests/update_spec.rb @@ -12,10 +12,11 @@ RSpec.describe Mutations::MergeRequests::Update do describe '#resolve' do let(:attributes) { { title: 'new title', description: 'new description', target_branch: 'new-branch' } } + let(:arguments) { attributes } let(:mutated_merge_request) { subject[:merge_request] } subject do - mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, **attributes) + mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, **arguments) end it_behaves_like 'permission level for merge request mutation is correctly verified' @@ -61,6 +62,24 @@ RSpec.describe Mutations::MergeRequests::Update do expect(mutated_merge_request).to have_attributes(attributes) end end + + context 'when closing the MR' do + let(:arguments) { { state_event: ::Types::MergeRequestStateEventEnum.values['CLOSED'].value } } + + it 'closes the MR' do + expect(mutated_merge_request).to be_closed + end + end + + context 'when re-opening the MR' do + let(:arguments) { { state_event: ::Types::MergeRequestStateEventEnum.values['OPEN'].value } } + + it 'closes the MR' do + merge_request.close! + + expect(mutated_merge_request).to be_open + end + end end end end diff --git a/spec/graphql/mutations/security/ci_configuration/configure_sast_spec.rb b/spec/graphql/mutations/security/ci_configuration/configure_sast_spec.rb new file mode 100644 index 00000000000..ed03a1cb906 --- /dev/null +++ b/spec/graphql/mutations/security/ci_configuration/configure_sast_spec.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::Security::CiConfiguration::ConfigureSast 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_it_be(:service_result_json) do + { + status: "success", + success_path: "http://127.0.0.1:3000/root/demo-historic-secrets/-/merge_requests/new?", + errors: nil + } + end + + let_it_be(:service_error_result_json) do + { + status: "error", + success_path: nil, + errors: %w(error1 error2) + } + end + + let(:context) do + GraphQL::Query::Context.new( + query: OpenStruct.new(schema: nil), + values: { current_user: user }, + object: nil + ) + end + + specify { expect(described_class).to require_graphql_authorizations(:push_code) } + + describe '#resolve' do + subject { mutation.resolve(project_path: project.full_path, configuration: {}) } + + let(:result) { subject } + + 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 + + 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 + + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'when the user does not have permission to create a new branch' do + before_all do + project.add_developer(user) + end + + let(:error_message) { 'You are not allowed to create protected branches on this project.' } + + it 'returns an array of errors' do + allow_next_instance_of(::Files::MultiService) do |multi_service| + allow(multi_service).to receive(:execute).and_raise(Gitlab::Git::PreReceiveError.new("GitLab: #{error_message}")) + end + + expect(result).to match( + status: :error, + success_path: nil, + errors: match_array([error_message]) + ) + end + end + + context 'when the user can create a merge request' do + before_all do + project.add_developer(user) + end + + context 'when service successfully generates a path to create a new merge request' do + it 'returns a success path' do + allow_next_instance_of(::Security::CiConfiguration::SastCreateService) do |service| + allow(service).to receive(:execute).and_return(service_result_json) + end + + expect(result).to match( + status: 'success', + success_path: service_result_json[:success_path], + errors: [] + ) + end + end + + context 'when service can not generate any path to create a new merge request' do + it 'returns an array of errors' do + allow_next_instance_of(::Security::CiConfiguration::SastCreateService) do |service| + allow(service).to receive(:execute).and_return(service_error_result_json) + end + + expect(result).to match( + status: 'error', + success_path: be_nil, + errors: match_array(service_error_result_json[:errors]) + ) + end + end + end + end +end diff --git a/spec/graphql/resolvers/base_resolver_spec.rb b/spec/graphql/resolvers/base_resolver_spec.rb index 8a24b69eb6f..8d2ae238bfe 100644 --- a/spec/graphql/resolvers/base_resolver_spec.rb +++ b/spec/graphql/resolvers/base_resolver_spec.rb @@ -277,8 +277,8 @@ RSpec.describe Resolvers::BaseResolver do describe '#offset_pagination' do let(:instance) { resolver_instance(resolver) } - it 'is sugar for OffsetActiveRecordRelationConnection.new' do - expect(instance.offset_pagination(User.none)).to be_a(::Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection) + it 'is sugar for OffsetPaginatedRelation.new' do + expect(instance.offset_pagination(User.none)).to be_a(::Gitlab::Graphql::Pagination::OffsetPaginatedRelation) end end end diff --git a/spec/graphql/resolvers/board_list_issues_resolver_spec.rb b/spec/graphql/resolvers/board_list_issues_resolver_spec.rb index e7c56a526f4..5eda840854a 100644 --- a/spec/graphql/resolvers/board_list_issues_resolver_spec.rb +++ b/spec/graphql/resolvers/board_list_issues_resolver_spec.rb @@ -23,19 +23,19 @@ RSpec.describe Resolvers::BoardListIssuesResolver do it 'returns the issues in the correct order' do # by relative_position and then ID - issues = resolve_board_list_issues.items + issues = resolve_board_list_issues expect(issues.map(&:id)).to eq [issue3.id, issue1.id, issue2.id] end it 'finds only issues matching filters' do - result = resolve_board_list_issues(args: { filters: { label_name: [label.title], not: { label_name: [label2.title] } } }).items + result = resolve_board_list_issues(args: { filters: { label_name: [label.title], not: { label_name: [label2.title] } } }) expect(result).to match_array([issue1, issue3]) end it 'finds only issues matching search param' do - result = resolve_board_list_issues(args: { filters: { search: issue1.title } }).items + result = resolve_board_list_issues(args: { filters: { search: issue1.title } }) expect(result).to match_array([issue1]) end diff --git a/spec/graphql/resolvers/board_lists_resolver_spec.rb b/spec/graphql/resolvers/board_lists_resolver_spec.rb index 71ebec4dc7e..fdcebd30bb3 100644 --- a/spec/graphql/resolvers/board_lists_resolver_spec.rb +++ b/spec/graphql/resolvers/board_lists_resolver_spec.rb @@ -21,7 +21,7 @@ RSpec.describe Resolvers::BoardListsResolver do end it 'does not create the backlog list' do - lists = resolve_board_lists.items + lists = resolve_board_lists expect(lists.count).to eq 1 expect(lists[0].list_type).to eq 'closed' @@ -38,7 +38,7 @@ RSpec.describe Resolvers::BoardListsResolver do let!(:backlog_list) { create(:backlog_list, board: board) } it 'returns a list of board lists' do - lists = resolve_board_lists.items + lists = resolve_board_lists expect(lists.count).to eq 3 expect(lists.map(&:list_type)).to eq %w(backlog label closed) @@ -50,7 +50,7 @@ RSpec.describe Resolvers::BoardListsResolver do end it 'returns the complete list of board lists for this user' do - lists = resolve_board_lists.items + lists = resolve_board_lists expect(lists.count).to eq 3 end @@ -58,7 +58,7 @@ RSpec.describe Resolvers::BoardListsResolver do context 'when querying for a single list' do it 'returns specified list' do - list = resolve_board_lists(args: { id: global_id_of(label_list) }).items + list = resolve_board_lists(args: { id: global_id_of(label_list) }) expect(list).to eq [label_list] end @@ -69,13 +69,13 @@ RSpec.describe Resolvers::BoardListsResolver do external_label = create(:group_label, group: group) external_list = create(:list, board: external_board, label: external_label) - list = resolve_board_lists(args: { id: global_id_of(external_list) }).items + list = resolve_board_lists(args: { id: global_id_of(external_list) }) expect(list).to eq List.none end it 'raises an argument error if list ID is not valid' do - expect { resolve_board_lists(args: { id: 'test' }).items } + expect { resolve_board_lists(args: { id: 'test' }) } .to raise_error(Gitlab::Graphql::Errors::ArgumentError) end end diff --git a/spec/graphql/resolvers/ci/config_resolver_spec.rb b/spec/graphql/resolvers/ci/config_resolver_spec.rb index ca7ae73fef8..73e9fab9f99 100644 --- a/spec/graphql/resolvers/ci/config_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/config_resolver_spec.rb @@ -36,7 +36,8 @@ RSpec.describe Resolvers::Ci::ConfigResolver do File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml')) end - it 'lints the ci config file' do + it 'lints the ci config file and returns the merged yaml file' do + expect(response[:merged_yaml]).to eq(content) expect(response[:status]).to eq(:valid) expect(response[:errors]).to be_empty end diff --git a/spec/graphql/resolvers/container_repositories_resolver_spec.rb b/spec/graphql/resolvers/container_repositories_resolver_spec.rb index b888d79626e..a17d2a7b0d5 100644 --- a/spec/graphql/resolvers/container_repositories_resolver_spec.rb +++ b/spec/graphql/resolvers/container_repositories_resolver_spec.rb @@ -27,6 +27,34 @@ RSpec.describe Resolvers::ContainerRepositoriesResolver do it { is_expected.to contain_exactly(named_container_repository) } end + + context 'with a sort argument' do + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + let_it_be(:sort_repository) do + create(:container_repository, name: 'bar', project: project, created_at: 1.day.ago) + end + + let_it_be(:sort_repository2) do + create(:container_repository, name: 'foo', project: project, created_at: 1.hour.ago, updated_at: 1.hour.ago) + end + + [:created_desc, :updated_asc, :name_desc].each do |order| + context "#{order}" do + let(:args) { { sort: order } } + + it { is_expected.to eq([sort_repository2, sort_repository]) } + end + end + + [:created_asc, :updated_desc, :name_asc].each do |order| + context "#{order}" do + let(:args) { { sort: order } } + + it { is_expected.to eq([sort_repository, sort_repository2]) } + end + end + end end context 'with authorized user' do diff --git a/spec/graphql/resolvers/group_labels_resolver_spec.rb b/spec/graphql/resolvers/group_labels_resolver_spec.rb new file mode 100644 index 00000000000..ed94f12502a --- /dev/null +++ b/spec/graphql/resolvers/group_labels_resolver_spec.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::GroupLabelsResolver do + include GraphqlHelpers + + using RSpec::Parameterized::TableSyntax + + let_it_be(:current_user) { create(:user) } + let_it_be(:group, reload: true) { create(:group, :private) } + let_it_be(:subgroup, reload: true) { create(:group, :private, parent: group) } + let_it_be(:sub_subgroup, reload: true) { create(:group, :private, parent: subgroup) } + let_it_be(:project, reload: true) { create(:project, :private, group: sub_subgroup) } + let_it_be(:label1) { create(:label, project: project, name: 'project feature') } + let_it_be(:label2) { create(:label, project: project, name: 'new project feature') } + let_it_be(:group_label1) { create(:group_label, group: group, name: 'group feature') } + let_it_be(:group_label2) { create(:group_label, group: group, name: 'new group feature') } + let_it_be(:subgroup_label1) { create(:group_label, group: subgroup, name: 'subgroup feature') } + let_it_be(:subgroup_label2) { create(:group_label, group: subgroup, name: 'new subgroup feature') } + let_it_be(:sub_subgroup_label1) { create(:group_label, group: sub_subgroup, name: 'sub_subgroup feature') } + let_it_be(:sub_subgroup_label2) { create(:group_label, group: sub_subgroup, name: 'new sub_subgroup feature') } + + specify do + expect(described_class).to have_nullable_graphql_type(Types::LabelType.connection_type) + end + + describe '#resolve' do + context 'with unauthorized user' do + it 'raises error' do + expect { resolve_labels(subgroup) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'with authorized user' do + it 'does not raise error' do + group.add_guest(current_user) + + expect { resolve_labels(subgroup) }.not_to raise_error + end + end + + context 'without parent' do + it 'returns no labels' do + expect(resolve_labels(nil)).to eq(Label.none) + end + end + + context 'at group level' do + before_all do + group.add_developer(current_user) + end + + # because :include_ancestor_groups, :include_descendant_groups, :only_group_labels default to false + # the `nil` value would be equivalent to passing in `false` so just check for `nil` option + where(:include_ancestor_groups, :include_descendant_groups, :only_group_labels, :search_term, :test) do + nil | nil | nil | nil | -> { expect(subject).to contain_exactly(subgroup_label1, subgroup_label2) } + nil | nil | true | nil | -> { expect(subject).to contain_exactly(subgroup_label1, subgroup_label2) } + nil | true | nil | nil | -> { expect(subject).to contain_exactly(subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2, label1, label2) } + nil | true | true | nil | -> { expect(subject).to contain_exactly(subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) } + true | nil | nil | nil | -> { expect(subject).to contain_exactly(group_label1, group_label2, subgroup_label1, subgroup_label2) } + true | nil | true | nil | -> { expect(subject).to contain_exactly(group_label1, group_label2, subgroup_label1, subgroup_label2) } + true | true | nil | nil | -> { expect(subject).to contain_exactly(group_label1, group_label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2, label1, label2) } + true | true | true | nil | -> { expect(subject).to contain_exactly(group_label1, group_label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) } + + nil | nil | nil | 'new' | -> { expect(subject).to contain_exactly(subgroup_label2) } + nil | nil | true | 'new' | -> { expect(subject).to contain_exactly(subgroup_label2) } + nil | true | nil | 'new' | -> { expect(subject).to contain_exactly(subgroup_label2, sub_subgroup_label2, label2) } + nil | true | true | 'new' | -> { expect(subject).to contain_exactly(subgroup_label2, sub_subgroup_label2) } + true | nil | nil | 'new' | -> { expect(subject).to contain_exactly(group_label2, subgroup_label2) } + true | nil | true | 'new' | -> { expect(subject).to contain_exactly(group_label2, subgroup_label2) } + true | true | nil | 'new' | -> { expect(subject).to contain_exactly(group_label2, subgroup_label2, sub_subgroup_label2, label2) } + true | true | true | 'new' | -> { expect(subject).to contain_exactly(group_label2, subgroup_label2, sub_subgroup_label2) } + end + + with_them do + let(:params) do + { + include_ancestor_groups: include_ancestor_groups, + include_descendant_groups: include_descendant_groups, + only_group_labels: only_group_labels, + search_term: search_term + } + end + + subject { resolve_labels(subgroup, params) } + + it { self.instance_exec(&test) } + end + end + end + + def resolve_labels(parent, args = {}, context = { current_user: current_user }) + resolve(described_class, obj: parent, args: args, ctx: context) + end +end diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index 269ee9eabf9..8980f4aa19d 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -195,11 +195,11 @@ RSpec.describe Resolvers::IssuesResolver do let_it_be(:priority_issue4) { create(:issue, project: project) } it 'sorts issues ascending' do - expect(resolve_issues(sort: :priority_asc).items).to eq([priority_issue3, priority_issue1, priority_issue2, priority_issue4]) + expect(resolve_issues(sort: :priority_asc).to_a).to eq([priority_issue3, priority_issue1, priority_issue2, priority_issue4]) end it 'sorts issues descending' do - expect(resolve_issues(sort: :priority_desc).items).to eq([priority_issue1, priority_issue3, priority_issue2, priority_issue4]) + expect(resolve_issues(sort: :priority_desc).to_a).to eq([priority_issue1, priority_issue3, priority_issue2, priority_issue4]) end end @@ -214,11 +214,11 @@ RSpec.describe Resolvers::IssuesResolver do let_it_be(:label_issue4) { create(:issue, project: project) } it 'sorts issues ascending' do - expect(resolve_issues(sort: :label_priority_asc).items).to eq([label_issue3, label_issue1, label_issue2, label_issue4]) + expect(resolve_issues(sort: :label_priority_asc).to_a).to eq([label_issue3, label_issue1, label_issue2, label_issue4]) end it 'sorts issues descending' do - expect(resolve_issues(sort: :label_priority_desc).items).to eq([label_issue2, label_issue3, label_issue1, label_issue4]) + expect(resolve_issues(sort: :label_priority_desc).to_a).to eq([label_issue2, label_issue3, label_issue1, label_issue4]) end end @@ -231,11 +231,11 @@ RSpec.describe Resolvers::IssuesResolver do let_it_be(:milestone_issue3) { create(:issue, project: project, milestone: late_milestone) } it 'sorts issues ascending' do - expect(resolve_issues(sort: :milestone_due_asc).items).to eq([milestone_issue2, milestone_issue3, milestone_issue1]) + expect(resolve_issues(sort: :milestone_due_asc).to_a).to eq([milestone_issue2, milestone_issue3, milestone_issue1]) end it 'sorts issues descending' do - expect(resolve_issues(sort: :milestone_due_desc).items).to eq([milestone_issue3, milestone_issue2, milestone_issue1]) + expect(resolve_issues(sort: :milestone_due_desc).to_a).to eq([milestone_issue3, milestone_issue2, milestone_issue1]) end end diff --git a/spec/graphql/resolvers/labels_resolver_spec.rb b/spec/graphql/resolvers/labels_resolver_spec.rb new file mode 100644 index 00000000000..3d027a6c8d5 --- /dev/null +++ b/spec/graphql/resolvers/labels_resolver_spec.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::LabelsResolver do + include GraphqlHelpers + + using RSpec::Parameterized::TableSyntax + + let_it_be(:current_user) { create(:user) } + let_it_be(:group, reload: true) { create(:group, :private) } + let_it_be(:subgroup, reload: true) { create(:group, :private, parent: group) } + let_it_be(:sub_subgroup, reload: true) { create(:group, :private, parent: subgroup) } + let_it_be(:project, reload: true) { create(:project, :private, group: subgroup) } + let_it_be(:label1) { create(:label, project: project, name: 'project feature') } + let_it_be(:label2) { create(:label, project: project, name: 'new project feature') } + let_it_be(:group_label1) { create(:group_label, group: group, name: 'group feature') } + let_it_be(:group_label2) { create(:group_label, group: group, name: 'new group feature') } + let_it_be(:subgroup_label1) { create(:group_label, group: subgroup, name: 'subgroup feature') } + let_it_be(:subgroup_label2) { create(:group_label, group: subgroup, name: 'new subgroup feature') } + let_it_be(:sub_subgroup_label1) { create(:group_label, group: sub_subgroup, name: 'sub_subgroup feature') } + let_it_be(:sub_subgroup_label2) { create(:group_label, group: sub_subgroup, name: 'new sub_subgroup feature') } + + specify do + expect(described_class).to have_nullable_graphql_type(Types::LabelType.connection_type) + end + + describe '#resolve' do + context 'with unauthorized user' do + it 'returns no labels' do + expect { resolve_labels(project) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'with authorized user' do + it 'returns no labels' do + group.add_guest(current_user) + + expect { resolve_labels(project) }.not_to raise_error + end + end + + context 'without parent' do + it 'returns no labels' do + expect(resolve_labels(nil)).to eq(Label.none) + end + end + + context 'at project level' do + before_all do + group.add_developer(current_user) + end + + # because :include_ancestor_groups, :include_descendant_groups, :only_group_labels default to false + # the `nil` value would be equivalent to passing in `false` so just check for `nil` option + where(:include_ancestor_groups, :include_descendant_groups, :only_group_labels, :search_term, :test) do + nil | nil | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2) } + nil | nil | true | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2) } + nil | true | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) } + nil | true | true | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) } + true | nil | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2) } + true | nil | true | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2) } + true | true | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) } + true | true | true | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) } + + nil | nil | nil | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2) } + nil | nil | true | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2) } + nil | true | nil | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2, sub_subgroup_label2) } + nil | true | true | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2, sub_subgroup_label2) } + true | nil | nil | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2) } + true | nil | true | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2) } + true | true | nil | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2, sub_subgroup_label2) } + true | true | true | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2, sub_subgroup_label2) } + end + + with_them do + let(:params) do + { + include_ancestor_groups: include_ancestor_groups, + include_descendant_groups: include_descendant_groups, + only_group_labels: only_group_labels, + search_term: search_term + } + end + + subject { resolve_labels(project, params) } + + it { self.instance_exec(&test) } + end + end + end + + def resolve_labels(parent, args = {}, context = { current_user: current_user }) + resolve(described_class, obj: parent, args: args, ctx: context) + end +end diff --git a/spec/graphql/resolvers/merge_requests_resolver_spec.rb b/spec/graphql/resolvers/merge_requests_resolver_spec.rb index 50b9243efa5..c5c368fc88f 100644 --- a/spec/graphql/resolvers/merge_requests_resolver_spec.rb +++ b/spec/graphql/resolvers/merge_requests_resolver_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe Resolvers::MergeRequestsResolver do include GraphqlHelpers + include SortingHelper let_it_be(:project) { create(:project, :repository) } let_it_be(:milestone) { create(:milestone, project: project) } @@ -30,6 +31,16 @@ RSpec.describe Resolvers::MergeRequestsResolver do end describe '#resolve' do + # One for the initial auth, then MRs, and the load of project and project_feature (for further auth): + # SELECT MAX("project_authorizations"."access_level") AS maximum_access_level, + # "project_authorizations"."user_id" AS project_authorizations_user_id + # FROM "project_authorizations" + # WHERE "project_authorizations"."project_id" = 2 AND "project_authorizations"."user_id" = 2 + # GROUP BY "project_authorizations"."user_id" + # SELECT "merge_requests".* FROM "merge_requests" WHERE "merge_requests"."target_project_id" = 2 + # AND "merge_requests"."iid" = 1 ORDER BY "merge_requests"."id" DESC + # SELECT "projects".* FROM "projects" WHERE "projects"."id" = 2 + # SELECT "project_features".* FROM "project_features" WHERE "project_features"."project_id" = 2 let(:queries_per_project) { 3 } context 'no arguments' do @@ -72,15 +83,17 @@ RSpec.describe Resolvers::MergeRequestsResolver do expect(result).to contain_exactly(merge_request_1, merge_request_2, merge_request_3) end - it 'can batch-resolve merge requests from different projects' do + it 'can batch-resolve merge requests from different projects', :request_store, :use_clean_rails_memory_store_caching do # 2 queries for project_authorizations, and 2 for merge_requests - result = batch_sync(max_queries: queries_per_project * 2) do - resolve_mr(project, iids: [iid_1]) + - resolve_mr(project, iids: [iid_2]) + - resolve_mr(other_project, iids: [other_iid]) + results = batch_sync(max_queries: queries_per_project * 2) do + a = resolve_mr(project, iids: [iid_1]) + b = resolve_mr(project, iids: [iid_2]) + c = resolve_mr(other_project, iids: [other_iid]) + + [a, b, c].flat_map(&:to_a) end - expect(result).to contain_exactly(merge_request_1, merge_request_2, other_merge_request) + expect(results).to contain_exactly(merge_request_1, merge_request_2, other_merge_request) end it 'resolves an unknown iid to be empty' do @@ -134,9 +147,9 @@ RSpec.describe Resolvers::MergeRequestsResolver do it 'takes more than one argument' do mrs = [merge_request_3, merge_request_4] branches = mrs.map(&:target_branch) - result = resolve_mr(project, target_branches: branches ) + result = resolve_mr(project, target_branches: branches) - expect(result.compact).to match_array(mrs) + expect(result).to match_array(mrs) end end @@ -173,7 +186,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do it 'returns merge requests merged between the given period' do result = resolve_mr(project, merged_after: 20.days.ago, merged_before: 5.days.ago) - expect(result).to eq([merge_request_1]) + expect(result).to contain_exactly(merge_request_1) end it 'does not return anything' do @@ -187,7 +200,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do it 'filters merge requests by milestone title' do result = resolve_mr(project, milestone_title: milestone.title) - expect(result).to eq([merge_request_with_milestone]) + expect(result).to contain_exactly(merge_request_with_milestone) end it 'does not find anything' do @@ -203,18 +216,29 @@ RSpec.describe Resolvers::MergeRequestsResolver do result = resolve_mr(project, source_branches: [merge_request_4.source_branch], state: 'locked') - expect(result.compact).to contain_exactly(merge_request_4) + expect(result).to contain_exactly(merge_request_4) end end describe 'sorting' do + let(:mrs) do + [ + merge_request_with_milestone, merge_request_6, merge_request_5, merge_request_4, + merge_request_3, merge_request_2, merge_request_1 + ] + end + context 'when sorting by created' do it 'sorts merge requests ascending' do - expect(resolve_mr(project, sort: 'created_asc')).to eq [merge_request_1, merge_request_2, merge_request_3, merge_request_4, merge_request_5, merge_request_6, merge_request_with_milestone] + expect(resolve_mr(project, sort: 'created_asc')) + .to match_array(mrs) + .and be_sorted(:created_at, :asc) end it 'sorts merge requests descending' do - expect(resolve_mr(project, sort: 'created_desc')).to eq [merge_request_with_milestone, merge_request_6, merge_request_5, merge_request_4, merge_request_3, merge_request_2, merge_request_1] + expect(resolve_mr(project, sort: 'created_desc')) + .to match_array(mrs) + .and be_sorted(:created_at, :desc) end end @@ -225,11 +249,19 @@ RSpec.describe Resolvers::MergeRequestsResolver do end it 'sorts merge requests ascending' do - expect(resolve_mr(project, sort: :merged_at_asc)).to eq [merge_request_1, merge_request_3, merge_request_with_milestone, merge_request_6, merge_request_5, merge_request_4, merge_request_2] + expect(resolve_mr(project, sort: :merged_at_asc)) + .to match_array(mrs) + .and be_sorted(->(mr) { [merged_at(mr), -mr.id] }) end it 'sorts merge requests descending' do - expect(resolve_mr(project, sort: :merged_at_desc)).to eq [merge_request_3, merge_request_1, merge_request_with_milestone, merge_request_6, merge_request_5, merge_request_4, merge_request_2] + expect(resolve_mr(project, sort: :merged_at_desc)) + .to match_array(mrs) + .and be_sorted(->(mr) { [-merged_at(mr), -mr.id] }) + end + + def merged_at(mr) + nils_last(mr.metrics.merged_at) end context 'when label filter is given and the optimized_issuable_label_filter feature flag is off' do diff --git a/spec/graphql/resolvers/package_details_resolver_spec.rb b/spec/graphql/resolvers/package_details_resolver_spec.rb index 825b2aed40a..1bdc069b3bb 100644 --- a/spec/graphql/resolvers/package_details_resolver_spec.rb +++ b/spec/graphql/resolvers/package_details_resolver_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe Resolvers::PackageDetailsResolver do include GraphqlHelpers + include ::Gitlab::Graphql::Laziness let_it_be_with_reload(:project) { create(:project) } let_it_be(:user) { project.owner } @@ -11,10 +12,10 @@ RSpec.describe Resolvers::PackageDetailsResolver do describe '#resolve' do let(:args) do - { id: package.to_global_id.to_s } + { id: global_id_of(package) } end - subject { resolve(described_class, ctx: { current_user: user }, args: args).sync } + subject { force(resolve(described_class, ctx: { current_user: user }, args: args)) } it { is_expected.to eq(package) } end diff --git a/spec/graphql/resolvers/packages_resolver_spec.rb b/spec/graphql/resolvers/packages_resolver_spec.rb index 9aec2c7e036..bc0588daf7f 100644 --- a/spec/graphql/resolvers/packages_resolver_spec.rb +++ b/spec/graphql/resolvers/packages_resolver_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Resolvers::PackagesResolver do include GraphqlHelpers let_it_be(:user) { create(:user) } - let_it_be(:project) { create(:project) } + let_it_be(:project) { create(:project, :public) } let_it_be(:package) { create(:package, project: project) } describe '#resolve' do diff --git a/spec/graphql/resolvers/release_milestones_resolver_spec.rb b/spec/graphql/resolvers/release_milestones_resolver_spec.rb index 5f66cba859d..f05069998d0 100644 --- a/spec/graphql/resolvers/release_milestones_resolver_spec.rb +++ b/spec/graphql/resolvers/release_milestones_resolver_spec.rb @@ -6,18 +6,19 @@ RSpec.describe Resolvers::ReleaseMilestonesResolver do include GraphqlHelpers let_it_be(:release) { create(:release, :with_milestones, milestones_count: 2) } + let_it_be(:current_user) { create(:user, developer_projects: [release.project]) } let(:resolved) do - resolve(described_class, obj: release) + resolve(described_class, obj: release, ctx: { current_user: current_user }) end describe '#resolve' do - it "returns an OffsetActiveRecordRelationConnection" do - expect(resolved).to be_a(::Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection) + it "uses offset-pagination" do + expect(resolved).to be_a(::Gitlab::Graphql::Pagination::OffsetPaginatedRelation) end it "includes the release's milestones in the returned OffsetActiveRecordRelationConnection" do - expect(resolved.items).to eq(release.milestones.order_by_dates_and_title) + expect(resolved.to_a).to eq(release.milestones.order_by_dates_and_title) end end end diff --git a/spec/graphql/resolvers/release_resolver_spec.rb b/spec/graphql/resolvers/release_resolver_spec.rb index 04765fc68e9..782c9604f15 100644 --- a/spec/graphql/resolvers/release_resolver_spec.rb +++ b/spec/graphql/resolvers/release_resolver_spec.rb @@ -36,7 +36,7 @@ RSpec.describe Resolvers::ReleaseResolver do let(:args) { {} } it 'raises an error' do - expect { resolve_release }.to raise_error(ArgumentError, "missing keyword: :tag_name") + expect { resolve_release }.to raise_error(ArgumentError) end end end diff --git a/spec/graphql/resolvers/terraform/states_resolver_spec.rb b/spec/graphql/resolvers/terraform/states_resolver_spec.rb index 64b515528cd..91d48cd782b 100644 --- a/spec/graphql/resolvers/terraform/states_resolver_spec.rb +++ b/spec/graphql/resolvers/terraform/states_resolver_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' RSpec.describe Resolvers::Terraform::StatesResolver do include GraphqlHelpers - it { expect(described_class.type).to eq(Types::Terraform::StateType) } + it { expect(described_class).to have_nullable_graphql_type(Types::Terraform::StateType.connection_type) } it { expect(described_class.null).to be_truthy } describe '#resolve' do @@ -31,3 +31,21 @@ RSpec.describe Resolvers::Terraform::StatesResolver do end end end + +RSpec.describe Resolvers::Terraform::StatesResolver.single do + it { expect(described_class).to be < Resolvers::Terraform::StatesResolver } + + describe 'arguments' do + subject { described_class.arguments[argument] } + + describe 'name' do + let(:argument) { 'name' } + + it do + expect(subject).to be_present + expect(subject.type.to_s).to eq('String!') + expect(subject.description).to be_present + end + end + end +end diff --git a/spec/graphql/types/ci/pipeline_type_spec.rb b/spec/graphql/types/ci/pipeline_type_spec.rb index d435e337ad7..2a1e030480d 100644 --- a/spec/graphql/types/ci/pipeline_type_spec.rb +++ b/spec/graphql/types/ci/pipeline_type_spec.rb @@ -12,7 +12,7 @@ RSpec.describe Types::Ci::PipelineType do id iid sha before_sha status detailed_status config_source duration coverage created_at updated_at started_at finished_at committed_at stages user retryable cancelable jobs source_job downstream - upstream path project active user_permissions + upstream path project active user_permissions warnings ] if Gitlab.ee? diff --git a/spec/graphql/types/ci_configuration/sast/analyzers_entity_input_type_spec.rb b/spec/graphql/types/ci_configuration/sast/analyzers_entity_input_type_spec.rb new file mode 100644 index 00000000000..ac18f8d53a1 --- /dev/null +++ b/spec/graphql/types/ci_configuration/sast/analyzers_entity_input_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Types::CiConfiguration::Sast::AnalyzersEntityInputType do + it { expect(described_class.graphql_name).to eq('SastCiConfigurationAnalyzersEntityInput') } + + it { expect(described_class.arguments.keys).to match_array(%w[enabled name variables]) } +end diff --git a/spec/graphql/types/ci_configuration/sast/analyzers_entity_type_spec.rb b/spec/graphql/types/ci_configuration/sast/analyzers_entity_type_spec.rb new file mode 100644 index 00000000000..27f6703b429 --- /dev/null +++ b/spec/graphql/types/ci_configuration/sast/analyzers_entity_type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['SastCiConfigurationAnalyzersEntity'] do + let(:fields) { %i[name label enabled description variables] } + + it { expect(described_class.graphql_name).to eq('SastCiConfigurationAnalyzersEntity') } + + it { expect(described_class).to have_graphql_fields(fields) } +end diff --git a/spec/graphql/types/ci_configuration/sast/entity_input_type_spec.rb b/spec/graphql/types/ci_configuration/sast/entity_input_type_spec.rb new file mode 100644 index 00000000000..cefcf64164a --- /dev/null +++ b/spec/graphql/types/ci_configuration/sast/entity_input_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Types::CiConfiguration::Sast::EntityInputType do + it { expect(described_class.graphql_name).to eq('SastCiConfigurationEntityInput') } + + it { expect(described_class.arguments.keys).to match_array(%w[field defaultValue value]) } +end diff --git a/spec/graphql/types/ci_configuration/sast/entity_type_spec.rb b/spec/graphql/types/ci_configuration/sast/entity_type_spec.rb new file mode 100644 index 00000000000..762798670a5 --- /dev/null +++ b/spec/graphql/types/ci_configuration/sast/entity_type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['SastCiConfigurationEntity'] do + let(:fields) { %i[field label description type options default_value value size] } + + it { expect(described_class.graphql_name).to eq('SastCiConfigurationEntity') } + + it { expect(described_class).to have_graphql_fields(fields) } +end diff --git a/spec/graphql/types/ci_configuration/sast/input_type_spec.rb b/spec/graphql/types/ci_configuration/sast/input_type_spec.rb new file mode 100644 index 00000000000..9f9d1dea98f --- /dev/null +++ b/spec/graphql/types/ci_configuration/sast/input_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ::Types::CiConfiguration::Sast::InputType do + it { expect(described_class.graphql_name).to eq('SastCiConfigurationInput') } + + it { expect(described_class.arguments.keys).to match_array(%w[global pipeline analyzers]) } +end diff --git a/spec/graphql/types/ci_configuration/sast/options_entity_spec.rb b/spec/graphql/types/ci_configuration/sast/options_entity_spec.rb new file mode 100644 index 00000000000..c60c8b9c84a --- /dev/null +++ b/spec/graphql/types/ci_configuration/sast/options_entity_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['SastCiConfigurationOptionsEntity'] do + let(:fields) { %i[label value] } + + it { expect(described_class.graphql_name).to eq('SastCiConfigurationOptionsEntity') } + + it { expect(described_class).to have_graphql_fields(fields) } +end diff --git a/spec/graphql/types/ci_configuration/sast/type_spec.rb b/spec/graphql/types/ci_configuration/sast/type_spec.rb new file mode 100644 index 00000000000..e7a8cd436e4 --- /dev/null +++ b/spec/graphql/types/ci_configuration/sast/type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['SastCiConfiguration'] do + let(:fields) { %i[global pipeline analyzers] } + + it { expect(described_class.graphql_name).to eq('SastCiConfiguration') } + + it { expect(described_class).to have_graphql_fields(fields) } +end diff --git a/spec/graphql/types/ci_configuration/sast/ui_component_size_enum_spec.rb b/spec/graphql/types/ci_configuration/sast/ui_component_size_enum_spec.rb new file mode 100644 index 00000000000..23184df809f --- /dev/null +++ b/spec/graphql/types/ci_configuration/sast/ui_component_size_enum_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::CiConfiguration::Sast::UiComponentSizeEnum do + specify { expect(described_class.graphql_name).to eq('SastUiComponentSize') } + + it 'exposes all sizes of ui components' do + expect(described_class.values.keys).to include(*%w[SMALL MEDIUM LARGE]) + end +end diff --git a/spec/graphql/types/container_repository_sort_enum_spec.rb b/spec/graphql/types/container_repository_sort_enum_spec.rb new file mode 100644 index 00000000000..eb936c6d3a1 --- /dev/null +++ b/spec/graphql/types/container_repository_sort_enum_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['ContainerRepositorySort'] do + specify { expect(described_class.graphql_name).to eq('ContainerRepositorySort') } + + it_behaves_like 'common sort values' + + it 'exposes all the existing issue sort values' do + expect(described_class.values.keys).to include( + *%w[NAME_ASC NAME_DESC] + ) + end +end diff --git a/spec/graphql/types/event_type_spec.rb b/spec/graphql/types/event_type_spec.rb new file mode 100644 index 00000000000..10c3b5e18ca --- /dev/null +++ b/spec/graphql/types/event_type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::EventType do + specify { expect(described_class.graphql_name).to eq('Event') } + + specify { expect(described_class).to require_graphql_authorizations(:read_event) } + + specify { expect(described_class).to have_graphql_fields(:id, :author, :action, :created_at, :updated_at) } +end diff --git a/spec/graphql/types/eventable_type_spec.rb b/spec/graphql/types/eventable_type_spec.rb new file mode 100644 index 00000000000..c1c7bf6d65a --- /dev/null +++ b/spec/graphql/types/eventable_type_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::EventableType do + it 'exposes events field' do + expect(described_class).to have_graphql_fields(:events) + end +end diff --git a/spec/graphql/types/group_type_spec.rb b/spec/graphql/types/group_type_spec.rb index de19e8b602a..bba702ba3e9 100644 --- a/spec/graphql/types/group_type_spec.rb +++ b/spec/graphql/types/group_type_spec.rb @@ -38,5 +38,7 @@ RSpec.describe GitlabSchema.types['Group'] do it { is_expected.to have_graphql_resolver(Resolvers::GroupMembersResolver) } end - it_behaves_like 'a GraphQL type with labels' + it_behaves_like 'a GraphQL type with labels' do + let(:labels_resolver_arguments) { [:search_term, :includeAncestorGroups, :includeDescendantGroups, :onlyGroupLabels] } + end end diff --git a/spec/graphql/types/merge_request_state_event_enum_spec.rb b/spec/graphql/types/merge_request_state_event_enum_spec.rb new file mode 100644 index 00000000000..94214b29755 --- /dev/null +++ b/spec/graphql/types/merge_request_state_event_enum_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['MergeRequestNewState'] do + it 'has the appropriate values' do + expect(described_class.values).to contain_exactly( + ['OPEN', have_attributes(value: 'reopen')], + ['CLOSED', have_attributes(value: 'close')] + ) + end +end diff --git a/spec/graphql/types/packages/composer/details_type_spec.rb b/spec/graphql/types/packages/composer/details_type_spec.rb deleted file mode 100644 index 2e4cb965ded..00000000000 --- a/spec/graphql/types/packages/composer/details_type_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe GitlabSchema.types['PackageComposerDetails'] do - it { expect(described_class.graphql_name).to eq('PackageComposerDetails') } - - it 'includes all the package fields' do - expected_fields = %w[ - id name version created_at updated_at package_type tags project pipelines versions - ] - - expect(described_class).to include_graphql_fields(*expected_fields) - end - - it 'includes composer specific files' do - expected_fields = %w[ - composer_metadatum - ] - - expect(described_class).to include_graphql_fields(*expected_fields) - end -end diff --git a/spec/graphql/types/packages/composer/metadatum_type_spec.rb b/spec/graphql/types/packages/composer/metadatum_type_spec.rb index 0f47d8f1812..a950c10a41d 100644 --- a/spec/graphql/types/packages/composer/metadatum_type_spec.rb +++ b/spec/graphql/types/packages/composer/metadatum_type_spec.rb @@ -2,9 +2,7 @@ require 'spec_helper' -RSpec.describe GitlabSchema.types['PackageComposerMetadatumType'] do - it { expect(described_class.graphql_name).to eq('PackageComposerMetadatumType') } - +RSpec.describe GitlabSchema.types['ComposerMetadata'] do it 'includes composer metadatum fields' do expected_fields = %w[ target_sha composer_json diff --git a/spec/graphql/types/packages/package_type_enum_spec.rb b/spec/graphql/types/packages/package_type_enum_spec.rb index 407d5786f65..ccd91485e4b 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]) + expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN RUBYGEMS]) end end diff --git a/spec/graphql/types/packages/package_type_spec.rb b/spec/graphql/types/packages/package_type_spec.rb index 7003a4d4d07..43289a019b3 100644 --- a/spec/graphql/types/packages/package_type_spec.rb +++ b/spec/graphql/types/packages/package_type_spec.rb @@ -3,11 +3,12 @@ require 'spec_helper' RSpec.describe GitlabSchema.types['Package'] do - it { expect(described_class.graphql_name).to eq('Package') } - it 'includes all the package fields' do expected_fields = %w[ - id name version created_at updated_at package_type tags project pipelines versions + id name version package_type + created_at updated_at + project + tags pipelines versions ] expect(described_class).to include_graphql_fields(*expected_fields) diff --git a/spec/graphql/types/packages/package_without_versions_type_spec.rb b/spec/graphql/types/packages/package_without_versions_type_spec.rb new file mode 100644 index 00000000000..faa79e588d5 --- /dev/null +++ b/spec/graphql/types/packages/package_without_versions_type_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['PackageWithoutVersions'] do + it 'includes all the package fields' do + expected_fields = %w[ + id name version created_at updated_at package_type tags project pipelines + ] + + expect(described_class).to include_graphql_fields(*expected_fields) + end +end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 9d0d7a3918a..9579ef8b99b 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -31,12 +31,171 @@ RSpec.describe GitlabSchema.types['Project'] do container_expiration_policy service_desk_enabled service_desk_address issue_status_counts terraform_states alert_management_integrations container_repositories container_repositories_count - pipeline_analytics squash_read_only + pipeline_analytics squash_read_only sast_ci_configuration ] expect(described_class).to include_graphql_fields(*expected_fields) end + describe 'sast_ci_configuration' do + let_it_be(:project) { create(:project) } + let_it_be(:user) { create(:user) } + + before do + stub_licensed_features(security_dashboard: true) + project.add_developer(user) + allow(project.repository).to receive(:blob_data_at).and_return(gitlab_ci_yml_content) + end + + include_context 'read ci configuration for sast enabled project' + + let(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + sastCiConfiguration { + global { + nodes { + type + options { + nodes { + label + value + } + } + field + label + defaultValue + value + size + } + } + pipeline { + nodes { + type + options { + nodes { + label + value + } + } + field + label + defaultValue + value + size + } + } + analyzers { + nodes { + name + label + enabled + } + } + } + } + } + ) + end + + subject { GitlabSchema.execute(query, context: { current_user: user }).as_json } + + it "returns the project's sast configuration for global variables" do + secure_analyzers_prefix = subject.dig('data', 'project', 'sastCiConfiguration', 'global', 'nodes').first + expect(secure_analyzers_prefix['type']).to eq('string') + expect(secure_analyzers_prefix['field']).to eq('SECURE_ANALYZERS_PREFIX') + expect(secure_analyzers_prefix['label']).to eq('Image prefix') + expect(secure_analyzers_prefix['defaultValue']).to eq('registry.gitlab.com/gitlab-org/security-products/analyzers') + expect(secure_analyzers_prefix['value']).to eq('registry.gitlab.com/gitlab-org/security-products/analyzers') + expect(secure_analyzers_prefix['size']).to eq('LARGE') + expect(secure_analyzers_prefix['options']).to be_nil + end + + it "returns the project's sast configuration for pipeline variables" do + pipeline_stage = subject.dig('data', 'project', 'sastCiConfiguration', 'pipeline', 'nodes').first + expect(pipeline_stage['type']).to eq('string') + expect(pipeline_stage['field']).to eq('stage') + expect(pipeline_stage['label']).to eq('Stage') + expect(pipeline_stage['defaultValue']).to eq('test') + expect(pipeline_stage['value']).to eq('test') + expect(pipeline_stage['size']).to eq('MEDIUM') + end + + it "returns the project's sast configuration for analyzer variables" do + analyzer = subject.dig('data', 'project', 'sastCiConfiguration', 'analyzers', 'nodes').first + expect(analyzer['name']).to eq('brakeman') + expect(analyzer['label']).to eq('Brakeman') + expect(analyzer['enabled']).to eq(true) + end + + context "with guest user" do + before do + project.add_guest(user) + end + + context 'when project is private' do + let(:project) { create(:project, :private, :repository) } + + it "returns no configuration" do + secure_analyzers_prefix = subject.dig('data', 'project', 'sastCiConfiguration') + expect(secure_analyzers_prefix).to be_nil + end + end + + context 'when project is public' do + let(:project) { create(:project, :public, :repository) } + + context 'when repository is accessible by everyone' do + it "returns the project's sast configuration for global variables" do + secure_analyzers_prefix = subject.dig('data', 'project', 'sastCiConfiguration', 'global', 'nodes').first + + expect(secure_analyzers_prefix['type']).to eq('string') + expect(secure_analyzers_prefix['field']).to eq('SECURE_ANALYZERS_PREFIX') + end + end + end + end + + context "with non-member user" do + before do + project.team.truncate + end + + context 'when project is private' do + let(:project) { create(:project, :private, :repository) } + + it "returns no configuration" do + secure_analyzers_prefix = subject.dig('data', 'project', 'sastCiConfiguration') + expect(secure_analyzers_prefix).to be_nil + end + end + + context 'when project is public' do + let(:project) { create(:project, :public, :repository) } + + context 'when repository is accessible by everyone' do + it "returns the project's sast configuration for global variables" do + secure_analyzers_prefix = subject.dig('data', 'project', 'sastCiConfiguration', 'global', 'nodes').first + expect(secure_analyzers_prefix['type']).to eq('string') + expect(secure_analyzers_prefix['field']).to eq('SECURE_ANALYZERS_PREFIX') + end + end + + context 'when repository is accessible only by team members' do + it "returns no configuration" do + project.project_feature.update!(merge_requests_access_level: ProjectFeature::DISABLED, + builds_access_level: ProjectFeature::DISABLED, + repository_access_level: ProjectFeature::PRIVATE) + + secure_analyzers_prefix = subject.dig('data', 'project', 'sastCiConfiguration') + expect(secure_analyzers_prefix).to be_nil + end + end + end + end + end + describe 'issue field' do subject { described_class.fields['issue'] } @@ -159,6 +318,13 @@ RSpec.describe GitlabSchema.types['Project'] do it { is_expected.to have_graphql_type(Types::ContainerExpirationPolicyType) } end + describe 'terraform state field' do + subject { described_class.fields['terraformState'] } + + it { is_expected.to have_graphql_type(Types::Terraform::StateType) } + it { is_expected.to have_graphql_resolver(Resolvers::Terraform::StatesResolver.single) } + end + describe 'terraform states field' do subject { described_class.fields['terraformStates'] } @@ -166,7 +332,9 @@ RSpec.describe GitlabSchema.types['Project'] do it { is_expected.to have_graphql_resolver(Resolvers::Terraform::StatesResolver) } end - it_behaves_like 'a GraphQL type with labels' + it_behaves_like 'a GraphQL type with labels' do + let(:labels_resolver_arguments) { [:search_term, :includeAncestorGroups] } + end describe 'jira_imports' do subject { resolve_field(:jira_imports, project) } diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb index 3e716865e56..fea0a3bd37e 100644 --- a/spec/graphql/types/query_type_spec.rb +++ b/spec/graphql/types/query_type_spec.rb @@ -95,9 +95,9 @@ RSpec.describe GitlabSchema.types['Query'] do it { is_expected.to have_graphql_type(Types::ContainerRepositoryDetailsType) } end - describe 'package_composer_details field' do - subject { described_class.fields['packageComposerDetails'] } + describe 'package field' do + subject { described_class.fields['package'] } - it { is_expected.to have_graphql_type(Types::Packages::Composer::DetailsType) } + it { is_expected.to have_graphql_type(Types::Packages::PackageType) } end end diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb index 0eff33bb25b..5b3662383d8 100644 --- a/spec/graphql/types/user_type_spec.rb +++ b/spec/graphql/types/user_type_spec.rb @@ -10,6 +10,7 @@ RSpec.describe GitlabSchema.types['User'] do it 'has the expected fields' do expected_fields = %w[ id + bot user_permissions snippets name |