diff options
Diffstat (limited to 'spec/graphql/resolvers')
20 files changed, 696 insertions, 114 deletions
diff --git a/spec/graphql/resolvers/alert_management/http_integrations_resolver_spec.rb b/spec/graphql/resolvers/alert_management/http_integrations_resolver_spec.rb index 2cd61dd7bcf..a4d1101bc4f 100644 --- a/spec/graphql/resolvers/alert_management/http_integrations_resolver_spec.rb +++ b/spec/graphql/resolvers/alert_management/http_integrations_resolver_spec.rb @@ -14,7 +14,9 @@ RSpec.describe Resolvers::AlertManagement::HttpIntegrationsResolver do let_it_be(:inactive_http_integration) { create(:alert_management_http_integration, :inactive, project: project) } let_it_be(:other_proj_integration) { create(:alert_management_http_integration) } - subject { sync(resolve_http_integrations) } + let(:params) { {} } + + subject { sync(resolve_http_integrations(params)) } before do project.add_developer(developer) @@ -41,11 +43,25 @@ RSpec.describe Resolvers::AlertManagement::HttpIntegrationsResolver do let(:current_user) { maintainer } it { is_expected.to contain_exactly(active_http_integration) } + + context 'when HTTP Integration ID is given' do + context 'when integration is from the current project' do + let(:params) { { id: global_id_of(inactive_http_integration) } } + + it { is_expected.to contain_exactly(inactive_http_integration) } + end + + context 'when integration is from other project' do + let(:params) { { id: global_id_of(other_proj_integration) } } + + it { is_expected.to be_empty } + end + end end private def resolve_http_integrations(args = {}, context = { current_user: current_user }) - resolve(described_class, obj: project, ctx: context) + resolve(described_class, obj: project, args: args, ctx: context) end end diff --git a/spec/graphql/resolvers/alert_management/integrations_resolver_spec.rb b/spec/graphql/resolvers/alert_management/integrations_resolver_spec.rb index 36e409e0677..fb0fb6729d4 100644 --- a/spec/graphql/resolvers/alert_management/integrations_resolver_spec.rb +++ b/spec/graphql/resolvers/alert_management/integrations_resolver_spec.rb @@ -7,12 +7,16 @@ RSpec.describe Resolvers::AlertManagement::IntegrationsResolver do let_it_be(:current_user) { create(:user) } let_it_be(:project) { create(:project) } + let_it_be(:project2) { create(:project) } let_it_be(:prometheus_integration) { create(:prometheus_service, project: project) } let_it_be(:active_http_integration) { create(:alert_management_http_integration, project: project) } let_it_be(:inactive_http_integration) { create(:alert_management_http_integration, :inactive, project: project) } - let_it_be(:other_proj_integration) { create(:alert_management_http_integration) } + let_it_be(:other_proj_integration) { create(:alert_management_http_integration, project: project2) } + let_it_be(:other_proj_prometheus_integration) { create(:prometheus_service, project: project2) } - subject { sync(resolve_http_integrations) } + let(:params) { {} } + + subject { sync(resolve_http_integrations(params)) } specify do expect(described_class).to have_nullable_graphql_type(Types::AlertManagement::IntegrationType.connection_type) @@ -25,14 +29,43 @@ RSpec.describe Resolvers::AlertManagement::IntegrationsResolver do context 'user has permission' do before do project.add_maintainer(current_user) + project2.add_maintainer(current_user) end it { is_expected.to contain_exactly(active_http_integration, prometheus_integration) } + + context 'when HTTP Integration ID is given' do + context 'when integration is from the current project' do + let(:params) { { id: global_id_of(inactive_http_integration) } } + + it { is_expected.to contain_exactly(inactive_http_integration) } + end + + context 'when integration is from other project' do + let(:params) { { id: global_id_of(other_proj_integration) } } + + it { is_expected.to be_empty } + end + end + + context 'when Prometheus Integration ID is given' do + context 'when integration is from the current project' do + let(:params) { { id: global_id_of(prometheus_integration) } } + + it { is_expected.to contain_exactly(prometheus_integration) } + end + + context 'when integration is from other project' do + let(:params) { { id: global_id_of(other_proj_prometheus_integration) } } + + it { is_expected.to be_empty } + end + end end private def resolve_http_integrations(args = {}, context = { current_user: current_user }) - resolve(described_class, obj: project, ctx: context) + resolve(described_class, obj: project, args: args, ctx: context) end end diff --git a/spec/graphql/resolvers/blobs_resolver_spec.rb b/spec/graphql/resolvers/blobs_resolver_spec.rb new file mode 100644 index 00000000000..bc0344796ee --- /dev/null +++ b/spec/graphql/resolvers/blobs_resolver_spec.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::BlobsResolver do + include GraphqlHelpers + + describe '.resolver_complexity' do + it 'adds one per path being resolved' do + control = described_class.resolver_complexity({}, child_complexity: 1) + + expect(described_class.resolver_complexity({ paths: %w[a b c] }, child_complexity: 1)) + .to eq(control + 3) + end + end + + describe '#resolve' do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :repository) } + + let(:repository) { project.repository } + let(:args) { { paths: paths, ref: ref } } + let(:paths) { [] } + let(:ref) { nil } + + subject(:resolve_blobs) { resolve(described_class, obj: repository, args: args, ctx: { current_user: user }) } + + context 'when unauthorized' do + it 'raises an exception' do + expect { resolve_blobs }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context 'when authorized' do + before do + project.add_developer(user) + end + + context 'using no filter' do + it 'returns nothing' do + is_expected.to be_empty + end + end + + context 'using paths filter' do + let(:paths) { ['README.md'] } + + it 'returns the specified blobs for HEAD' do + is_expected.to contain_exactly(have_attributes(path: 'README.md')) + end + + context 'specifying a non-existent blob' do + let(:paths) { ['non-existent'] } + + it 'returns nothing' do + is_expected.to be_empty + end + end + + context 'specifying a different ref' do + let(:ref) { 'add-pdf-file' } + let(:paths) { ['files/pdf/test.pdf', 'README.md'] } + + it 'returns the specified blobs for that ref' do + is_expected.to contain_exactly( + have_attributes(path: 'files/pdf/test.pdf'), + have_attributes(path: 'README.md') + ) + end + end + end + end + end +end diff --git a/spec/graphql/resolvers/board_list_issues_resolver_spec.rb b/spec/graphql/resolvers/board_list_issues_resolver_spec.rb index 5eda840854a..6ffc8b045e9 100644 --- a/spec/graphql/resolvers/board_list_issues_resolver_spec.rb +++ b/spec/graphql/resolvers/board_list_issues_resolver_spec.rb @@ -39,6 +39,24 @@ RSpec.describe Resolvers::BoardListIssuesResolver do expect(result).to match_array([issue1]) end + + it 'raises an exception if both assignee_username and assignee_wildcard_id are present' do + expect do + resolve_board_list_issues(args: { filters: { assignee_username: ['username'], assignee_wildcard_id: 'NONE' } }) + end.to raise_error(Gitlab::Graphql::Errors::ArgumentError) + end + + 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]) + end + + it 'accepts assignee wildcard id ANY' do + result = resolve_board_list_issues(args: { filters: { assignee_wildcard_id: 'ANY' } }) + + expect(result).to match_array([]) + end end end diff --git a/spec/graphql/resolvers/ci/jobs_resolver_spec.rb b/spec/graphql/resolvers/ci/jobs_resolver_spec.rb index c44f6b623d7..1b69bf7f63a 100644 --- a/spec/graphql/resolvers/ci/jobs_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/jobs_resolver_spec.rb @@ -13,6 +13,7 @@ RSpec.describe Resolvers::Ci::JobsResolver do create(:ci_build, :sast, name: 'DAST job', pipeline: pipeline) create(:ci_build, :dast, name: 'SAST job', pipeline: pipeline) create(:ci_build, :container_scanning, name: 'Container scanning job', pipeline: pipeline) + create(:ci_build, name: 'Job with tags', pipeline: pipeline, tag_list: ['review']) end describe '#resolve' do @@ -24,7 +25,8 @@ RSpec.describe Resolvers::Ci::JobsResolver do have_attributes(name: 'Normal job'), have_attributes(name: 'DAST job'), have_attributes(name: 'SAST job'), - have_attributes(name: 'Container scanning job') + have_attributes(name: 'Container scanning job'), + have_attributes(name: 'Job with tags') ) end end @@ -43,5 +45,18 @@ RSpec.describe Resolvers::Ci::JobsResolver do ) end end + + context 'when a job has tags' do + it "returns jobs with tags when applicable" do + jobs = resolve(described_class, obj: pipeline) + expect(jobs).to contain_exactly( + have_attributes(tag_list: []), + have_attributes(tag_list: []), + have_attributes(tag_list: []), + have_attributes(tag_list: []), + have_attributes(tag_list: ['review']) + ) + end + end end end diff --git a/spec/graphql/resolvers/ci/runner_platforms_resolver_spec.rb b/spec/graphql/resolvers/ci/runner_platforms_resolver_spec.rb index 1eb6f363d5b..3cb6e94e81e 100644 --- a/spec/graphql/resolvers/ci/runner_platforms_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/runner_platforms_resolver_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Resolvers::Ci::RunnerPlatformsResolver do subject(:resolve_subject) { resolve(described_class) } it 'returns all possible runner platforms' do - expect(resolve_subject).to include( + expect(resolve_subject).to contain_exactly( hash_including(name: :linux), hash_including(name: :osx), hash_including(name: :windows), hash_including(name: :docker), hash_including(name: :kubernetes) diff --git a/spec/graphql/resolvers/ci/runner_setup_resolver_spec.rb b/spec/graphql/resolvers/ci/runner_setup_resolver_spec.rb index 3d004290d9b..13ef89023d9 100644 --- a/spec/graphql/resolvers/ci/runner_setup_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/runner_setup_resolver_spec.rb @@ -8,12 +8,11 @@ RSpec.describe Resolvers::Ci::RunnerSetupResolver do describe '#resolve' do let(:user) { create(:user) } - subject(:resolve_subject) { resolve(described_class, ctx: { current_user: user }, args: { platform: platform, architecture: 'amd64' }.merge(target_param)) } + subject(:resolve_subject) { resolve(described_class, ctx: { current_user: user }, args: { platform: platform, architecture: 'amd64' }) } context 'with container platforms' do let(:platform) { 'docker' } let(:project) { create(:project) } - let(:target_param) { { project_id: project.to_global_id } } it 'returns install instructions' do expect(resolve_subject[:install_instructions]).not_to eq(nil) @@ -27,77 +26,9 @@ RSpec.describe Resolvers::Ci::RunnerSetupResolver do context 'with regular platforms' do let(:platform) { 'linux' } - context 'without target parameter' do - let(:target_param) { {} } - - context 'when user is not admin' do - it 'returns access error' do - expect { resolve_subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end - end - - context 'when user is admin' do - before do - user.update!(admin: true) - end - - it 'returns install and register instructions' do - expect(resolve_subject.keys).to contain_exactly(:install_instructions, :register_instructions) - expect(resolve_subject.values).not_to include(nil) - end - end - end - - context 'with project target parameter' do - let(:project) { create(:project) } - let(:target_param) { { project_id: project.to_global_id } } - - context 'when user has access to admin builds on project' do - before do - project.add_maintainer(user) - end - - it 'returns install and register instructions' do - expect(resolve_subject.keys).to contain_exactly(:install_instructions, :register_instructions) - expect(resolve_subject.values).not_to include(nil) - end - end - - context 'when user does not have access to admin builds on project' do - before do - project.add_developer(user) - end - - it 'returns access error' do - expect { resolve_subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end - end - end - - context 'with group target parameter' do - let(:group) { create(:group) } - let(:target_param) { { group_id: group.to_global_id } } - - context 'when user has access to admin builds on group' do - before do - group.add_owner(user) - end - - it 'returns install and register instructions' do - expect(resolve_subject.keys).to contain_exactly(:install_instructions, :register_instructions) - expect(resolve_subject.values).not_to include(nil) - end - end - - context 'when user does not have access to admin builds on group' do - before do - group.add_developer(user) - end - - it 'returns access error' do - expect { resolve_subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end - end + it 'returns install and register instructions' do + expect(resolve_subject.keys).to contain_exactly(:install_instructions, :register_instructions) + expect(resolve_subject.values).not_to include(nil) end end end diff --git a/spec/graphql/resolvers/ci/test_report_summary_resolver_spec.rb b/spec/graphql/resolvers/ci/test_report_summary_resolver_spec.rb new file mode 100644 index 00000000000..e78bd06b567 --- /dev/null +++ b/spec/graphql/resolvers/ci/test_report_summary_resolver_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Ci::TestReportSummaryResolver do + include GraphqlHelpers + + describe '#resolve' do + let(:user) { create(:user) } + let(:project) { create(:project, :public, :repository) } + + subject(:resolve_subject) { resolve(described_class, obj: pipeline) } + + context 'when pipeline has build report results' do + let(:pipeline) { create(:ci_pipeline, :with_report_results, project: project) } + + it 'returns test report summary data' do + expect(resolve_subject.keys).to contain_exactly(:total, :test_suites) + expect(resolve_subject[:test_suites][0].keys).to contain_exactly(:build_ids, :name, :total_time, :total_count, :success_count, :failed_count, :skipped_count, :error_count, :suite_error) + expect(resolve_subject[:total][:time]).to eq(0.42) + expect(resolve_subject[:total][:count]).to eq(2) + expect(resolve_subject[:total][:success]).to eq(0) + expect(resolve_subject[:total][:failed]).to eq(0) + expect(resolve_subject[:total][:skipped]).to eq(0) + expect(resolve_subject[:total][:error]).to eq(2) + expect(resolve_subject[:total][:suite_error]).to eq(nil) + end + end + + context 'when pipeline does not have build report results' do + let(:pipeline) { create(:ci_pipeline, project: project) } + + it 'renders test report summary data' do + expect(resolve_subject.keys).to contain_exactly(:total, :test_suites) + expect(resolve_subject[:test_suites]).to eq([]) + expect(resolve_subject[:total][:time]).to eq(0) + expect(resolve_subject[:total][:count]).to eq(0) + expect(resolve_subject[:total][:success]).to eq(0) + expect(resolve_subject[:total][:failed]).to eq(0) + expect(resolve_subject[:total][:skipped]).to eq(0) + expect(resolve_subject[:total][:error]).to eq(0) + expect(resolve_subject[:total][:suite_error]).to eq(nil) + end + end + end +end diff --git a/spec/graphql/resolvers/ci/test_suite_resolver_spec.rb b/spec/graphql/resolvers/ci/test_suite_resolver_spec.rb new file mode 100644 index 00000000000..606c6eb03a3 --- /dev/null +++ b/spec/graphql/resolvers/ci/test_suite_resolver_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Ci::TestSuiteResolver do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public, :repository) } + + describe '#resolve' do + subject(:test_suite) { resolve(described_class, obj: pipeline, args: { build_ids: build_ids }) } + + context 'when pipeline has builds with test reports' do + let_it_be(:main_pipeline) { create(:ci_pipeline, :with_test_reports_with_three_failures, project: project) } + let_it_be(:pipeline) { create(:ci_pipeline, :with_test_reports_with_three_failures, project: project, ref: 'new-feature') } + + let(:suite_name) { 'test' } + let(:build_ids) { pipeline.latest_builds.pluck(:id) } + + before do + build = main_pipeline.builds.last + build.update_column(:finished_at, 1.day.ago) # Just to be sure we are included in the report window + + # The JUnit fixture for the given build has 3 failures. + # This service will create 1 test case failure record for each. + Ci::TestFailureHistoryService.new(main_pipeline).execute + end + + it 'renders test suite data' do + expect(test_suite[:name]).to eq('test') + + # Each test failure in this pipeline has a matching failure in the default branch + recent_failures = test_suite[:test_cases].map { |tc| tc[:recent_failures] } + expect(recent_failures).to eq([ + { count: 1, base_branch: 'master' }, + { count: 1, base_branch: 'master' }, + { count: 1, base_branch: 'master' } + ]) + end + end + + context 'when pipeline has no builds that matches the given build_ids' do + let_it_be(:pipeline) { create(:ci_empty_pipeline) } + + let(:suite_name) { 'test' } + let(:build_ids) { [non_existing_record_id] } + + it 'returns nil' do + expect(test_suite).to be_nil + end + end + end +end diff --git a/spec/graphql/resolvers/concerns/looks_ahead_spec.rb b/spec/graphql/resolvers/concerns/looks_ahead_spec.rb index 27ac1572cab..4c244da5c62 100644 --- a/spec/graphql/resolvers/concerns/looks_ahead_spec.rb +++ b/spec/graphql/resolvers/concerns/looks_ahead_spec.rb @@ -38,11 +38,8 @@ RSpec.describe LooksAhead do user = Class.new(GraphQL::Schema::Object) do graphql_name 'User' field :name, String, null: true - field :issues, issue.connection_type, - null: true - field :issues_with_lookahead, issue.connection_type, - resolver: issues_resolver, - null: true + field :issues, issue.connection_type, null: true + field :issues_with_lookahead, issue.connection_type, resolver: issues_resolver, null: true end Class.new(GraphQL::Schema) do @@ -101,7 +98,7 @@ RSpec.describe LooksAhead do expect(res['errors']).to be_blank expect(res.dig('data', 'findUser', 'name')).to eq(the_user.name) - %w(issues issuesWithLookahead).each do |field| + %w[issues issuesWithLookahead].each do |field| expect(all_issue_titles(res, field)).to match_array(issue_titles) expect(all_label_ids(res, field)).to match_array(expected_label_ids) end diff --git a/spec/graphql/resolvers/group_milestones_resolver_spec.rb b/spec/graphql/resolvers/group_milestones_resolver_spec.rb index d8ff8e9c1f2..dd3f1676538 100644 --- a/spec/graphql/resolvers/group_milestones_resolver_spec.rb +++ b/spec/graphql/resolvers/group_milestones_resolver_spec.rb @@ -136,5 +136,56 @@ RSpec.describe Resolvers::GroupMilestonesResolver do expect(resolve_group_milestones(args)).to match_array([milestone1, milestone2, milestone3]) end end + + describe 'include_descendants and include_ancestors' do + let_it_be(:parent_group) { create(:group, :public) } + let_it_be(:group) { create(:group, :public, parent: parent_group) } + let_it_be(:accessible_group) { create(:group, :private, parent: group) } + let_it_be(:accessible_project) { create(:project, group: accessible_group) } + let_it_be(:inaccessible_group) { create(:group, :private, parent: group) } + let_it_be(:inaccessible_project) { create(:project, :private, group: group) } + let_it_be(:milestone1) { create(:milestone, group: group) } + let_it_be(:milestone2) { create(:milestone, group: accessible_group) } + let_it_be(:milestone3) { create(:milestone, project: accessible_project) } + let_it_be(:milestone4) { create(:milestone, group: inaccessible_group) } + let_it_be(:milestone5) { create(:milestone, project: inaccessible_project) } + let_it_be(:milestone6) { create(:milestone, group: parent_group) } + + before do + accessible_group.add_developer(current_user) + end + + context 'when including neither ancestor or descendant milestones in a public group' do + let(:args) { {} } + + it 'finds milestones only in accessible projects and groups' do + expect(resolve_group_milestones(args)).to match_array([milestone1]) + end + end + + context 'when including descendant milestones in a public group' do + let(:args) { { include_descendants: true } } + + it 'finds milestones only in accessible projects and groups' do + expect(resolve_group_milestones(args)).to match_array([milestone1, milestone2, milestone3]) + end + end + + context 'when including ancestor milestones in a public group' do + let(:args) { { include_ancestors: true } } + + it 'finds milestones only in accessible projects and groups' do + expect(resolve_group_milestones(args)).to match_array([milestone1, milestone6]) + end + end + + context 'when including both ancestor or descendant milestones in a public group' do + let(:args) { { include_descendants: true, include_ancestors: true } } + + it 'finds milestones only in accessible projects and groups' do + expect(resolve_group_milestones(args)).to match_array([milestone1, milestone2, milestone3, milestone6]) + end + end + end end end diff --git a/spec/graphql/resolvers/issue_status_counts_resolver_spec.rb b/spec/graphql/resolvers/issue_status_counts_resolver_spec.rb index decc3569d6c..3fbd9bd2368 100644 --- a/spec/graphql/resolvers/issue_status_counts_resolver_spec.rb +++ b/spec/graphql/resolvers/issue_status_counts_resolver_spec.rb @@ -69,6 +69,14 @@ RSpec.describe Resolvers::IssueStatusCountsResolver do expect(result.closed).to eq 1 end + context 'when both assignee_username and assignee_usernames are provided' do + it 'raises a mutually exclusive filter error' do + expect do + resolve_issue_status_counts(assignee_usernames: [current_user.username], assignee_username: current_user.username) + end.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'only one of [assigneeUsernames, assigneeUsername] arguments is allowed at the same time.') + end + end + private def resolve_issue_status_counts(args = {}, context = { current_user: current_user }) diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index 6e802bf7d25..7c2ceb50066 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -46,10 +46,6 @@ RSpec.describe Resolvers::IssuesResolver do expect(resolve_issues(milestone_title: [milestone.title])).to contain_exactly(issue1) end - it 'filters by assignee_username' do - expect(resolve_issues(assignee_username: [assignee.username])).to contain_exactly(issue2) - end - it 'filters by two assignees' do assignee2 = create(:user) issue2.update!(assignees: [assignee, assignee2]) @@ -78,6 +74,24 @@ RSpec.describe Resolvers::IssuesResolver do expect(resolve_issues(label_name: [label1.title, label2.title])).to contain_exactly(issue2) end + describe 'filters by assignee_username' do + it 'filters by assignee_username' do + expect(resolve_issues(assignee_username: [assignee.username])).to contain_exactly(issue2) + end + + it 'filters by assignee_usernames' do + expect(resolve_issues(assignee_usernames: [assignee.username])).to contain_exactly(issue2) + end + + context 'when both assignee_username and assignee_usernames are provided' do + it 'raises a mutually exclusive filter error' do + expect do + resolve_issues(assignee_usernames: [assignee.username], assignee_username: assignee.username) + end.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'only one of [assigneeUsernames, assigneeUsername] arguments is allowed at the same time.') + end + end + end + describe 'filters by created_at' do it 'filters by created_before' do expect(resolve_issues(created_before: 2.hours.ago)).to contain_exactly(issue1) @@ -144,6 +158,29 @@ RSpec.describe Resolvers::IssuesResolver do end end + describe 'filters by negated params' do + it 'returns issues without the specified iids' do + expect(resolve_issues(not: { iids: [issue1.iid] })).to contain_exactly(issue2) + end + + it 'returns issues without the specified label names' do + expect(resolve_issues(not: { label_name: [label1.title] })).to be_empty + expect(resolve_issues(not: { label_name: [label2.title] })).to contain_exactly(issue1) + end + + it 'returns issues without the specified milestone' do + expect(resolve_issues(not: { milestone_title: [milestone.title] })).to contain_exactly(issue2) + end + + it 'returns issues without the specified assignee_usernames' do + expect(resolve_issues(not: { assignee_usernames: [assignee.username] })).to contain_exactly(issue1) + end + + it 'returns issues without the specified assignee_id' do + expect(resolve_issues(not: { assignee_id: [assignee.id] })).to contain_exactly(issue1) + end + end + describe 'sorting' do context 'when sorting by created' do it 'sorts issues ascending' do diff --git a/spec/graphql/resolvers/merge_requests_resolver_spec.rb b/spec/graphql/resolvers/merge_requests_resolver_spec.rb index 7dd968d90a8..aec6c6c6708 100644 --- a/spec/graphql/resolvers/merge_requests_resolver_spec.rb +++ b/spec/graphql/resolvers/merge_requests_resolver_spec.rb @@ -7,6 +7,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do include SortingHelper let_it_be(:project) { create(:project, :repository) } + let_it_be(:other_project) { create(:project, :repository) } let_it_be(:milestone) { create(:milestone, project: project) } let_it_be(:current_user) { create(:user) } let_it_be(:other_user) { create(:user) } @@ -16,10 +17,17 @@ RSpec.describe Resolvers::MergeRequestsResolver do let_it_be(:merge_request_3) { create(:merge_request, :unique_branches, **common_attrs) } let_it_be(:merge_request_4) { create(:merge_request, :unique_branches, :locked, **common_attrs) } let_it_be(:merge_request_5) { create(:merge_request, :simple, :locked, **common_attrs) } - let_it_be(:merge_request_6) { create(:labeled_merge_request, :unique_branches, labels: create_list(:label, 2, project: project), **common_attrs) } - let_it_be(:merge_request_with_milestone) { create(:merge_request, :unique_branches, **common_attrs, milestone: milestone) } - let_it_be(:other_project) { create(:project, :repository) } - let_it_be(:other_merge_request) { create(:merge_request, source_project: other_project, target_project: other_project) } + let_it_be(:merge_request_6) do + create(:labeled_merge_request, :unique_branches, **common_attrs, labels: create_list(:label, 2, project: project)) + end + + let_it_be(:merge_request_with_milestone) do + create(:merge_request, :unique_branches, **common_attrs, milestone: milestone) + end + + let_it_be(:other_merge_request) do + create(:merge_request, source_project: other_project, target_project: other_project) + end let(:iid_1) { merge_request_1.iid } let(:iid_2) { merge_request_2.iid } @@ -41,13 +49,16 @@ RSpec.describe Resolvers::MergeRequestsResolver do # AND "merge_requests"."iid" = 1 ORDER BY "merge_requests"."id" DESC # SELECT "projects".* FROM "projects" WHERE "projects"."id" = 2 # SELECT "project_features".* FROM "project_features" WHERE "project_features"."project_id" = 2 - let(:queries_per_project) { 3 } + let(:queries_per_project) { 4 } - context 'no arguments' do + context 'without arguments' do it 'returns all merge requests' do result = resolve_mr(project) - expect(result).to contain_exactly(merge_request_1, merge_request_2, merge_request_3, merge_request_4, merge_request_5, merge_request_6, merge_request_with_milestone) + expect(result).to contain_exactly( + merge_request_1, merge_request_2, merge_request_3, merge_request_4, merge_request_5, + merge_request_6, merge_request_with_milestone + ) end it 'returns only merge requests that the current user can see' do @@ -57,7 +68,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do end end - context 'by iid alone' do + context 'with iid alone' do it 'batch-resolves by target project full path and individual IID', :request_store do # 1 query for project_authorizations, and 1 for merge_requests result = batch_sync(max_queries: queries_per_project) do @@ -83,7 +94,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do expect(result).to contain_exactly(merge_request_1, merge_request_2, merge_request_3) end - it 'can batch-resolve merge requests from different projects', :request_store, :use_clean_rails_memory_store_caching do + it 'can batch-resolve merge requests from different projects', :request_store do # 2 queries for project_authorizations, and 2 for merge_requests results = batch_sync(max_queries: queries_per_project * 2) do a = resolve_mr(project, iids: [iid_1]) @@ -121,7 +132,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do end end - context 'by source branches' do + context 'with source branches argument' do it 'takes one argument' do result = resolve_mr(project, source_branches: [merge_request_3.source_branch]) @@ -131,13 +142,13 @@ RSpec.describe Resolvers::MergeRequestsResolver do it 'takes more than one argument' do mrs = [merge_request_3, merge_request_4] branches = mrs.map(&:source_branch) - result = resolve_mr(project, source_branches: branches ) + result = resolve_mr(project, source_branches: branches) expect(result).to match_array(mrs) end end - context 'by target branches' do + context 'with target branches argument' do it 'takes one argument' do result = resolve_mr(project, target_branches: [merge_request_3.target_branch]) @@ -153,7 +164,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do end end - context 'by state' do + context 'with state argument' do it 'takes one argument' do result = resolve_mr(project, state: 'locked') @@ -161,7 +172,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do end end - context 'by label' do + context 'with label argument' do let_it_be(:label) { merge_request_6.labels.first } let_it_be(:with_label) { create(:labeled_merge_request, :closed, labels: [label], **common_attrs) } @@ -178,7 +189,18 @@ RSpec.describe Resolvers::MergeRequestsResolver do end end - context 'by merged_after and merged_before' do + context 'with negated label argument' do + let_it_be(:label) { merge_request_6.labels.first } + let_it_be(:with_label) { create(:labeled_merge_request, :closed, labels: [label], **common_attrs) } + + it 'excludes merge requests with given label from selection' do + result = resolve_mr(project, not: { labels: [label.title] }) + + expect(result).not_to include(merge_request_6, with_label) + end + end + + context 'with merged_after and merged_before arguments' do before do merge_request_1.metrics.update!(merged_at: 10.days.ago) end @@ -196,7 +218,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do end end - context 'by milestone' do + context 'with milestone argument' do it 'filters merge requests by milestone title' do result = resolve_mr(project, milestone_title: milestone.title) @@ -210,9 +232,17 @@ RSpec.describe Resolvers::MergeRequestsResolver do end end + context 'with negated milestone argument' do + it 'filters out merge requests with given milestone title' do + result = resolve_mr(project, not: { milestone_title: milestone.title }) + + expect(result).not_to include(merge_request_with_milestone) + end + end + describe 'combinations' do it 'requires all filters' do - create(:merge_request, :closed, source_project: project, target_project: project, source_branch: merge_request_4.source_branch) + create(:merge_request, :closed, **common_attrs, source_branch: merge_request_4.source_branch) result = resolve_mr(project, source_branches: [merge_request_4.source_branch], state: 'locked') diff --git a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb index 147a02e1d79..618d012bd6d 100644 --- a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb +++ b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb @@ -112,7 +112,7 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do subject(:projects) { resolve_projects(args) } let(:include_subgroups) { false } - let(:project_3) { create(:project, name: 'Project', path: 'project', namespace: namespace) } + let!(:project_3) { create(:project, name: 'Project', path: 'project', namespace: namespace) } context 'when ids is provided' do let(:ids) { [project_3.to_global_id.to_s] } diff --git a/spec/graphql/resolvers/project_jobs_resolver_spec.rb b/spec/graphql/resolvers/project_jobs_resolver_spec.rb new file mode 100644 index 00000000000..94df2999163 --- /dev/null +++ b/spec/graphql/resolvers/project_jobs_resolver_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::ProjectJobsResolver do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :repository) } + let_it_be(:irrelevant_project) { create(:project, :repository) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be(:irrelevant_pipeline) { create(:ci_pipeline, project: irrelevant_project) } + let_it_be(:build_one) { create(:ci_build, :success, name: 'Build One', pipeline: pipeline) } + let_it_be(:build_two) { create(:ci_build, :success, name: 'Build Two', pipeline: pipeline) } + let_it_be(:build_three) { create(:ci_build, :failed, name: 'Build Three', pipeline: pipeline) } + + let(:irrelevant_build) { create(:ci_build, name: 'Irrelevant Build', pipeline: irrelevant_pipeline)} + let(:args) { {} } + let(:current_user) { create(:user) } + + subject { resolve_jobs(args) } + + describe '#resolve' do + context 'with authorized user' do + before do + project.add_developer(current_user) + end + + context 'with statuses argument' do + let(:args) { { statuses: [Types::Ci::JobStatusEnum.coerce_isolated_input('SUCCESS')] } } + + it { is_expected.to contain_exactly(build_one, build_two) } + end + + context 'without statuses argument' do + it { is_expected.to contain_exactly(build_one, build_two, build_three) } + end + end + + context 'with unauthorized user' do + let(:current_user) { nil } + + it { is_expected.to be_nil } + end + end + + private + + def resolve_jobs(args = {}, context = { current_user: current_user }) + resolve(described_class, obj: project, args: args, ctx: context) + end +end diff --git a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb index 69127c4b061..3d33e0b500d 100644 --- a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb +++ b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb @@ -7,6 +7,7 @@ RSpec.describe Resolvers::ProjectPipelineResolver do let_it_be(:project) { create(:project) } let_it_be(:pipeline) { create(:ci_pipeline, project: project, iid: '1234', sha: 'sha') } + let_it_be(:other_project_pipeline) { create(:ci_pipeline, project: project, iid: '1235', sha: 'sha2') } let_it_be(:other_pipeline) { create(:ci_pipeline) } let(:current_user) { create(:user) } @@ -23,6 +24,11 @@ RSpec.describe Resolvers::ProjectPipelineResolver do end it 'resolves pipeline for the passed iid' do + expect(Ci::PipelinesFinder) + .to receive(:new) + .with(project, current_user, iids: ['1234']) + .and_call_original + result = batch_sync do resolve_pipeline(project, { iid: '1234' }) end @@ -31,6 +37,11 @@ RSpec.describe Resolvers::ProjectPipelineResolver do end it 'resolves pipeline for the passed sha' do + expect(Ci::PipelinesFinder) + .to receive(:new) + .with(project, current_user, sha: ['sha']) + .and_call_original + result = batch_sync do resolve_pipeline(project, { sha: 'sha' }) end @@ -39,8 +50,6 @@ RSpec.describe Resolvers::ProjectPipelineResolver do end it 'keeps the queries under the threshold for iid' do - create(:ci_pipeline, project: project, iid: '1235') - control = ActiveRecord::QueryRecorder.new do batch_sync { resolve_pipeline(project, { iid: '1234' }) } end @@ -54,8 +63,6 @@ RSpec.describe Resolvers::ProjectPipelineResolver do end it 'keeps the queries under the threshold for sha' do - create(:ci_pipeline, project: project, sha: 'sha2') - control = ActiveRecord::QueryRecorder.new do batch_sync { resolve_pipeline(project, { sha: 'sha' }) } end diff --git a/spec/graphql/resolvers/repository_branch_names_resolver_spec.rb b/spec/graphql/resolvers/repository_branch_names_resolver_spec.rb new file mode 100644 index 00000000000..398dd7a2e2e --- /dev/null +++ b/spec/graphql/resolvers/repository_branch_names_resolver_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::RepositoryBranchNamesResolver do + include GraphqlHelpers + + let(:project) { create(:project, :repository) } + + describe '#resolve' do + subject(:resolve_branch_names) do + resolve( + described_class, + obj: project.repository, + args: { search_pattern: pattern }, + ctx: { current_user: project.creator } + ) + end + + context 'with empty search pattern' do + let(:pattern) { '' } + + it 'returns nil' do + expect(resolve_branch_names).to eq(nil) + end + end + + context 'with a valid search pattern' do + let(:pattern) { 'mas*' } + + it 'returns matching branches' do + expect(resolve_branch_names).to match_array(['master']) + end + end + end +end diff --git a/spec/graphql/resolvers/timelog_resolver_spec.rb b/spec/graphql/resolvers/timelog_resolver_spec.rb new file mode 100644 index 00000000000..585cd657e35 --- /dev/null +++ b/spec/graphql/resolvers/timelog_resolver_spec.rb @@ -0,0 +1,168 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::TimelogResolver do + include GraphqlHelpers + + specify do + expect(described_class).to have_non_null_graphql_type(::Types::TimelogType.connection_type) + end + + context "with a group" do + let_it_be(:current_user) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :public, group: group) } + + before_all do + group.add_developer(current_user) + project.add_developer(current_user) + end + + before do + group.clear_memoization(:timelogs) + end + + describe '#resolve' do + let_it_be(:issue) { create(:issue, project: project) } + let_it_be(:issue2) { create(:issue, project: project) } + let_it_be(:timelog1) { create(:issue_timelog, issue: issue, spent_at: 2.days.ago.beginning_of_day) } + let_it_be(:timelog2) { create(:issue_timelog, issue: issue2, spent_at: 2.days.ago.end_of_day) } + let_it_be(:timelog3) { create(:issue_timelog, issue: issue2, spent_at: 10.days.ago) } + + let(:args) { { start_time: 6.days.ago, end_time: 2.days.ago.noon } } + + it 'finds all timelogs within given dates' do + timelogs = resolve_timelogs(**args) + + expect(timelogs).to contain_exactly(timelog1) + end + + it 'return nothing when user has insufficient permissions' do + user = create(:user) + group.add_guest(current_user) + + expect(resolve_timelogs(user: user, **args)).to be_empty + end + + context 'when start_time and end_date are present' do + let(:args) { { start_time: 6.days.ago, end_date: 2.days.ago } } + + it 'finds timelogs until the end of day of end_date' do + timelogs = resolve_timelogs(**args) + + expect(timelogs).to contain_exactly(timelog1, timelog2) + end + end + + context 'when start_date and end_time are present' do + let(:args) { { start_date: 6.days.ago, end_time: 2.days.ago.noon } } + + it 'finds all timelogs within start_date and end_time' do + timelogs = resolve_timelogs(**args) + + expect(timelogs).to contain_exactly(timelog1) + end + end + + context 'when arguments are invalid' do + let_it_be(:error_class) { Gitlab::Graphql::Errors::ArgumentError } + + context 'when no time or date arguments are present' do + let(:args) { {} } + + it 'returns correct error' do + expect { resolve_timelogs(**args) } + .to raise_error(error_class, /Start and End arguments must be present/) + end + end + + context 'when only start_time is present' do + let(:args) { { start_time: 6.days.ago } } + + it 'returns correct error' do + expect { resolve_timelogs(**args) } + .to raise_error(error_class, /Both Start and End arguments must be present/) + end + end + + context 'when only end_time is present' do + let(:args) { { end_time: 2.days.ago } } + + it 'returns correct error' do + expect { resolve_timelogs(**args) } + .to raise_error(error_class, /Both Start and End arguments must be present/) + end + end + + context 'when only start_date is present' do + let(:args) { { start_date: 6.days.ago } } + + it 'returns correct error' do + expect { resolve_timelogs(**args) } + .to raise_error(error_class, /Both Start and End arguments must be present/) + end + end + + context 'when only end_date is present' do + let(:args) { { end_date: 2.days.ago } } + + it 'returns correct error' do + expect { resolve_timelogs(**args) } + .to raise_error(error_class, /Both Start and End arguments must be present/) + end + end + + context 'when start_time and start_date are present' do + let(:args) { { start_time: 6.days.ago, start_date: 6.days.ago } } + + it 'returns correct error' do + expect { resolve_timelogs(**args) } + .to raise_error(error_class, /Both Start and End arguments must be present/) + end + end + + context 'when end_time and end_date are present' do + let(:args) { { end_time: 2.days.ago, end_date: 2.days.ago } } + + it 'returns correct error' do + expect { resolve_timelogs(**args) } + .to raise_error(error_class, /Both Start and End arguments must be present/) + end + end + + context 'when three arguments are present' do + let(:args) { { start_date: 6.days.ago, end_date: 2.days.ago, end_time: 2.days.ago } } + + it 'returns correct error' do + expect { resolve_timelogs(**args) } + .to raise_error(error_class, /Only Time or Date arguments must be present/) + end + end + + context 'when start argument is after end argument' do + let(:args) { { start_time: 2.days.ago, end_time: 6.days.ago } } + + it 'returns correct error' do + expect { resolve_timelogs(**args) } + .to raise_error(error_class, /Start argument must be before End argument/) + end + end + + context 'when time range is more than 60 days' do + let(:args) { { start_time: 3.months.ago, end_time: 2.days.ago } } + + it 'returns correct error' do + expect { resolve_timelogs(**args) } + .to raise_error(error_class, /The time range period cannot contain more than 60 days/) + end + end + end + end + end + + def resolve_timelogs(user: current_user, **args) + context = { current_user: user } + resolve(described_class, obj: group, args: args, ctx: context) + end +end diff --git a/spec/graphql/resolvers/users/snippets_resolver_spec.rb b/spec/graphql/resolvers/users/snippets_resolver_spec.rb index 11a5b7517e0..04fe3213a99 100644 --- a/spec/graphql/resolvers/users/snippets_resolver_spec.rb +++ b/spec/graphql/resolvers/users/snippets_resolver_spec.rb @@ -75,9 +75,19 @@ RSpec.describe Resolvers::Users::SnippetsResolver do end.to raise_error(GraphQL::CoercionError) end end + + context 'when user profile is private' do + it 'does not return snippets for that user' do + expect(resolve_snippets(obj: other_user)).to contain_exactly(other_personal_snippet, other_project_snippet) + + other_user.update!(private_profile: true) + + expect(resolve_snippets(obj: other_user)).to be_empty + end + end end - def resolve_snippets(args: {}) - resolve(described_class, args: args, ctx: { current_user: current_user }, obj: current_user) + def resolve_snippets(args: {}, context_user: current_user, obj: current_user) + resolve(described_class, args: args, ctx: { current_user: context_user }, obj: obj) end end |