diff options
Diffstat (limited to 'spec/graphql/resolvers')
9 files changed, 363 insertions, 183 deletions
diff --git a/spec/graphql/resolvers/board_list_issues_resolver_spec.rb b/spec/graphql/resolvers/board_list_issues_resolver_spec.rb index 6ffc8b045e9..26040f4ec1a 100644 --- a/spec/graphql/resolvers/board_list_issues_resolver_spec.rb +++ b/spec/graphql/resolvers/board_list_issues_resolver_spec.rb @@ -17,21 +17,38 @@ RSpec.describe Resolvers::BoardListIssuesResolver do # auth is handled by the parent object context 'when authorized' do - let!(:issue1) { create(:issue, project: project, labels: [label], relative_position: 10) } - let!(:issue2) { create(:issue, project: project, labels: [label, label2], relative_position: 12) } - let!(:issue3) { create(:issue, project: project, labels: [label, label3], relative_position: 10) } + let!(:issue1) { create(:issue, project: project, labels: [label], relative_position: 10, milestone: started_milestone) } + let!(:issue2) { create(:issue, project: project, labels: [label, label2], relative_position: 12, milestone: started_milestone) } + let!(:issue3) { create(:issue, project: project, labels: [label, label3], relative_position: 10, milestone: future_milestone) } + let!(:issue4) { create(:issue, project: project, labels: [label], relative_position: nil) } - it 'returns the issues in the correct order' do + let(:wildcard_started) { 'STARTED' } + let(:filters) { { milestone_title: ["started"], milestone_wildcard_id: wildcard_started } } + + it 'raises a mutually exclusive filter error when milstone wildcard and title are provided' do + expect do + resolve_board_list_issues(args: { filters: filters }) + end.to raise_error(Gitlab::Graphql::Errors::ArgumentError) + end + + it 'returns issues in the correct order with non-nil relative positions', :aggregate_failures do # by relative_position and then ID - issues = resolve_board_list_issues + result = resolve_board_list_issues - expect(issues.map(&:id)).to eq [issue3.id, issue1.id, issue2.id] + expect(result.map(&:id)).to eq [issue3.id, issue1.id, issue2.id, issue4.id] + expect(result.map(&:relative_position)).not_to include(nil) end it 'finds only issues matching filters' do result = resolve_board_list_issues(args: { filters: { label_name: [label.title], not: { label_name: [label2.title] } } }) - expect(result).to match_array([issue1, issue3]) + expect(result).to match_array([issue1, issue3, issue4]) + end + + it 'finds only issues filtered by milestone wildcard' do + result = resolve_board_list_issues(args: { filters: { milestone_wildcard_id: wildcard_started } }) + + expect(result).to match_array([issue1, issue2]) end it 'finds only issues matching search param' do @@ -49,7 +66,7 @@ RSpec.describe Resolvers::BoardListIssuesResolver do it 'accepts assignee wildcard id NONE' do result = resolve_board_list_issues(args: { filters: { assignee_wildcard_id: 'NONE' } }) - expect(result).to match_array([issue1, issue2, issue3]) + expect(result).to match_array([issue1, issue2, issue3, issue4]) end it 'accepts assignee wildcard id ANY' do @@ -71,6 +88,9 @@ RSpec.describe Resolvers::BoardListIssuesResolver do let(:board_parent) { user_project } let(:project) { user_project } + let_it_be(:started_milestone) { create(:milestone, project: user_project, title: 'started milestone', start_date: 1.day.ago, due_date: 1.day.from_now) } + let_it_be(:future_milestone) { create(:milestone, project: user_project, title: 'future milestone', start_date: 1.day.from_now) } + it_behaves_like 'group and project board list issues resolver' end @@ -84,11 +104,14 @@ RSpec.describe Resolvers::BoardListIssuesResolver do let(:board_parent) { group } let!(:project) { create(:project, :private, group: group) } + let_it_be(:started_milestone) { create(:milestone, group: group, title: 'started milestone', start_date: 1.day.ago, due_date: 1.day.from_now) } + let_it_be(:future_milestone) { create(:milestone, group: group, title: 'future milestone', start_date: 1.day.from_now) } + it_behaves_like 'group and project board list issues resolver' end end def resolve_board_list_issues(args: {}, current_user: user) - resolve(described_class, obj: list, args: args, ctx: { current_user: current_user }) + resolve(described_class, obj: list, args: args, ctx: { current_user: current_user }).items end end diff --git a/spec/graphql/resolvers/ci/group_runners_resolver_spec.rb b/spec/graphql/resolvers/ci/group_runners_resolver_spec.rb new file mode 100644 index 00000000000..89a2437a189 --- /dev/null +++ b/spec/graphql/resolvers/ci/group_runners_resolver_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Ci::GroupRunnersResolver do + include GraphqlHelpers + + describe '#resolve' do + subject { resolve(described_class, obj: obj, ctx: { current_user: user }, args: args) } + + include_context 'runners resolver setup' + + let(:obj) { group } + let(:args) { {} } + + # First, we can do a couple of basic real tests to verify common cases. That ensures that the code works. + context 'when user cannot see runners' do + it 'returns no runners' do + expect(subject.items.to_a).to eq([]) + end + end + + context 'with user as group owner' do + before do + group.add_owner(user) + end + + it 'returns all the runners' do + expect(subject.items.to_a).to contain_exactly(inactive_project_runner, offline_project_runner, group_runner, subgroup_runner) + end + + context 'with membership direct' do + let(:args) { { membership: :direct } } + + it 'returns only direct runners' do + expect(subject.items.to_a).to contain_exactly(group_runner) + end + end + end + + # Then, we can check specific edge cases for this resolver + context 'with obj set to nil' do + let(:obj) { nil } + + it 'raises an error' do + expect { subject }.to raise_error('Expected group missing') + end + end + + context 'with obj not set to group' do + let(:obj) { build(:project) } + + it 'raises an error' do + expect { subject }.to raise_error('Expected group missing') + end + end + + # Here we have a mocked part. We assume that all possible edge cases are covered in RunnersFinder spec. So we don't need to test them twice. + # Only thing we can do is to verify that args from the resolver is correctly transformed to params of the Finder and we return the Finder's result back. + describe 'Allowed query arguments' do + let(:finder) { instance_double(::Ci::RunnersFinder) } + let(:args) do + { + status: 'active', + type: :group_type, + tag_list: ['active_runner'], + search: 'abc', + sort: :contacted_asc, + membership: :descendants + } + end + + let(:expected_params) do + { + status_status: 'active', + type_type: :group_type, + tag_name: ['active_runner'], + preload: { tag_name: nil }, + search: 'abc', + sort: 'contacted_asc', + membership: :descendants, + group: group + } + end + + it 'calls RunnersFinder with expected arguments' do + allow(::Ci::RunnersFinder).to receive(:new).with(current_user: user, params: expected_params).once.and_return(finder) + allow(finder).to receive(:execute).once.and_return([:execute_return_value]) + + expect(subject.items.to_a).to eq([:execute_return_value]) + end + end + end +end diff --git a/spec/graphql/resolvers/ci/runners_resolver_spec.rb b/spec/graphql/resolvers/ci/runners_resolver_spec.rb index 5ac15d5729f..bb8dadeca40 100644 --- a/spec/graphql/resolvers/ci/runners_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/runners_resolver_spec.rb @@ -5,185 +5,70 @@ require 'spec_helper' RSpec.describe Resolvers::Ci::RunnersResolver do include GraphqlHelpers - let_it_be(:user) { create_default(:user, :admin) } - let_it_be(:group) { create(:group, :public) } - let_it_be(:project) { create(:project, :repository, :public) } - - let_it_be(:inactive_project_runner) do - create(:ci_runner, :project, projects: [project], description: 'inactive project runner', token: 'abcdef', active: false, contacted_at: 1.minute.ago, tag_list: %w(project_runner)) - end - - let_it_be(:offline_project_runner) do - create(:ci_runner, :project, projects: [project], description: 'offline project runner', token: 'defghi', contacted_at: 1.day.ago, tag_list: %w(project_runner active_runner)) - end - - let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group], token: 'mnopqr', description: 'group runner', contacted_at: 1.second.ago) } - let_it_be(:instance_runner) { create(:ci_runner, :instance, description: 'shared runner', token: 'stuvxz', contacted_at: 2.minutes.ago, tag_list: %w(instance_runner active_runner)) } - describe '#resolve' do - subject { resolve(described_class, ctx: { current_user: user }, args: args).items.to_a } - - let(:args) do - {} - end - - context 'when the user cannot see runners' do - let(:user) { create(:user) } - - it 'returns no runners' do - is_expected.to be_empty - end - end - - context 'without sort' do - it 'returns all the runners' do - is_expected.to contain_exactly(inactive_project_runner, offline_project_runner, group_runner, instance_runner) - end - end - - context 'with a sort argument' do - context "set to :contacted_asc" do - let(:args) do - { sort: :contacted_asc } - end - - it { is_expected.to eq([offline_project_runner, instance_runner, inactive_project_runner, group_runner]) } - end - - context "set to :contacted_desc" do - let(:args) do - { sort: :contacted_desc } - end - - it { is_expected.to eq([offline_project_runner, instance_runner, inactive_project_runner, group_runner].reverse) } - end - - context "set to :created_at_desc" do - let(:args) do - { sort: :created_at_desc } - end + let(:obj) { nil } + let(:args) { {} } - it { is_expected.to eq([instance_runner, group_runner, offline_project_runner, inactive_project_runner]) } - end - - context "set to :created_at_asc" do - let(:args) do - { sort: :created_at_asc } - end - - it { is_expected.to eq([instance_runner, group_runner, offline_project_runner, inactive_project_runner].reverse) } - end - end + subject { resolve(described_class, obj: obj, ctx: { current_user: user }, args: args) } - context 'when type is filtered' do - let(:args) do - { type: runner_type.to_s } - end + include_context 'runners resolver setup' - context 'to instance runners' do - let(:runner_type) { :instance_type } + # First, we can do a couple of basic real tests to verify common cases. That ensures that the code works. + context 'when user cannot see runners' do + let(:user) { build(:user) } - it 'returns the instance runner' do - is_expected.to contain_exactly(instance_runner) - end - end - - context 'to group runners' do - let(:runner_type) { :group_type } - - it 'returns the group runner' do - is_expected.to contain_exactly(group_runner) - end - end - - context 'to project runners' do - let(:runner_type) { :project_type } - - it 'returns the project runner' do - is_expected.to contain_exactly(inactive_project_runner, offline_project_runner) - end + it 'returns no runners' do + expect(subject.items.to_a).to eq([]) end end - context 'when status is filtered' do - let(:args) do - { status: runner_status.to_s } - end - - context 'to active runners' do - let(:runner_status) { :active } - - it 'returns the instance and group runners' do - is_expected.to contain_exactly(offline_project_runner, group_runner, instance_runner) - end - end - - context 'to offline runners' do - let(:runner_status) { :offline } + context 'when user can see runners' do + let(:obj) { nil } - it 'returns the offline project runner' do - is_expected.to contain_exactly(offline_project_runner) - end + it 'returns all the runners' do + expect(subject.items.to_a).to contain_exactly(inactive_project_runner, offline_project_runner, group_runner, subgroup_runner, instance_runner) end end - context 'when tag list is filtered' do - let(:args) do - { tag_list: tag_list } - end - - context 'with "project_runner" tag' do - let(:tag_list) { ['project_runner'] } + # Then, we can check specific edge cases for this resolver + context 'with obj not set to nil' do + let(:obj) { build(:project) } - it 'returns the project_runner runners' do - is_expected.to contain_exactly(offline_project_runner, inactive_project_runner) - end - end - - context 'with "project_runner" and "active_runner" tags as comma-separated string' do - let(:tag_list) { ['project_runner,active_runner'] } - - it 'returns the offline_project_runner runner' do - is_expected.to contain_exactly(offline_project_runner) - end - end - - context 'with "active_runner" and "instance_runner" tags as array' do - let(:tag_list) { %w[instance_runner active_runner] } - - it 'returns the offline_project_runner runner' do - is_expected.to contain_exactly(instance_runner) - end + it 'raises an error' do + expect { subject }.to raise_error(a_string_including('Unexpected parent type')) end end - context 'when text is filtered' do + # Here we have a mocked part. We assume that all possible edge cases are covered in RunnersFinder spec. So we don't need to test them twice. + # Only thing we can do is to verify that args from the resolver is correctly transformed to params of the Finder and we return the Finder's result back. + describe 'Allowed query arguments' do + let(:finder) { instance_double(::Ci::RunnersFinder) } let(:args) do - { search: search_term } - end - - context 'to "project"' do - let(:search_term) { 'project' } - - it 'returns both project runners' do - is_expected.to contain_exactly(inactive_project_runner, offline_project_runner) - end - end - - context 'to "group"' do - let(:search_term) { 'group' } - - it 'returns group runner' do - is_expected.to contain_exactly(group_runner) - end - end - - context 'to "defghi"' do - let(:search_term) { 'defghi' } - - it 'returns runners containing term in token' do - is_expected.to contain_exactly(offline_project_runner) - end + { + status: 'active', + type: :instance_type, + tag_list: ['active_runner'], + search: 'abc', + sort: :contacted_asc + } + end + + let(:expected_params) do + { + status_status: 'active', + type_type: :instance_type, + tag_name: ['active_runner'], + preload: { tag_name: nil }, + search: 'abc', + sort: 'contacted_asc' + } + end + + it 'calls RunnersFinder with expected arguments' do + allow(::Ci::RunnersFinder).to receive(:new).with(current_user: user, params: expected_params).once.and_return(finder) + allow(finder).to receive(:execute).once.and_return([:execute_return_value]) + + expect(subject.items.to_a).to eq([:execute_return_value]) end end end diff --git a/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb b/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb index 6f6855c8f84..865e892b12d 100644 --- a/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb +++ b/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb @@ -27,7 +27,7 @@ RSpec.describe ResolvesPipelines do project.add_developer(current_user) end - it { is_expected.to have_graphql_arguments(:status, :ref, :sha) } + it { is_expected.to have_graphql_arguments(:status, :ref, :sha, :source) } it 'finds all pipelines' do expect(resolve_pipelines).to contain_exactly(pipeline, failed_pipeline, ref_pipeline, sha_pipeline) @@ -45,6 +45,30 @@ RSpec.describe ResolvesPipelines do expect(resolve_pipelines(sha: 'deadbeef')).to contain_exactly(sha_pipeline) end + context 'filtering by source' do + let_it_be(:source_pipeline) { create(:ci_pipeline, project: project, source: 'web') } + + context 'when `dast_view_scans` feature flag is disabled' do + before do + stub_feature_flags(dast_view_scans: false) + end + + it 'does not filter by source' do + expect(resolve_pipelines(source: 'web')).to contain_exactly(pipeline, failed_pipeline, ref_pipeline, sha_pipeline, source_pipeline) + end + end + + context 'when `dast_view_scans` feature flag is enabled' do + it 'does filter by source' do + expect(resolve_pipelines(source: 'web')).to contain_exactly(source_pipeline) + end + + it 'returns all the pipelines' do + expect(resolve_pipelines).to contain_exactly(pipeline, failed_pipeline, ref_pipeline, sha_pipeline, source_pipeline) + end + end + end + it 'does not return any pipelines if the user does not have access' do expect(resolve_pipelines({}, {})).to be_empty end diff --git a/spec/graphql/resolvers/group_resolver_spec.rb b/spec/graphql/resolvers/group_resolver_spec.rb index a03e7854177..ed406d14772 100644 --- a/spec/graphql/resolvers/group_resolver_spec.rb +++ b/spec/graphql/resolvers/group_resolver_spec.rb @@ -20,10 +20,15 @@ RSpec.describe Resolvers::GroupResolver do end it 'resolves an unknown full_path to nil' do - result = batch_sync { resolve_group('unknown/project') } + result = batch_sync { resolve_group('unknown/group') } expect(result).to be_nil end + + it 'treats group full path as case insensitive' do + result = batch_sync { resolve_group(group1.full_path.upcase) } + expect(result).to eq group1 + end end def resolve_group(full_path) diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index 6e187e57729..e992b2b04ae 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Resolvers::IssuesResolver do include GraphqlHelpers let_it_be(:current_user) { create(:user) } + let_it_be(:reporter) { create(:user) } let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, group: group) } @@ -19,6 +20,7 @@ RSpec.describe Resolvers::IssuesResolver do let_it_be(:issue4) { create(:issue) } let_it_be(:label1) { create(:label, project: project) } let_it_be(:label2) { create(:label, project: project) } + let_it_be(:upvote_award) { create(:award_emoji, :upvote, user: current_user, awardable: issue1) } specify do expect(described_class).to have_nullable_graphql_type(Types::IssueType.connection_type) @@ -27,6 +29,7 @@ RSpec.describe Resolvers::IssuesResolver do context "with a project" do before_all do project.add_developer(current_user) + project.add_reporter(reporter) create(:label_link, label: label1, target: issue1) create(:label_link, label: label1, target: issue2) create(:label_link, label: label2, target: issue2) @@ -198,6 +201,27 @@ RSpec.describe Resolvers::IssuesResolver do end end + context 'filtering by reaction emoji' do + let_it_be(:downvoted_issue) { create(:issue, project: project) } + let_it_be(:downvote_award) { create(:award_emoji, :downvote, user: current_user, awardable: downvoted_issue) } + + it 'filters by reaction emoji' do + expect(resolve_issues(my_reaction_emoji: upvote_award.name)).to contain_exactly(issue1) + end + + it 'filters by reaction emoji wildcard "none"' do + expect(resolve_issues(my_reaction_emoji: 'none')).to contain_exactly(issue2) + end + + it 'filters by reaction emoji wildcard "any"' do + expect(resolve_issues(my_reaction_emoji: 'any')).to contain_exactly(issue1, downvoted_issue) + end + + it 'filters by negated reaction emoji' do + expect(resolve_issues(not: { my_reaction_emoji: downvote_award.name })).to contain_exactly(issue1, issue2) + end + end + context 'when searching issues' do it 'returns correct issues' do expect(resolve_issues(search: 'foo')).to contain_exactly(issue2) @@ -235,6 +259,14 @@ RSpec.describe Resolvers::IssuesResolver do it 'returns issues without the specified assignee_id' do expect(resolve_issues(not: { assignee_id: [assignee.id] })).to contain_exactly(issue1) end + + context 'when filtering by negated author' do + let_it_be(:issue_by_reporter) { create(:issue, author: reporter, project: project, state: :opened) } + + it 'returns issues without the specified author_username' do + expect(resolve_issues(not: { author_username: issue1.author.username })).to contain_exactly(issue_by_reporter) + end + end end describe 'sorting' do @@ -382,6 +414,22 @@ RSpec.describe Resolvers::IssuesResolver do end end end + + context 'when sorting by title' do + let_it_be(:project) { create(:project, :public) } + let_it_be(:issue1) { create(:issue, project: project, title: 'foo') } + let_it_be(:issue2) { create(:issue, project: project, title: 'bar') } + let_it_be(:issue3) { create(:issue, project: project, title: 'baz') } + let_it_be(:issue4) { create(:issue, project: project, title: 'Baz 2') } + + it 'sorts issues ascending' do + expect(resolve_issues(sort: :title_asc).to_a).to eq [issue2, issue3, issue4, issue1] + end + + it 'sorts issues descending' do + expect(resolve_issues(sort: :title_desc).to_a).to eq [issue1, issue4, issue3, issue2] + end + end end it 'returns issues user can see' do diff --git a/spec/graphql/resolvers/merge_requests_resolver_spec.rb b/spec/graphql/resolvers/merge_requests_resolver_spec.rb index 64ee0d4f9cc..a897acf7eba 100644 --- a/spec/graphql/resolvers/merge_requests_resolver_spec.rb +++ b/spec/graphql/resolvers/merge_requests_resolver_spec.rb @@ -294,16 +294,6 @@ RSpec.describe Resolvers::MergeRequestsResolver do nils_last(mr.metrics.merged_at) end - context 'when label filter is given and the optimized_issuable_label_filter feature flag is off' do - before do - stub_feature_flags(optimized_issuable_label_filter: false) - end - - it 'does not raise PG::GroupingError' do - expect { resolve_mr(project, sort: :merged_at_desc, labels: %w[a b]) }.not_to raise_error - end - end - context 'when sorting by closed at' do before do merge_request_1.metrics.update!(latest_closed_at: 10.days.ago) diff --git a/spec/graphql/resolvers/project_resolver_spec.rb b/spec/graphql/resolvers/project_resolver_spec.rb index d0661c27b95..cd3fdc788e6 100644 --- a/spec/graphql/resolvers/project_resolver_spec.rb +++ b/spec/graphql/resolvers/project_resolver_spec.rb @@ -25,6 +25,11 @@ RSpec.describe Resolvers::ProjectResolver do expect(result).to be_nil end + + it 'treats project full path as case insensitive' do + result = batch_sync { resolve_project(project1.full_path.upcase) } + expect(result).to eq project1 + end end it 'does not increase complexity depending on number of load limits' do diff --git a/spec/graphql/resolvers/users/groups_resolver_spec.rb b/spec/graphql/resolvers/users/groups_resolver_spec.rb new file mode 100644 index 00000000000..0fdb6da5ae9 --- /dev/null +++ b/spec/graphql/resolvers/users/groups_resolver_spec.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Users::GroupsResolver do + include GraphqlHelpers + include AdminModeHelper + + describe '#resolve' do + let_it_be(:user) { create(:user) } + let_it_be(:guest_group) { create(:group, name: 'public guest', path: 'public-guest') } + let_it_be(:private_maintainer_group) { create(:group, :private, name: 'b private maintainer', path: 'b-private-maintainer') } + let_it_be(:public_developer_group) { create(:group, project_creation_level: nil, name: 'c public developer', path: 'c-public-developer') } + let_it_be(:public_maintainer_group) { create(:group, name: 'a public maintainer', path: 'a-public-maintainer') } + + subject(:resolved_items) { resolve_groups(args: group_arguments, current_user: current_user, obj: resolver_object) } + + let(:group_arguments) { {} } + let(:current_user) { user } + let(:resolver_object) { user } + + before_all do + guest_group.add_guest(user) + private_maintainer_group.add_maintainer(user) + public_developer_group.add_developer(user) + public_maintainer_group.add_maintainer(user) + end + + context 'when paginatable_namespace_drop_down_for_project_creation feature flag is disabled' do + before do + stub_feature_flags(paginatable_namespace_drop_down_for_project_creation: false) + end + + it { is_expected.to be_nil } + end + + context 'when resolver object is current user' do + context 'when permission is :create_projects' do + let(:group_arguments) { { permission_scope: :create_projects } } + + specify do + is_expected.to match( + [ + public_maintainer_group, + private_maintainer_group, + public_developer_group + ] + ) + end + end + + specify do + is_expected.to match( + [ + public_maintainer_group, + private_maintainer_group, + public_developer_group, + guest_group + ] + ) + end + + context 'when search is provided' do + let(:group_arguments) { { search: 'maintainer' } } + + specify do + is_expected.to match( + [ + public_maintainer_group, + private_maintainer_group + ] + ) + end + end + end + + context 'when resolver object is different from current user' do + let(:current_user) { create(:user) } + + it { is_expected.to be_nil } + + context 'when current_user is admin' do + let(:current_user) { create(:user, :admin) } + + before do + enable_admin_mode!(current_user) + end + + specify do + is_expected.to match( + [ + public_maintainer_group, + private_maintainer_group, + public_developer_group, + guest_group + ] + ) + end + end + end + end + + def resolve_groups(args:, current_user:, obj:) + resolve(described_class, args: args, ctx: { current_user: current_user }, obj: obj)&.items + end +end |