summaryrefslogtreecommitdiff
path: root/spec/requests/api/graphql
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-09-19 01:45:44 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-09-19 01:45:44 +0000
commit85dc423f7090da0a52c73eb66faf22ddb20efff9 (patch)
tree9160f299afd8c80c038f08e1545be119f5e3f1e1 /spec/requests/api/graphql
parent15c2c8c66dbe422588e5411eee7e68f1fa440bb8 (diff)
downloadgitlab-ce-85dc423f7090da0a52c73eb66faf22ddb20efff9.tar.gz
Add latest changes from gitlab-org/gitlab@13-4-stable-ee
Diffstat (limited to 'spec/requests/api/graphql')
-rw-r--r--spec/requests/api/graphql/boards/board_list_issues_query_spec.rb8
-rw-r--r--spec/requests/api/graphql/current_user_todos_spec.rb81
-rw-r--r--spec/requests/api/graphql/group/group_members_spec.rb67
-rw-r--r--spec/requests/api/graphql/group_query_spec.rb11
-rw-r--r--spec/requests/api/graphql/instance_statistics_measurements_spec.rb21
-rw-r--r--spec/requests/api/graphql/issue/issue_spec.rb126
-rw-r--r--spec/requests/api/graphql/metrics/dashboard_query_spec.rb155
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/add_spec.rb4
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb4
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb4
-rw-r--r--spec/requests/api/graphql/mutations/boards/destroy_spec.rb79
-rw-r--r--spec/requests/api/graphql/mutations/boards/lists/create_spec.rb54
-rw-r--r--spec/requests/api/graphql/mutations/boards/lists/update_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/branches/create_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/ci/pipeline_cancel_spec.rb51
-rw-r--r--spec/requests/api/graphql/mutations/ci/pipeline_destroy_spec.rb31
-rw-r--r--spec/requests/api/graphql/mutations/ci/pipeline_retry_spec.rb45
-rw-r--r--spec/requests/api/graphql/mutations/commits/create_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/design_management/upload_spec.rb32
-rw-r--r--spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_locked_spec.rb7
-rw-r--r--spec/requests/api/graphql/mutations/issues/set_severity_spec.rb57
-rw-r--r--spec/requests/api/graphql/mutations/issues/update_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/merge_requests/create_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb4
-rw-r--r--spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/notes/destroy_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/notes/update/note_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/snippets/create_spec.rb126
-rw-r--r--spec/requests/api/graphql/mutations/snippets/destroy_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/snippets/update_spec.rb105
-rw-r--r--spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb3
-rw-r--r--spec/requests/api/graphql/mutations/todos/mark_done_spec.rb11
-rw-r--r--spec/requests/api/graphql/mutations/todos/restore_spec.rb11
-rw-r--r--spec/requests/api/graphql/namespace/projects_spec.rb39
-rw-r--r--spec/requests/api/graphql/project/issue/designs/notes_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb148
-rw-r--r--spec/requests/api/graphql/project/merge_request_spec.rb3
-rw-r--r--spec/requests/api/graphql/project/merge_requests_spec.rb46
-rw-r--r--spec/requests/api/graphql/project/release_spec.rb60
-rw-r--r--spec/requests/api/graphql/project/releases_spec.rb19
-rw-r--r--spec/requests/api/graphql/project_query_spec.rb48
-rw-r--r--spec/requests/api/graphql/user/starred_projects_query_spec.rb73
46 files changed, 1283 insertions, 287 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 ae1abb50a40..3628171fcc1 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
@@ -30,7 +30,7 @@ RSpec.describe 'get board lists' do
nodes {
lists {
nodes {
- issues {
+ issues(filters: {labelName: "#{label2.title}"}) {
count
nodes {
#{all_graphql_fields_for('issues'.classify)}
@@ -51,8 +51,8 @@ RSpec.describe 'get board lists' do
shared_examples 'group and project board list issues query' do
let!(:board) { create(:board, resource_parent: board_parent) }
let!(:label_list) { create(:list, board: board, label: label, position: 10) }
- let!(:issue1) { create(:issue, project: issue_project, labels: [label], relative_position: 9) }
- let!(:issue2) { create(:issue, project: issue_project, labels: [label], relative_position: 2) }
+ let!(:issue1) { create(:issue, project: issue_project, labels: [label, label2], relative_position: 9) }
+ let!(:issue2) { create(:issue, project: issue_project, labels: [label, label2], relative_position: 2) }
let!(:issue3) { create(:issue, project: issue_project, labels: [label], relative_position: 9) }
let!(:issue4) { create(:issue, project: issue_project, labels: [label2], relative_position: 432) }
@@ -72,7 +72,7 @@ RSpec.describe 'get board lists' do
it 'can access the issues' do
post_graphql(query("id: \"#{global_id_of(label_list)}\""), current_user: user)
- expect(issue_titles).to eq([issue2.title, issue3.title, issue1.title])
+ expect(issue_titles).to eq([issue2.title, issue1.title])
end
end
end
diff --git a/spec/requests/api/graphql/current_user_todos_spec.rb b/spec/requests/api/graphql/current_user_todos_spec.rb
new file mode 100644
index 00000000000..b657f15d0e9
--- /dev/null
+++ b/spec/requests/api/graphql/current_user_todos_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'A Todoable that implements the CurrentUserTodos interface' do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:todoable) { create(:issue, project: project) }
+ let_it_be(:done_todo) { create(:todo, state: :done, target: todoable, user: current_user) }
+ let_it_be(:pending_todo) { create(:todo, state: :pending, target: todoable, user: current_user) }
+ let(:state) { 'null' }
+
+ let(:todoable_response) do
+ graphql_data_at(:project, :issue, :currentUserTodos, :nodes)
+ end
+
+ let(:query) do
+ <<~GQL
+ {
+ project(fullPath: "#{project.full_path}") {
+ issue(iid: "#{todoable.iid}") {
+ currentUserTodos(state: #{state}) {
+ nodes {
+ #{all_graphql_fields_for('Todo', max_depth: 1)}
+ }
+ }
+ }
+ }
+ }
+ GQL
+ end
+
+ it 'returns todos of the current user' do
+ post_graphql(query, current_user: current_user)
+
+ expect(todoable_response).to contain_exactly(
+ a_hash_including('id' => global_id_of(done_todo)),
+ a_hash_including('id' => global_id_of(pending_todo))
+ )
+ end
+
+ it 'does not return todos of another user', :aggregate_failures do
+ post_graphql(query, current_user: create(:user))
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(todoable_response).to be_empty
+ end
+
+ it 'does not error when there is no logged in user', :aggregate_failures do
+ post_graphql(query)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(todoable_response).to be_empty
+ end
+
+ context 'when `state` argument is `pending`' do
+ let(:state) { 'pending' }
+
+ it 'returns just the pending todo' do
+ post_graphql(query, current_user: current_user)
+
+ expect(todoable_response).to contain_exactly(
+ a_hash_including('id' => global_id_of(pending_todo))
+ )
+ end
+ end
+
+ context 'when `state` argument is `done`' do
+ let(:state) { 'done' }
+
+ it 'returns just the done todo' do
+ post_graphql(query, current_user: current_user)
+
+ expect(todoable_response).to contain_exactly(
+ a_hash_including('id' => global_id_of(done_todo))
+ )
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/group/group_members_spec.rb b/spec/requests/api/graphql/group/group_members_spec.rb
new file mode 100644
index 00000000000..84b2fd63d46
--- /dev/null
+++ b/spec/requests/api/graphql/group/group_members_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting group members information' do
+ include GraphqlHelpers
+
+ let_it_be(:group) { create(:group, :public) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:user_1) { create(:user, username: 'user') }
+ let_it_be(:user_2) { create(:user, username: 'test') }
+
+ let(:member_data) { graphql_data['group']['groupMembers']['edges'] }
+
+ before do
+ [user_1, user_2].each { |user| group.add_guest(user) }
+ end
+
+ context 'when the request is correct' do
+ it_behaves_like 'a working graphql query' do
+ before do
+ fetch_members(user)
+ end
+ end
+
+ it 'returns group members successfully' do
+ fetch_members(user)
+
+ expect(graphql_errors).to be_nil
+ expect_array_response(user_1.to_global_id.to_s, user_2.to_global_id.to_s)
+ end
+
+ it 'returns members that match the search query' do
+ fetch_members(user, { search: 'test' })
+
+ expect(graphql_errors).to be_nil
+ expect_array_response(user_2.to_global_id.to_s)
+ end
+ end
+
+ def fetch_members(user = nil, args = {})
+ post_graphql(members_query(args), current_user: user)
+ end
+
+ def members_query(args = {})
+ members_node = <<~NODE
+ edges {
+ node {
+ user {
+ id
+ }
+ }
+ }
+ NODE
+
+ graphql_query_for("group",
+ { full_path: group.full_path },
+ [query_graphql_field("groupMembers", args, members_node)]
+ )
+ end
+
+ def expect_array_response(*items)
+ expect(response).to have_gitlab_http_status(:success)
+ expect(member_data).to be_an Array
+ expect(member_data.map { |node| node["node"]["user"]["id"] }).to match_array(items)
+ end
+end
diff --git a/spec/requests/api/graphql/group_query_spec.rb b/spec/requests/api/graphql/group_query_spec.rb
index d99bff2e349..83180c7d7a5 100644
--- a/spec/requests/api/graphql/group_query_spec.rb
+++ b/spec/requests/api/graphql/group_query_spec.rb
@@ -89,18 +89,13 @@ RSpec.describe 'getting group information', :do_not_mock_admin_mode do
end
it 'avoids N+1 queries' do
- control_count = ActiveRecord::QueryRecorder.new do
- post_graphql(group_query(group1), current_user: admin)
- end.count
+ pending('See: https://gitlab.com/gitlab-org/gitlab/-/issues/245272')
queries = [{ query: group_query(group1) },
{ query: group_query(group2) }]
- expect do
- post_multiplex(queries, current_user: admin)
- end.not_to exceed_query_limit(control_count)
-
- expect(graphql_errors).to contain_exactly(nil, nil)
+ expect { post_multiplex(queries, current_user: admin) }
+ .to issue_same_number_of_queries_as { post_graphql(group_query(group1), current_user: admin) }
end
end
diff --git a/spec/requests/api/graphql/instance_statistics_measurements_spec.rb b/spec/requests/api/graphql/instance_statistics_measurements_spec.rb
new file mode 100644
index 00000000000..b8cbe54534a
--- /dev/null
+++ b/spec/requests/api/graphql/instance_statistics_measurements_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'InstanceStatisticsMeasurements' do
+ include GraphqlHelpers
+
+ let(:current_user) { create(:user, :admin) }
+ let!(:instance_statistics_measurement_1) { create(:instance_statistics_measurement, :project_count, recorded_at: 20.days.ago, count: 5) }
+ let!(:instance_statistics_measurement_2) { create(:instance_statistics_measurement, :project_count, recorded_at: 10.days.ago, count: 10) }
+
+ let(:query) { graphql_query_for(:instanceStatisticsMeasurements, 'identifier: PROJECTS', 'nodes { count }') }
+
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it 'returns measurement objects' do
+ expect(graphql_data.dig('instanceStatisticsMeasurements', 'nodes')).to eq([{ "count" => 10 }, { "count" => 5 }])
+ end
+end
diff --git a/spec/requests/api/graphql/issue/issue_spec.rb b/spec/requests/api/graphql/issue/issue_spec.rb
new file mode 100644
index 00000000000..1c9d6b25856
--- /dev/null
+++ b/spec/requests/api/graphql/issue/issue_spec.rb
@@ -0,0 +1,126 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query.issue(id)' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:current_user) { create(:user) }
+
+ let(:issue_data) { graphql_data['issue'] }
+
+ let_it_be(:issue_params) { { 'id' => issue.to_global_id.to_s } }
+ let(:issue_fields) { all_graphql_fields_for('Issue'.classify) }
+
+ let(:query) do
+ graphql_query_for('issue', issue_params, issue_fields)
+ end
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+ end
+
+ context 'when the user does not have access to the issue' do
+ it 'returns nil' do
+ project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
+
+ post_graphql(query)
+
+ expect(issue_data).to be nil
+ end
+ end
+
+ context 'when the user does have access' do
+ before do
+ project.add_guest(current_user)
+ end
+
+ it 'returns the issue' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issue_data).to include(
+ 'title' => issue.title,
+ 'description' => issue.description
+ )
+ end
+
+ context 'selecting any single field' do
+ where(:field) do
+ scalar_fields_of('Issue').map { |name| [name] }
+ end
+
+ with_them do
+ it_behaves_like 'a working graphql query' do
+ let(:issue_fields) do
+ field
+ end
+
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it "returns the Issue and field #{params['field']}" do
+ expect(issue_data.keys).to eq([field])
+ end
+ end
+ end
+ end
+
+ context 'selecting multiple fields' do
+ let(:issue_fields) { %w(title description) }
+
+ it 'returns the Issue with the specified fields' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issue_data.keys).to eq( %w(title description) )
+ expect(issue_data['title']).to eq(issue.title)
+ expect(issue_data['description']).to eq(issue.description)
+ end
+ end
+
+ context 'when passed a non-Issue gid' do
+ let(:mr) {create(:merge_request)}
+
+ it 'returns an error' do
+ gid = mr.to_global_id.to_s
+ issue_params['id'] = gid
+
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_errors).not_to be nil
+ expect(graphql_errors.first['message']).to eq("\"#{gid}\" does not represent an instance of Issue")
+ end
+ end
+ end
+
+ context 'when there is a confidential issue' do
+ let!(:confidential_issue) do
+ create(:issue, :confidential, project: project)
+ end
+
+ let(:issue_params) { { 'id' => confidential_issue.to_global_id.to_s } }
+
+ context 'when the user cannot see confidential issues' do
+ it 'returns nil ' do
+ post_graphql(query, current_user: current_user)
+
+ expect(issue_data).to be nil
+ end
+ end
+
+ context 'when the user can see confidential issues' do
+ it 'returns the confidential issue' do
+ project.add_developer(current_user)
+
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data.count).to eq(1)
+ expect(issue_data['confidential']).to be(true)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/metrics/dashboard_query_spec.rb b/spec/requests/api/graphql/metrics/dashboard_query_spec.rb
index 456b0a5dea1..e01f59ee6a0 100644
--- a/spec/requests/api/graphql/metrics/dashboard_query_spec.rb
+++ b/spec/requests/api/graphql/metrics/dashboard_query_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe 'Getting Metrics Dashboard' do
let_it_be(:current_user) { create(:user) }
let(:project) { create(:project) }
- let!(:environment) { create(:environment, project: project) }
+ let(:environment) { create(:environment, project: project) }
let(:query) do
graphql_query_for(
@@ -25,73 +25,156 @@ RSpec.describe 'Getting Metrics Dashboard' do
)
end
- context 'for anonymous user' do
+ context 'with metrics_dashboard_exhaustive_validations feature flag off' do
before do
- post_graphql(query, current_user: current_user)
+ stub_feature_flags(metrics_dashboard_exhaustive_validations: false)
end
- context 'requested dashboard is available' do
- let(:path) { 'config/prometheus/common_metrics.yml' }
+ context 'for anonymous user' do
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ context 'requested dashboard is available' do
+ let(:path) { 'config/prometheus/common_metrics.yml' }
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns nil' do
+ dashboard = graphql_data.dig('project', 'environments', 'nodes')
+
+ expect(dashboard).to be_nil
+ end
+ end
+ end
+
+ context 'for user with developer access' do
+ before do
+ project.add_developer(current_user)
+ post_graphql(query, current_user: current_user)
+ end
+
+ context 'requested dashboard is available' do
+ let(:path) { 'config/prometheus/common_metrics.yml' }
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns metrics dashboard' do
+ dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
+
+ expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => nil)
+ end
+
+ context 'invalid dashboard' do
+ let(:path) { '.gitlab/dashboards/metrics.yml' }
+ let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "---\ndashboard: 'test'" }) }
+
+ it 'returns metrics dashboard' do
+ dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
+
+ expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["panel_groups: should be an array of panel_groups objects"])
+ end
+ end
+
+ context 'empty dashboard' do
+ let(:path) { '.gitlab/dashboards/metrics.yml' }
+ let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "" }) }
+
+ it 'returns metrics dashboard' do
+ dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
- it_behaves_like 'a working graphql query'
+ expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["dashboard: can't be blank", "panel_groups: should be an array of panel_groups objects"])
+ end
+ end
+ end
+
+ context 'requested dashboard can not be found' do
+ let(:path) { 'config/prometheus/i_am_not_here.yml' }
- it 'returns nil' do
- dashboard = graphql_data.dig('project', 'environments', 'nodes')
+ it_behaves_like 'a working graphql query'
- expect(dashboard).to be_nil
+ it 'returns nil' do
+ dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
+
+ expect(dashboard).to be_nil
+ end
end
end
end
- context 'for user with developer access' do
+ context 'with metrics_dashboard_exhaustive_validations feature flag on' do
before do
- project.add_developer(current_user)
- post_graphql(query, current_user: current_user)
+ stub_feature_flags(metrics_dashboard_exhaustive_validations: true)
end
- context 'requested dashboard is available' do
- let(:path) { 'config/prometheus/common_metrics.yml' }
+ context 'for anonymous user' do
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ context 'requested dashboard is available' do
+ let(:path) { 'config/prometheus/common_metrics.yml' }
+
+ it_behaves_like 'a working graphql query'
- it_behaves_like 'a working graphql query'
+ it 'returns nil' do
+ dashboard = graphql_data.dig('project', 'environments', 'nodes')
- it 'returns metrics dashboard' do
- dashboard = graphql_data.dig('project', 'environments', 'nodes')[0]['metricsDashboard']
+ expect(dashboard).to be_nil
+ end
+ end
+ end
- expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => nil)
+ context 'for user with developer access' do
+ before do
+ project.add_developer(current_user)
+ post_graphql(query, current_user: current_user)
end
- context 'invalid dashboard' do
- let(:path) { '.gitlab/dashboards/metrics.yml' }
- let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "---\ndashboard: 'test'" }) }
+ context 'requested dashboard is available' do
+ let(:path) { 'config/prometheus/common_metrics.yml' }
+
+ it_behaves_like 'a working graphql query'
it 'returns metrics dashboard' do
dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
- expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["panel_groups: should be an array of panel_groups objects"])
+ expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => nil)
end
- end
- context 'empty dashboard' do
- let(:path) { '.gitlab/dashboards/metrics.yml' }
- let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "" }) }
+ context 'invalid dashboard' do
+ let(:path) { '.gitlab/dashboards/metrics.yml' }
+ let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "---\ndashboard: 'test'" }) }
- it 'returns metrics dashboard' do
- dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
+ it 'returns metrics dashboard' do
+ dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
- expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["dashboard: can't be blank", "panel_groups: should be an array of panel_groups objects"])
+ expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["root is missing required keys: panel_groups"])
+ end
+ end
+
+ context 'empty dashboard' do
+ let(:path) { '.gitlab/dashboards/metrics.yml' }
+ let(:project) { create(:project, :repository, :custom_repo, namespace: current_user.namespace, files: { path => "" }) }
+
+ it 'returns metrics dashboard' do
+ dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
+
+ expect(dashboard).to eql("path" => path, "schemaValidationWarnings" => ["root is missing required keys: dashboard, panel_groups"])
+ end
end
end
- end
- context 'requested dashboard can not be found' do
- let(:path) { 'config/prometheus/i_am_not_here.yml' }
+ context 'requested dashboard can not be found' do
+ let(:path) { 'config/prometheus/i_am_not_here.yml' }
- it_behaves_like 'a working graphql query'
+ it_behaves_like 'a working graphql query'
- it 'returns nil' do
- dashboard = graphql_data.dig('project', 'environments', 'nodes')[0]['metricsDashboard']
+ it 'returns nil' do
+ dashboard = graphql_data.dig('project', 'environments', 'nodes', 0, 'metricsDashboard')
- expect(dashboard).to be_nil
+ expect(dashboard).to be_nil
+ end
end
end
end
diff --git a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
index 1891300dace..1d38bb39d59 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
@@ -32,9 +32,7 @@ RSpec.describe 'Adding an AwardEmoji' do
context 'when the user does not have permission' do
it_behaves_like 'a mutation that does not create an AwardEmoji'
-
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when the user has permission' do
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 665b511abb8..c6e8800de1f 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
@@ -33,9 +33,7 @@ RSpec.describe 'Removing an AwardEmoji' do
shared_examples 'a mutation that does not authorize the user' do
it_behaves_like 'a mutation that does not destroy an AwardEmoji'
-
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when the current_user does not own the award emoji' 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 ab4a213fde3..2df59ce97ca 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
@@ -143,8 +143,6 @@ RSpec.describe 'Toggling an AwardEmoji' do
context 'when the user does not have permission' do
it_behaves_like 'a mutation that does not create or destroy an AwardEmoji'
-
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
end
end
diff --git a/spec/requests/api/graphql/mutations/boards/destroy_spec.rb b/spec/requests/api/graphql/mutations/boards/destroy_spec.rb
new file mode 100644
index 00000000000..a6d894e698d
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/boards/destroy_spec.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Boards::Destroy do
+ include GraphqlHelpers
+
+ let_it_be(:current_user, reload: true) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project) }
+ let_it_be(:board) { create(:board, project: project) }
+ let_it_be(:other_board) { create(:board, project: project) }
+ let(:mutation) do
+ variables = {
+ id: GitlabSchema.id_from_object(board).to_s
+ }
+
+ graphql_mutation(:destroy_board, variables)
+ end
+
+ subject { post_graphql_mutation(mutation, current_user: current_user) }
+
+ def mutation_response
+ graphql_mutation_response(:destroy_board)
+ end
+
+ context 'when the user does not have permission' do
+ it_behaves_like 'a mutation that returns a top-level access error'
+
+ it 'does not destroy the board' do
+ expect { subject }.not_to change { Board.count }
+ end
+ end
+
+ context 'when the user has permission' do
+ before do
+ project.add_maintainer(current_user)
+ end
+
+ context 'when given id is not for a board' do
+ let_it_be(:board) { build_stubbed(:issue, project: project) }
+
+ it 'returns an error' do
+ subject
+
+ expect(graphql_errors.first['message']).to include('does not represent an instance of Board')
+ end
+ end
+
+ context 'when everything is ok' do
+ it 'destroys the board' do
+ expect { subject }.to change { Board.count }.from(2).to(1)
+ end
+
+ it 'returns an empty board' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to have_key('board')
+ expect(mutation_response['board']).to be_nil
+ end
+ end
+
+ context 'when there is only 1 board for the parent' do
+ before do
+ other_board.destroy!
+ end
+
+ it 'does not destroy the board' do
+ expect { subject }.not_to change { Board.count }.from(1)
+ end
+
+ it 'returns an error and not nil board' do
+ subject
+
+ expect(mutation_response['errors']).not_to be_empty
+ expect(mutation_response['board']).not_to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/boards/lists/create_spec.rb b/spec/requests/api/graphql/mutations/boards/lists/create_spec.rb
new file mode 100644
index 00000000000..328f4fb7b6e
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/boards/lists/create_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Create a label or backlog board list' do
+ include GraphqlHelpers
+
+ let_it_be(:group) { create(:group, :private) }
+ let_it_be(:board) { create(:board, group: group) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:dev_label) do
+ create(:group_label, title: 'Development', color: '#FFAABB', group: group)
+ end
+
+ let(:current_user) { user }
+ let(:mutation) { graphql_mutation(:board_list_create, input) }
+ let(:mutation_response) { graphql_mutation_response(:board_list_create) }
+
+ context 'the user is not allowed to read board lists' do
+ let(:input) { { board_id: board.to_global_id.to_s, backlog: true } }
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+ end
+
+ context 'when user has permissions to admin board lists' do
+ before do
+ group.add_reporter(current_user)
+ end
+
+ describe 'backlog list' do
+ let(:input) { { board_id: board.to_global_id.to_s, backlog: true } }
+
+ it 'creates the list' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['list'])
+ .to include('position' => nil, 'listType' => 'backlog')
+ end
+ end
+
+ describe 'label list' do
+ let(:input) { { board_id: board.to_global_id.to_s, label_id: dev_label.to_global_id.to_s } }
+
+ it 'creates the list' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['list'])
+ .to include('position' => 0, 'listType' => 'label', 'label' => include('title' => 'Development'))
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/boards/lists/update_spec.rb b/spec/requests/api/graphql/mutations/boards/lists/update_spec.rb
index 8a6d2cb3994..8e24e053211 100644
--- a/spec/requests/api/graphql/mutations/boards/lists/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/boards/lists/update_spec.rb
@@ -15,8 +15,7 @@ RSpec.describe 'Update of an existing board list' do
let(:mutation_response) { graphql_mutation_response(:update_board_list) }
context 'the user is not allowed to read board lists' do
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
end
before do
diff --git a/spec/requests/api/graphql/mutations/branches/create_spec.rb b/spec/requests/api/graphql/mutations/branches/create_spec.rb
index 082b445bf3e..fc09f57a389 100644
--- a/spec/requests/api/graphql/mutations/branches/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/branches/create_spec.rb
@@ -15,8 +15,7 @@ RSpec.describe 'Creation of a new branch' do
let(:mutation_response) { graphql_mutation_response(:create_branch) }
context 'the user is not allowed to create a branch' do
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when user has permissions to create a branch' do
diff --git a/spec/requests/api/graphql/mutations/ci/pipeline_cancel_spec.rb b/spec/requests/api/graphql/mutations/ci/pipeline_cancel_spec.rb
new file mode 100644
index 00000000000..a20ac823550
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/ci/pipeline_cancel_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'PipelineCancel' do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project, user: user) }
+
+ let(:mutation) do
+ variables = {
+ id: pipeline.to_global_id.to_s
+ }
+ graphql_mutation(:pipeline_cancel, variables, 'errors')
+ end
+
+ let(:mutation_response) { graphql_mutation_response(:pipeline_cancel) }
+
+ before_all do
+ project.add_maintainer(user)
+ end
+
+ it 'does not cancel any pipelines not owned by the current user' do
+ build = create(:ci_build, :running, pipeline: pipeline)
+
+ post_graphql_mutation(mutation, current_user: create(:user))
+
+ expect(graphql_errors).not_to be_empty
+ expect(build).not_to be_canceled
+ end
+
+ it 'returns a error if the pipline cannot be be canceled' do
+ build = create(:ci_build, :success, pipeline: pipeline)
+
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(mutation_response).to include('errors' => include(eq 'Pipeline is not cancelable'))
+ expect(build).not_to be_canceled
+ end
+
+ it "cancels all cancelable builds from a pipeline" do
+ build = create(:ci_build, :running, pipeline: pipeline)
+
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(build.reload).to be_canceled
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/ci/pipeline_destroy_spec.rb b/spec/requests/api/graphql/mutations/ci/pipeline_destroy_spec.rb
new file mode 100644
index 00000000000..08959d354e2
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/ci/pipeline_destroy_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'PipelineDestroy' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { project.owner }
+ let_it_be(:pipeline) { create(:ci_pipeline, :success, project: project, user: user) }
+
+ let(:mutation) do
+ variables = {
+ id: pipeline.to_global_id.to_s
+ }
+ graphql_mutation(:pipeline_destroy, variables, 'errors')
+ end
+
+ it 'returns an error if the user is not allowed to destroy the pipeline' do
+ post_graphql_mutation(mutation, current_user: create(:user))
+
+ expect(graphql_errors).not_to be_empty
+ end
+
+ it 'destroys a pipeline' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect { pipeline.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/ci/pipeline_retry_spec.rb b/spec/requests/api/graphql/mutations/ci/pipeline_retry_spec.rb
new file mode 100644
index 00000000000..f6acf29c321
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/ci/pipeline_retry_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'PipelineRetry' do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project, user: user) }
+
+ let(:mutation) do
+ variables = {
+ id: pipeline.to_global_id.to_s
+ }
+ graphql_mutation(:pipeline_retry, variables,
+ <<-QL
+ errors
+ pipeline {
+ id
+ }
+ QL
+ )
+ end
+
+ let(:mutation_response) { graphql_mutation_response(:pipeline_retry) }
+
+ before_all do
+ project.add_maintainer(user)
+ end
+
+ it 'returns an error if the user is not allowed to retry the pipeline' do
+ post_graphql_mutation(mutation, current_user: create(:user))
+
+ expect(graphql_errors).not_to be_empty
+ end
+
+ it 'retries a pipeline' do
+ pipeline_id = ::Gitlab::GlobalId.build(pipeline, id: pipeline.id).to_s
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['pipeline']['id']).to eq(pipeline_id)
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/commits/create_spec.rb b/spec/requests/api/graphql/mutations/commits/create_spec.rb
index 9e4a96700bb..ac4fa7cfe83 100644
--- a/spec/requests/api/graphql/mutations/commits/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/commits/create_spec.rb
@@ -24,8 +24,7 @@ RSpec.describe 'Creation of a new commit' do
let(:mutation_response) { graphql_mutation_response(:commit_create) }
context 'the user is not allowed to create a commit' do
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when user has permissions to create a commit' do
diff --git a/spec/requests/api/graphql/mutations/design_management/upload_spec.rb b/spec/requests/api/graphql/mutations/design_management/upload_spec.rb
index 9a9c7107b20..2189ae3c519 100644
--- a/spec/requests/api/graphql/mutations/design_management/upload_spec.rb
+++ b/spec/requests/api/graphql/mutations/design_management/upload_spec.rb
@@ -12,11 +12,11 @@ RSpec.describe "uploading designs" do
let(:files) { [fixture_file_upload("spec/fixtures/dk.png")] }
let(:variables) { {} }
- let(:mutation) do
+ def mutation
input = {
project_path: project.full_path,
iid: issue.iid,
- files: files
+ files: files.dup
}.merge(variables)
graphql_mutation(:design_management_upload, input)
end
@@ -30,31 +30,15 @@ RSpec.describe "uploading designs" do
end
it "returns an error if the user is not allowed to upload designs" do
- post_graphql_mutation(mutation, current_user: create(:user))
+ post_graphql_mutation_with_uploads(mutation, current_user: create(:user))
expect(graphql_errors).to be_present
end
- it "succeeds (backward compatibility)" do
- post_graphql_mutation(mutation, current_user: current_user)
+ it "succeeds, and responds with the created designs" do
+ post_graphql_mutation_with_uploads(mutation, current_user: current_user)
expect(graphql_errors).not_to be_present
- end
-
- it 'succeeds' do
- file_path_in_params = ['designManagementUploadInput', 'files', 0]
- params = mutation_to_apollo_uploads_param(mutation, files: [file_path_in_params])
-
- workhorse_post_with_file(api('/', current_user, version: 'graphql'),
- params: params,
- file_key: '1'
- )
-
- expect(graphql_errors).not_to be_present
- end
-
- it "responds with the created designs" do
- post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response).to include(
"designs" => a_collection_containing_exactly(
@@ -65,7 +49,7 @@ RSpec.describe "uploading designs" do
it "can respond with skipped designs" do
2.times do
- post_graphql_mutation(mutation, current_user: current_user)
+ post_graphql_mutation_with_uploads(mutation, current_user: current_user)
files.each(&:rewind)
end
@@ -80,7 +64,7 @@ RSpec.describe "uploading designs" do
let(:variables) { { iid: "123" } }
it "returns an error" do
- post_graphql_mutation(mutation, current_user: create(:user))
+ post_graphql_mutation_with_uploads(mutation, current_user: create(:user))
expect(graphql_errors).not_to be_empty
end
@@ -92,7 +76,7 @@ RSpec.describe "uploading designs" do
expect(service).to receive(:execute).and_return({ status: :error, message: "Something went wrong" })
end
- post_graphql_mutation(mutation, current_user: current_user)
+ post_graphql_mutation_with_uploads(mutation, current_user: current_user)
expect(mutation_response["errors"].first).to eq("Something went wrong")
end
end
diff --git a/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb b/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb
index 457c37e900b..450996bf76b 100644
--- a/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb
+++ b/spec/requests/api/graphql/mutations/discussions/toggle_resolve_spec.rb
@@ -20,8 +20,7 @@ RSpec.describe 'Toggling the resolve status of a discussion' do
context 'when the user does not have permission' do
let_it_be(:current_user) { create(:user) }
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ["The resource that you are attempting to access does not exist or you don't have permission to perform this action"]
+ it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when user has permission' do
diff --git a/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb b/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb
index f1d55430e02..4989d096925 100644
--- a/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/set_locked_spec.rb
@@ -32,12 +32,7 @@ RSpec.describe 'Setting an issue as locked' do
end
context 'when the user is not allowed to update the issue' do
- it 'returns an error' do
- error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
- post_graphql_mutation(mutation, current_user: current_user)
-
- expect(graphql_errors).to include(a_hash_including('message' => error))
- end
+ it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when user is allowed to update the issue' do
diff --git a/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb b/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb
new file mode 100644
index 00000000000..96fd2368765
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/issues/set_severity_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Setting severity level of an incident' do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let(:incident) { create(:incident) }
+ let(:project) { incident.project }
+ let(:input) { { severity: 'CRITICAL' } }
+
+ let(:mutation) do
+ variables = {
+ project_path: project.full_path,
+ iid: incident.iid.to_s
+ }
+
+ graphql_mutation(:issue_set_severity, variables.merge(input),
+ <<-QL.strip_heredoc
+ clientMutationId
+ errors
+ issue {
+ iid
+ severity
+ }
+ QL
+ )
+ end
+
+ def mutation_response
+ graphql_mutation_response(:issue_set_severity)
+ end
+
+ context 'when the user is not allowed to update the incident' do
+ it 'returns an error' do
+ error = "The resource that you are attempting to access does not exist or you don't have permission to perform this action"
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(graphql_errors).to include(a_hash_including('message' => error))
+ end
+ end
+
+ context 'when the user is allowed to update the incident' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'updates the issue' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response.dig('issue', 'severity')).to eq('CRITICAL')
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/issues/update_spec.rb b/spec/requests/api/graphql/mutations/issues/update_spec.rb
index fd983c683be..af52f9d57a3 100644
--- a/spec/requests/api/graphql/mutations/issues/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/update_spec.rb
@@ -20,8 +20,7 @@ RSpec.describe 'Update of an existing issue' do
let(:mutation_response) { graphql_mutation_response(:update_issue) }
context 'the user is not allowed to update issue' do
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when user has permissions to update issue' do
diff --git a/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb
index 9297ca054c7..bf759521dc0 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/create_spec.rb
@@ -24,8 +24,7 @@ RSpec.describe 'Creation of a new merge request' do
let(:mutation_response) { graphql_mutation_response(:merge_request_create) }
context 'the user is not allowed to create a branch' do
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when user has permissions to create a merge request' do
diff --git a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb
index 0e2da94f0f9..10ca2cf1cf8 100644
--- a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb
@@ -101,7 +101,7 @@ RSpec.describe Mutations::Metrics::Dashboard::Annotations::Create do
graphql_mutation(:create_annotation, variables)
end
- it_behaves_like 'a mutation that returns top-level errors', errors: ['invalid_id is not a valid GitLab id.']
+ it_behaves_like 'a mutation that returns top-level errors', errors: ['invalid_id is not a valid GitLab ID.']
end
end
end
@@ -188,7 +188,7 @@ RSpec.describe Mutations::Metrics::Dashboard::Annotations::Create do
graphql_mutation(:create_annotation, variables)
end
- it_behaves_like 'a mutation that returns top-level errors', errors: ['invalid_id is not a valid GitLab id.']
+ it_behaves_like 'a mutation that returns top-level errors', errors: ['invalid_id is not a valid GitLab ID.']
end
end
diff --git a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb
index 2459a6f3828..7357f3e1e35 100644
--- a/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb
+++ b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/delete_spec.rb
@@ -45,7 +45,7 @@ RSpec.describe Mutations::Metrics::Dashboard::Annotations::Delete do
graphql_mutation(:delete_annotation, variables)
end
- it_behaves_like 'a mutation that returns top-level errors', errors: ['invalid_id is not a valid GitLab id.']
+ it_behaves_like 'a mutation that returns top-level errors', errors: ['invalid_id is not a valid GitLab ID.']
end
context 'when the delete fails' do
diff --git a/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
index e847c46be1b..21da1332465 100644
--- a/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
@@ -44,7 +44,7 @@ RSpec.describe 'Adding a DiffNote' do
it_behaves_like 'a Note mutation when there are active record validation errors', model: DiffNote
context do
- let(:diff_refs) { build(:merge_request).diff_refs } # Allow fake diff refs so arguments are valid
+ let(:diff_refs) { build(:commit).diff_refs } # Allow fake diff refs so arguments are valid
it_behaves_like 'a Note mutation when the given resource id is not for a Noteable'
end
diff --git a/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb
index 896a398e308..8bc68e6017c 100644
--- a/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb
@@ -47,7 +47,7 @@ RSpec.describe 'Adding an image DiffNote' do
it_behaves_like 'a Note mutation when there are active record validation errors', model: DiffNote
context do
- let(:diff_refs) { build(:merge_request).diff_refs } # Allow fake diff refs so arguments are valid
+ let(:diff_refs) { build(:commit).diff_refs } # Allow fake diff refs so arguments are valid
it_behaves_like 'a Note mutation when the given resource id is not for a Noteable'
end
diff --git a/spec/requests/api/graphql/mutations/notes/destroy_spec.rb b/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
index 6002a5b5b9d..49f09fadfea 100644
--- a/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
@@ -21,8 +21,7 @@ RSpec.describe 'Destroying a Note' do
context 'when the user does not have permission' do
let(:current_user) { create(:user) }
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
it 'does not destroy the Note' do
expect do
diff --git a/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb
index 463a872d95d..0c00906d6bf 100644
--- a/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/update/image_diff_note_spec.rb
@@ -59,8 +59,7 @@ RSpec.describe 'Updating an image DiffNote' do
context 'when the user does not have permission' do
let_it_be(:current_user) { create(:user) }
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
it 'does not update the DiffNote' do
post_graphql_mutation(mutation, current_user: current_user)
diff --git a/spec/requests/api/graphql/mutations/notes/update/note_spec.rb b/spec/requests/api/graphql/mutations/notes/update/note_spec.rb
index 0d93afe9434..5a92ffe61b8 100644
--- a/spec/requests/api/graphql/mutations/notes/update/note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/update/note_spec.rb
@@ -22,8 +22,7 @@ RSpec.describe 'Updating a Note' do
context 'when the user does not have permission' do
let_it_be(:current_user) { create(:user) }
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
it 'does not update the Note' do
post_graphql_mutation(mutation, current_user: current_user)
diff --git a/spec/requests/api/graphql/mutations/snippets/create_spec.rb b/spec/requests/api/graphql/mutations/snippets/create_spec.rb
index 56a5f4907c1..1bb446de708 100644
--- a/spec/requests/api/graphql/mutations/snippets/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/create_spec.rb
@@ -7,22 +7,24 @@ RSpec.describe 'Creating a Snippet' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
- let(:content) { 'Initial content' }
+
let(:description) { 'Initial description' }
let(:title) { 'Initial title' }
- let(:file_name) { 'Initial file_name' }
let(:visibility_level) { 'public' }
+ let(:action) { :create }
+ let(:file_1) { { filePath: 'example_file1', content: 'This is the example file 1' }}
+ let(:file_2) { { filePath: 'example_file2', content: 'This is the example file 2' }}
+ let(:actions) { [{ action: action }.merge(file_1), { action: action }.merge(file_2)] }
let(:project_path) { nil }
let(:uploaded_files) { nil }
let(:mutation_vars) do
{
- content: content,
description: description,
visibility_level: visibility_level,
- file_name: file_name,
title: title,
project_path: project_path,
- uploaded_files: uploaded_files
+ uploaded_files: uploaded_files,
+ blob_actions: actions
}
end
@@ -62,24 +64,47 @@ RSpec.describe 'Creating a Snippet' do
context 'when the user has permission' do
let(:current_user) { user }
- context 'with PersonalSnippet' do
- it 'creates the Snippet' do
+ shared_examples 'does not create snippet' do
+ it 'does not create the Snippet' do
expect do
subject
- end.to change { Snippet.count }.by(1)
+ end.not_to change { Snippet.count }
end
- it 'returns the created Snippet' do
+ it 'does not return Snippet' do
subject
- expect(mutation_response['snippet']['blob']['richData']).to be_nil
- expect(mutation_response['snippet']['blob']['plainData']).to match(content)
+ expect(mutation_response['snippet']).to be_nil
+ end
+ end
+
+ shared_examples 'creates snippet' do
+ it 'returns the created Snippet' do
+ expect do
+ subject
+ end.to change { Snippet.count }.by(1)
+
expect(mutation_response['snippet']['title']).to eq(title)
expect(mutation_response['snippet']['description']).to eq(description)
- expect(mutation_response['snippet']['fileName']).to eq(file_name)
expect(mutation_response['snippet']['visibilityLevel']).to eq(visibility_level)
- expect(mutation_response['snippet']['project']).to be_nil
+ expect(mutation_response['snippet']['blobs'][0]['plainData']).to match(file_1[:content])
+ expect(mutation_response['snippet']['blobs'][0]['fileName']).to match(file_1[:file_path])
+ expect(mutation_response['snippet']['blobs'][1]['plainData']).to match(file_2[:content])
+ expect(mutation_response['snippet']['blobs'][1]['fileName']).to match(file_2[:file_path])
end
+
+ context 'when action is invalid' do
+ let(:file_1) { { filePath: 'example_file1' }}
+
+ it_behaves_like 'a mutation that returns errors in the response', errors: ['Snippet actions have invalid data']
+ it_behaves_like 'does not create snippet'
+ end
+
+ it_behaves_like 'snippet edit usage data counters'
+ end
+
+ context 'with PersonalSnippet' do
+ it_behaves_like 'creates snippet'
end
context 'with ProjectSnippet' do
@@ -89,23 +114,7 @@ RSpec.describe 'Creating a Snippet' do
project.add_developer(current_user)
end
- it 'creates the Snippet' do
- expect do
- subject
- end.to change { Snippet.count }.by(1)
- end
-
- it 'returns the created Snippet' do
- subject
-
- expect(mutation_response['snippet']['blob']['richData']).to be_nil
- expect(mutation_response['snippet']['blob']['plainData']).to match(content)
- expect(mutation_response['snippet']['title']).to eq(title)
- expect(mutation_response['snippet']['description']).to eq(description)
- expect(mutation_response['snippet']['fileName']).to eq(file_name)
- expect(mutation_response['snippet']['visibilityLevel']).to eq(visibility_level)
- expect(mutation_response['snippet']['project']['fullPath']).to eq(project_path)
- end
+ it_behaves_like 'creates snippet'
context 'when the project path is invalid' do
let(:project_path) { 'foobar' }
@@ -122,61 +131,8 @@ RSpec.describe 'Creating a Snippet' do
it_behaves_like 'a mutation that returns top-level errors',
errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
end
- end
-
- shared_examples 'does not create snippet' do
- it 'does not create the Snippet' do
- expect do
- subject
- end.not_to change { Snippet.count }
- end
-
- it 'does not return Snippet' do
- subject
-
- expect(mutation_response['snippet']).to be_nil
- end
- end
-
- context 'when snippet is created using the files param' do
- let(:action) { :create }
- let(:file_1) { { filePath: 'example_file1', content: 'This is the example file 1' }}
- let(:file_2) { { filePath: 'example_file2', content: 'This is the example file 2' }}
- let(:actions) { [{ action: action }.merge(file_1), { action: action }.merge(file_2)] }
- let(:mutation_vars) do
- {
- description: description,
- visibility_level: visibility_level,
- project_path: project_path,
- title: title,
- blob_actions: actions
- }
- end
-
- it 'creates the Snippet' do
- expect do
- subject
- end.to change { Snippet.count }.by(1)
- end
-
- it 'returns the created Snippet' do
- subject
- expect(mutation_response['snippet']['title']).to eq(title)
- expect(mutation_response['snippet']['description']).to eq(description)
- expect(mutation_response['snippet']['visibilityLevel']).to eq(visibility_level)
- expect(mutation_response['snippet']['blobs'][0]['plainData']).to match(file_1[:content])
- expect(mutation_response['snippet']['blobs'][0]['fileName']).to match(file_1[:file_path])
- expect(mutation_response['snippet']['blobs'][1]['plainData']).to match(file_2[:content])
- expect(mutation_response['snippet']['blobs'][1]['fileName']).to match(file_2[:file_path])
- end
-
- context 'when action is invalid' do
- let(:file_1) { { filePath: 'example_file1' }}
-
- it_behaves_like 'a mutation that returns errors in the response', errors: ['Snippet actions have invalid data']
- it_behaves_like 'does not create snippet'
- end
+ it_behaves_like 'snippet edit usage data counters'
end
context 'when there are ActiveRecord validation errors' do
@@ -187,7 +143,7 @@ RSpec.describe 'Creating a Snippet' do
end
context 'when there non ActiveRecord errors' do
- let(:file_name) { 'invalid://file/path' }
+ let(:file_1) { { filePath: 'invalid://file/path', content: 'foobar' }}
it_behaves_like 'a mutation that returns errors in the response', errors: ['Repository Error creating the snippet - Invalid file name']
it_behaves_like 'does not create snippet'
diff --git a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
index c861564c66b..b71f87d2702 100644
--- a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
@@ -56,7 +56,7 @@ RSpec.describe 'Destroying a Snippet' do
post_graphql_mutation(mutation, current_user: current_user)
expect(graphql_errors)
- .to include(a_hash_including('message' => "#{snippet_gid} is not a valid id for Snippet."))
+ .to include(a_hash_including('message' => "#{snippet_gid} is not a valid ID for Snippet."))
end
it 'does not destroy the Snippet' do
diff --git a/spec/requests/api/graphql/mutations/snippets/update_spec.rb b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
index 3f39c0ab851..58ce74b9263 100644
--- a/spec/requests/api/graphql/mutations/snippets/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
@@ -12,18 +12,20 @@ RSpec.describe 'Updating a Snippet' do
let(:updated_content) { 'Updated content' }
let(:updated_description) { 'Updated description' }
let(:updated_title) { 'Updated_title' }
- let(:updated_file_name) { 'Updated file_name' }
let(:current_user) { snippet.author }
-
+ let(:updated_file) { 'CHANGELOG' }
+ let(:deleted_file) { 'README' }
let(:snippet_gid) { GitlabSchema.id_from_object(snippet).to_s }
let(:mutation_vars) do
{
id: snippet_gid,
- content: updated_content,
description: updated_description,
visibility_level: 'public',
- file_name: updated_file_name,
- title: updated_title
+ title: updated_title,
+ blob_actions: [
+ { action: :update, filePath: updated_file, content: updated_content },
+ { action: :delete, filePath: deleted_file }
+ ]
}
end
@@ -50,21 +52,32 @@ RSpec.describe 'Updating a Snippet' do
end
context 'when the user has permission' do
- it 'updates the Snippet' do
+ it 'updates the snippet record' do
post_graphql_mutation(mutation, current_user: current_user)
expect(snippet.reload.title).to eq(updated_title)
end
- it 'returns the updated Snippet' do
+ it 'updates the Snippet' do
+ blob_to_update = blob_at(updated_file)
+ blob_to_delete = blob_at(deleted_file)
+
+ expect(blob_to_update.data).not_to eq updated_content
+ expect(blob_to_delete).to be_present
+
post_graphql_mutation(mutation, current_user: current_user)
- expect(mutation_response['snippet']['blob']['richData']).to be_nil
- expect(mutation_response['snippet']['blob']['plainData']).to match(updated_content)
- expect(mutation_response['snippet']['title']).to eq(updated_title)
- expect(mutation_response['snippet']['description']).to eq(updated_description)
- expect(mutation_response['snippet']['fileName']).to eq(updated_file_name)
- expect(mutation_response['snippet']['visibilityLevel']).to eq('public')
+ blob_to_update = blob_at(updated_file)
+ blob_to_delete = blob_at(deleted_file)
+
+ aggregate_failures do
+ expect(blob_to_update.data).to eq updated_content
+ expect(blob_to_delete).to be_nil
+ expect(blob_in_mutation_response(updated_file)['plainData']).to match(updated_content)
+ expect(mutation_response['snippet']['title']).to eq(updated_title)
+ expect(mutation_response['snippet']['description']).to eq(updated_description)
+ expect(mutation_response['snippet']['visibilityLevel']).to eq('public')
+ end
end
context 'when there are ActiveRecord validation errors' do
@@ -79,16 +92,29 @@ RSpec.describe 'Updating a Snippet' do
end
it 'returns the Snippet with its original values' do
+ blob_to_update = blob_at(updated_file)
+ blob_to_delete = blob_at(deleted_file)
+
post_graphql_mutation(mutation, current_user: current_user)
- expect(mutation_response['snippet']['blob']['richData']).to be_nil
- expect(mutation_response['snippet']['blob']['plainData']).to match(original_content)
- expect(mutation_response['snippet']['title']).to eq(original_title)
- expect(mutation_response['snippet']['description']).to eq(original_description)
- expect(mutation_response['snippet']['fileName']).to eq(original_file_name)
- expect(mutation_response['snippet']['visibilityLevel']).to eq('private')
+ aggregate_failures do
+ expect(blob_at(updated_file).data).to eq blob_to_update.data
+ expect(blob_at(deleted_file).data).to eq blob_to_delete.data
+ expect(blob_in_mutation_response(deleted_file)['plainData']).not_to be_nil
+ expect(mutation_response['snippet']['title']).to eq(original_title)
+ expect(mutation_response['snippet']['description']).to eq(original_description)
+ expect(mutation_response['snippet']['visibilityLevel']).to eq('private')
+ end
end
end
+
+ def blob_in_mutation_response(filename)
+ mutation_response['snippet']['blobs'].select { |blob| blob['name'] == filename }[0]
+ end
+
+ def blob_at(filename)
+ snippet.repository.blob_at('HEAD', filename)
+ end
end
end
@@ -96,6 +122,7 @@ RSpec.describe 'Updating a Snippet' do
let(:snippet) do
create(:personal_snippet,
:private,
+ :repository,
file_name: original_file_name,
title: original_title,
content: original_content,
@@ -104,6 +131,7 @@ RSpec.describe 'Updating a Snippet' do
it_behaves_like 'graphql update actions'
it_behaves_like 'when the snippet is not found'
+ it_behaves_like 'snippet edit usage data counters'
end
describe 'ProjectSnippet' do
@@ -111,6 +139,7 @@ RSpec.describe 'Updating a Snippet' do
let(:snippet) do
create(:project_snippet,
:private,
+ :repository,
project: project,
author: create(:user),
file_name: original_file_name,
@@ -145,44 +174,10 @@ RSpec.describe 'Updating a Snippet' do
expect(errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)
end
end
- end
-
- it_behaves_like 'when the snippet is not found'
- end
-
- context 'when using the files params' do
- let!(:snippet) { create(:personal_snippet, :private, :repository) }
- let(:updated_content) { 'updated_content' }
- let(:updated_file) { 'CHANGELOG' }
- let(:deleted_file) { 'README' }
- let(:mutation_vars) do
- {
- id: snippet_gid,
- blob_actions: [
- { action: :update, filePath: updated_file, content: updated_content },
- { action: :delete, filePath: deleted_file }
- ]
- }
- end
- it 'updates the Snippet' do
- blob_to_update = blob_at(updated_file)
- expect(blob_to_update.data).not_to eq updated_content
-
- blob_to_delete = blob_at(deleted_file)
- expect(blob_to_delete).to be_present
-
- post_graphql_mutation(mutation, current_user: current_user)
-
- blob_to_update = blob_at(updated_file)
- expect(blob_to_update.data).to eq updated_content
-
- blob_to_delete = blob_at(deleted_file)
- expect(blob_to_delete).to be_nil
+ it_behaves_like 'snippet edit usage data counters'
end
- def blob_at(filename)
- snippet.repository.blob_at('HEAD', filename)
- end
+ it_behaves_like 'when the snippet is not found'
end
end
diff --git a/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb b/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb
index ed5552f3e30..705ef28ffd4 100644
--- a/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/mark_all_done_spec.rb
@@ -59,7 +59,6 @@ RSpec.describe 'Marking all todos done' do
context 'when user is not logged in' do
let(:current_user) { nil }
- it_behaves_like 'a mutation that returns top-level errors',
- errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+ it_behaves_like 'a mutation that returns a top-level access error'
end
end
diff --git a/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb b/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb
index 9c4733f6769..8bf8b96aff5 100644
--- a/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/mark_done_spec.rb
@@ -63,14 +63,11 @@ RSpec.describe 'Marking todos done' do
context 'when todo does not belong to requesting user' do
let(:input) { { id: other_user_todo.to_global_id.to_s } }
- let(:access_error) { 'The resource that you are attempting to access does not exist or you don\'t have permission to perform this action' }
- it 'contains the expected error' do
- post_graphql_mutation(mutation, current_user: current_user)
+ it_behaves_like 'a mutation that returns a top-level access error'
- errors = json_response['errors']
- expect(errors).not_to be_blank
- expect(errors.first['message']).to eq(access_error)
+ it 'results in the correct todo states' do
+ post_graphql_mutation(mutation, current_user: current_user)
expect(todo1.reload.state).to eq('pending')
expect(todo2.reload.state).to eq('done')
@@ -80,7 +77,7 @@ RSpec.describe 'Marking todos done' do
context 'when using an invalid gid' do
let(:input) { { id: 'invalid_gid' } }
- let(:invalid_gid_error) { 'invalid_gid is not a valid GitLab id.' }
+ let(:invalid_gid_error) { 'invalid_gid is not a valid GitLab ID.' }
it 'contains the expected error' do
post_graphql_mutation(mutation, current_user: current_user)
diff --git a/spec/requests/api/graphql/mutations/todos/restore_spec.rb b/spec/requests/api/graphql/mutations/todos/restore_spec.rb
index 6dedde56e13..8451dcdf587 100644
--- a/spec/requests/api/graphql/mutations/todos/restore_spec.rb
+++ b/spec/requests/api/graphql/mutations/todos/restore_spec.rb
@@ -63,14 +63,11 @@ RSpec.describe 'Restoring Todos' do
context 'when todo does not belong to requesting user' do
let(:input) { { id: other_user_todo.to_global_id.to_s } }
- let(:access_error) { 'The resource that you are attempting to access does not exist or you don\'t have permission to perform this action' }
- it 'contains the expected error' do
- post_graphql_mutation(mutation, current_user: current_user)
+ it_behaves_like 'a mutation that returns a top-level access error'
- errors = json_response['errors']
- expect(errors).not_to be_blank
- expect(errors.first['message']).to eq(access_error)
+ it 'results in the correct todo states' do
+ post_graphql_mutation(mutation, current_user: current_user)
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('pending')
@@ -80,7 +77,7 @@ RSpec.describe 'Restoring Todos' do
context 'when using an invalid gid' do
let(:input) { { id: 'invalid_gid' } }
- let(:invalid_gid_error) { 'invalid_gid is not a valid GitLab id.' }
+ let(:invalid_gid_error) { 'invalid_gid is not a valid GitLab ID.' }
it 'contains the expected error' do
post_graphql_mutation(mutation, current_user: current_user)
diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb
index 0b634e6b689..03160719389 100644
--- a/spec/requests/api/graphql/namespace/projects_spec.rb
+++ b/spec/requests/api/graphql/namespace/projects_spec.rb
@@ -78,4 +78,43 @@ RSpec.describe 'getting projects' do
it_behaves_like 'a graphql namespace'
end
+
+ describe 'sorting and pagination' do
+ let(:data_path) { [:namespace, :projects] }
+
+ def pagination_query(params, page_info)
+ graphql_query_for(
+ 'namespace',
+ { 'fullPath' => subject.full_path },
+ <<~QUERY
+ projects(includeSubgroups: #{include_subgroups}, search: "#{search}", #{params}) {
+ #{page_info} edges {
+ node {
+ #{all_graphql_fields_for('Project')}
+ }
+ }
+ }
+ QUERY
+ )
+ end
+
+ def pagination_results_data(data)
+ data.map { |project| project.dig('node', 'name') }
+ end
+
+ context 'when sorting by similarity' do
+ let!(:project_1) { create(:project, name: 'Project', path: 'project', namespace: subject) }
+ let!(:project_2) { create(:project, name: 'Test Project', path: 'test-project', namespace: subject) }
+ let!(:project_3) { create(:project, name: 'Test', path: 'test', namespace: subject) }
+ let!(:project_4) { create(:project, name: 'Test Project Other', path: 'other-test-project', namespace: subject) }
+ let(:search) { 'test' }
+ let(:current_user) { user }
+
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { 'SIMILARITY' }
+ let(:first_param) { 2 }
+ let(:expected_results) { [project_3.name, project_2.name, project_4.name] }
+ end
+ end
+ end
end
diff --git a/spec/requests/api/graphql/project/issue/designs/notes_spec.rb b/spec/requests/api/graphql/project/issue/designs/notes_spec.rb
index ae5c8363d0f..65191e057c7 100644
--- a/spec/requests/api/graphql/project/issue/designs/notes_spec.rb
+++ b/spec/requests/api/graphql/project/issue/designs/notes_spec.rb
@@ -14,8 +14,6 @@ RSpec.describe 'Getting designs related to an issue' do
before do
enable_design_management
-
- note
end
it_behaves_like 'a working graphql query' do
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
index 06e613a09bc..5d4276f47ca 100644
--- a/spec/requests/api/graphql/project/issues_spec.rb
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -5,12 +5,13 @@ require 'spec_helper'
RSpec.describe 'getting an issue list for a project' do
include GraphqlHelpers
- let(:project) { create(:project, :repository, :public) }
- let(:current_user) { create(:user) }
let(:issues_data) { graphql_data['project']['issues']['edges'] }
- let!(:issues) do
+
+ let_it_be(:project) { create(:project, :repository, :public) }
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:issues, reload: true) do
[create(:issue, project: project, discussion_locked: true),
- create(:issue, project: project)]
+ create(:issue, :with_alert, project: project)]
end
let(:fields) do
@@ -85,7 +86,7 @@ RSpec.describe 'getting an issue list for a project' do
end
context 'when there is a confidential issue' do
- let!(:confidential_issue) do
+ let_it_be(:confidential_issue) do
create(:issue, :confidential, project: project)
end
@@ -256,9 +257,140 @@ RSpec.describe 'getting an issue list for a project' do
end
end
- def grab_iids(data = issues_data)
- data.map do |issue|
- issue.dig('node', 'iid').to_i
+ context 'fetching alert management alert' do
+ let(:fields) do
+ <<~QUERY
+ edges {
+ node {
+ iid
+ alertManagementAlert {
+ title
+ }
+ }
+ }
+ QUERY
+ end
+
+ # Alerts need to have developer permission and above
+ before do
+ project.add_developer(current_user)
+ end
+
+ it 'avoids N+1 queries' do
+ control = ActiveRecord::QueryRecorder.new { post_graphql(query, current_user: current_user) }
+
+ create(:alert_management_alert, :with_issue, project: project )
+
+ expect { post_graphql(query, current_user: current_user) }.not_to exceed_query_limit(control)
+ end
+
+ it 'returns the alert data' do
+ post_graphql(query, current_user: current_user)
+
+ alert_titles = issues_data.map { |issue| issue.dig('node', 'alertManagementAlert', 'title') }
+ expected_titles = issues.map { |issue| issue.alert_management_alert&.title }
+
+ expect(alert_titles).to contain_exactly(*expected_titles)
+ end
+ end
+
+ context 'fetching labels' do
+ let(:fields) do
+ <<~QUERY
+ edges {
+ node {
+ id
+ labels {
+ nodes {
+ id
+ }
+ }
+ }
+ }
+ QUERY
+ end
+
+ before do
+ issues.each do |issue|
+ # create a label for each issue we have to properly test N+1
+ label = create(:label, project: project)
+ issue.update!(labels: [label])
+ end
+ end
+
+ def response_label_ids(response_data)
+ response_data.map do |edge|
+ edge['node']['labels']['nodes'].map { |u| u['id'] }
+ end.flatten
+ end
+
+ def labels_as_global_ids(issues)
+ issues.map(&:labels).flatten.map(&:to_global_id).map(&:to_s)
+ end
+
+ 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(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)])]
+
+ 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)
+ expect(response_label_ids(issues_data)).to match_array(labels_as_global_ids(new_issues))
+ end
+ end
+
+ context 'fetching assignees' do
+ let(:fields) do
+ <<~QUERY
+ edges {
+ node {
+ id
+ assignees {
+ nodes {
+ id
+ }
+ }
+ }
+ }
+ QUERY
+ end
+
+ before do
+ issues.each do |issue|
+ # create an assignee for each issue we have to properly test N+1
+ assignee = create(:user)
+ issue.update!(assignees: [assignee])
+ end
+ end
+
+ def response_assignee_ids(response_data)
+ response_data.map do |edge|
+ edge['node']['assignees']['nodes'].map { |node| node['id'] }
+ end.flatten
+ end
+
+ def assignees_as_global_ids(issues)
+ issues.map(&:assignees).flatten.map(&:to_global_id).map(&:to_s)
+ end
+
+ 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(response_assignee_ids(issues_data)).to match_array(assignees_as_global_ids(issues))
+
+ new_issues = issues + [create(:issue, project: project, assignees: [create(:user)])]
+
+ 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)
+ expect(response_assignee_ids(issues_data)).to match_array(assignees_as_global_ids(new_issues))
end
end
end
diff --git a/spec/requests/api/graphql/project/merge_request_spec.rb b/spec/requests/api/graphql/project/merge_request_spec.rb
index c39358a2db1..fae52fe814d 100644
--- a/spec/requests/api/graphql/project/merge_request_spec.rb
+++ b/spec/requests/api/graphql/project/merge_request_spec.rb
@@ -124,7 +124,8 @@ RSpec.describe 'getting merge request information nested in a project' do
'removeSourceBranch' => false,
'cherryPickOnCurrentMergeRequest' => false,
'revertOnCurrentMergeRequest' => false,
- 'updateMergeRequest' => false
+ 'updateMergeRequest' => false,
+ 'canMerge' => false
}
post_graphql(query, current_user: current_user)
diff --git a/spec/requests/api/graphql/project/merge_requests_spec.rb b/spec/requests/api/graphql/project/merge_requests_spec.rb
index bb63a5994b0..22b003501a1 100644
--- a/spec/requests/api/graphql/project/merge_requests_spec.rb
+++ b/spec/requests/api/graphql/project/merge_requests_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe 'getting merge request listings nested in a project' do
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:current_user) { create(:user) }
- let_it_be(:label) { create(:label) }
+ let_it_be(:label) { create(:label, project: project) }
let_it_be(:merge_request_a) { create(:labeled_merge_request, :unique_branches, source_project: project, labels: [label]) }
let_it_be(:merge_request_b) { create(:merge_request, :closed, :unique_branches, source_project: project) }
let_it_be(:merge_request_c) { create(:labeled_merge_request, :closed, :unique_branches, source_project: project, labels: [label]) }
@@ -210,4 +210,48 @@ RSpec.describe 'getting merge request listings nested in a project' do
include_examples 'N+1 query check'
end
end
+ describe 'sorting and pagination' do
+ let(:data_path) { [:project, :mergeRequests] }
+
+ def pagination_query(params, page_info)
+ graphql_query_for(
+ :project,
+ { full_path: project.full_path },
+ <<~QUERY
+ mergeRequests(#{params}) {
+ #{page_info} edges {
+ node {
+ id
+ }
+ }
+ }
+ QUERY
+ )
+ end
+
+ def pagination_results_data(data)
+ data.map { |project| project.dig('node', 'id') }
+ end
+
+ context 'when sorting by merged_at DESC' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { 'MERGED_AT_DESC' }
+ let(:first_param) { 2 }
+
+ let(:expected_results) do
+ [
+ merge_request_b,
+ merge_request_c,
+ merge_request_d,
+ merge_request_a
+ ].map(&:to_gid).map(&:to_s)
+ end
+
+ before do
+ merge_request_c.metrics.update!(merged_at: 5.days.ago)
+ merge_request_b.metrics.update!(merged_at: 1.day.ago)
+ end
+ end
+ end
+ end
end
diff --git a/spec/requests/api/graphql/project/release_spec.rb b/spec/requests/api/graphql/project/release_spec.rb
index f9c19d9747d..8fce29d0dc6 100644
--- a/spec/requests/api/graphql/project/release_spec.rb
+++ b/spec/requests/api/graphql/project/release_spec.rb
@@ -10,6 +10,8 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
let_it_be(:guest) { create(:user) }
let_it_be(:reporter) { create(:user) }
let_it_be(:stranger) { create(:user) }
+ let_it_be(:link_filepath) { '/direct/asset/link/path' }
+ let_it_be(:released_at) { Time.now - 1.day }
let(:params_for_issues_and_mrs) { { scope: 'all', state: 'opened', release_tag: release.tag } }
let(:post_query) { post_graphql(query, current_user: current_user) }
@@ -38,6 +40,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
name
createdAt
releasedAt
+ upcomingRelease
})
end
@@ -53,7 +56,8 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
'descriptionHtml' => release.description_html,
'name' => release.name,
'createdAt' => release.created_at.iso8601,
- 'releasedAt' => release.released_at.iso8601
+ 'releasedAt' => release.released_at.iso8601,
+ 'upcomingRelease' => false
})
end
end
@@ -127,7 +131,7 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
let(:release_fields) do
query_graphql_field(:assets, nil,
- query_graphql_field(:links, nil, 'nodes { id name url external }'))
+ query_graphql_field(:links, nil, 'nodes { id name url external, directAssetUrl }'))
end
it 'finds all release links' do
@@ -138,7 +142,8 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
'id' => global_id_of(link),
'name' => link.name,
'url' => link.url,
- 'external' => link.external?
+ 'external' => link.external?,
+ 'directAssetUrl' => link.filepath ? Gitlab::Routing.url_helpers.project_release_url(project, release) << link.filepath : link.url
}
end
@@ -268,9 +273,9 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
let_it_be(:project) { create(:project, :repository, :private) }
let_it_be(:milestone_1) { create(:milestone, project: project) }
let_it_be(:milestone_2) { create(:milestone, project: project) }
- let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) }
+ let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2], released_at: released_at) }
let_it_be(:release_link_1) { create(:release_link, release: release) }
- let_it_be(:release_link_2) { create(:release_link, release: release) }
+ let_it_be(:release_link_2) { create(:release_link, release: release, filepath: link_filepath) }
before_all do
project.add_developer(developer)
@@ -309,9 +314,9 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:milestone_1) { create(:milestone, project: project) }
let_it_be(:milestone_2) { create(:milestone, project: project) }
- let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) }
+ let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2], released_at: released_at) }
let_it_be(:release_link_1) { create(:release_link, release: release) }
- let_it_be(:release_link_2) { create(:release_link, release: release) }
+ let_it_be(:release_link_2) { create(:release_link, release: release, filepath: link_filepath) }
before_all do
project.add_developer(developer)
@@ -371,4 +376,45 @@ RSpec.describe 'Query.project(fullPath).release(tagName)' do
it_behaves_like 'no access to the release field'
end
end
+
+ describe 'upcoming release' do
+ let(:path) { path_prefix }
+ let(:project) { create(:project, :repository, :private) }
+ let(:release) { create(:release, :with_evidence, project: project, released_at: released_at) }
+ let(:current_user) { developer }
+
+ let(:release_fields) do
+ query_graphql_field(%{
+ releasedAt
+ upcomingRelease
+ })
+ end
+
+ before do
+ project.add_developer(developer)
+ post_query
+ end
+
+ context 'future release' do
+ let(:released_at) { Time.now + 1.day }
+
+ it 'finds all release data' do
+ expect(data).to eq({
+ 'releasedAt' => release.released_at.iso8601,
+ 'upcomingRelease' => true
+ })
+ end
+ end
+
+ context 'past release' do
+ let(:released_at) { Time.now - 1.day }
+
+ it 'finds all release data' do
+ expect(data).to eq({
+ 'releasedAt' => release.released_at.iso8601,
+ 'upcomingRelease' => false
+ })
+ end
+ end
+ end
end
diff --git a/spec/requests/api/graphql/project/releases_spec.rb b/spec/requests/api/graphql/project/releases_spec.rb
index 7e418bbaa5b..7c57c0e9177 100644
--- a/spec/requests/api/graphql/project/releases_spec.rb
+++ b/spec/requests/api/graphql/project/releases_spec.rb
@@ -14,6 +14,7 @@ RSpec.describe 'Query.project(fullPath).releases()' do
graphql_query_for(:project, { fullPath: project.full_path },
%{
releases {
+ count
nodes {
tagName
tagPath
@@ -53,6 +54,20 @@ RSpec.describe 'Query.project(fullPath).releases()' do
stub_default_url_options(host: 'www.example.com')
end
+ shared_examples 'correct total count' do
+ let(:data) { graphql_data.dig('project', 'releases') }
+
+ before do
+ create_list(:release, 2, project: project)
+
+ post_query
+ end
+
+ it 'returns the total count' do
+ expect(data['count']).to eq(project.releases.count)
+ end
+ end
+
shared_examples 'full access to all repository-related fields' do
describe 'repository-related fields' do
before do
@@ -92,6 +107,8 @@ RSpec.describe 'Query.project(fullPath).releases()' do
)
end
end
+
+ it_behaves_like 'correct total count'
end
shared_examples 'no access to any repository-related fields' do
@@ -119,6 +136,8 @@ RSpec.describe 'Query.project(fullPath).releases()' do
)
end
end
+
+ it_behaves_like 'correct total count'
end
# editUrl is tested separately becuase its permissions
diff --git a/spec/requests/api/graphql/project_query_spec.rb b/spec/requests/api/graphql/project_query_spec.rb
index c6049e098be..4b8ffb0675c 100644
--- a/spec/requests/api/graphql/project_query_spec.rb
+++ b/spec/requests/api/graphql/project_query_spec.rb
@@ -5,7 +5,8 @@ require 'spec_helper'
RSpec.describe 'getting project information' do
include GraphqlHelpers
- let(:project) { create(:project, :repository) }
+ let(:group) { create(:group) }
+ let(:project) { create(:project, :repository, group: group) }
let(:current_user) { create(:user) }
let(:query) do
@@ -60,6 +61,51 @@ RSpec.describe 'getting project information' do
expect(graphql_data['project']['pipelines']['edges'].size).to eq(1)
end
end
+
+ it 'includes inherited members in project_members' do
+ group_member = create(:group_member, group: group)
+ project_member = create(:project_member, project: project)
+ member_query = <<~GQL
+ query {
+ project(fullPath: "#{project.full_path}") {
+ projectMembers {
+ nodes {
+ id
+ user {
+ username
+ }
+ ... on ProjectMember {
+ project {
+ id
+ }
+ }
+ ... on GroupMember {
+ group {
+ id
+ }
+ }
+ }
+ }
+ }
+ }
+ GQL
+
+ post_graphql(member_query, current_user: current_user)
+
+ member_ids = graphql_data.dig('project', 'projectMembers', 'nodes')
+ expect(member_ids).to include(
+ a_hash_including(
+ 'id' => group_member.to_global_id.to_s,
+ 'group' => { 'id' => group.to_global_id.to_s }
+ )
+ )
+ expect(member_ids).to include(
+ a_hash_including(
+ 'id' => project_member.to_global_id.to_s,
+ 'project' => { 'id' => project.to_global_id.to_s }
+ )
+ )
+ end
end
describe 'performance' do
diff --git a/spec/requests/api/graphql/user/starred_projects_query_spec.rb b/spec/requests/api/graphql/user/starred_projects_query_spec.rb
new file mode 100644
index 00000000000..8a1bd3d172f
--- /dev/null
+++ b/spec/requests/api/graphql/user/starred_projects_query_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Getting starredProjects of the user' do
+ include GraphqlHelpers
+
+ let(:query) do
+ graphql_query_for(:user, user_params, user_fields)
+ end
+
+ let(:user_params) { { username: user.username } }
+
+ let_it_be(:project_a) { create(:project, :public) }
+ let_it_be(:project_b) { create(:project, :private) }
+ let_it_be(:project_c) { create(:project, :private) }
+ let_it_be(:user, reload: true) { create(:user) }
+
+ let(:user_fields) { 'starredProjects { nodes { id } }' }
+ let(:starred_projects) { graphql_data_at(:user, :starred_projects, :nodes) }
+
+ before do
+ project_b.add_reporter(user)
+ project_c.add_reporter(user)
+
+ user.toggle_star(project_a)
+ user.toggle_star(project_b)
+ user.toggle_star(project_c)
+
+ post_graphql(query)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'found only public project' do
+ expect(starred_projects).to contain_exactly(
+ a_hash_including('id' => global_id_of(project_a))
+ )
+ end
+
+ context 'the current user is the user' do
+ let(:current_user) { user }
+
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it 'found all projects' do
+ expect(starred_projects).to contain_exactly(
+ a_hash_including('id' => global_id_of(project_a)),
+ a_hash_including('id' => global_id_of(project_b)),
+ a_hash_including('id' => global_id_of(project_c))
+ )
+ end
+ end
+
+ context 'the current user is a member of a private project the user starred' do
+ let_it_be(:other_user) { create(:user) }
+
+ before do
+ project_b.add_reporter(other_user)
+
+ post_graphql(query, current_user: other_user)
+ end
+
+ it 'finds public and member projects' do
+ expect(starred_projects).to contain_exactly(
+ a_hash_including('id' => global_id_of(project_a)),
+ a_hash_including('id' => global_id_of(project_b))
+ )
+ end
+ end
+end