diff options
Diffstat (limited to 'spec/graphql/resolvers')
18 files changed, 1112 insertions, 20 deletions
diff --git a/spec/graphql/resolvers/alert_management/alert_status_counts_resolver_spec.rb b/spec/graphql/resolvers/alert_management/alert_status_counts_resolver_spec.rb new file mode 100644 index 00000000000..8eb28c8c945 --- /dev/null +++ b/spec/graphql/resolvers/alert_management/alert_status_counts_resolver_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::AlertManagement::AlertStatusCountsResolver do + include GraphqlHelpers + + describe '#resolve' do + let_it_be(:current_user) { create(:user) } + let_it_be(:project) { create(:project) } + let(:args) { {} } + + subject { resolve_alert_status_counts(args) } + + it { is_expected.to be_a(Gitlab::AlertManagement::AlertStatusCounts) } + specify { expect(subject.project).to eq(project) } + + private + + def resolve_alert_status_counts(args = {}, context = { current_user: current_user }) + resolve(described_class, obj: project, args: args, ctx: context) + end + end +end diff --git a/spec/graphql/resolvers/alert_management_alert_resolver_spec.rb b/spec/graphql/resolvers/alert_management_alert_resolver_spec.rb new file mode 100644 index 00000000000..971a81a826d --- /dev/null +++ b/spec/graphql/resolvers/alert_management_alert_resolver_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::AlertManagementAlertResolver do + include GraphqlHelpers + + let_it_be(:current_user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:alert_1) { create(:alert_management_alert, :resolved, project: project, ended_at: 1.year.ago, events: 2, severity: :high) } + let_it_be(:alert_2) { create(:alert_management_alert, :ignored, project: project, events: 1, severity: :critical) } + let_it_be(:alert_other_proj) { create(:alert_management_alert) } + + let(:args) { {} } + + subject { resolve_alerts(args) } + + context 'user does not have permission' do + it { is_expected.to eq(AlertManagement::Alert.none) } + end + + context 'user has permission' do + before do + project.add_developer(current_user) + end + + it { is_expected.to contain_exactly(alert_1, alert_2) } + + context 'finding by iid' do + let(:args) { { iid: alert_1.iid } } + + it { is_expected.to contain_exactly(alert_1) } + end + + context 'finding by status' do + let(:args) { { status: [Types::AlertManagement::StatusEnum.values['IGNORED'].value] } } + + it { is_expected.to contain_exactly(alert_2) } + end + + describe 'sorting' do + # Other sorting examples in spec/finders/alert_management/alerts_finder_spec.rb + context 'when sorting by events count' do + let_it_be(:alert_count_6) { create(:alert_management_alert, project: project, events: 6) } + let_it_be(:alert_count_3) { create(:alert_management_alert, project: project, events: 3) } + + it 'sorts alerts ascending' do + expect(resolve_alerts(sort: :events_count_asc)).to eq [alert_2, alert_1, alert_count_3, alert_count_6] + end + + it 'sorts alerts descending' do + expect(resolve_alerts(sort: :events_count_desc)).to eq [alert_count_6, alert_count_3, alert_1, alert_2] + end + end + end + end + + private + + def resolve_alerts(args = {}, context = { current_user: current_user }) + resolve(described_class, obj: project, args: args, ctx: context) + end +end diff --git a/spec/graphql/resolvers/board_lists_resolver_spec.rb b/spec/graphql/resolvers/board_lists_resolver_spec.rb new file mode 100644 index 00000000000..5f6c440a8ed --- /dev/null +++ b/spec/graphql/resolvers/board_lists_resolver_spec.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::BoardListsResolver do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:guest) { create(:user) } + let_it_be(:unauth_user) { create(:user) } + let_it_be(:project) { create(:project, creator_id: user.id, namespace: user.namespace ) } + let_it_be(:group) { create(:group, :private) } + let_it_be(:project_label) { create(:label, project: project, name: 'Development') } + let_it_be(:group_label) { create(:group_label, group: group, name: 'Development') } + + shared_examples_for 'group and project board lists resolver' do + let(:board) { create(:board, resource_parent: board_parent) } + + before do + board_parent.add_developer(user) + end + + it 'does not create the backlog list' do + lists = resolve_board_lists.items + + expect(lists.count).to eq 1 + expect(lists[0].list_type).to eq 'closed' + end + + context 'with unauthorized user' do + it 'raises an error' do + expect do + resolve_board_lists(current_user: unauth_user) + end.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'when authorized' do + let!(:label_list) { create(:list, board: board, label: label) } + let!(:backlog_list) { create(:backlog_list, board: board) } + + it 'returns a list of board lists' do + lists = resolve_board_lists.items + + expect(lists.count).to eq 3 + expect(lists.map(&:list_type)).to eq %w(backlog label closed) + end + + context 'when another user has list preferences' do + before do + board.lists.first.update_preferences_for(guest, collapsed: true) + end + + it 'returns the complete list of board lists for this user' do + lists = resolve_board_lists.items + + expect(lists.count).to eq 3 + end + end + end + end + + describe '#resolve' do + context 'when project boards' do + let(:board_parent) { project } + let(:label) { project_label } + + it_behaves_like 'group and project board lists resolver' + end + + context 'when group boards' do + let(:board_parent) { group } + let(:label) { group_label } + + it_behaves_like 'group and project board lists resolver' + end + end + + def resolve_board_lists(args: {}, current_user: user) + resolve(described_class, obj: board, args: args, ctx: { current_user: current_user }) + end +end diff --git a/spec/graphql/resolvers/branch_commit_resolver_spec.rb b/spec/graphql/resolvers/branch_commit_resolver_spec.rb new file mode 100644 index 00000000000..22e1de8f375 --- /dev/null +++ b/spec/graphql/resolvers/branch_commit_resolver_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::BranchCommitResolver do + include GraphqlHelpers + + subject(:commit) { resolve(described_class, obj: branch) } + + let_it_be(:repository) { create(:project, :repository).repository } + let(:branch) { repository.find_branch('master') } + + describe '#resolve' do + it 'resolves commit' do + is_expected.to eq(repository.commits('master', limit: 1).last) + end + + context 'when branch does not exist' do + let(:branch) { nil } + + it 'returns nil' do + is_expected.to be_nil + end + end + end +end diff --git a/spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb b/spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb new file mode 100644 index 00000000000..a5054ae3ebf --- /dev/null +++ b/spec/graphql/resolvers/design_management/design_at_version_resolver_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::DesignManagement::DesignAtVersionResolver do + include GraphqlHelpers + include DesignManagementTestHelpers + + let_it_be(:issue) { create(:issue) } + let_it_be(:project) { issue.project } + let_it_be(:user) { create(:user) } + let_it_be(:design_a) { create(:design, issue: issue) } + let_it_be(:version_a) { create(:design_version, issue: issue, created_designs: [design_a]) } + + let(:current_user) { user } + let(:object) { issue.design_collection } + let(:global_id) { GitlabSchema.id_from_object(design_at_version).to_s } + + let(:design_at_version) { ::DesignManagement::DesignAtVersion.new(design: design_a, version: version_a) } + + let(:resource_not_available) { ::Gitlab::Graphql::Errors::ResourceNotAvailable } + + before do + enable_design_management + project.add_developer(user) + end + + describe '#resolve' do + context 'when the user cannot see designs' do + let(:current_user) { create(:user) } + + it 'raises ResourceNotAvailable' do + expect { resolve_design }.to raise_error(resource_not_available) + end + end + + it 'returns the specified design' do + expect(resolve_design).to eq(design_at_version) + end + + context 'the ID belongs to a design on another issue' do + let(:other_dav) do + create(:design_at_version, issue: create(:issue, project: project)) + end + + let(:global_id) { global_id_of(other_dav) } + + it 'raises ResourceNotAvailable' do + expect { resolve_design }.to raise_error(resource_not_available) + end + + context 'the current object does not constrain the issue' do + let(:object) { nil } + + it 'returns the object' do + expect(resolve_design).to eq(other_dav) + end + end + end + end + + private + + def resolve_design + args = { id: global_id } + ctx = { current_user: current_user } + eager_resolve(described_class, obj: object, args: args, ctx: ctx) + end +end diff --git a/spec/graphql/resolvers/design_management/design_resolver_spec.rb b/spec/graphql/resolvers/design_management/design_resolver_spec.rb new file mode 100644 index 00000000000..857acc3d371 --- /dev/null +++ b/spec/graphql/resolvers/design_management/design_resolver_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::DesignManagement::DesignResolver do + include GraphqlHelpers + include DesignManagementTestHelpers + + before do + enable_design_management + end + + describe '#resolve' do + let_it_be(:issue) { create(:issue) } + let_it_be(:project) { issue.project } + let_it_be(:first_version) { create(:design_version) } + let_it_be(:first_design) { create(:design, issue: issue, versions: [first_version]) } + let_it_be(:current_user) { create(:user) } + let_it_be(:design_on_other_issue) do + create(:design, issue: create(:issue, project: project), versions: [create(:design_version)]) + end + + let(:args) { { id: GitlabSchema.id_from_object(first_design).to_s } } + let(:gql_context) { { current_user: current_user } } + + before do + project.add_developer(current_user) + end + + context 'when the user cannot see designs' do + let(:gql_context) { { current_user: create(:user) } } + + it 'returns nothing' do + expect(resolve_design).to be_nil + end + end + + context 'when no argument has been passed' do + let(:args) { {} } + + it 'raises an error' do + expect { resolve_design }.to raise_error(::Gitlab::Graphql::Errors::ArgumentError, /must/) + end + end + + context 'when both arguments have been passed' do + let(:args) { { filename: first_design.filename, id: GitlabSchema.id_from_object(first_design).to_s } } + + it 'raises an error' do + expect { resolve_design }.to raise_error(::Gitlab::Graphql::Errors::ArgumentError, /may/) + end + end + + context 'by ID' do + it 'returns the specified design' do + expect(resolve_design).to eq(first_design) + end + + context 'the ID belongs to a design on another issue' do + let(:args) { { id: GitlabSchema.id_from_object(design_on_other_issue).to_s } } + + it 'returns nothing' do + expect(resolve_design).to be_nil + end + end + end + + context 'by filename' do + let(:args) { { filename: first_design.filename } } + + it 'returns the specified design' do + expect(resolve_design).to eq(first_design) + end + + context 'the filename belongs to a design on another issue' do + let(:args) { { filename: design_on_other_issue.filename } } + + it 'returns nothing' do + expect(resolve_design).to be_nil + end + end + end + end + + def resolve_design + resolve(described_class, obj: issue.design_collection, args: args, ctx: gql_context) + end +end diff --git a/spec/graphql/resolvers/design_management/designs_resolver_spec.rb b/spec/graphql/resolvers/design_management/designs_resolver_spec.rb new file mode 100644 index 00000000000..28fc9e2151d --- /dev/null +++ b/spec/graphql/resolvers/design_management/designs_resolver_spec.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::DesignManagement::DesignsResolver do + include GraphqlHelpers + include DesignManagementTestHelpers + + before do + enable_design_management + end + + describe '#resolve' do + let_it_be(:issue) { create(:issue) } + let_it_be(:project) { issue.project } + let_it_be(:first_version) { create(:design_version) } + let_it_be(:first_design) { create(:design, issue: issue, versions: [first_version]) } + let_it_be(:current_user) { create(:user) } + let(:gql_context) { { current_user: current_user } } + let(:args) { {} } + + before do + project.add_developer(current_user) + end + + context 'when the user cannot see designs' do + let(:gql_context) { { current_user: create(:user) } } + + it 'returns nothing' do + expect(resolve_designs).to be_empty + end + end + + context 'for a design collection' do + context 'which contains just a single design' do + it 'returns just that design' do + expect(resolve_designs).to contain_exactly(first_design) + end + end + + context 'which contains another design' do + it 'returns all designs' do + second_version = create(:design_version) + second_design = create(:design, issue: issue, versions: [second_version]) + + expect(resolve_designs).to contain_exactly(first_design, second_design) + end + end + end + + describe 'filtering' do + describe 'by filename' do + let(:second_version) { create(:design_version) } + let(:second_design) { create(:design, issue: issue, versions: [second_version]) } + let(:args) { { filenames: [second_design.filename] } } + + it 'resolves to just the relevant design, ignoring designs with the same filename on different issues' do + create(:design, issue: create(:issue, project: project), filename: second_design.filename) + + expect(resolve_designs).to contain_exactly(second_design) + end + end + + describe 'by id' do + let(:second_version) { create(:design_version) } + let(:second_design) { create(:design, issue: issue, versions: [second_version]) } + + context 'the ID is on the current issue' do + let(:args) { { ids: [GitlabSchema.id_from_object(second_design).to_s] } } + + it 'resolves to just the relevant design' do + expect(resolve_designs).to contain_exactly(second_design) + end + end + + context 'the ID is on a different issue' do + let(:third_version) { create(:design_version) } + let(:third_design) { create(:design, issue: create(:issue, project: project), versions: [third_version]) } + + let(:args) { { ids: [GitlabSchema.id_from_object(third_design).to_s] } } + + it 'ignores it' do + expect(resolve_designs).to be_empty + end + end + end + end + end + + def resolve_designs + resolve(described_class, obj: issue.design_collection, args: args, ctx: gql_context) + end +end diff --git a/spec/graphql/resolvers/design_management/version/design_at_version_resolver_spec.rb b/spec/graphql/resolvers/design_management/version/design_at_version_resolver_spec.rb new file mode 100644 index 00000000000..cc9c0436885 --- /dev/null +++ b/spec/graphql/resolvers/design_management/version/design_at_version_resolver_spec.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::DesignManagement::Version::DesignAtVersionResolver do + include GraphqlHelpers + + include_context 'four designs in three versions' + + let(:current_user) { authorized_user } + let(:gql_context) { { current_user: current_user } } + + let(:version) { third_version } + let(:design) { design_a } + + let(:all_singular_args) do + { + design_at_version_id: global_id_of(dav(design)), + design_id: global_id_of(design), + filename: design.filename + } + end + + shared_examples 'a bad argument' do + let(:err_class) { ::Gitlab::Graphql::Errors::ArgumentError } + + it 'raises an appropriate error' do + expect { resolve_objects }.to raise_error(err_class) + end + end + + describe '#resolve' do + describe 'passing combinations of arguments' do + context 'passing no arguments' do + let(:args) { {} } + + it_behaves_like 'a bad argument' + end + + context 'passing all arguments' do + let(:args) { all_singular_args } + + it_behaves_like 'a bad argument' + end + + context 'passing any two arguments' do + let(:args) { all_singular_args.slice(*all_singular_args.keys.sample(2)) } + + it_behaves_like 'a bad argument' + end + end + + %i[design_at_version_id design_id filename].each do |arg| + describe "passing #{arg}" do + let(:args) { all_singular_args.slice(arg) } + + it 'finds the design' do + expect(resolve_objects).to eq(dav(design)) + end + + context 'when the user cannot see designs' do + let(:current_user) { create(:user) } + + it 'returns nothing' do + expect(resolve_objects).to be_nil + end + end + end + end + + describe 'attempting to retrieve an object not visible at this version' do + let(:design) { design_d } + + %i[design_at_version_id design_id filename].each do |arg| + describe "passing #{arg}" do + let(:args) { all_singular_args.slice(arg) } + + it 'does not find the design' do + expect(resolve_objects).to be_nil + end + end + end + end + end + + def resolve_objects + resolve(described_class, obj: version, args: args, ctx: gql_context) + end + + def dav(design) + build(:design_at_version, design: design, version: version) + end +end diff --git a/spec/graphql/resolvers/design_management/version/designs_at_version_resolver_spec.rb b/spec/graphql/resolvers/design_management/version/designs_at_version_resolver_spec.rb new file mode 100644 index 00000000000..123b26862d0 --- /dev/null +++ b/spec/graphql/resolvers/design_management/version/designs_at_version_resolver_spec.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::DesignManagement::Version::DesignsAtVersionResolver do + include GraphqlHelpers + + include_context 'four designs in three versions' + + let_it_be(:current_user) { authorized_user } + let(:gql_context) { { current_user: current_user } } + + let(:version) { third_version } + + describe '.single' do + let(:single) { ::Resolvers::DesignManagement::Version::DesignAtVersionResolver } + + it 'returns the single context resolver' do + expect(described_class.single).to eq(single) + end + end + + describe '#resolve' do + let(:args) { {} } + + context 'when the user cannot see designs' do + let(:current_user) { create(:user) } + + it 'returns nothing' do + expect(resolve_objects).to be_empty + end + end + + context 'for the current version' do + it 'returns all designs visible at that version' do + expect(resolve_objects).to contain_exactly(dav(design_a), dav(design_b), dav(design_c)) + end + end + + context 'for a previous version with more objects' do + let(:version) { second_version } + + it 'returns objects that were later deleted' do + expect(resolve_objects).to contain_exactly(dav(design_a), dav(design_b), dav(design_c), dav(design_d)) + end + end + + context 'for a previous version with fewer objects' do + let(:version) { first_version } + + it 'does not return objects that were later created' do + expect(resolve_objects).to contain_exactly(dav(design_a)) + end + end + + describe 'filtering' do + describe 'by filename' do + let(:red_herring) { create(:design, issue: create(:issue, project: project)) } + let(:args) { { filenames: [design_b.filename, red_herring.filename] } } + + it 'resolves to just the relevant design' do + create(:design, issue: create(:issue, project: project), filename: design_b.filename) + + expect(resolve_objects).to contain_exactly(dav(design_b)) + end + end + + describe 'by id' do + let(:red_herring) { create(:design, issue: create(:issue, project: project)) } + let(:args) { { ids: [design_a, red_herring].map { |x| global_id_of(x) } } } + + it 'resolves to just the relevant design, ignoring objects on other issues' do + expect(resolve_objects).to contain_exactly(dav(design_a)) + end + end + end + end + + def resolve_objects + resolve(described_class, obj: version, args: args, ctx: gql_context) + end + + def dav(design) + build(:design_at_version, design: design, version: version) + end +end diff --git a/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb b/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb new file mode 100644 index 00000000000..ef50598d241 --- /dev/null +++ b/spec/graphql/resolvers/design_management/version_in_collection_resolver_spec.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::DesignManagement::VersionInCollectionResolver do + include GraphqlHelpers + include DesignManagementTestHelpers + + let(:resolver) { described_class } + + describe '#resolve' do + let_it_be(:issue) { create(:issue) } + let_it_be(:current_user) { create(:user) } + let_it_be(:first_version) { create(:design_version, issue: issue) } + + let(:project) { issue.project } + let(:params) { {} } + + before do + enable_design_management + project.add_developer(current_user) + end + + let(:appropriate_error) { ::Gitlab::Graphql::Errors::ArgumentError } + + subject(:result) { resolve_version(issue.design_collection) } + + context 'Neither id nor sha is passed as parameters' do + it 'raises an appropriate error' do + expect { result }.to raise_error(appropriate_error) + end + end + + context 'we pass an id' do + let(:params) { { id: global_id_of(first_version) } } + + it { is_expected.to eq(first_version) } + end + + context 'we pass a sha' do + let(:params) { { sha: first_version.sha } } + + it { is_expected.to eq(first_version) } + end + + context 'we pass an inconsistent mixture of sha and version id' do + let(:params) { { sha: first_version.sha, id: global_id_of(create(:design_version)) } } + + it { is_expected.to be_nil } + end + + context 'we pass the id of something that is not a design_version' do + let(:params) { { id: global_id_of(project) } } + + it 'raises an appropriate error' do + expect { result }.to raise_error(appropriate_error) + end + end + end + + def resolve_version(obj, context = { current_user: current_user }) + resolve(resolver, obj: obj, args: params, ctx: context) + end +end diff --git a/spec/graphql/resolvers/design_management/version_resolver_spec.rb b/spec/graphql/resolvers/design_management/version_resolver_spec.rb new file mode 100644 index 00000000000..e7c09351204 --- /dev/null +++ b/spec/graphql/resolvers/design_management/version_resolver_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::DesignManagement::VersionResolver do + include GraphqlHelpers + include DesignManagementTestHelpers + + let_it_be(:issue) { create(:issue) } + let_it_be(:current_user) { create(:user) } + let_it_be(:version) { create(:design_version, issue: issue) } + let_it_be(:developer) { create(:user) } + + let(:project) { issue.project } + let(:params) { { id: global_id_of(version) } } + + before do + enable_design_management + project.add_developer(developer) + end + + context 'the current user is not authorized' do + let(:current_user) { create(:user) } + + it 'raises an error on resolution' do + expect { resolve_version }.to raise_error(::Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'the current user is authorized' do + let(:current_user) { developer } + + context 'the id parameter is provided' do + it 'returns the specified version' do + expect(resolve_version).to eq(version) + end + end + end + + def resolve_version + resolve(described_class, obj: nil, args: params, ctx: { current_user: current_user }) + end +end diff --git a/spec/graphql/resolvers/design_management/versions_resolver_spec.rb b/spec/graphql/resolvers/design_management/versions_resolver_spec.rb new file mode 100644 index 00000000000..d5bab025e45 --- /dev/null +++ b/spec/graphql/resolvers/design_management/versions_resolver_spec.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::DesignManagement::VersionsResolver do + include GraphqlHelpers + include DesignManagementTestHelpers + + describe '#resolve' do + let(:resolver) { described_class } + let_it_be(:issue) { create(:issue) } + let_it_be(:authorized_user) { create(:user) } + let_it_be(:first_version) { create(:design_version, issue: issue) } + let_it_be(:other_version) { create(:design_version, issue: issue) } + let_it_be(:first_design) { create(:design, issue: issue, versions: [first_version, other_version]) } + let_it_be(:other_design) { create(:design, :with_versions, issue: issue) } + + let(:project) { issue.project } + let(:params) { {} } + let(:current_user) { authorized_user } + let(:parent_args) { { irrelevant: 1.2 } } + let(:parent) { double('Parent', parent: nil, irep_node: double(arguments: parent_args)) } + + before do + enable_design_management + project.add_developer(authorized_user) + end + + shared_examples 'a source of versions' do + subject(:result) { resolve_versions(object) } + + let_it_be(:all_versions) { object.versions.ordered } + + context 'when the user is not authorized' do + let(:current_user) { create(:user) } + + it { is_expected.to be_empty } + end + + context 'without constraints' do + it 'returns the ordered versions' do + expect(result).to eq(all_versions) + end + end + + context 'when constrained' do + let_it_be(:matching) { all_versions.earlier_or_equal_to(first_version) } + + shared_examples 'a query for all_versions up to the first_version' do + it { is_expected.to eq(matching) } + end + + context 'by earlier_or_equal_to_id' do + let(:params) { { id: global_id_of(first_version) } } + + it_behaves_like 'a query for all_versions up to the first_version' + end + + context 'by earlier_or_equal_to_sha' do + let(:params) { { sha: first_version.sha } } + + it_behaves_like 'a query for all_versions up to the first_version' + end + + context 'by earlier_or_equal_to_sha AND earlier_or_equal_to_id' do + context 'and they match' do + # This usage is rather dumb, but so long as they match, this will + # return successfully + let(:params) do + { + sha: first_version.sha, + id: global_id_of(first_version) + } + end + + it_behaves_like 'a query for all_versions up to the first_version' + end + + context 'and they do not match' do + let(:params) do + { + sha: first_version.sha, + id: global_id_of(other_version) + } + end + + it 'raises a suitable error' do + expect { result }.to raise_error(GraphQL::ExecutionError) + end + end + end + + context 'by at_version in parent' do + let(:parent_args) { { atVersion: global_id_of(first_version) } } + + it_behaves_like 'a query for all_versions up to the first_version' + end + end + end + + describe 'a design collection' do + let_it_be(:object) { DesignManagement::DesignCollection.new(issue) } + + it_behaves_like 'a source of versions' + end + + describe 'a design' do + let_it_be(:object) { first_design } + + it_behaves_like 'a source of versions' + end + + def resolve_versions(obj, context = { current_user: current_user }) + eager_resolve(resolver, obj: obj, args: params.merge(parent: parent), ctx: context) + end + end +end diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index 53e0a9e3724..b7cc9bc6d71 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -125,12 +125,11 @@ describe Resolvers::IssuesResolver do end context 'when sorting by due date' do - let(:project) { create(:project) } - - let!(:due_issue1) { create(:issue, project: project, due_date: 3.days.from_now) } - let!(:due_issue2) { create(:issue, project: project, due_date: nil) } - let!(:due_issue3) { create(:issue, project: project, due_date: 2.days.ago) } - let!(:due_issue4) { create(:issue, project: project, due_date: nil) } + let_it_be(:project) { create(:project) } + let_it_be(:due_issue1) { create(:issue, project: project, due_date: 3.days.from_now) } + let_it_be(:due_issue2) { create(:issue, project: project, due_date: nil) } + let_it_be(:due_issue3) { create(:issue, project: project, due_date: 2.days.ago) } + let_it_be(:due_issue4) { create(:issue, project: project, due_date: nil) } it 'sorts issues ascending' do expect(resolve_issues(sort: :due_date_asc)).to eq [due_issue3, due_issue1, due_issue4, due_issue2] @@ -142,17 +141,72 @@ describe Resolvers::IssuesResolver do end context 'when sorting by relative position' do - let(:project) { create(:project) } - - let!(:relative_issue1) { create(:issue, project: project, relative_position: 2000) } - let!(:relative_issue2) { create(:issue, project: project, relative_position: nil) } - let!(:relative_issue3) { create(:issue, project: project, relative_position: 1000) } - let!(:relative_issue4) { create(:issue, project: project, relative_position: nil) } + let_it_be(:project) { create(:project) } + let_it_be(:relative_issue1) { create(:issue, project: project, relative_position: 2000) } + let_it_be(:relative_issue2) { create(:issue, project: project, relative_position: nil) } + let_it_be(:relative_issue3) { create(:issue, project: project, relative_position: 1000) } + let_it_be(:relative_issue4) { create(:issue, project: project, relative_position: nil) } it 'sorts issues ascending' do expect(resolve_issues(sort: :relative_position_asc)).to eq [relative_issue3, relative_issue1, relative_issue4, relative_issue2] end end + + context 'when sorting by priority' do + let_it_be(:project) { create(:project) } + let_it_be(:early_milestone) { create(:milestone, project: project, due_date: 10.days.from_now) } + let_it_be(:late_milestone) { create(:milestone, project: project, due_date: 30.days.from_now) } + let_it_be(:priority_label1) { create(:label, project: project, priority: 1) } + let_it_be(:priority_label2) { create(:label, project: project, priority: 5) } + let_it_be(:priority_issue1) { create(:issue, project: project, labels: [priority_label1], milestone: late_milestone) } + let_it_be(:priority_issue2) { create(:issue, project: project, labels: [priority_label2]) } + let_it_be(:priority_issue3) { create(:issue, project: project, milestone: early_milestone) } + 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]) + end + + it 'sorts issues descending' do + expect(resolve_issues(sort: :priority_desc).items).to eq([priority_issue1, priority_issue3, priority_issue2, priority_issue4]) + end + end + + context 'when sorting by label priority' do + let_it_be(:project) { create(:project) } + let_it_be(:label1) { create(:label, project: project, priority: 1) } + let_it_be(:label2) { create(:label, project: project, priority: 5) } + let_it_be(:label3) { create(:label, project: project, priority: 10) } + let_it_be(:label_issue1) { create(:issue, project: project, labels: [label1]) } + let_it_be(:label_issue2) { create(:issue, project: project, labels: [label2]) } + let_it_be(:label_issue3) { create(:issue, project: project, labels: [label1, label3]) } + 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]) + end + + it 'sorts issues descending' do + expect(resolve_issues(sort: :label_priority_desc).items).to eq([label_issue2, label_issue3, label_issue1, label_issue4]) + end + end + + context 'when sorting by milestone due date' do + let_it_be(:project) { create(:project) } + let_it_be(:early_milestone) { create(:milestone, project: project, due_date: 10.days.from_now) } + let_it_be(:late_milestone) { create(:milestone, project: project, due_date: 30.days.from_now) } + let_it_be(:milestone_issue1) { create(:issue, project: project) } + let_it_be(:milestone_issue2) { create(:issue, project: project, milestone: early_milestone) } + 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]) + end + + it 'sorts issues descending' do + expect(resolve_issues(sort: :milestone_due_desc).items).to eq([milestone_issue3, milestone_issue2, milestone_issue1]) + end + end end it 'returns issues user can see' do diff --git a/spec/graphql/resolvers/milestone_resolver_spec.rb b/spec/graphql/resolvers/milestone_resolver_spec.rb index 297130c2027..8e2c67fdc03 100644 --- a/spec/graphql/resolvers/milestone_resolver_spec.rb +++ b/spec/graphql/resolvers/milestone_resolver_spec.rb @@ -8,14 +8,14 @@ describe Resolvers::MilestoneResolver do describe '#resolve' 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) + end + context 'for group milestones' do let_it_be(:now) { Time.now } let_it_be(:group) { create(:group, :private) } - def resolve_group_milestones(args = {}, context = { current_user: current_user }) - resolve(described_class, obj: group, args: args, ctx: context) - end - before do group.add_developer(current_user) end @@ -89,5 +89,25 @@ describe Resolvers::MilestoneResolver do end end end + + context 'when including descendant milestones in a public group' do + let_it_be(:group) { create(:group, :public) } + let(:args) { { include_descendants: true } } + + it 'finds milestones only in accessible projects and groups' do + accessible_group = create(:group, :private, parent: group) + accessible_project = create(:project, group: accessible_group) + accessible_group.add_developer(current_user) + inaccessible_group = create(:group, :private, parent: group) + inaccessible_project = create(:project, :private, group: group) + milestone1 = create(:milestone, group: group) + milestone2 = create(:milestone, group: accessible_group) + milestone3 = create(:milestone, project: accessible_project) + create(:milestone, group: inaccessible_group) + create(:milestone, project: inaccessible_project) + + expect(resolve_group_milestones(args)).to match_array([milestone1, milestone2, milestone3]) + end + end end end diff --git a/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb b/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb index 47889126531..7146bfb441b 100644 --- a/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb +++ b/spec/graphql/resolvers/projects/jira_imports_resolver_spec.rb @@ -16,7 +16,7 @@ describe Resolvers::Projects::JiraImportsResolver do context 'when anonymous user' do let(:current_user) { nil } - it_behaves_like 'no jira import access' + it_behaves_like 'no Jira import access' end end @@ -25,7 +25,7 @@ describe Resolvers::Projects::JiraImportsResolver do project.add_guest(user) end - it_behaves_like 'no jira import data present' + it_behaves_like 'no Jira import data present' it 'does not raise access error' do expect do @@ -47,14 +47,14 @@ describe Resolvers::Projects::JiraImportsResolver do stub_feature_flags(jira_issue_import: false) end - it_behaves_like 'no jira import access' + it_behaves_like 'no Jira import access' end context 'when user cannot read Jira imports' do context 'when anonymous user' do let(:current_user) { nil } - it_behaves_like 'no jira import access' + it_behaves_like 'no Jira import access' end end diff --git a/spec/graphql/resolvers/projects_resolver_spec.rb b/spec/graphql/resolvers/projects_resolver_spec.rb new file mode 100644 index 00000000000..73ff99a2520 --- /dev/null +++ b/spec/graphql/resolvers/projects_resolver_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::ProjectsResolver do + include GraphqlHelpers + + describe '#resolve' do + subject { resolve(described_class, obj: nil, args: filters, ctx: { current_user: current_user }) } + + let_it_be(:project) { create(:project, :public) } + let_it_be(:other_project) { create(:project, :public) } + let_it_be(:private_project) { create(:project, :private) } + let_it_be(:other_private_project) { create(:project, :private) } + + let_it_be(:user) { create(:user) } + + let(:filters) { {} } + + before_all do + project.add_developer(user) + private_project.add_developer(user) + end + + context 'when user is not logged in' do + let(:current_user) { nil } + + context 'when no filters are applied' do + it 'returns all public projects' do + is_expected.to contain_exactly(project, other_project) + end + + context 'when search filter is provided' do + let(:filters) { { search: project.name } } + + it 'returns matching project' do + is_expected.to contain_exactly(project) + end + end + + context 'when membership filter is provided' do + let(:filters) { { membership: true } } + + it 'returns empty list' do + is_expected.to be_empty + end + end + end + end + + context 'when user is logged in' do + let(:current_user) { user } + + context 'when no filters are applied' do + it 'returns all visible projects for the user' do + is_expected.to contain_exactly(project, other_project, private_project) + end + + context 'when search filter is provided' do + let(:filters) { { search: project.name } } + + it 'returns matching project' do + is_expected.to contain_exactly(project) + end + end + + context 'when membership filter is provided' do + let(:filters) { { membership: true } } + + it 'returns projects that user is member of' do + is_expected.to contain_exactly(project, private_project) + end + end + end + end + end +end diff --git a/spec/graphql/resolvers/release_resolver_spec.rb b/spec/graphql/resolvers/release_resolver_spec.rb new file mode 100644 index 00000000000..71aa4bbb439 --- /dev/null +++ b/spec/graphql/resolvers/release_resolver_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::ReleaseResolver do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :private) } + let_it_be(:release) { create(:release, project: project) } + let_it_be(:developer) { create(:user) } + let_it_be(:public_user) { create(:user) } + + let(:args) { { tag_name: release.tag } } + + before do + project.add_developer(developer) + end + + describe '#resolve' do + context 'when the user does not have access to the project' do + let(:current_user) { public_user } + + it 'returns nil' do + expect(resolve_release).to be_nil + end + end + + context "when the user has full access to the project's releases" do + let(:current_user) { developer } + + it 'returns the release associated with the specified tag' do + expect(resolve_release).to eq(release) + end + + context 'when no tag_name argument was passed' do + let(:args) { {} } + + it 'raises an error' do + expect { resolve_release }.to raise_error(ArgumentError, "missing keyword: tag_name") + end + end + end + end + + private + + def resolve_release + context = { current_user: current_user } + resolve(described_class, obj: project, args: args, ctx: context) + end +end diff --git a/spec/graphql/resolvers/releases_resolver_spec.rb b/spec/graphql/resolvers/releases_resolver_spec.rb new file mode 100644 index 00000000000..9de539b417a --- /dev/null +++ b/spec/graphql/resolvers/releases_resolver_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Resolvers::ReleasesResolver do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :private) } + let_it_be(:release_v1) { create(:release, project: project, tag: 'v1.0.0') } + let_it_be(:release_v2) { create(:release, project: project, tag: 'v2.0.0') } + let_it_be(:developer) { create(:user) } + let_it_be(:public_user) { create(:user) } + + before do + project.add_developer(developer) + end + + describe '#resolve' do + context 'when the user does not have access to the project' do + let(:current_user) { public_user } + + it 'returns an empty array' do + expect(resolve_releases).to eq([]) + end + end + + context "when the user has full access to the project's releases" do + let(:current_user) { developer } + + it 'returns all releases associated to the project' do + expect(resolve_releases).to eq([release_v1, release_v2]) + end + end + end + + private + + def resolve_releases + context = { current_user: current_user } + resolve(described_class, obj: project, args: {}, ctx: context) + end +end |