Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/requests/api/graphql')
-rw-r--r--spec/requests/api/graphql/boards/board_list_issues_query_spec.rb40
-rw-r--r--spec/requests/api/graphql/boards/board_lists_query_spec.rb63
-rw-r--r--spec/requests/api/graphql/ci/jobs_spec.rb4
-rw-r--r--spec/requests/api/graphql/ci/pipelines_spec.rb6
-rw-r--r--spec/requests/api/graphql/ci/runner_spec.rb3
-rw-r--r--spec/requests/api/graphql/group/work_item_types_spec.rb11
-rw-r--r--spec/requests/api/graphql/issues_spec.rb117
-rw-r--r--spec/requests/api/graphql/metadata_query_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/ci/pipeline_schedule_take_ownership_spec.rb41
-rw-r--r--spec/requests/api/graphql/mutations/ci/runners_registration_token/reset_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/container_repository/destroy_spec.rb23
-rw-r--r--spec/requests/api/graphql/mutations/incident_management/timeline_event/create_spec.rb32
-rw-r--r--spec/requests/api/graphql/mutations/incident_management/timeline_event/promote_from_note_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/incident_management/timeline_event/update_spec.rb12
-rw-r--r--spec/requests/api/graphql/mutations/incident_management/timeline_event_tag/create_spec.rb57
-rw-r--r--spec/requests/api/graphql/mutations/issues/create_spec.rb33
-rw-r--r--spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb14
-rw-r--r--spec/requests/api/graphql/mutations/work_items/create_spec.rb64
-rw-r--r--spec/requests/api/graphql/mutations/work_items/delete_spec.rb14
-rw-r--r--spec/requests/api/graphql/mutations/work_items/delete_task_spec.rb14
-rw-r--r--spec/requests/api/graphql/mutations/work_items/update_spec.rb105
-rw-r--r--spec/requests/api/graphql/mutations/work_items/update_task_spec.rb16
-rw-r--r--spec/requests/api/graphql/packages/package_spec.rb20
-rw-r--r--spec/requests/api/graphql/project/branch_protections/merge_access_levels_spec.rb104
-rw-r--r--spec/requests/api/graphql/project/branch_protections/push_access_levels_spec.rb104
-rw-r--r--spec/requests/api/graphql/project/branch_rules_spec.rb143
-rw-r--r--spec/requests/api/graphql/project/incident_management/timeline_events_spec.rb43
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb286
-rw-r--r--spec/requests/api/graphql/project/languages_spec.rb62
-rw-r--r--spec/requests/api/graphql/project/tree/tree_spec.rb86
-rw-r--r--spec/requests/api/graphql/project/work_item_types_spec.rb11
-rw-r--r--spec/requests/api/graphql/project/work_items_spec.rb25
-rw-r--r--spec/requests/api/graphql/work_item_spec.rb46
35 files changed, 1004 insertions, 606 deletions
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