From 7021455bd1ed7b125c55eb1b33c5a01f2bc55ee0 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 17 Nov 2022 11:33:21 +0000 Subject: Add latest changes from gitlab-org/gitlab@15-6-stable-ee --- .../graphql/boards/board_list_issues_query_spec.rb | 40 ++- .../api/graphql/boards/board_lists_query_spec.rb | 63 ++++- spec/requests/api/graphql/ci/jobs_spec.rb | 4 +- spec/requests/api/graphql/ci/pipelines_spec.rb | 6 +- spec/requests/api/graphql/ci/runner_spec.rb | 3 +- .../api/graphql/group/work_item_types_spec.rb | 11 - spec/requests/api/graphql/issues_spec.rb | 117 +++++++++ spec/requests/api/graphql/metadata_query_spec.rb | 3 +- .../graphql/mutations/award_emojis/remove_spec.rb | 2 +- .../graphql/mutations/award_emojis/toggle_spec.rb | 2 +- .../ci/pipeline_schedule_take_ownership_spec.rb | 41 +++ .../ci/runners_registration_token/reset_spec.rb | 2 +- .../mutations/container_repository/destroy_spec.rb | 23 +- .../timeline_event/create_spec.rb | 32 ++- .../timeline_event/promote_from_note_spec.rb | 2 +- .../timeline_event/update_spec.rb | 12 +- .../timeline_event_tag/create_spec.rb | 57 ++++ .../api/graphql/mutations/issues/create_spec.rb | 33 +-- .../mutations/work_items/create_from_task_spec.rb | 14 - .../graphql/mutations/work_items/create_spec.rb | 64 ++++- .../graphql/mutations/work_items/delete_spec.rb | 14 - .../mutations/work_items/delete_task_spec.rb | 14 - .../graphql/mutations/work_items/update_spec.rb | 105 ++++++-- .../mutations/work_items/update_task_spec.rb | 16 -- spec/requests/api/graphql/packages/package_spec.rb | 20 ++ .../branch_protections/merge_access_levels_spec.rb | 104 +------- .../branch_protections/push_access_levels_spec.rb | 104 +------- .../api/graphql/project/branch_rules_spec.rb | 143 ++++++++--- .../incident_management/timeline_events_spec.rb | 43 ++++ spec/requests/api/graphql/project/issues_spec.rb | 286 ++++++++------------- .../requests/api/graphql/project/languages_spec.rb | 62 +++++ .../requests/api/graphql/project/tree/tree_spec.rb | 86 ++++++- .../api/graphql/project/work_item_types_spec.rb | 11 - .../api/graphql/project/work_items_spec.rb | 25 +- spec/requests/api/graphql/work_item_spec.rb | 46 +++- 35 files changed, 1004 insertions(+), 606 deletions(-) create mode 100644 spec/requests/api/graphql/issues_spec.rb create mode 100644 spec/requests/api/graphql/mutations/ci/pipeline_schedule_take_ownership_spec.rb create mode 100644 spec/requests/api/graphql/mutations/incident_management/timeline_event_tag/create_spec.rb create mode 100644 spec/requests/api/graphql/project/languages_spec.rb (limited to 'spec/requests/api/graphql') diff --git a/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb b/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb index 484ddc3469b..9bed720c815 100644 --- a/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb +++ b/spec/requests/api/graphql/boards/board_list_issues_query_spec.rb @@ -7,7 +7,7 @@ RSpec.describe 'get board lists' do let_it_be(:user) { 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(: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(:project_label2) { create(:label, project: project, name: 'Testing') } @@ -21,6 +21,7 @@ RSpec.describe 'get board lists' do let(:board_data) { graphql_data[board_parent_type]['boards']['nodes'][0] } let(:lists_data) { board_data['lists']['nodes'][0] } let(:issues_data) { lists_data['issues']['nodes'] } + let(:issue_params) { { filters: { label_name: label2.title, confidential: confidential }, first: 3 } } def query(list_params = params) graphql_query_for( @@ -31,7 +32,7 @@ RSpec.describe 'get board lists' do nodes { lists { nodes { - issues(filters: {labelName: "#{label2.title}", confidential: #{confidential}}, first: 3) { + issues(#{attributes_to_graphql(issue_params)}) { count nodes { #{all_graphql_fields_for('issues'.classify)} @@ -77,18 +78,23 @@ RSpec.describe 'get board lists' do end context 'when user can read the board' do - before do + before_all do board_parent.add_reporter(user) - post_graphql(query("id: \"#{global_id_of(label_list)}\""), current_user: user) end + subject { post_graphql(query("id: \"#{global_id_of(label_list)}\""), current_user: user) } + it 'can access the issues', :aggregate_failures do + subject + # ties for relative positions are broken by id in ascending order by default expect(issue_titles).to eq([issue2.title, issue1.title, issue3.title]) expect(issue_relative_positions).not_to include(nil) end it 'does not set the relative positions of the issues not being returned', :aggregate_failures do + subject + expect(issue_id).not_to include(issue6.id) expect(issue3.relative_position).to be_nil end @@ -97,10 +103,36 @@ RSpec.describe 'get board lists' do let(:confidential) { true } it 'returns matching issue' do + subject + expect(issue_titles).to match_array([issue7.title]) expect(issue_relative_positions).not_to include(nil) end end + + context 'when filtering by a unioned argument' do + let(:another_user) { create(:user) } + let(:issue_params) { { filters: { or: { assignee_usernames: [user.username, another_user.username] } } } } + + it 'returns correctly filtered issues' do + issue1.assignee_ids = user.id + issue2.assignee_ids = another_user.id + + subject + + expect(issue_id).to contain_exactly(issue1.to_gid.to_s, issue2.to_gid.to_s) + end + + context 'when feature flag is disabled' do + it 'returns an error' do + stub_feature_flags(or_issuable_queries: false) + + subject + + expect_graphql_errors_to_include("'or' arguments are only allowed when the `or_issuable_queries` feature flag is enabled.") + end + end + end end end diff --git a/spec/requests/api/graphql/boards/board_lists_query_spec.rb b/spec/requests/api/graphql/boards/board_lists_query_spec.rb index 6fe2e41cf35..ad7df5c9344 100644 --- a/spec/requests/api/graphql/boards/board_lists_query_spec.rb +++ b/spec/requests/api/graphql/boards/board_lists_query_spec.rb @@ -49,7 +49,7 @@ RSpec.describe 'get board lists' do end shared_examples 'group and project board lists query' do - let!(:board) { create(:board, resource_parent: board_parent) } + let_it_be(:board) { create(:board, resource_parent: board_parent) } context 'when the user does not have access to the board' do it 'returns nil' do @@ -107,16 +107,20 @@ RSpec.describe 'get board lists' do end context 'when querying for a single list' do + let_it_be(:label_list) { create(:list, board: board, label: label, position: 10) } + let_it_be(:issues) do + [ + create(:issue, project: project, labels: [label, label2]), + create(:issue, project: project, labels: [label, label2], confidential: true), + create(:issue, project: project, labels: [label]) + ] + end + before do board_parent.add_reporter(user) end it 'returns the correct list with issue count for matching issue filters' do - label_list = create(:list, board: board, label: label, position: 10) - create(:issue, project: project, labels: [label, label2]) - create(:issue, project: project, labels: [label, label2], confidential: true) - create(:issue, project: project, labels: [label]) - post_graphql( query( id: global_id_of(label_list), @@ -131,21 +135,56 @@ RSpec.describe 'get board lists' do expect(list_node['issuesCount']).to eq 1 end end + + context 'when filtering by a unioned argument' do + let_it_be(:another_user) { create(:user) } + + it 'returns correctly filtered issues' do + issues[0].assignee_ids = user.id + issues[1].assignee_ids = another_user.id + + post_graphql( + query( + id: global_id_of(label_list), + issueFilters: { or: { assignee_usernames: [user.username, another_user.username] } } + ), current_user: user + ) + + expect(lists_data[0]['node']['issuesCount']).to eq 2 + end + + context 'when feature flag is disabled' do + it 'returns an error' do + stub_feature_flags(or_issuable_queries: false) + + post_graphql( + query( + id: global_id_of(label_list), + issueFilters: { or: { assignee_usernames: [user.username, another_user.username] } } + ), current_user: user + ) + + expect_graphql_errors_to_include( + "'or' arguments are only allowed when the `or_issuable_queries` feature flag is enabled." + ) + end + end + end end end describe 'for a project' do - let(:board_parent) { project } - let(:label) { project_label } - let(:label2) { project_label2 } + let_it_be(:board_parent) { project } + let_it_be(:label) { project_label } + let_it_be(:label2) { project_label2 } it_behaves_like 'group and project board lists query' end describe 'for a group' do - let(:board_parent) { group } - let(:label) { group_label } - let(:label2) { group_label2 } + let_it_be(:board_parent) { group } + let_it_be(:label) { group_label } + let_it_be(:label2) { group_label2 } before do allow(board_parent).to receive(:multiple_issue_boards_available?).and_return(false) diff --git a/spec/requests/api/graphql/ci/jobs_spec.rb b/spec/requests/api/graphql/ci/jobs_spec.rb index 47e3221c567..a161c5c98ed 100644 --- a/spec/requests/api/graphql/ci/jobs_spec.rb +++ b/spec/requests/api/graphql/ci/jobs_spec.rb @@ -109,7 +109,7 @@ RSpec.describe 'Query.project.pipeline' do 'name' => 'docker 1 2', 'needs' => { 'nodes' => [] }, 'previousStageJobsOrNeeds' => { 'nodes' => [ - a_hash_including( 'name' => 'my test job' ) + a_hash_including('name' => 'my test job') ] } ), a_hash_including( @@ -129,7 +129,7 @@ RSpec.describe 'Query.project.pipeline' do 'name' => 'rspec 2 2', 'needs' => { 'nodes' => [a_hash_including('name' => 'my test job')] }, 'previousStageJobsOrNeeds' => { 'nodes' => [ - a_hash_including('name' => 'my test job' ) + a_hash_including('name' => 'my test job') ] } ) ) diff --git a/spec/requests/api/graphql/ci/pipelines_spec.rb b/spec/requests/api/graphql/ci/pipelines_spec.rb index f471a152603..948704e8770 100644 --- a/spec/requests/api/graphql/ci/pipelines_spec.rb +++ b/spec/requests/api/graphql/ci/pipelines_spec.rb @@ -419,7 +419,7 @@ RSpec.describe 'Query.project(fullPath).pipelines' do end before do - create(:ci_sources_pipeline, source_pipeline: upstream_pipeline, pipeline: pipeline ) + create(:ci_sources_pipeline, source_pipeline: upstream_pipeline, pipeline: pipeline) post_graphql(query, current_user: user) end @@ -441,10 +441,10 @@ RSpec.describe 'Query.project(fullPath).pipelines' do pipeline_2 = create(:ci_pipeline, project: project, user: user) upstream_pipeline_2 = create(:ci_pipeline, project: upstream_project, user: user) - create(:ci_sources_pipeline, source_pipeline: upstream_pipeline_2, pipeline: pipeline_2 ) + create(:ci_sources_pipeline, source_pipeline: upstream_pipeline_2, pipeline: pipeline_2) pipeline_3 = create(:ci_pipeline, project: project, user: user) upstream_pipeline_3 = create(:ci_pipeline, project: upstream_project, user: user) - create(:ci_sources_pipeline, source_pipeline: upstream_pipeline_3, pipeline: pipeline_3 ) + create(:ci_sources_pipeline, source_pipeline: upstream_pipeline_3, pipeline: pipeline_3) expect do post_graphql(query, current_user: second_user) diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb index bd90753f9ad..94c0a3c41bd 100644 --- a/spec/requests/api/graphql/ci/runner_spec.rb +++ b/spec/requests/api/graphql/ci/runner_spec.rb @@ -104,7 +104,8 @@ RSpec.describe 'Query.runner(id)' do 'userPermissions' => { 'readRunner' => true, 'updateRunner' => true, - 'deleteRunner' => true + 'deleteRunner' => true, + 'assignRunner' => true } ) expect(runner_data['tagList']).to match_array runner.tag_list diff --git a/spec/requests/api/graphql/group/work_item_types_spec.rb b/spec/requests/api/graphql/group/work_item_types_spec.rb index d6b0673e4f8..35090e2a89f 100644 --- a/spec/requests/api/graphql/group/work_item_types_spec.rb +++ b/spec/requests/api/graphql/group/work_item_types_spec.rb @@ -57,15 +57,4 @@ RSpec.describe 'getting a list of work item types for a group' do expect(graphql_data).to eq('group' => nil) end end - - context 'when the work_items feature flag is disabled' do - before do - stub_feature_flags(work_items: false) - post_graphql(query, current_user: current_user) - end - - it 'returns null' do - expect(graphql_data.dig('group', 'workItemTypes')).to be_nil - end - end end diff --git a/spec/requests/api/graphql/issues_spec.rb b/spec/requests/api/graphql/issues_spec.rb new file mode 100644 index 00000000000..8838ad78f72 --- /dev/null +++ b/spec/requests/api/graphql/issues_spec.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'getting an issue list at root level' do + include GraphqlHelpers + + let_it_be(:developer) { create(:user) } + let_it_be(:reporter) { create(:user) } + let_it_be(:group1) { create(:group).tap { |group| group.add_developer(developer) } } + let_it_be(:group2) { create(:group).tap { |group| group.add_developer(developer) } } + let_it_be(:project_a) { create(:project, :repository, :public, group: group1) } + let_it_be(:project_b) { create(:project, :repository, :private, group: group1) } + let_it_be(:project_c) { create(:project, :repository, :public, group: group2) } + let_it_be(:project_d) { create(:project, :repository, :private, group: group2) } + let_it_be(:early_milestone) { create(:milestone, project: project_d, due_date: 10.days.from_now) } + let_it_be(:late_milestone) { create(:milestone, project: project_c, due_date: 30.days.from_now) } + let_it_be(:priority1) { create(:label, project: project_c, priority: 1) } + let_it_be(:priority2) { create(:label, project: project_d, priority: 5) } + let_it_be(:priority3) { create(:label, project: project_a, priority: 10) } + + let_it_be(:issue_a) { create(:issue, project: project_a, labels: [priority3]) } + let_it_be(:issue_b) { create(:issue, :with_alert, project: project_b, discussion_locked: true) } + let_it_be(:issue_c) do + create( + :issue, + project: project_c, + title: 'title matching issue plus', + labels: [priority1], + milestone: late_milestone + ) + end + + let_it_be(:issue_d) { create(:issue, :with_alert, project: project_d, discussion_locked: true, labels: [priority2]) } + let_it_be(:issue_e) { create(:issue, project: project_d, milestone: early_milestone) } + + let(:issue_filter_params) { {} } + + let(:fields) do + <<~QUERY + nodes { + #{all_graphql_fields_for('issues'.classify)} + } + QUERY + end + + before_all do + group2.add_reporter(reporter) + end + + context 'when the root_level_issues_query feature flag is disabled' do + before do + stub_feature_flags(root_level_issues_query: false) + end + + it 'the field returns null' do + post_graphql(query, current_user: developer) + + expect(graphql_data).to eq('issues' => nil) + end + end + + it_behaves_like 'graphql issue list request spec' do + subject(:post_query) { post_graphql(query, current_user: current_user) } + + let(:current_user) { developer } + let(:another_user) { reporter } + let(:issues_data) { graphql_data['issues']['nodes'] } + let(:issue_ids) { graphql_dig_at(issues_data, :id) } + + # filters + let(:expected_negated_assignee_issues) { [issue_b, issue_c, issue_d, issue_e] } + let(:expected_unioned_assignee_issues) { [issue_a, issue_c] } + let(:voted_issues) { [issue_a, issue_c] } + let(:no_award_issues) { [issue_b, issue_d, issue_e] } + let(:locked_discussion_issues) { [issue_b, issue_d] } + let(:unlocked_discussion_issues) { [issue_a, issue_c, issue_e] } + let(:search_title_term) { 'matching issue' } + let(:title_search_issue) { issue_c } + + # sorting + let(:data_path) { [:issues] } + let(:expected_severity_sorted_asc) { [issue_c, issue_a, issue_b, issue_e, issue_d] } + let(:expected_priority_sorted_asc) { [issue_e, issue_c, issue_d, issue_a, issue_b] } + let(:expected_priority_sorted_desc) { [issue_c, issue_e, issue_a, issue_d, issue_b] } + + before_all do + issue_a.assignee_ids = developer.id + issue_c.assignee_ids = reporter.id + + create(:award_emoji, :upvote, user: developer, awardable: issue_a) + create(:award_emoji, :upvote, user: developer, awardable: issue_c) + + # severity sorting + create(:issuable_severity, issue: issue_a, severity: :unknown) + create(:issuable_severity, issue: issue_b, severity: :low) + create(:issuable_severity, issue: issue_d, severity: :critical) + create(:issuable_severity, issue: issue_e, severity: :high) + end + + def pagination_query(params) + graphql_query_for( + :issues, + params, + "#{page_info} nodes { id }" + ) + end + end + + def query(params = issue_filter_params) + graphql_query_for( + :issues, + params, + fields + ) + end +end diff --git a/spec/requests/api/graphql/metadata_query_spec.rb b/spec/requests/api/graphql/metadata_query_spec.rb index 840bd7c018c..435e1b5b596 100644 --- a/spec/requests/api/graphql/metadata_query_spec.rb +++ b/spec/requests/api/graphql/metadata_query_spec.rb @@ -17,7 +17,8 @@ RSpec.describe 'getting project information' do 'enabled' => Gitlab::Kas.enabled?, 'version' => expected_kas_version, 'externalUrl' => expected_kas_external_url - } + }, + 'enterprise' => Gitlab.ee? } } end diff --git a/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb index 7cd39f93ae7..e81621209fb 100644 --- a/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb +++ b/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb @@ -20,7 +20,7 @@ RSpec.describe 'Removing an AwardEmoji' do end def create_award_emoji(user) - create(:award_emoji, name: emoji_name, awardable: awardable, user: user ) + create(:award_emoji, name: emoji_name, awardable: awardable, user: user) end shared_examples 'a mutation that does not destroy an AwardEmoji' do diff --git a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb index 7ddffa1ab0a..b151da72b55 100644 --- a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb +++ b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb @@ -32,7 +32,7 @@ RSpec.describe 'Toggling an AwardEmoji' do end def create_award_emoji(user) - create(:award_emoji, name: emoji_name, awardable: awardable, user: user ) + create(:award_emoji, name: emoji_name, awardable: awardable, user: user) end context 'when the user has permission' do diff --git a/spec/requests/api/graphql/mutations/ci/pipeline_schedule_take_ownership_spec.rb b/spec/requests/api/graphql/mutations/ci/pipeline_schedule_take_ownership_spec.rb new file mode 100644 index 00000000000..8dfbf20d00b --- /dev/null +++ b/spec/requests/api/graphql/mutations/ci/pipeline_schedule_take_ownership_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'PipelineScheduleTakeOwnership' do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:owner) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: owner) } + + let(:mutation) do + graphql_mutation( + :pipeline_schedule_take_ownership, + { id: pipeline_schedule_id }, + <<-QL + errors + QL + ) + end + + let(:pipeline_schedule_id) { pipeline_schedule.to_global_id.to_s } + + before_all do + project.add_maintainer(user) + end + + it 'returns an error if the user is not allowed to take ownership of the schedule' do + post_graphql_mutation(mutation, current_user: create(:user)) + + expect(graphql_errors).not_to be_empty + end + + it 'takes ownership of the schedule' do + post_graphql_mutation(mutation, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + expect(graphql_errors).to be_nil + end +end diff --git a/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb b/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb index 6818ba33e74..54e63df96a6 100644 --- a/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb +++ b/spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb @@ -87,7 +87,7 @@ RSpec.describe 'RunnersRegistrationTokenReset' do include_context('when unauthorized', 'group') include_context 'when authorized', 'group' do - let_it_be(:user) { create_default(:group_member, :owner, user: create(:user), group: group ).user } + let_it_be(:user) { create_default(:group_member, :owner, user: create(:user), group: group).user } def get_token group.reload.runners_token diff --git a/spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb b/spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb index c4121cfed42..5a27d39ecbc 100644 --- a/spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb +++ b/spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb @@ -33,11 +33,11 @@ RSpec.describe 'Destroying a container repository' do end shared_examples 'destroying the container repository' do - it 'destroy the container repository' do + it 'marks the container repository as delete_scheduled' do expect(::Packages::CreateEventService) .to receive(:new).with(nil, user, event_name: :delete_repository, scope: :container).and_call_original expect(DeleteContainerRepositoryWorker) - .to receive(:perform_async).with(user.id, container_repository.id) + .not_to receive(:perform_async) expect { subject }.to change { ::Packages::Event.count }.by(1) @@ -80,6 +80,25 @@ RSpec.describe 'Destroying a container repository' do it_behaves_like params[:shared_examples_name] end + + context 'with container_registry_delete_repository_with_cron_worker disabled' do + before do + project.add_maintainer(user) + stub_feature_flags(container_registry_delete_repository_with_cron_worker: false) + end + + it 'enqueues a removal job' do + expect(::Packages::CreateEventService) + .to receive(:new).with(nil, user, event_name: :delete_repository, scope: :container).and_call_original + expect(DeleteContainerRepositoryWorker) + .to receive(:perform_async).with(user.id, container_repository.id) + + expect { subject }.to change { ::Packages::Event.count }.by(1) + + expect(container_repository_mutation_response).to match_schema('graphql/container_repository') + expect(container_repository_mutation_response['status']).to eq('DELETE_SCHEDULED') + end + end end context 'with invalid id' do diff --git a/spec/requests/api/graphql/mutations/incident_management/timeline_event/create_spec.rb b/spec/requests/api/graphql/mutations/incident_management/timeline_event/create_spec.rb index 923e12a3c06..fc3b666dd3d 100644 --- a/spec/requests/api/graphql/mutations/incident_management/timeline_event/create_spec.rb +++ b/spec/requests/api/graphql/mutations/incident_management/timeline_event/create_spec.rb @@ -10,8 +10,16 @@ RSpec.describe 'Creating an incident timeline event' do let_it_be(:incident) { create(:incident, project: project) } let_it_be(:event_occurred_at) { Time.current } let_it_be(:note) { 'demo note' } + let_it_be(:tag1) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 1') } + let_it_be(:tag2) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 2') } + + let(:input) do + { incident_id: incident.to_global_id.to_s, + note: note, + occurred_at: event_occurred_at, + timeline_event_tag_names: [tag1.name] } + end - let(:input) { { incident_id: incident.to_global_id.to_s, note: note, occurred_at: event_occurred_at } } let(:mutation) do graphql_mutation(:timeline_event_create, input) do <<~QL @@ -22,6 +30,7 @@ RSpec.describe 'Creating an incident timeline event' do author { id username } incident { id title } note + timelineEventTags { nodes { name } } editable action occurredAt @@ -57,4 +66,25 @@ RSpec.describe 'Creating an incident timeline event' do 'occurredAt' => event_occurred_at.iso8601 ) end + + context 'when note is more than 280 characters long' do + let_it_be(:note) { 'n' * 281 } + + it_behaves_like 'timeline event mutation responds with validation error', + error_message: 'Timeline text is too long (maximum is 280 characters)' + end + + context 'when timeline event tags are passed' do + it 'creates incident timeline event with tags', :aggregate_failures do + post_graphql_mutation(mutation, current_user: user) + + timeline_event_response = mutation_response['timelineEvent'] + tag_names = timeline_event_response['timelineEventTags']['nodes'] + + expect(response).to have_gitlab_http_status(:success) + expect(timeline_event_response).to include( + 'timelineEventTags' => { 'nodes' => tag_names } + ) + end + end end diff --git a/spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb b/spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb index 85eaec90f47..62eeecb3fb7 100644 --- a/spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb +++ b/spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb @@ -9,7 +9,7 @@ RSpec.describe 'Promote an incident timeline event from a comment' do let_it_be(:user) { create(:user) } let_it_be(:project) { create(:project) } let_it_be(:incident) { create(:incident, project: project) } - let_it_be(:comment) { create(:note, project: project, noteable: incident) } + let_it_be(:comment) { create(:note, project: project, noteable: incident, note: 'a' * 281) } let(:input) { { note_id: comment.to_global_id.to_s } } let(:mutation) do diff --git a/spec/requests/api/graphql/mutations/incident_management/timeline_event/update_spec.rb b/spec/requests/api/graphql/mutations/incident_management/timeline_event/update_spec.rb index 1c4439cec6f..542d51b990f 100644 --- a/spec/requests/api/graphql/mutations/incident_management/timeline_event/update_spec.rb +++ b/spec/requests/api/graphql/mutations/incident_management/timeline_event/update_spec.rb @@ -13,11 +13,12 @@ RSpec.describe 'Updating an incident timeline event' do end let(:occurred_at) { 1.minute.ago.iso8601 } + let(:note) { 'Updated note' } let(:variables) do { id: timeline_event.to_global_id.to_s, - note: 'Updated note', + note: note, occurred_at: occurred_at } end @@ -70,11 +71,18 @@ RSpec.describe 'Updating an incident timeline event' do 'id' => incident.to_global_id.to_s, 'title' => incident.title }, - 'note' => 'Updated note', + 'note' => note, 'noteHtml' => timeline_event.note_html, 'occurredAt' => occurred_at, 'createdAt' => timeline_event.created_at.iso8601, 'updatedAt' => timeline_event.updated_at.iso8601 ) end + + context 'when note is more than 280 characters long' do + let(:note) { 'n' * 281 } + + it_behaves_like 'timeline event mutation responds with validation error', + error_message: 'Timeline text is too long (maximum is 280 characters)' + end end diff --git a/spec/requests/api/graphql/mutations/incident_management/timeline_event_tag/create_spec.rb b/spec/requests/api/graphql/mutations/incident_management/timeline_event_tag/create_spec.rb new file mode 100644 index 00000000000..7476499d9da --- /dev/null +++ b/spec/requests/api/graphql/mutations/incident_management/timeline_event_tag/create_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Creating a timeline event tag' do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project) } + let_it_be(:name) { 'Test tag 1' } + + let(:input) { { project_path: project.full_path, name: name } } + let(:mutation) do + graphql_mutation(:timeline_event_tag_create, input) do + <<~QL + clientMutationId + errors + timelineEventTag { + id + name + } + QL + end + end + + let(:mutation_response) { graphql_mutation_response(:timeline_event_tag_create) } + + context 'when user has permissions to create timeline event tag' do + before do + project.add_maintainer(user) + end + + it 'creates timeline event tag', :aggregate_failures do + post_graphql_mutation(mutation, current_user: user) + + timeline_event_tag_response = mutation_response['timelineEventTag'] + + expect(response).to have_gitlab_http_status(:success) + expect(timeline_event_tag_response).to include( + 'name' => name + ) + end + end + + context 'when user does not have permissions to create timeline event tag' do + before do + project.add_developer(user) + end + + it 'raises error' do + post_graphql_mutation(mutation, current_user: user) + + expect(mutation_response).to be_nil + expect_graphql_errors_to_include(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR) + end + end +end diff --git a/spec/requests/api/graphql/mutations/issues/create_spec.rb b/spec/requests/api/graphql/mutations/issues/create_spec.rb index 9345735afe4..a489b7424e8 100644 --- a/spec/requests/api/graphql/mutations/issues/create_spec.rb +++ b/spec/requests/api/graphql/mutations/issues/create_spec.rb @@ -58,34 +58,15 @@ RSpec.describe 'Create an issue' do input['type'] = 'TASK' end - context 'when work_items feature flag is disabled' do - before do - stub_feature_flags(work_items: false) - end + it 'creates an issue with TASK type' do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.to change(Issue, :count).by(1) - it 'creates an issue with the default ISSUE type' do - expect do - post_graphql_mutation(mutation, current_user: current_user) - end.to change(Issue, :count).by(1) + created_issue = Issue.last - created_issue = Issue.last - - expect(created_issue.work_item_type.base_type).to eq('issue') - expect(created_issue.issue_type).to eq('issue') - end - end - - context 'when work_items feature flag is enabled' do - it 'creates an issue with TASK type' do - expect do - post_graphql_mutation(mutation, current_user: current_user) - end.to change(Issue, :count).by(1) - - created_issue = Issue.last - - expect(created_issue.work_item_type.base_type).to eq('task') - expect(created_issue.issue_type).to eq('task') - end + expect(created_issue.work_item_type.base_type).to eq('task') + expect(created_issue.issue_type).to eq('task') end end diff --git a/spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb b/spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb index e7f4917ddde..c6a980b5cef 100644 --- a/spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb +++ b/spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb @@ -71,19 +71,5 @@ RSpec.describe "Create a work item from a task in a work item's description" do it_behaves_like 'has spam protection' do let(:mutation_class) { ::Mutations::WorkItems::CreateFromTask } end - - context 'when the work_items feature flag is disabled' do - before do - stub_feature_flags(work_items: false) - end - - it 'does nothing and returns and error' do - expect do - post_graphql_mutation(mutation, current_user: current_user) - end.to not_change(WorkItem, :count) - - expect(mutation_response['errors']).to contain_exactly('`work_items` feature flag disabled for this project') - end - end end end diff --git a/spec/requests/api/graphql/mutations/work_items/create_spec.rb b/spec/requests/api/graphql/mutations/work_items/create_spec.rb index 8233821053f..be3917316c3 100644 --- a/spec/requests/api/graphql/mutations/work_items/create_spec.rb +++ b/spec/requests/api/graphql/mutations/work_items/create_spec.rb @@ -154,17 +154,65 @@ RSpec.describe 'Create a work item' do end end - context 'when the work_items feature flag is disabled' do - before do - stub_feature_flags(work_items: false) + context 'with milestone widget input' do + let(:widgets_response) { mutation_response['workItem']['widgets'] } + let(:fields) do + <<~FIELDS + workItem { + widgets { + type + ... on WorkItemWidgetMilestone { + milestone { + id + } + } + } + } + errors + FIELDS end - it 'does not create the work item and returns an error' do - expect do - post_graphql_mutation(mutation, current_user: current_user) - end.to not_change(WorkItem, :count) + let(:mutation) { graphql_mutation(:workItemCreate, input.merge('projectPath' => project.full_path), fields) } - expect(mutation_response['errors']).to contain_exactly('`work_items` feature flag disabled for this project') + context 'when setting milestone on work item creation' do + let_it_be(:project_milestone) { create(:milestone, project: project) } + let_it_be(:group_milestone) { create(:milestone, project: project) } + + let(:input) do + { + title: 'some WI', + workItemTypeId: WorkItems::Type.default_by_type(:task).to_global_id.to_s, + milestoneWidget: { 'milestoneId' => milestone.to_global_id.to_s } + } + end + + shared_examples "work item's milestone is set" do + it "sets the work item's milestone" do + expect do + post_graphql_mutation(mutation, current_user: current_user) + end.to change(WorkItem, :count).by(1) + + expect(response).to have_gitlab_http_status(:success) + expect(widgets_response).to include( + { + 'type' => 'MILESTONE', + 'milestone' => { 'id' => milestone.to_global_id.to_s } + } + ) + end + end + + context 'when assigning a project milestone' do + it_behaves_like "work item's milestone is set" do + let(:milestone) { project_milestone } + end + end + + context 'when assigning a group milestone' do + it_behaves_like "work item's milestone is set" do + let(:milestone) { group_milestone } + end + end end end end diff --git a/spec/requests/api/graphql/mutations/work_items/delete_spec.rb b/spec/requests/api/graphql/mutations/work_items/delete_spec.rb index 14c8b757a57..0a84225a7ab 100644 --- a/spec/requests/api/graphql/mutations/work_items/delete_spec.rb +++ b/spec/requests/api/graphql/mutations/work_items/delete_spec.rb @@ -31,19 +31,5 @@ RSpec.describe 'Delete a work item' do expect(response).to have_gitlab_http_status(:success) expect(mutation_response['project']).to include('id' => work_item.project.to_global_id.to_s) end - - context 'when the work_items feature flag is disabled' do - before do - stub_feature_flags(work_items: false) - end - - it 'does not delete the work item' do - expect do - post_graphql_mutation(mutation, current_user: current_user) - end.to not_change(WorkItem, :count) - - expect(mutation_response['errors']).to contain_exactly('`work_items` feature flag disabled for this project') - end - end end end diff --git a/spec/requests/api/graphql/mutations/work_items/delete_task_spec.rb b/spec/requests/api/graphql/mutations/work_items/delete_task_spec.rb index e576d0ee7ef..c44939c8d54 100644 --- a/spec/requests/api/graphql/mutations/work_items/delete_task_spec.rb +++ b/spec/requests/api/graphql/mutations/work_items/delete_task_spec.rb @@ -75,19 +75,5 @@ RSpec.describe "Delete a task in a work item's description" do expect(mutation_response['errors']).to contain_exactly('Stale work item. Check lock version') end end - - context 'when the work_items feature flag is disabled' do - before do - stub_feature_flags(work_items: false) - end - - it 'does nothing and returns and error' do - expect do - post_graphql_mutation(mutation, current_user: current_user) - end.to not_change(WorkItem, :count) - - expect(mutation_response['errors']).to contain_exactly('`work_items` feature flag disabled for this project') - end - end end end diff --git a/spec/requests/api/graphql/mutations/work_items/update_spec.rb b/spec/requests/api/graphql/mutations/work_items/update_spec.rb index 6b0129c457f..96736457f26 100644 --- a/spec/requests/api/graphql/mutations/work_items/update_spec.rb +++ b/spec/requests/api/graphql/mutations/work_items/update_spec.rb @@ -5,8 +5,11 @@ require 'spec_helper' RSpec.describe 'Update a work item' do include GraphqlHelpers - let_it_be(:project) { create(:project) } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } let_it_be(:developer) { create(:user).tap { |user| project.add_developer(user) } } + let_it_be(:reporter) { create(:user).tap { |user| project.add_reporter(user) } } + let_it_be(:guest) { create(:user).tap { |user| project.add_guest(user) } } let_it_be(:work_item, refind: true) { create(:work_item, project: project) } let(:work_item_event) { 'CLOSE' } @@ -543,6 +546,91 @@ RSpec.describe 'Update a work item' do end end + context 'when updating milestone' do + let_it_be(:project_milestone) { create(:milestone, project: project) } + let_it_be(:group_milestone) { create(:milestone, project: project) } + + let(:input) { { 'milestoneWidget' => { 'milestoneId' => new_milestone&.to_global_id&.to_s } } } + + let(:fields) do + <<~FIELDS + workItem { + widgets { + type + ... on WorkItemWidgetMilestone { + milestone { + id + } + } + } + } + errors + FIELDS + end + + shared_examples "work item's milestone is updated" do + it "updates the work item's milestone" do + expect do + post_graphql_mutation(mutation, current_user: current_user) + + work_item.reload + end.to change(work_item, :milestone).from(old_milestone).to(new_milestone) + + expect(response).to have_gitlab_http_status(:success) + end + end + + shared_examples "work item's milestone is not updated" do + it "ignores the update request" do + expect do + post_graphql_mutation(mutation, current_user: current_user) + + work_item.reload + end.to not_change(work_item, :milestone) + + expect(response).to have_gitlab_http_status(:success) + end + end + + context 'when user cannot set work item metadata' do + let(:current_user) { guest } + let(:old_milestone) { nil } + + it_behaves_like "work item's milestone is not updated" do + let(:new_milestone) { project_milestone } + end + end + + context 'when user can set work item metadata' do + let(:current_user) { reporter } + + context 'when assigning a project milestone' do + it_behaves_like "work item's milestone is updated" do + let(:old_milestone) { nil } + let(:new_milestone) { project_milestone } + end + end + + context 'when assigning a group milestone' do + it_behaves_like "work item's milestone is updated" do + let(:old_milestone) { nil } + let(:new_milestone) { group_milestone } + end + end + + context "when unsetting the work item's milestone" do + it_behaves_like "work item's milestone is updated" do + let(:old_milestone) { group_milestone } + let(:new_milestone) { nil } + + before do + work_item.update!(milestone: old_milestone) + end + end + end + end + end + context 'when unsupported widget input is sent' do let_it_be(:test_case) { create(:work_item_type, :default, :test_case, name: 'some_test_case_name') } let_it_be(:work_item) { create(:work_item, work_item_type: test_case, project: project) } @@ -556,20 +644,5 @@ RSpec.describe 'Update a work item' do it_behaves_like 'a mutation that returns top-level errors', errors: ["Following widget keys are not supported by some_test_case_name type: [:hierarchy_widget]"] end - - context 'when the work_items feature flag is disabled' do - before do - stub_feature_flags(work_items: false) - end - - it 'does not update the work item and returns and error' do - expect do - post_graphql_mutation(mutation, current_user: current_user) - work_item.reload - end.to not_change(work_item, :title) - - expect(mutation_response['errors']).to contain_exactly('`work_items` feature flag disabled for this project') - end - end end end diff --git a/spec/requests/api/graphql/mutations/work_items/update_task_spec.rb b/spec/requests/api/graphql/mutations/work_items/update_task_spec.rb index 32468a46ace..55285be5a5d 100644 --- a/spec/requests/api/graphql/mutations/work_items/update_task_spec.rb +++ b/spec/requests/api/graphql/mutations/work_items/update_task_spec.rb @@ -74,22 +74,6 @@ RSpec.describe 'Update a work item task' do it_behaves_like 'has spam protection' do let(:mutation_class) { ::Mutations::WorkItems::UpdateTask } end - - context 'when the work_items feature flag is disabled' do - before do - stub_feature_flags(work_items: false) - end - - it 'does not update the task item and returns and error' do - expect do - post_graphql_mutation(mutation, current_user: current_user) - work_item.reload - task.reload - end.to not_change(task, :title) - - expect(mutation_response['errors']).to contain_exactly('`work_items` feature flag disabled for this project') - end - end end context 'when user does not have permissions to update a work item' do diff --git a/spec/requests/api/graphql/packages/package_spec.rb b/spec/requests/api/graphql/packages/package_spec.rb index e9f82d66775..02a3206f587 100644 --- a/spec/requests/api/graphql/packages/package_spec.rb +++ b/spec/requests/api/graphql/packages/package_spec.rb @@ -206,5 +206,25 @@ RSpec.describe 'package details' do expect(graphql_data_at(:package, :composer_config_repository_url)).to eq("localhost/#{group.id}") end end + + context 'web_path' do + before do + subject + end + + it 'returns web_path correctly' do + expect(graphql_data_at(:package, :_links, :web_path)).to eq("/#{project.full_path}/-/packages/#{composer_package.id}") + end + + context 'with terraform module' do + let_it_be(:terraform_package) { create(:terraform_module_package, project: project) } + + let(:package_global_id) { global_id_of(terraform_package) } + + it 'returns web_path correctly' do + expect(graphql_data_at(:package, :_links, :web_path)).to eq("/#{project.full_path}/-/infrastructure_registry/#{terraform_package.id}") + end + end + end end end diff --git a/spec/requests/api/graphql/project/branch_protections/merge_access_levels_spec.rb b/spec/requests/api/graphql/project/branch_protections/merge_access_levels_spec.rb index cb5006ec8e4..a80f683ea93 100644 --- a/spec/requests/api/graphql/project/branch_protections/merge_access_levels_spec.rb +++ b/spec/requests/api/graphql/project/branch_protections/merge_access_levels_spec.rb @@ -3,107 +3,5 @@ require 'spec_helper' RSpec.describe 'getting merge access levels for a branch protection' do - include GraphqlHelpers - - let_it_be(:current_user) { create(:user) } - - let(:merge_access_level_data) { merge_access_levels_data[0] } - - let(:merge_access_levels_data) do - graphql_data_at('project', - 'branchRules', - 'nodes', - 0, - 'branchProtection', - 'mergeAccessLevels', - 'nodes') - end - - let(:project) { protected_branch.project } - - let(:merge_access_levels_count) { protected_branch.merge_access_levels.size } - - let(:variables) { { path: project.full_path } } - - let(:fields) { all_graphql_fields_for('MergeAccessLevel') } - - let(:query) do - <<~GQL - query($path: ID!) { - project(fullPath: $path) { - branchRules(first: 1) { - nodes { - branchProtection { - mergeAccessLevels { - nodes { - #{fields} - } - } - } - } - } - } - } - GQL - end - - context 'when the user does not have read_protected_branch abilities' do - let_it_be(:protected_branch) { create(:protected_branch) } - - before do - project.add_guest(current_user) - post_graphql(query, current_user: current_user, variables: variables) - end - - it_behaves_like 'a working graphql query' - - it { expect(merge_access_levels_data).not_to be_present } - end - - shared_examples 'merge access request' do - let(:merge_access) { protected_branch.merge_access_levels.first } - - before do - project.add_maintainer(current_user) - post_graphql(query, current_user: current_user, variables: variables) - end - - it_behaves_like 'a working graphql query' - - it 'returns all merge access levels' do - expect(merge_access_levels_data.size).to eq(merge_access_levels_count) - end - - it 'includes access_level' do - expect(merge_access_level_data['accessLevel']) - .to eq(merge_access.access_level) - end - - it 'includes access_level_description' do - expect(merge_access_level_data['accessLevelDescription']) - .to eq(merge_access.humanize) - end - end - - context 'when the user does have read_protected_branch abilities' do - let(:merge_access) { protected_branch.merge_access_levels.first } - - context 'when no one has access' do - let_it_be(:protected_branch) { create(:protected_branch, :no_one_can_merge) } - - it_behaves_like 'merge access request' - end - - context 'when developers have access' do - let_it_be(:protected_branch) { create(:protected_branch, :developers_can_merge) } - - it_behaves_like 'merge access request' - end - - context 'when maintainers have access' do - let_it_be(:protected_branch) { create(:protected_branch, :maintainers_can_merge) } - - it_behaves_like 'merge access request' - end - end + include_examples 'perform graphql requests for AccessLevel type objects', :merge end diff --git a/spec/requests/api/graphql/project/branch_protections/push_access_levels_spec.rb b/spec/requests/api/graphql/project/branch_protections/push_access_levels_spec.rb index 59f9c7d61cb..cfdaf1096c3 100644 --- a/spec/requests/api/graphql/project/branch_protections/push_access_levels_spec.rb +++ b/spec/requests/api/graphql/project/branch_protections/push_access_levels_spec.rb @@ -3,107 +3,5 @@ require 'spec_helper' RSpec.describe 'getting push access levels for a branch protection' do - include GraphqlHelpers - - let_it_be(:current_user) { create(:user) } - - let(:push_access_level_data) { push_access_levels_data[0] } - - let(:push_access_levels_data) do - graphql_data_at('project', - 'branchRules', - 'nodes', - 0, - 'branchProtection', - 'pushAccessLevels', - 'nodes') - end - - let(:project) { protected_branch.project } - - let(:push_access_levels_count) { protected_branch.push_access_levels.size } - - let(:variables) { { path: project.full_path } } - - let(:fields) { all_graphql_fields_for('PushAccessLevel'.classify) } - - let(:query) do - <<~GQL - query($path: ID!) { - project(fullPath: $path) { - branchRules(first: 1) { - nodes { - branchProtection { - pushAccessLevels { - nodes { - #{fields} - } - } - } - } - } - } - } - GQL - end - - context 'when the user does not have read_protected_branch abilities' do - let_it_be(:protected_branch) { create(:protected_branch) } - - before do - project.add_guest(current_user) - post_graphql(query, current_user: current_user, variables: variables) - end - - it_behaves_like 'a working graphql query' - - it { expect(push_access_levels_data).not_to be_present } - end - - shared_examples 'push access request' do - let(:push_access) { protected_branch.push_access_levels.first } - - before do - project.add_maintainer(current_user) - post_graphql(query, current_user: current_user, variables: variables) - end - - it_behaves_like 'a working graphql query' - - it 'returns all push access levels' do - expect(push_access_levels_data.size).to eq(push_access_levels_count) - end - - it 'includes access_level' do - expect(push_access_level_data['accessLevel']) - .to eq(push_access.access_level) - end - - it 'includes access_level_description' do - expect(push_access_level_data['accessLevelDescription']) - .to eq(push_access.humanize) - end - end - - context 'when the user does have read_protected_branch abilities' do - let(:push_access) { protected_branch.push_access_levels.first } - - context 'when no one has access' do - let_it_be(:protected_branch) { create(:protected_branch, :no_one_can_push) } - - it_behaves_like 'push access request' - end - - context 'when developers have access' do - let_it_be(:protected_branch) { create(:protected_branch, :developers_can_push) } - - it_behaves_like 'push access request' - end - - context 'when maintainers have access' do - let_it_be(:protected_branch) { create(:protected_branch, :maintainers_can_push) } - - it_behaves_like 'push access request' - end - end + include_examples 'perform graphql requests for AccessLevel type objects', :push end diff --git a/spec/requests/api/graphql/project/branch_rules_spec.rb b/spec/requests/api/graphql/project/branch_rules_spec.rb index 1aaf0e9edc7..ed866305445 100644 --- a/spec/requests/api/graphql/project/branch_rules_spec.rb +++ b/spec/requests/api/graphql/project/branch_rules_spec.rb @@ -7,10 +7,9 @@ RSpec.describe 'getting list of branch rules for a project' do let_it_be(:project) { create(:project, :repository, :public) } let_it_be(:current_user) { create(:user) } - let_it_be(:branch_name_a) { 'branch_name_a' } - let_it_be(:branch_name_b) { 'wildcard-*' } + let_it_be(:branch_name_a) { TestEnv::BRANCH_SHA.each_key.first } + let_it_be(:branch_name_b) { 'diff-*' } let_it_be(:branch_rules) { [branch_rule_a, branch_rule_b] } - let_it_be(:branch_rule_a) do create(:protected_branch, project: project, name: branch_name_a) end @@ -21,7 +20,6 @@ RSpec.describe 'getting list of branch rules for a project' do let(:branch_rules_data) { graphql_data_at('project', 'branchRules', 'edges') } let(:variables) { { path: project.full_path } } - # fields must use let as the all_graphql_fields_for also configures some spies let(:fields) { all_graphql_fields_for('BranchRule') } let(:query) do <<~GQL @@ -60,49 +58,124 @@ RSpec.describe 'getting list of branch rules for a project' do context 'when the user does have read_protected_branch abilities' do before do project.add_maintainer(current_user) - post_graphql(query, current_user: current_user, variables: variables) end - it_behaves_like 'a working graphql query' + describe 'queries' do + before do + # rubocop:disable RSpec/AnyInstanceOf + allow_any_instance_of(User).to receive(:update_tracked_fields!) + allow_any_instance_of(Users::ActivityService).to receive(:execute) + # rubocop:enable RSpec/AnyInstanceOf + allow_next_instance_of(Resolvers::ProjectResolver) do |resolver| + allow(resolver).to receive(:resolve) + .with(full_path: project.full_path) + .and_return(project) + end + allow(project.repository).to receive(:branch_names).and_call_original + allow(project.repository.gitaly_ref_client).to receive(:branch_names).and_call_original + end + + it 'matching_branches_count avoids N+1 queries' do + query = <<~GQL + query($path: ID!) { + project(fullPath: $path) { + branchRules { + nodes { + matchingBranchesCount + } + } + } + } + GQL + + control = ActiveRecord::QueryRecorder.new do + post_graphql(query, current_user: current_user, variables: variables) + end + + # Verify the response includes the field + expect_n_matching_branches_count_fields(2) + + create(:protected_branch, project: project) + create(:protected_branch, name: '*', project: project) + + expect do + post_graphql(query, current_user: current_user, variables: variables) + end.not_to exceed_all_query_limit(control) + + expect(project.repository).to have_received(:branch_names).at_least(2).times + expect(project.repository.gitaly_ref_client).to have_received(:branch_names).once - it 'returns branch rules data' do - expect(branch_rules_data.dig(0, 'node', 'name')).to be_present - expect(branch_rules_data.dig(0, 'node', 'isDefault')).to be(true).or be(false) - expect(branch_rules_data.dig(0, 'node', 'branchProtection')).to be_present - expect(branch_rules_data.dig(0, 'node', 'createdAt')).to be_present - expect(branch_rules_data.dig(0, 'node', 'updatedAt')).to be_present - - expect(branch_rules_data.dig(1, 'node', 'name')).to be_present - expect(branch_rules_data.dig(1, 'node', 'isDefault')).to be(true).or be(false) - expect(branch_rules_data.dig(1, 'node', 'branchProtection')).to be_present - expect(branch_rules_data.dig(1, 'node', 'createdAt')).to be_present - expect(branch_rules_data.dig(1, 'node', 'updatedAt')).to be_present + expect_n_matching_branches_count_fields(4) + end + + def expect_n_matching_branches_count_fields(count) + branch_rule_nodes = graphql_data_at('project', 'branchRules', 'nodes') + expect(branch_rule_nodes.count).to eq(count) + branch_rule_nodes.each do |node| + expect(node['matchingBranchesCount']).to be_present + end + end end - context 'when limiting the number of results' do - let(:branch_rule_limit) { 1 } - let(:variables) { { path: project.full_path, n: branch_rule_limit } } - let(:next_variables) do - { path: project.full_path, n: branch_rule_limit, cursor: last_cursor } + describe 'response' do + before do + post_graphql(query, current_user: current_user, variables: variables) end it_behaves_like 'a working graphql query' - it 'returns pagination information' do - expect(branch_rules_data.size).to eq(branch_rule_limit) - expect(has_next_page).to be_truthy - expect(has_prev_page).to be_falsey - post_graphql(query, current_user: current_user, variables: next_variables) - expect(branch_rules_data.size).to eq(branch_rule_limit) - expect(has_next_page).to be_falsey - expect(has_prev_page).to be_truthy + it 'includes all fields', :aggregate_failures do + # Responses will be sorted alphabetically. Branch names for this spec + # come from an external constant so we check which is first + br_a_idx = branch_name_a < branch_name_b ? 0 : 1 + br_b_idx = 1 - br_a_idx + + branch_rule_a_data = branch_rules_data.dig(br_a_idx, 'node') + branch_rule_b_data = branch_rules_data.dig(br_b_idx, 'node') + + expect(branch_rule_a_data['name']).to eq(branch_name_a) + expect(branch_rule_a_data['isDefault']).to be(true).or be(false) + expect(branch_rule_a_data['branchProtection']).to be_present + expect(branch_rule_a_data['matchingBranchesCount']).to eq(1) + expect(branch_rule_a_data['createdAt']).to be_present + expect(branch_rule_a_data['updatedAt']).to be_present + + wildcard_count = TestEnv::BRANCH_SHA.keys.count do |branch_name| + branch_name.starts_with?('diff-') + end + expect(branch_rule_b_data['name']).to eq(branch_name_b) + expect(branch_rule_b_data['isDefault']).to be(true).or be(false) + expect(branch_rule_b_data['branchProtection']).to be_present + expect(branch_rule_b_data['matchingBranchesCount']).to eq(wildcard_count) + expect(branch_rule_b_data['createdAt']).to be_present + expect(branch_rule_b_data['updatedAt']).to be_present end - context 'when no limit is provided' do - let(:branch_rule_limit) { nil } + context 'when limiting the number of results' do + let(:branch_rule_limit) { 1 } + let(:variables) { { path: project.full_path, n: branch_rule_limit } } + let(:next_variables) do + { path: project.full_path, n: branch_rule_limit, cursor: last_cursor } + end + + it_behaves_like 'a working graphql query' + + it 'returns pagination information' do + expect(branch_rules_data.size).to eq(branch_rule_limit) + expect(has_next_page).to be_truthy + expect(has_prev_page).to be_falsey + post_graphql(query, current_user: current_user, variables: next_variables) + expect(branch_rules_data.size).to eq(branch_rule_limit) + expect(has_next_page).to be_falsey + expect(has_prev_page).to be_truthy + end + + context 'when no limit is provided' do + let(:branch_rule_limit) { nil } - it 'returns all branch_rules' do - expect(branch_rules_data.size).to eq(branch_rules.size) + it 'returns all branch_rules' do + expect(branch_rules_data.size).to eq(branch_rules.size) + end end end end diff --git a/spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb b/spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb index bcbb1f11d43..544d2d7bd95 100644 --- a/spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb +++ b/spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb @@ -48,6 +48,7 @@ RSpec.describe 'getting incident timeline events' do note noteHtml promotedFromNote { id body } + timelineEventTags { nodes { name } } editable action occurredAt @@ -100,6 +101,7 @@ RSpec.describe 'getting incident timeline events' do 'id' => promoted_from_note.to_global_id.to_s, 'body' => promoted_from_note.note }, + 'timelineEventTags' => { 'nodes' => [] }, 'editable' => true, 'action' => timeline_event.action, 'occurredAt' => timeline_event.occurred_at.iso8601, @@ -108,6 +110,47 @@ RSpec.describe 'getting incident timeline events' do ) end + context 'when timelineEvent tags are linked' do + let_it_be(:tag1) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 1') } + let_it_be(:tag2) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 2') } + let_it_be(:timeline_event_tag_link) do + create(:incident_management_timeline_event_tag_link, + timeline_event: timeline_event, + timeline_event_tag: tag1) + end + + it_behaves_like 'a working graphql query' + + it 'returns the set tags' do + expect(timeline_events.first['timelineEventTags']['nodes'].first['name']).to eq(tag1.name) + end + + context 'when different timeline events are loaded' do + it 'avoids N+1 queries' do + control = ActiveRecord::QueryRecorder.new do + post_graphql(query, current_user: current_user) + end + + new_event = create(:incident_management_timeline_event, + incident: incident, + project: project, + updated_by_user: updated_by_user, + promoted_from_note: promoted_from_note, + note: "Referencing #{issue.to_reference(full: true)} - Full URL #{issue_url}" + ) + + create(:incident_management_timeline_event_tag_link, + timeline_event: new_event, + timeline_event_tag: tag2 + ) + + expect(incident.incident_management_timeline_events.length).to eq(3) + expect(post_graphql(query, current_user: current_user)).not_to exceed_query_limit(control) + expect(timeline_events.count).to eq(3) + end + end + end + context 'when filtering by id' do let(:params) { { incident_id: incident.to_global_id.to_s, id: timeline_event.to_global_id.to_s } } diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb index 3b8beb4f798..214165cb171 100644 --- a/spec/requests/api/graphql/project/issues_spec.rb +++ b/spec/requests/api/graphql/project/issues_spec.rb @@ -8,84 +8,74 @@ RSpec.describe 'getting an issue list for a project' do let_it_be(:group) { create(:group) } let_it_be(:project) { create(:project, :repository, :public, group: group) } let_it_be(:current_user) { create(:user) } - let_it_be(:issue_a, reload: true) { create(:issue, project: project, discussion_locked: true) } - let_it_be(:issue_b, reload: true) { create(:issue, :with_alert, project: project) } - let_it_be(:issues, reload: true) { [issue_a, issue_b] } + let_it_be(:another_user) { create(:user).tap { |u| group.add_reporter(u) } } + 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(:priority1) { create(:label, project: project, priority: 1) } + let_it_be(:priority2) { create(:label, project: project, priority: 5) } + let_it_be(:priority3) { create(:label, project: project, priority: 10) } + + let_it_be(:issue_a, reload: true) { create(:issue, project: project, discussion_locked: true, labels: [priority3]) } + let_it_be(:issue_b, reload: true) { create(:issue, :with_alert, project: project, title: 'title matching issue i') } + let_it_be(:issue_c) { create(:issue, project: project, labels: [priority1], milestone: late_milestone) } + let_it_be(:issue_d) { create(:issue, project: project, labels: [priority2]) } + let_it_be(:issue_e) { create(:issue, project: project, milestone: early_milestone) } + let_it_be(:issues, reload: true) { [issue_a, issue_b, issue_c, issue_d, issue_e] } let(:issue_a_gid) { issue_a.to_global_id.to_s } let(:issue_b_gid) { issue_b.to_global_id.to_s } - let(:issues_data) { graphql_data['project']['issues']['edges'] } + let(:issues_data) { graphql_data['project']['issues']['nodes'] } let(:issue_filter_params) { {} } let(:fields) do <<~QUERY - edges { - node { - #{all_graphql_fields_for('issues'.classify)} - } + nodes { + #{all_graphql_fields_for('issues'.classify)} } QUERY end - it_behaves_like 'a working graphql query' do - before do - post_graphql(query, current_user: current_user) - end - end - - it 'includes a web_url' do - post_graphql(query, current_user: current_user) - - expect(issues_data[0]['node']['webUrl']).to be_present - end - - it 'includes discussion locked' do - post_graphql(query, current_user: current_user) - - expect(issues_data[0]['node']['discussionLocked']).to eq(false) - expect(issues_data[1]['node']['discussionLocked']).to eq(true) - end - - context 'when both assignee_username filters are provided' do - let(:issue_filter_params) { { assignee_username: current_user.username, assignee_usernames: [current_user.username] } } - - it 'returns a mutually exclusive param error' do - post_graphql(query, current_user: current_user) - - expect_graphql_errors_to_include('only one of [assigneeUsernames, assigneeUsername] arguments is allowed at the same time.') - end - end - - context 'filtering by my_reaction_emoji' do - using RSpec::Parameterized::TableSyntax - - let_it_be(:upvote_award) { create(:award_emoji, :upvote, user: current_user, awardable: issue_a) } - - where(:value, :gids) do - 'thumbsup' | lazy { [issue_a_gid] } - 'ANY' | lazy { [issue_a_gid] } - 'any' | lazy { [issue_a_gid] } - 'AnY' | lazy { [issue_a_gid] } - 'NONE' | lazy { [issue_b_gid] } - 'thumbsdown' | lazy { [] } + # All new specs should be added to the shared example if the change also + # affects the `issues` query at the root level of the API. + # Shared example also used in spec/requests/api/graphql/issues_spec.rb + it_behaves_like 'graphql issue list request spec' do + subject(:post_query) { post_graphql(query, current_user: current_user) } + + # filters + let(:expected_negated_assignee_issues) { [issue_b, issue_c, issue_d, issue_e] } + let(:expected_unioned_assignee_issues) { [issue_a, issue_b] } + let(:voted_issues) { [issue_a] } + let(:no_award_issues) { [issue_b, issue_c, issue_d, issue_e] } + let(:locked_discussion_issues) { [issue_a] } + let(:unlocked_discussion_issues) { [issue_b, issue_c, issue_d, issue_e] } + let(:search_title_term) { 'matching issue' } + let(:title_search_issue) { issue_b } + + # sorting + let(:data_path) { [:project, :issues] } + let(:expected_severity_sorted_asc) { [issue_c, issue_a, issue_b, issue_e, issue_d] } + let(:expected_priority_sorted_asc) { [issue_e, issue_c, issue_d, issue_a, issue_b] } + let(:expected_priority_sorted_desc) { [issue_c, issue_e, issue_a, issue_d, issue_b] } + + before_all do + issue_a.assignee_ids = current_user.id + issue_b.assignee_ids = another_user.id + + create(:award_emoji, :upvote, user: current_user, awardable: issue_a) + + # severity sorting + create(:issuable_severity, issue: issue_a, severity: :unknown) + create(:issuable_severity, issue: issue_b, severity: :low) + create(:issuable_severity, issue: issue_d, severity: :critical) + create(:issuable_severity, issue: issue_e, severity: :high) end - with_them do - let(:issue_filter_params) { { my_reaction_emoji: value } } - - it 'returns correctly filtered issues' do - post_graphql(query, current_user: current_user) - - expect(issues_ids).to eq(gids) - end - end - end - - context 'when filtering by search' do - it_behaves_like 'query with a search term' do - let(:issuable_data) { issues_data } - let(:user) { current_user } - let_it_be(:issuable) { create(:issue, project: project, description: 'bar') } + def pagination_query(params) + graphql_query_for( + :project, + { full_path: project.full_path }, + query_graphql_field(:issues, params, "#{page_info} nodes { id }") + ) end end @@ -155,10 +145,10 @@ RSpec.describe 'getting an issue list for a project' do it 'returns issues without confidential issues' do post_graphql(query, current_user: current_user) - expect(issues_data.size).to eq(2) + expect(issues_data.size).to eq(5) issues_data.each do |issue| - expect(issue.dig('node', 'confidential')).to eq(false) + expect(issue['confidential']).to eq(false) end end @@ -178,7 +168,7 @@ RSpec.describe 'getting an issue list for a project' do it 'returns correctly filtered issues' do post_graphql(query, current_user: current_user) - expect(issues_ids).to contain_exactly(issue_a_gid, issue_b_gid) + expect(issue_ids).to match_array(issues.map { |i| i.to_gid.to_s }) end end end @@ -191,13 +181,13 @@ RSpec.describe 'getting an issue list for a project' do it 'returns issues with confidential issues' do post_graphql(query, current_user: current_user) - expect(issues_data.size).to eq(3) + expect(issues_data.size).to eq(6) confidentials = issues_data.map do |issue| - issue.dig('node', 'confidential') + issue['confidential'] end - expect(confidentials).to eq([true, false, false]) + expect(confidentials).to contain_exactly(true, false, false, false, false, false) end context 'filtering for confidential issues' do @@ -206,7 +196,7 @@ RSpec.describe 'getting an issue list for a project' do it 'returns correctly filtered issues' do post_graphql(query, current_user: current_user) - expect(issues_ids).to contain_exactly(confidential_issue_gid) + expect(issue_ids).to contain_exactly(confidential_issue_gid) end end @@ -216,7 +206,7 @@ RSpec.describe 'getting an issue list for a project' do it 'returns correctly filtered issues' do post_graphql(query, current_user: current_user) - expect(issues_ids).to contain_exactly(issue_a_gid, issue_b_gid) + expect(issue_ids).to match_array([issue_a, issue_b, issue_c, issue_d, issue_e].map { |i| i.to_gid.to_s }) end end end @@ -238,37 +228,7 @@ RSpec.describe 'getting an issue list for a project' do data.map { |issue| issue['iid'].to_i } end - context 'when sorting by severity' do - let_it_be(:severty_issue1) { create(:issue, project: sort_project) } - let_it_be(:severty_issue2) { create(:issue, project: sort_project) } - let_it_be(:severty_issue3) { create(:issue, project: sort_project) } - let_it_be(:severty_issue4) { create(:issue, project: sort_project) } - let_it_be(:severty_issue5) { create(:issue, project: sort_project) } - - before(:all) do - create(:issuable_severity, issue: severty_issue1, severity: :unknown) - create(:issuable_severity, issue: severty_issue2, severity: :low) - create(:issuable_severity, issue: severty_issue4, severity: :critical) - create(:issuable_severity, issue: severty_issue5, severity: :high) - end - - context 'when ascending' do - it_behaves_like 'sorted paginated query' do - let(:sort_param) { :SEVERITY_ASC } - let(:first_param) { 2 } - let(:all_records) { [severty_issue3.iid, severty_issue1.iid, severty_issue2.iid, severty_issue5.iid, severty_issue4.iid] } - end - end - - context 'when descending' do - it_behaves_like 'sorted paginated query' do - let(:sort_param) { :SEVERITY_DESC } - let(:first_param) { 2 } - let(:all_records) { [severty_issue4.iid, severty_issue5.iid, severty_issue2.iid, severty_issue1.iid, severty_issue3.iid] } - end - end - end - + # rubocop:disable RSpec/MultipleMemoizedHelpers context 'when sorting by due date' do let_it_be(:due_issue1) { create(:issue, project: sort_project, due_date: 3.days.from_now) } let_it_be(:due_issue2) { create(:issue, project: sort_project, due_date: nil) } @@ -314,41 +274,6 @@ RSpec.describe 'getting an issue list for a project' do end end - context 'when sorting by priority' do - let_it_be(:on_project) { { project: sort_project } } - let_it_be(:early_milestone) { create(:milestone, **on_project, due_date: 10.days.from_now) } - let_it_be(:late_milestone) { create(:milestone, **on_project, due_date: 30.days.from_now) } - let_it_be(:priority_1) { create(:label, **on_project, priority: 1) } - let_it_be(:priority_2) { create(:label, **on_project, priority: 5) } - let_it_be(:priority_issue1) { create(:issue, **on_project, labels: [priority_1], milestone: late_milestone) } - let_it_be(:priority_issue2) { create(:issue, **on_project, labels: [priority_2]) } - let_it_be(:priority_issue3) { create(:issue, **on_project, milestone: early_milestone) } - let_it_be(:priority_issue4) { create(:issue, **on_project) } - - context 'when ascending' do - it_behaves_like 'sorted paginated query' do - let(:sort_param) { :PRIORITY_ASC } - let(:first_param) { 2 } - let(:all_records) do - [ - priority_issue3.iid, priority_issue1.iid, - priority_issue2.iid, priority_issue4.iid - ] - end - end - end - - context 'when descending' do - it_behaves_like 'sorted paginated query' do - let(:sort_param) { :PRIORITY_DESC } - let(:first_param) { 2 } - let(:all_records) do - [priority_issue1.iid, priority_issue3.iid, priority_issue2.iid, priority_issue4.iid] - end - end - end - end - context 'when sorting by label priority' do let_it_be(:label1) { create(:label, project: sort_project, priority: 1) } let_it_be(:label2) { create(:label, project: sort_project, priority: 5) } @@ -374,6 +299,7 @@ RSpec.describe 'getting an issue list for a project' do end end end + # rubocop:enable RSpec/MultipleMemoizedHelpers context 'when sorting by milestone due date' do let_it_be(:early_milestone) { create(:milestone, project: sort_project, due_date: 10.days.from_now) } @@ -403,14 +329,17 @@ RSpec.describe 'getting an issue list for a project' do context 'when fetching alert management alert' do let(:fields) do <<~QUERY - edges { - node { + nodes { iid alertManagementAlert { title } + alertManagementAlerts { + nodes { + title + } + } } - } QUERY end @@ -430,11 +359,22 @@ RSpec.describe 'getting an issue list for a project' do it 'returns the alert data' do post_graphql(query, current_user: current_user) - alert_titles = issues_data.map { |issue| issue.dig('node', 'alertManagementAlert', 'title') } + alert_titles = issues_data.map { |issue| issue.dig('alertManagementAlert', 'title') } expected_titles = issues.map { |issue| issue.alert_management_alert&.title } expect(alert_titles).to contain_exactly(*expected_titles) end + + it 'returns the alerts data' do + post_graphql(query, current_user: current_user) + + alert_titles = issues_data.map { |issue| issue.dig('alertManagementAlerts', 'nodes') } + expected_titles = issues.map do |issue| + issue.alert_management_alerts.map { |alert| { 'title' => alert.title } } + end + + expect(alert_titles).to contain_exactly(*expected_titles) + end end context 'when fetching customer_relations_contacts' do @@ -469,13 +409,11 @@ RSpec.describe 'getting an issue list for a project' do context 'when fetching labels' do let(:fields) do <<~QUERY - edges { - node { - id - labels { - nodes { - id - } + nodes { + id + labels { + nodes { + id } } } @@ -491,8 +429,8 @@ RSpec.describe 'getting an issue list for a project' do end def response_label_ids(response_data) - response_data.map do |edge| - edge['node']['labels']['nodes'].map { |u| u['id'] } + response_data.map do |node| + node['labels']['nodes'].map { |u| u['id'] } end.flatten end @@ -502,7 +440,7 @@ RSpec.describe 'getting an issue list for a project' do it 'avoids N+1 queries', :aggregate_failures do control = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: current_user) } - expect(issues_data.count).to eq(2) + expect(issues_data.count).to eq(5) expect(response_label_ids(issues_data)).to match_array(labels_as_global_ids(issues)) new_issues = issues + [create(:issue, project: project, labels: [create(:label, project: project)])] @@ -510,8 +448,8 @@ RSpec.describe 'getting an issue list for a project' do expect { post_graphql(query, current_user: current_user) }.not_to exceed_query_limit(control) # graphql_data is memoized (see spec/support/helpers/graphql_helpers.rb) # so we have to parse the body ourselves the second time - issues_data = Gitlab::Json.parse(response.body)['data']['project']['issues']['edges'] - expect(issues_data.count).to eq(3) + issues_data = Gitlab::Json.parse(response.body)['data']['project']['issues']['nodes'] + expect(issues_data.count).to eq(6) expect(response_label_ids(issues_data)).to match_array(labels_as_global_ids(new_issues)) end end @@ -519,13 +457,11 @@ RSpec.describe 'getting an issue list for a project' do context 'when fetching assignees' do let(:fields) do <<~QUERY - edges { - node { - id - assignees { - nodes { - id - } + nodes { + id + assignees { + nodes { + id } } } @@ -541,8 +477,8 @@ RSpec.describe 'getting an issue list for a project' do end def response_assignee_ids(response_data) - response_data.map do |edge| - edge['node']['assignees']['nodes'].map { |node| node['id'] } + response_data.map do |node| + node['assignees']['nodes'].map { |node| node['id'] } end.flatten end @@ -552,7 +488,7 @@ RSpec.describe 'getting an issue list for a project' do it 'avoids N+1 queries', :aggregate_failures do control = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: current_user) } - expect(issues_data.count).to eq(2) + expect(issues_data.count).to eq(5) expect(response_assignee_ids(issues_data)).to match_array(assignees_as_global_ids(issues)) new_issues = issues + [create(:issue, project: project, assignees: [create(:user)])] @@ -560,8 +496,8 @@ RSpec.describe 'getting an issue list for a project' do expect { post_graphql(query, current_user: current_user) }.not_to exceed_query_limit(control) # graphql_data is memoized (see spec/support/helpers/graphql_helpers.rb) # so we have to parse the body ourselves the second time - issues_data = Gitlab::Json.parse(response.body)['data']['project']['issues']['edges'] - expect(issues_data.count).to eq(3) + issues_data = Gitlab::Json.parse(response.body)['data']['project']['issues']['nodes'] + expect(issues_data.count).to eq(6) expect(response_assignee_ids(issues_data)).to match_array(assignees_as_global_ids(new_issues)) end end @@ -572,11 +508,9 @@ RSpec.describe 'getting an issue list for a project' do let(:statuses) { issue_data.to_h { |issue| [issue['iid'], issue['escalationStatus']] } } let(:fields) do <<~QUERY - edges { - node { - id - escalationStatus - } + nodes { + id + escalationStatus } QUERY end @@ -588,9 +522,9 @@ RSpec.describe 'getting an issue list for a project' do it 'returns the escalation status values' do post_graphql(query, current_user: current_user) - statuses = issues_data.map { |issue| issue.dig('node', 'escalationStatus') } + statuses = issues_data.map { |issue| issue['escalationStatus'] } - expect(statuses).to contain_exactly(escalation_status.status_name.upcase.to_s, nil) + expect(statuses).to contain_exactly(escalation_status.status_name.upcase.to_s, nil, nil, nil, nil) end it 'avoids N+1 queries', :aggregate_failures do @@ -726,8 +660,8 @@ RSpec.describe 'getting an issue list for a project' do end end - def issues_ids - graphql_dig_at(issues_data, :node, :id) + def issue_ids + graphql_dig_at(issues_data, :id) end def query(params = issue_filter_params) diff --git a/spec/requests/api/graphql/project/languages_spec.rb b/spec/requests/api/graphql/project/languages_spec.rb new file mode 100644 index 00000000000..6ef500cde41 --- /dev/null +++ b/spec/requests/api/graphql/project/languages_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Project.languages' do + include GraphqlHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, :public, :repository) } + let_it_be(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + languages { + name + share + color + } + } + } + ) + end + + let_it_be(:test_languages) do + [{ value: 66.69, label: "Ruby", color: "#701516", highlight: "#701516" }, + { value: 22.98, label: "JavaScript", color: "#f1e05a", highlight: "#f1e05a" }, + { value: 7.91, label: "HTML", color: "#e34c26", highlight: "#e34c26" }, + { value: 2.42, label: "CoffeeScript", color: "#244776", highlight: "#244776" }] + end + + let_it_be(:expected_languages) do + test_languages.map { |lang| { 'name' => lang[:label], 'share' => lang[:value], 'color' => lang[:color] } } + end + + before do + allow(project.repository).to receive(:languages).and_return(test_languages) + end + + context "when the languages haven't been detected yet" do + it 'returns expected languages on second request as detection is done asynchronously', :sidekiq_inline do + post_graphql(query, current_user: user) + + expect(graphql_data_at(:project, :languages)).to eq([]) + + post_graphql(query, current_user: user) + + expect(graphql_data_at(:project, :languages)).to eq(expected_languages) + end + end + + context 'when the languages were detected before' do + before do + Projects::DetectRepositoryLanguagesService.new(project, project.first_owner).execute + end + + it 'returns repository languages' do + post_graphql(query, current_user: user) + + expect(graphql_data_at(:project, :languages)).to eq(expected_languages) + end + end +end diff --git a/spec/requests/api/graphql/project/tree/tree_spec.rb b/spec/requests/api/graphql/project/tree/tree_spec.rb index 25e878a5b1a..e63e0d3dd04 100644 --- a/spec/requests/api/graphql/project/tree/tree_spec.rb +++ b/spec/requests/api/graphql/project/tree/tree_spec.rb @@ -4,7 +4,8 @@ require 'spec_helper' RSpec.describe 'getting a tree in a project' do include GraphqlHelpers - let(:project) { create(:project, :repository) } + let_it_be(:project) { create(:project, :repository) } + let(:current_user) { project.first_owner } let(:path) { "" } let(:ref) { "master" } @@ -82,6 +83,89 @@ RSpec.describe 'getting a tree in a project' do end end + context 'when the ref points to a gpg-signed commit with a user' do + let_it_be(:name) { GpgHelpers::User1.names.first } + let_it_be(:email) { GpgHelpers::User1.emails.first } + let_it_be(:current_user) { create(:user, name: name, email: email).tap { |user| project.add_owner(user) } } + let_it_be(:gpg_key) { create(:gpg_key, user: current_user, key: GpgHelpers::User1.public_key) } + + let(:ref) { GpgHelpers::SIGNED_AND_AUTHORED_SHA } + let(:fields) do + <<~QUERY + tree(path:"#{path}", ref:"#{ref}") { + lastCommit { + signature { + ... on GpgSignature { + #{all_graphql_fields_for('GpgSignature'.classify, max_depth: 2)} + } + } + } + } + QUERY + end + + before do + post_graphql(query, current_user: current_user) + end + + it 'returns the expected signature data' do + signature = graphql_data['project']['repository']['tree']['lastCommit']['signature'] + expect(signature['commitSha']).to eq(ref) + expect(signature['user']['id']).to eq("gid://gitlab/User/#{current_user.id}") + expect(signature['gpgKeyUserName']).to eq(name) + expect(signature['gpgKeyUserEmail']).to eq(email) + expect(signature['verificationStatus']).to eq('VERIFIED') + expect(signature['project']['id']).to eq("gid://gitlab/Project/#{project.id}") + end + end + + context 'when the ref points to a X.509-signed commit' do + let_it_be(:email) { X509Helpers::User1.certificate_email } + let_it_be(:current_user) { create(:user, email: email).tap { |user| project.add_owner(user) } } + + let(:ref) { X509Helpers::User1.commit } + let(:fields) do + <<~QUERY + tree(path:"#{path}", ref:"#{ref}") { + lastCommit { + signature { + ... on X509Signature { + #{all_graphql_fields_for('X509Signature'.classify, max_depth: 2)} + } + } + } + } + QUERY + end + + before do + store = OpenSSL::X509::Store.new + store.add_cert(OpenSSL::X509::Certificate.new(X509Helpers::User1.trust_cert)) + allow(OpenSSL::X509::Store).to receive(:new).and_return(store) + post_graphql(query, current_user: current_user) + end + + it 'returns the expected signature data' do + signature = graphql_data['project']['repository']['tree']['lastCommit']['signature'] + expect(signature['commitSha']).to eq(ref) + expect(signature['verificationStatus']).to eq('VERIFIED') + expect(signature['project']['id']).to eq("gid://gitlab/Project/#{project.id}") + end + + it 'returns expected certificate data' do + signature = graphql_data['project']['repository']['tree']['lastCommit']['signature'] + certificate = signature['x509Certificate'] + expect(certificate['certificateStatus']).to eq('good') + expect(certificate['email']).to eq(X509Helpers::User1.certificate_email) + expect(certificate['id']).to be_present + expect(certificate['serialNumber']).to eq(X509Helpers::User1.certificate_serial.to_s) + expect(certificate['subject']).to eq(X509Helpers::User1.certificate_subject) + expect(certificate['subjectKeyIdentifier']).to eq(X509Helpers::User1.certificate_subject_key_identifier) + expect(certificate['createdAt']).to be_present + expect(certificate['updatedAt']).to be_present + end + end + context 'when current user is nil' do it 'returns empty project' do post_graphql(query, current_user: nil) diff --git a/spec/requests/api/graphql/project/work_item_types_spec.rb b/spec/requests/api/graphql/project/work_item_types_spec.rb index 157961c3f66..3d30baab816 100644 --- a/spec/requests/api/graphql/project/work_item_types_spec.rb +++ b/spec/requests/api/graphql/project/work_item_types_spec.rb @@ -57,15 +57,4 @@ RSpec.describe 'getting a list of work item types for a project' do expect(graphql_data).to eq('project' => nil) end end - - context 'when the work_items feature flag is disabled' do - before do - stub_feature_flags(work_items: false) - post_graphql(query, current_user: current_user) - end - - it 'returns null' do - expect(graphql_data.dig('project', 'workItemTypes')).to be_nil - end - end end diff --git a/spec/requests/api/graphql/project/work_items_spec.rb b/spec/requests/api/graphql/project/work_items_spec.rb index e82f6ad24a2..6d20799c9ec 100644 --- a/spec/requests/api/graphql/project/work_items_spec.rb +++ b/spec/requests/api/graphql/project/work_items_spec.rb @@ -10,6 +10,8 @@ RSpec.describe 'getting a work item list for a project' do let_it_be(:current_user) { create(:user) } let_it_be(:label1) { create(:label, project: project) } let_it_be(:label2) { create(:label, project: project) } + let_it_be(:milestone1) { create(:milestone, project: project) } + let_it_be(:milestone2) { create(:milestone, project: project) } let_it_be(:item1) { create(:work_item, project: project, discussion_locked: true, title: 'item1', labels: [label1]) } let_it_be(:item2) do @@ -19,7 +21,8 @@ RSpec.describe 'getting a work item list for a project' do title: 'item2', last_edited_by: current_user, last_edited_at: 1.day.ago, - labels: [label2] + labels: [label2], + milestone: milestone1 ) end @@ -55,7 +58,8 @@ RSpec.describe 'getting a work item list for a project' do :last_edited_by_user, last_edited_at: 1.week.ago, project: project, - labels: [label1, label2] + labels: [label1, label2], + milestone: milestone2 ) expect_graphql_errors_to_be_empty @@ -94,6 +98,11 @@ RSpec.describe 'getting a work item list for a project' do labels { nodes { id } } allowsScopedLabels } + ... on WorkItemWidgetMilestone { + milestone { + id + } + } } } GRAPHQL @@ -121,18 +130,6 @@ RSpec.describe 'getting a work item list for a project' do end end - context 'when work_items flag is disabled' do - before do - stub_feature_flags(work_items: false) - end - - it 'returns an empty list' do - post_graphql(query) - - expect(items_data).to eq([]) - end - end - it 'returns only items visible to user' do post_graphql(query, current_user: current_user) diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb index 2105e479ed2..a55de6adfb2 100644 --- a/spec/requests/api/graphql/work_item_spec.rb +++ b/spec/requests/api/graphql/work_item_spec.rb @@ -298,6 +298,40 @@ RSpec.describe 'Query.work_item(id)' do ) end end + + describe 'milestone widget' do + let_it_be(:milestone) { create(:milestone, project: project) } + + let(:work_item) { create(:work_item, project: project, milestone: milestone) } + + let(:work_item_fields) do + <<~GRAPHQL + id + widgets { + type + ... on WorkItemWidgetMilestone { + milestone { + id + } + } + } + GRAPHQL + end + + it 'returns widget information' do + expect(work_item_data).to include( + 'id' => work_item.to_gid.to_s, + 'widgets' => include( + hash_including( + 'type' => 'MILESTONE', + 'milestone' => { + 'id' => work_item.milestone.to_gid.to_s + } + ) + ) + ) + end + end end context 'when an Issue Global ID is provided' do @@ -323,16 +357,4 @@ RSpec.describe 'Query.work_item(id)' do ) end end - - context 'when the work_items feature flag is disabled' do - before do - stub_feature_flags(work_items: false) - end - - it 'returns nil' do - post_graphql(query) - - expect(work_item_data).to be_nil - end - end end -- cgit v1.2.3