diff options
Diffstat (limited to 'spec/graphql/resolvers')
29 files changed, 497 insertions, 117 deletions
diff --git a/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb b/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb index 14ebe85d80e..618735837e8 100644 --- a/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb +++ b/spec/graphql/resolvers/alert_management/alert_resolver_spec.rb @@ -68,6 +68,6 @@ RSpec.describe Resolvers::AlertManagement::AlertResolver do private def resolve_alerts(args = {}, context = { current_user: current_user }) - resolve(described_class, obj: project, args: args, ctx: context) + resolve(described_class, obj: project, args: args, ctx: context, arg_style: :internal) 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 392385d2a30..ab1f19abaad 100644 --- a/spec/graphql/resolvers/board_list_issues_resolver_spec.rb +++ b/spec/graphql/resolvers/board_list_issues_resolver_spec.rb @@ -128,6 +128,6 @@ RSpec.describe Resolvers::BoardListIssuesResolver do 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 }, arg_style: :internal) end end diff --git a/spec/graphql/resolvers/board_lists_resolver_spec.rb b/spec/graphql/resolvers/board_lists_resolver_spec.rb index 7a1d8590546..c882ad7c818 100644 --- a/spec/graphql/resolvers/board_lists_resolver_spec.rb +++ b/spec/graphql/resolvers/board_lists_resolver_spec.rb @@ -100,6 +100,8 @@ RSpec.describe Resolvers::BoardListsResolver do end def resolve_board_lists(args: {}, current_user: user) - resolve(described_class, obj: board, args: args, ctx: { current_user: current_user }) + resolve(described_class, obj: board, args: args, ctx: { current_user: current_user }, + arg_style: :internal + ) end end diff --git a/spec/graphql/resolvers/ci/group_runners_resolver_spec.rb b/spec/graphql/resolvers/ci/group_runners_resolver_spec.rb index 89a2437a189..f99f48f5b07 100644 --- a/spec/graphql/resolvers/ci/group_runners_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/group_runners_resolver_spec.rb @@ -6,7 +6,10 @@ RSpec.describe Resolvers::Ci::GroupRunnersResolver do include GraphqlHelpers describe '#resolve' do - subject { resolve(described_class, obj: obj, ctx: { current_user: user }, args: args) } + subject do + resolve(described_class, obj: obj, ctx: { current_user: user }, args: args, + arg_style: :internal) + end include_context 'runners resolver setup' diff --git a/spec/graphql/resolvers/ci/jobs_resolver_spec.rb b/spec/graphql/resolvers/ci/jobs_resolver_spec.rb index 1b69bf7f63a..6c228861ddf 100644 --- a/spec/graphql/resolvers/ci/jobs_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/jobs_resolver_spec.rb @@ -19,7 +19,7 @@ RSpec.describe Resolvers::Ci::JobsResolver do describe '#resolve' do context 'when security_report_types is empty' do it "returns all of the pipeline's jobs" do - jobs = resolve(described_class, obj: pipeline) + jobs = resolve(described_class, obj: pipeline, arg_style: :internal) expect(jobs).to contain_exactly( have_attributes(name: 'Normal job'), @@ -37,7 +37,8 @@ RSpec.describe Resolvers::Ci::JobsResolver do ::Types::Security::ReportTypeEnum.values['SAST'].value, ::Types::Security::ReportTypeEnum.values['DAST'].value ] - jobs = resolve(described_class, obj: pipeline, args: { security_report_types: report_types }) + jobs = resolve(described_class, obj: pipeline, args: { security_report_types: report_types }, + arg_style: :internal) expect(jobs).to contain_exactly( have_attributes(name: 'DAST job'), @@ -48,7 +49,7 @@ RSpec.describe Resolvers::Ci::JobsResolver do context 'when a job has tags' do it "returns jobs with tags when applicable" do - jobs = resolve(described_class, obj: pipeline) + jobs = resolve(described_class, obj: pipeline, arg_style: :internal) expect(jobs).to contain_exactly( have_attributes(tag_list: []), have_attributes(tag_list: []), diff --git a/spec/graphql/resolvers/ci/runners_resolver_spec.rb b/spec/graphql/resolvers/ci/runners_resolver_spec.rb index 9251fbf24d9..b1f5f7b3e43 100644 --- a/spec/graphql/resolvers/ci/runners_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/runners_resolver_spec.rb @@ -9,7 +9,10 @@ RSpec.describe Resolvers::Ci::RunnersResolver do let(:obj) { nil } let(:args) { {} } - subject { resolve(described_class, obj: obj, ctx: { current_user: user }, args: args) } + subject do + resolve(described_class, obj: obj, ctx: { current_user: user }, args: args, + arg_style: :internal) + end include_context 'runners resolver setup' diff --git a/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb b/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb index 852aaf66201..892ab53a53e 100644 --- a/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb +++ b/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb @@ -79,7 +79,7 @@ RSpec.describe ::CachingArrayResolver do expect(User).to receive(:from_union).twice.and_call_original results = users.in_groups_of(2, false).map do |users| - resolve(resolver, args: { username: users.map(&:username) }, schema: schema) + resolve(resolver, args: { username: users.map(&:username) }, schema: schema, arg_style: :internal) end expect(results.flat_map(&method(:force))).to match_array(users) @@ -208,6 +208,6 @@ RSpec.describe ::CachingArrayResolver do args = { is_admin: admin } opts = resolver.field_options allow(resolver).to receive(:field_options).and_return(opts.merge(max_page_size: max_page_size)) - resolve(resolver, args: args, ctx: query_context, schema: schema) + resolve(resolver, args: args, ctx: query_context, schema: schema, arg_style: :internal) end end diff --git a/spec/graphql/resolvers/concerns/resolves_groups_spec.rb b/spec/graphql/resolvers/concerns/resolves_groups_spec.rb index bfbbae29e92..d15c8f2ee42 100644 --- a/spec/graphql/resolvers/concerns/resolves_groups_spec.rb +++ b/spec/graphql/resolvers/concerns/resolves_groups_spec.rb @@ -27,11 +27,9 @@ RSpec.describe ResolvesGroups do let_it_be(:lookahead_fields) do <<~FIELDS - contacts { nodes { id } } containerRepositoriesCount customEmoji { nodes { id } } fullPath - organizations { nodes { id } } path dependencyProxyBlobCount dependencyProxyBlobs { nodes { fileName } } diff --git a/spec/graphql/resolvers/container_repositories_resolver_spec.rb b/spec/graphql/resolvers/container_repositories_resolver_spec.rb index a17d2a7b0d5..d7aa761320f 100644 --- a/spec/graphql/resolvers/container_repositories_resolver_spec.rb +++ b/spec/graphql/resolvers/container_repositories_resolver_spec.rb @@ -15,7 +15,10 @@ RSpec.describe Resolvers::ContainerRepositoriesResolver do describe '#resolve' do let(:object) { project } - subject { resolve(described_class, ctx: { current_user: user }, args: args, obj: object) } + subject do + resolve(described_class, ctx: { current_user: user }, args: args, obj: object, + arg_style: :internal) + end shared_examples 'returning container repositories' do it { is_expected.to contain_exactly(container_repositories) } diff --git a/spec/graphql/resolvers/container_repository_tags_resolver_spec.rb b/spec/graphql/resolvers/container_repository_tags_resolver_spec.rb index 4e7ea253c87..9747f663759 100644 --- a/spec/graphql/resolvers/container_repository_tags_resolver_spec.rb +++ b/spec/graphql/resolvers/container_repository_tags_resolver_spec.rb @@ -12,7 +12,10 @@ RSpec.describe Resolvers::ContainerRepositoryTagsResolver do let(:args) { { sort: nil } } describe '#resolve' do - let(:resolver) { resolve(described_class, ctx: { current_user: user }, obj: repository, args: args) } + let(:resolver) do + resolve(described_class, ctx: { current_user: user }, obj: repository, args: args, + arg_style: :internal) + end before do stub_container_registry_config(enabled: true) diff --git a/spec/graphql/resolvers/crm/contacts_resolver_spec.rb b/spec/graphql/resolvers/crm/contacts_resolver_spec.rb new file mode 100644 index 00000000000..98da4aeac28 --- /dev/null +++ b/spec/graphql/resolvers/crm/contacts_resolver_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Crm::ContactsResolver do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group, :crm_enabled) } + + let_it_be(:contact_a) do + create( + :contact, + group: group, + first_name: "ABC", + last_name: "DEF", + email: "ghi@test.com", + description: "LMNO", + state: "inactive" + ) + end + + let_it_be(:contact_b) do + create( + :contact, + group: group, + first_name: "PQR", + last_name: "STU", + email: "vwx@test.com", + description: "YZ", + state: "active" + ) + end + + describe '#resolve' do + context 'with unauthorized user' do + it 'does not rise an error and returns no contacts' do + expect { resolve_contacts(group) }.not_to raise_error + expect(resolve_contacts(group)).to be_empty + end + end + + context 'with authorized user' do + it 'does not rise an error and returns all contacts in the correct order' do + group.add_reporter(user) + + expect { resolve_contacts(group) }.not_to raise_error + expect(resolve_contacts(group)).to eq([contact_a, contact_b]) + end + end + + context 'without parent' do + it 'returns no contacts' do + expect(resolve_contacts(nil)).to be_empty + end + end + + context 'with a group parent' do + before do + group.add_developer(user) + end + + context 'when no filter is provided' do + it 'returns all the contacts in the correct order' do + expect(resolve_contacts(group)).to eq([contact_a, contact_b]) + end + end + + context 'when search term is provided' do + it 'returns the correct contacts' do + expect(resolve_contacts(group, { search: "x@test.com" })).to contain_exactly(contact_b) + end + end + + context 'when state is provided' do + it 'returns the correct contacts' do + expect(resolve_contacts(group, { state: :inactive })).to contain_exactly(contact_a) + end + end + + context 'when ids are provided' do + it 'returns the correct contacts' do + expect(resolve_contacts(group, { ids: [contact_a.to_global_id] })).to contain_exactly(contact_a) + end + end + end + end + + def resolve_contacts(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 new file mode 100644 index 00000000000..323f134ffc3 --- /dev/null +++ b/spec/graphql/resolvers/crm/organizations_resolver_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Crm::OrganizationsResolver do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group, :crm_enabled) } + + let_it_be(:organization_a) do + create( + :organization, + group: group, + name: "ABC", + state: "inactive" + ) + end + + let_it_be(:organization_b) do + create( + :organization, + group: group, + name: "DEF", + state: "active" + ) + end + + describe '#resolve' do + context 'with unauthorized user' do + it 'does not rise an error and returns no organizations' do + expect { resolve_organizations(group) }.not_to raise_error + expect(resolve_organizations(group)).to be_empty + end + end + + context 'with authorized user' do + it 'does not rise an error and returns all organizations in the correct order' do + group.add_reporter(user) + + expect { resolve_organizations(group) }.not_to raise_error + expect(resolve_organizations(group)).to eq([organization_a, organization_b]) + end + end + + context 'without parent' do + it 'returns no organizations' do + expect(resolve_organizations(nil)).to be_empty + end + end + + context 'with a group parent' do + before do + group.add_developer(user) + end + + context 'when no filter is provided' do + it 'returns all the organizations in the correct order' do + expect(resolve_organizations(group)).to eq([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) + end + end + + context 'when state is provided' do + it 'returns the correct organizations' do + expect(resolve_organizations(group, { state: :inactive })).to contain_exactly(organization_a) + end + end + + context 'when ids are provided' do + it 'returns the correct organizations' do + expect(resolve_organizations(group, { + ids: [organization_b.to_global_id] + })).to contain_exactly(organization_b) + end + end + end + end + + def resolve_organizations(parent, args = {}, context = { current_user: user }) + resolve(described_class, obj: parent, 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 8eab0222cf6..3a2ed445484 100644 --- a/spec/graphql/resolvers/design_management/versions_resolver_spec.rb +++ b/spec/graphql/resolvers/design_management/versions_resolver_spec.rb @@ -90,6 +90,8 @@ RSpec.describe Resolvers::DesignManagement::VersionsResolver do end context 'and they do not match' do + subject(:result) { resolve_versions(object) } + let(:params) do { earlier_or_equal_to_sha: first_version.sha, @@ -104,14 +106,6 @@ RSpec.describe Resolvers::DesignManagement::VersionsResolver do end end end - - context 'by at_version in parent' do - before do - query_context[:at_version_argument] = first_version.to_global_id - end - - it_behaves_like 'a query for all_versions up to the first_version' - end end end diff --git a/spec/graphql/resolvers/group_labels_resolver_spec.rb b/spec/graphql/resolvers/group_labels_resolver_spec.rb index 2031e534703..71290885e6b 100644 --- a/spec/graphql/resolvers/group_labels_resolver_spec.rb +++ b/spec/graphql/resolvers/group_labels_resolver_spec.rb @@ -38,7 +38,7 @@ RSpec.describe Resolvers::GroupLabelsResolver do it 'does not raise error' do group.add_guest(current_user) - expect { resolve_labels(subgroup) }.not_to raise_error + expect(resolve_labels(subgroup)).to be_instance_of(Gitlab::Graphql::Pagination::Keyset::Connection) end end diff --git a/spec/graphql/resolvers/group_milestones_resolver_spec.rb b/spec/graphql/resolvers/group_milestones_resolver_spec.rb index 7abc779a63c..a32a031a88f 100644 --- a/spec/graphql/resolvers/group_milestones_resolver_spec.rb +++ b/spec/graphql/resolvers/group_milestones_resolver_spec.rb @@ -10,7 +10,7 @@ RSpec.describe Resolvers::GroupMilestonesResolver do let_it_be(:current_user) { create(:user) } def resolve_group_milestones(args = {}, context = { current_user: current_user }) - resolve(described_class, obj: group, args: args, ctx: context) + resolve(described_class, obj: group, args: args, ctx: context, arg_style: :internal) end let_it_be(:now) { Time.now } @@ -126,16 +126,6 @@ RSpec.describe Resolvers::GroupMilestonesResolver do end end - context 'when user cannot read milestones' do - it 'generates an error' do - unauthorized_user = create(:user) - - expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable) do - resolve_group_milestones({}, { current_user: unauthorized_user }) - end - end - end - context 'when including descendant milestones in a public group' do let_it_be(:group) { create(:group, :public) } diff --git a/spec/graphql/resolvers/group_packages_resolver_spec.rb b/spec/graphql/resolvers/group_packages_resolver_spec.rb index eba3a5f2de8..c600f9c9f9a 100644 --- a/spec/graphql/resolvers/group_packages_resolver_spec.rb +++ b/spec/graphql/resolvers/group_packages_resolver_spec.rb @@ -2,15 +2,16 @@ require 'spec_helper' -RSpec.describe Resolvers::GroupPackagesResolver do +RSpec.describe 'Resolvers::GroupPackagesResolver' do include GraphqlHelpers + let_it_be(:described_class) { Resolvers::GroupPackagesResolver } let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group, :public) } let_it_be(:project) { create(:project, :public, group: group, path: 'a') } let(:args) do - { sort: :created_desc } + { sort: 'CREATED_DESC' } end describe '#resolve' do @@ -26,13 +27,13 @@ RSpec.describe Resolvers::GroupPackagesResolver do let_it_be(:package4) { create(:package, project: project2 ) } context 'filter by package_name' do - let(:args) { { sort: :project_path_desc } } + let(:args) { { sort: 'PROJECT_PATH_DESC' } } it { is_expected.to eq([package4, package2, package3, package]) } end context 'filter by package_type' do - let(:args) { { sort: :project_path_asc } } + let(:args) { { sort: 'PROJECT_PATH_ASC' } } it { is_expected.to eq([package, package3, package2, package4]) } end diff --git a/spec/graphql/resolvers/incident_management/timeline_events_resolver_spec.rb b/spec/graphql/resolvers/incident_management/timeline_events_resolver_spec.rb index 046cf242d56..6604141abfe 100644 --- a/spec/graphql/resolvers/incident_management/timeline_events_resolver_spec.rb +++ b/spec/graphql/resolvers/incident_management/timeline_events_resolver_spec.rb @@ -2,9 +2,10 @@ require 'spec_helper' -RSpec.describe Resolvers::IncidentManagement::TimelineEventsResolver do +RSpec.describe 'Resolvers::IncidentManagement::TimelineEventsResolver' do include GraphqlHelpers + let_it_be(:described_class) { Resolvers::IncidentManagement::TimelineEventsResolver } let_it_be(:current_user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:incident) { create(:incident, project: project) } diff --git a/spec/graphql/resolvers/issue_status_counts_resolver_spec.rb b/spec/graphql/resolvers/issue_status_counts_resolver_spec.rb index 77f4ce4cac5..86a4154f23b 100644 --- a/spec/graphql/resolvers/issue_status_counts_resolver_spec.rb +++ b/spec/graphql/resolvers/issue_status_counts_resolver_spec.rb @@ -70,7 +70,7 @@ RSpec.describe Resolvers::IssueStatusCountsResolver do end context 'when both assignee_username and assignee_usernames are provided' do - it 'generates a mutually exclusive filter error' do + it 'returns a mutually exclusive filter error' do expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, 'only one of [assigneeUsernames, assigneeUsername] arguments is allowed at the same time.') do resolve_issue_status_counts(assignee_usernames: [current_user.username], assignee_username: current_user.username) end diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index e6ec9d8c895..a5b5a8e4f72 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -428,6 +428,22 @@ RSpec.describe Resolvers::IssuesResolver do end end + context 'when sorting by closed at' do + let_it_be(:project) { create(:project, :public) } + let_it_be(:closed_issue1) { create(:issue, project: project, closed_at: 3.days.from_now) } + let_it_be(:closed_issue2) { create(:issue, project: project, closed_at: nil) } + let_it_be(:closed_issue3) { create(:issue, project: project, closed_at: 2.days.ago) } + let_it_be(:closed_issue4) { create(:issue, project: project, closed_at: nil) } + + it 'sorts issues ascending' do + expect(resolve_issues(sort: :closed_at_asc).to_a).to eq [closed_issue3, closed_issue1, closed_issue4, closed_issue2] + end + + it 'sorts issues descending' do + expect(resolve_issues(sort: :closed_at_desc).to_a).to eq [closed_issue1, closed_issue3, closed_issue4, closed_issue2] + end + end + context 'when sorting by due date' do let_it_be(:project) { create(:project, :public) } let_it_be(:due_issue1) { create(:issue, project: project, due_date: 3.days.from_now) } @@ -573,22 +589,6 @@ RSpec.describe Resolvers::IssuesResolver do issues = resolve_issues(sort: :created_desc).to_a expect(issues).to eq([resolved_incident, issue_no_status, triggered_incident]) end - - context 'when incident_escalations feature flag is disabled' do - before do - stub_feature_flags(incident_escalations: false) - end - - it 'defaults ascending status sort to created_desc' do - issues = resolve_issues(sort: :escalation_status_asc).to_a - expect(issues).to eq([resolved_incident, issue_no_status, triggered_incident]) - end - - it 'defaults descending status sort to created_desc' do - issues = resolve_issues(sort: :escalation_status_desc).to_a - expect(issues).to eq([resolved_incident, issue_no_status, triggered_incident]) - end - end end context 'when sorting with non-stable cursors' do @@ -701,6 +701,6 @@ RSpec.describe Resolvers::IssuesResolver do end def resolve_issues(args = {}, context = { current_user: current_user }) - resolve(described_class, obj: project, args: args, ctx: context) + resolve(described_class, obj: project, args: args, ctx: context, arg_style: :internal) end end diff --git a/spec/graphql/resolvers/merge_requests_resolver_spec.rb b/spec/graphql/resolvers/merge_requests_resolver_spec.rb index e4eaeb9bc3c..ab3d7edc6bd 100644 --- a/spec/graphql/resolvers/merge_requests_resolver_spec.rb +++ b/spec/graphql/resolvers/merge_requests_resolver_spec.rb @@ -174,7 +174,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do context 'with draft argument' do before do - merge_request_4.update!(title: MergeRequest.wip_title(merge_request_4.title)) + merge_request_4.update!(title: MergeRequest.draft_title(merge_request_4.title)) end context 'with draft: true argument' do @@ -411,6 +411,6 @@ RSpec.describe Resolvers::MergeRequestsResolver do end def resolve_mr(project, resolver: described_class, user: current_user, **args) - resolve(resolver, obj: project, args: args, ctx: { current_user: user }) + resolve(resolver, obj: project, args: args, ctx: { current_user: user }, arg_style: :internal) end end diff --git a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb index eb4d0ab6f37..78dd5173449 100644 --- a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb +++ b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb @@ -152,6 +152,6 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do end def resolve_projects(args = { include_subgroups: false, sort: nil, search: nil, ids: nil }, context = { current_user: current_user }) - resolve(described_class, obj: namespace, args: args, ctx: context) + resolve(described_class, obj: namespace, args: args, ctx: context, arg_style: :internal) end end diff --git a/spec/graphql/resolvers/packages_base_resolver_spec.rb b/spec/graphql/resolvers/packages_base_resolver_spec.rb index 8f9865c3785..7766fdd4994 100644 --- a/spec/graphql/resolvers/packages_base_resolver_spec.rb +++ b/spec/graphql/resolvers/packages_base_resolver_spec.rb @@ -6,7 +6,7 @@ RSpec.describe Resolvers::PackagesBaseResolver do include GraphqlHelpers describe '#resolve' do - subject { resolve(described_class) } + subject { resolve(described_class, arg_style: :internal) } it 'throws an error' do expect { subject }.to raise_error(NotImplementedError) diff --git a/spec/graphql/resolvers/project_milestones_resolver_spec.rb b/spec/graphql/resolvers/project_milestones_resolver_spec.rb index 2cf490c2b6a..ad1190e3df7 100644 --- a/spec/graphql/resolvers/project_milestones_resolver_spec.rb +++ b/spec/graphql/resolvers/project_milestones_resolver_spec.rb @@ -2,12 +2,15 @@ require 'spec_helper' -RSpec.describe Resolvers::ProjectMilestonesResolver do +RSpec.describe 'Resolvers::ProjectMilestonesResolver' do include GraphqlHelpers describe '#resolve' do + let_it_be(:described_class) { Resolvers::ProjectMilestonesResolver } let_it_be(:project) { create(:project, :private) } let_it_be(:current_user) { create(:user) } + let_it_be(:now) { Time.now } + let_it_be(:now_date) { now.to_date } before_all do project.add_developer(current_user) @@ -25,7 +28,7 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do it 'calls MilestonesFinder to retrieve all milestones' do expect(MilestonesFinder).to receive(:new) - .with(args(project_ids: project.id, state: 'all')) + .with(args(project_ids: project.id, state: 'all', sort: :due_date_asc)) .and_call_original resolve_project_milestones @@ -42,7 +45,8 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do it 'calls MilestonesFinder with correct parameters' do expect(MilestonesFinder).to receive(:new) - .with(args(project_ids: project.id, group_ids: contain_exactly(group, parent_group), state: 'all')) + .with(args(project_ids: project.id, group_ids: contain_exactly(group, parent_group), + state: 'all', sort: :due_date_asc)) .and_call_original resolve_project_milestones(include_ancestors: true) @@ -54,7 +58,7 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do milestone = create(:milestone, project: project) expect(MilestonesFinder).to receive(:new) - .with(args(ids: [milestone.id.to_s], project_ids: project.id, state: 'all')) + .with(args(ids: [milestone.id.to_s], project_ids: project.id, state: 'all', sort: :due_date_asc)) .and_call_original resolve_project_milestones(ids: [milestone.to_global_id]) @@ -64,7 +68,7 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do context 'by state' do it 'calls MilestonesFinder with correct parameters' do expect(MilestonesFinder).to receive(:new) - .with(args(project_ids: project.id, state: 'closed')) + .with(args(project_ids: project.id, state: 'closed', sort: :due_date_asc)) .and_call_original resolve_project_milestones(state: 'closed') @@ -74,13 +78,13 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do context 'by sort' do it 'calls MilestonesFinder with correct parameters' do expect(MilestonesFinder).to receive(:new) - .with(args(project_ids: project.id, state: 'all', sort: :due_date_desc)) + .with(args(project_ids: project.id, state: 'all', sort: :due_date_asc)) .and_call_original - resolve_project_milestones(sort: :due_date_desc) + resolve_project_milestones(sort: 'DUE_DATE_ASC') end - %i[expired_last_due_date_asc expired_last_due_date_desc].each do |sort_by| + %w[EXPIRED_LAST_DUE_DATE_ASC EXPIRED_LAST_DUE_DATE_DESC].each do |sort_by| it "uses offset-pagination when sorting by #{sort_by}" do resolved = resolve_project_milestones(sort: sort_by) @@ -92,11 +96,12 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do context 'by timeframe' do context 'when start_date and end_date are present' do it 'calls MilestonesFinder with correct parameters' do - start_date = Time.now - end_date = Time.now + 5.days + start_date = now + end_date = now + 5.days expect(MilestonesFinder).to receive(:new) - .with(args(project_ids: project.id, state: 'all', start_date: start_date, end_date: end_date)) + .with(args(project_ids: project.id, state: 'all', + start_date: start_date, end_date: end_date, sort: :due_date_asc)) .and_call_original resolve_project_milestones(start_date: start_date, end_date: end_date) @@ -105,7 +110,7 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do context 'when start date is after end_date' do it 'generates an error' do expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, 'startDate is after endDate') do - resolve_project_milestones(start_date: Time.now, end_date: Time.now - 2.days) + resolve_project_milestones(start_date: now, end_date: now - 2.days) end end end @@ -114,7 +119,7 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do context 'when only start_date is present' do it 'generates an error' do expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, /Both startDate and endDate/) do - resolve_project_milestones(start_date: Time.now) + resolve_project_milestones(start_date: now) end end end @@ -122,18 +127,19 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do context 'when only end_date is present' do it 'generates an error' do expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError, /Both startDate and endDate/) do - resolve_project_milestones(end_date: Time.now) + resolve_project_milestones(end_date: now) end end end context 'when passing a timeframe' do it 'calls MilestonesFinder with correct parameters' do - start_date = Time.now - end_date = Time.now + 5.days + start_date = now_date + end_date = now_date + 5.days expect(MilestonesFinder).to receive(:new) - .with(args(project_ids: project.id, state: 'all', start_date: start_date, end_date: end_date)) + .with(args(project_ids: project.id, state: 'all', + sort: :due_date_asc, start_date: start_date, end_date: end_date)) .and_call_original resolve_project_milestones(timeframe: { start: start_date, end: end_date }) @@ -144,7 +150,7 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do context 'when title is present' do it 'calls MilestonesFinder with correct parameters' do expect(MilestonesFinder).to receive(:new) - .with(args(title: '13.5', state: 'all', project_ids: project.id)) + .with(args(title: '13.5', state: 'all', sort: :due_date_asc, project_ids: project.id)) .and_call_original resolve_project_milestones(title: '13.5') @@ -154,7 +160,7 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do context 'when search_title is present' do it 'calls MilestonesFinder with correct parameters' do expect(MilestonesFinder).to receive(:new) - .with(args(search_title: '13', state: 'all', project_ids: project.id)) + .with(args(search_title: '13', state: 'all', sort: :due_date_asc, project_ids: project.id)) .and_call_original resolve_project_milestones(search_title: '13') @@ -163,24 +169,14 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do context 'when containing date is present' do it 'calls MilestonesFinder with correct parameters' do - t = Time.now + t = now expect(MilestonesFinder).to receive(:new) - .with(args(containing_date: t, state: 'all', project_ids: project.id)) + .with(args(containing_date: t, state: 'all', sort: :due_date_asc, project_ids: project.id)) .and_call_original resolve_project_milestones(containing_date: t) end end - - context 'when user cannot read milestones' do - it 'generates an error' do - unauthorized_user = create(:user) - - expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable) do - resolve_project_milestones({}, { current_user: unauthorized_user }) - end - end - end end end diff --git a/spec/graphql/resolvers/project_packages_resolver_spec.rb b/spec/graphql/resolvers/project_packages_resolver_spec.rb index 66a94bd42dd..c3ce30f6ef9 100644 --- a/spec/graphql/resolvers/project_packages_resolver_spec.rb +++ b/spec/graphql/resolvers/project_packages_resolver_spec.rb @@ -2,18 +2,22 @@ require 'spec_helper' -RSpec.describe Resolvers::ProjectPackagesResolver do +RSpec.describe 'Resolvers::ProjectPackagesResolver' do include GraphqlHelpers + let_it_be(:described_class) { Resolvers::ProjectPackagesResolver } let_it_be(:user) { create(:user) } let_it_be_with_reload(:project) { create(:project, :public) } let(:args) do - { sort: :created_desc } + { sort: 'CREATED_DESC' } end describe '#resolve' do - subject { resolve(described_class, ctx: { current_user: user }, obj: project, args: args).to_a } + subject do + resolve(described_class, ctx: { current_user: user }, obj: project, args: args) + .to_a + end it_behaves_like 'group and projects packages resolver' end diff --git a/spec/graphql/resolvers/releases_resolver_spec.rb b/spec/graphql/resolvers/releases_resolver_spec.rb index 89623be891f..6ba9a6c33a1 100644 --- a/spec/graphql/resolvers/releases_resolver_spec.rb +++ b/spec/graphql/resolvers/releases_resolver_spec.rb @@ -87,6 +87,6 @@ RSpec.describe Resolvers::ReleasesResolver do def resolve_releases context = { current_user: current_user } - resolve(described_class, obj: project, args: args, ctx: context) + resolve(described_class, obj: project, args: args, ctx: context, arg_style: :internal) end end diff --git a/spec/graphql/resolvers/user_resolver_spec.rb b/spec/graphql/resolvers/user_resolver_spec.rb index 446d765d3ee..d57b015b705 100644 --- a/spec/graphql/resolvers/user_resolver_spec.rb +++ b/spec/graphql/resolvers/user_resolver_spec.rb @@ -6,8 +6,29 @@ RSpec.describe Resolvers::UserResolver do include GraphqlHelpers describe '#resolve' do + let_it_be(:current_user) { nil } let_it_be(:user) { create(:user) } + shared_examples 'queries user' do + context 'authenticated access' do + let_it_be(:current_user) { create(:user) } + + it 'returns the correct user' do + expect( + resolve_user(args) + ).to eq(user) + end + end + + context 'unauthenticated access' do + it 'forbids search' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable) do + resolve_user(args) + end + end + end + end + context 'when neither an ID or a username is provided' do it 'generates an ArgumentError' do expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ArgumentError) do @@ -23,25 +44,21 @@ RSpec.describe Resolvers::UserResolver do end context 'by username' do - it 'returns the correct user' do - expect( - resolve_user(username: user.username) - ).to eq(user) + include_examples "queries user" do + let(:args) { { username: user.username } } end end context 'by ID' do - it 'returns the correct user' do - expect( - resolve_user(id: user.to_global_id) - ).to eq(user) + include_examples "queries user" do + let(:args) { { id: user.to_global_id } } end end end private - def resolve_user(args = {}) - sync(resolve(described_class, args: args)) + def resolve_user(args = {}, context = { current_user: current_user }) + sync(resolve(described_class, args: args, ctx: context)) end end diff --git a/spec/graphql/resolvers/users/groups_resolver_spec.rb b/spec/graphql/resolvers/users/groups_resolver_spec.rb index 5ac7aac4898..bbe9b6371cf 100644 --- a/spec/graphql/resolvers/users/groups_resolver_spec.rb +++ b/spec/graphql/resolvers/users/groups_resolver_spec.rb @@ -93,6 +93,6 @@ RSpec.describe Resolvers::Users::GroupsResolver do end def resolve_groups(args:, current_user:, obj:) - resolve(described_class, args: args, ctx: { current_user: current_user }, obj: obj)&.items + resolve(described_class, args: args, ctx: { current_user: current_user }, obj: obj, arg_style: :internal)&.items end end diff --git a/spec/graphql/resolvers/users_resolver_spec.rb b/spec/graphql/resolvers/users_resolver_spec.rb index 1ba296912a3..dda15303676 100644 --- a/spec/graphql/resolvers/users_resolver_spec.rb +++ b/spec/graphql/resolvers/users_resolver_spec.rb @@ -14,14 +14,6 @@ RSpec.describe Resolvers::UsersResolver do end describe '#resolve' do - it 'generates an error when read_users_list is not authorized' do - expect(Ability).to receive(:allowed?).with(current_user, :read_users_list).and_return(false) - - expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable) do - resolve_users - end - end - context 'when no arguments are passed' do it 'returns all users' do expect(resolve_users).to contain_exactly(user1, user2, current_user) @@ -79,8 +71,10 @@ RSpec.describe Resolvers::UsersResolver do end end - it 'allows to search by username' do - expect(resolve_users(args: { usernames: [user1.username] })).to contain_exactly(user1) + it 'prohibits search by username' do + expect_graphql_error_to_be_created(Gitlab::Graphql::Errors::ResourceNotAvailable) do + resolve_users(args: { usernames: [user1.username] }) + end end end end diff --git a/spec/graphql/resolvers/work_items_resolver_spec.rb b/spec/graphql/resolvers/work_items_resolver_spec.rb new file mode 100644 index 00000000000..29eac0ab46e --- /dev/null +++ b/spec/graphql/resolvers/work_items_resolver_spec.rb @@ -0,0 +1,190 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::WorkItemsResolver 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) } + let_it_be(:other_project) { create(:project, group: group) } + + let_it_be(:item1) do + create(:work_item, project: project, state: :opened, created_at: + 3.hours.ago, updated_at: 3.hours.ago) + end + + 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: + 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: + 1.hour.ago) + end + + let_it_be(:item4) { create(:work_item) } + + specify do + expect(described_class).to have_nullable_graphql_type(Types::WorkItemType.connection_type) + end + + context "with a project" do + before_all do + project.add_developer(current_user) + project.add_reporter(reporter) + end + + describe '#resolve' do + it 'finds all items' do + expect(resolve_items).to contain_exactly(item1, item2) + end + + it 'filters by state' do + expect(resolve_items(state: 'opened')).to contain_exactly(item1) + expect(resolve_items(state: 'closed')).to contain_exactly(item2) + 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 + end + end + + describe 'sorting' do + context 'when sorting by created' do + it 'sorts items ascending' do + expect(resolve_items(sort: 'created_asc').to_a).to eq [item1, item2] + end + + it 'sorts items descending' do + expect(resolve_items(sort: 'created_desc').to_a).to eq [item2, item1] + end + end + + context 'when sorting by title' do + let_it_be(:project) { create(:project, :public) } + let_it_be(:item1) { create(:work_item, project: project, title: 'foo') } + let_it_be(:item2) { create(:work_item, project: project, title: 'bar') } + let_it_be(:item3) { create(:work_item, project: project, title: 'baz') } + let_it_be(:item4) { create(:work_item, project: project, title: 'Baz 2') } + + it 'sorts items ascending' do + expect(resolve_items(sort: :title_asc).to_a).to eq [item2, item3, item4, item1] + end + + it 'sorts items descending' do + expect(resolve_items(sort: :title_desc).to_a).to eq [item1, item4, item3, item2] + end + end + end + + it 'returns items user can see' do + project.add_guest(current_user) + + create(:work_item, confidential: true) + + expect(resolve_items).to contain_exactly(item1, item2) + end + + it 'batches queries that only include IIDs', :request_store do + result = batch_sync(max_queries: 7) do + [item1, item2] + .map { |item| resolve_items(iid: item.iid.to_s) } + .flat_map(&:to_a) + end + + expect(result).to contain_exactly(item1, item2) + end + + it 'finds a specific item with iids', :request_store do + result = batch_sync(max_queries: 7) do + resolve_items(iids: [item1.iid]).to_a + end + + expect(result).to contain_exactly(item1) + end + + it 'finds multiple items with iids' do + create(:work_item, project: project, author: current_user) + + expect(batch_sync { resolve_items(iids: [item1.iid, item2.iid]).to_a }) + .to contain_exactly(item1, item2) + end + + it 'finds only the items within the project we are looking at' do + another_project = create(:project) + iids = [item1, item2].map(&:iid) + + iids.each do |iid| + create(:work_item, project: another_project, iid: iid) + end + + expect(batch_sync { resolve_items(iids: iids).to_a }).to contain_exactly(item1, item2) + end + end + end + + context "when passing a non existent, batch loaded project" do + let!(:project) do + BatchLoader::GraphQL.for("non-existent-path").batch do |_fake_paths, loader, _| + loader.call("non-existent-path", nil) + end + end + + it "returns nil without breaking" do + expect(resolve_items(iids: ["don't", "break"])).to be_empty + end + end + + def resolve_items(args = {}, context = { current_user: current_user }) + resolve(described_class, obj: project, args: args, ctx: context, arg_style: :internal) + end +end |