summaryrefslogtreecommitdiff
path: root/spec/requests/api/graphql
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-05-20 14:34:42 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-05-20 14:34:42 +0000
commit9f46488805e86b1bc341ea1620b866016c2ce5ed (patch)
treef9748c7e287041e37d6da49e0a29c9511dc34768 /spec/requests/api/graphql
parentdfc92d081ea0332d69c8aca2f0e745cb48ae5e6d (diff)
downloadgitlab-ce-9f46488805e86b1bc341ea1620b866016c2ce5ed.tar.gz
Add latest changes from gitlab-org/gitlab@13-0-stable-ee
Diffstat (limited to 'spec/requests/api/graphql')
-rw-r--r--spec/requests/api/graphql/boards/board_lists_query_spec.rb137
-rw-r--r--spec/requests/api/graphql/current_user/todos_query_spec.rb7
-rw-r--r--spec/requests/api/graphql/gitlab_schema_spec.rb2
-rw-r--r--spec/requests/api/graphql/group/milestones_spec.rb48
-rw-r--r--spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb72
-rw-r--r--spec/requests/api/graphql/mutations/alert_management/alerts/update_alert_status_spec.rb42
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/add_spec.rb2
-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/branches/create_spec.rb45
-rw-r--r--spec/requests/api/graphql/mutations/design_management/delete_spec.rb127
-rw-r--r--spec/requests/api/graphql/mutations/design_management/upload_spec.rb99
-rw-r--r--spec/requests/api/graphql/mutations/jira_import/start_spec.rb5
-rw-r--r--spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb231
-rw-r--r--spec/requests/api/graphql/mutations/snippets/create_spec.rb52
-rw-r--r--spec/requests/api/graphql/mutations/snippets/destroy_spec.rb13
-rw-r--r--spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb8
-rw-r--r--spec/requests/api/graphql/mutations/snippets/update_spec.rb25
-rw-r--r--spec/requests/api/graphql/project/alert_management/alert_status_counts_spec.rb61
-rw-r--r--spec/requests/api/graphql/project/alert_management/alerts_spec.rb139
-rw-r--r--spec/requests/api/graphql/project/grafana_integration_spec.rb10
-rw-r--r--spec/requests/api/graphql/project/issue/design_collection/version_spec.rb216
-rw-r--r--spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb113
-rw-r--r--spec/requests/api/graphql/project/issue/designs/designs_spec.rb388
-rw-r--r--spec/requests/api/graphql/project/issue/designs/notes_spec.rb70
-rw-r--r--spec/requests/api/graphql/project/issue_spec.rb189
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb209
-rw-r--r--spec/requests/api/graphql/project/jira_import_spec.rb3
-rw-r--r--spec/requests/api/graphql/query_spec.rb95
29 files changed, 2219 insertions, 193 deletions
diff --git a/spec/requests/api/graphql/boards/board_lists_query_spec.rb b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
new file mode 100644
index 00000000000..f0927487f85
--- /dev/null
+++ b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
@@ -0,0 +1,137 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'get board lists' do
+ include GraphqlHelpers
+
+ 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(: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') }
+ let_it_be(:group_label) { create(:group_label, group: group, name: 'Development') }
+ let_it_be(:group_label2) { create(:group_label, group: group, name: 'Testing') }
+
+ let(:params) { '' }
+ let(:board) { }
+ let(:board_parent_type) { board_parent.class.to_s.downcase }
+ let(:board_data) { graphql_data[board_parent_type]['boards']['edges'].first['node'] }
+ let(:lists_data) { board_data['lists']['edges'] }
+ let(:start_cursor) { board_data['lists']['pageInfo']['startCursor'] }
+ let(:end_cursor) { board_data['lists']['pageInfo']['endCursor'] }
+
+ def query(list_params = params)
+ graphql_query_for(
+ board_parent_type,
+ { 'fullPath' => board_parent.full_path },
+ <<~BOARDS
+ boards(first: 1) {
+ edges {
+ node {
+ #{field_with_params('lists', list_params)} {
+ pageInfo {
+ startCursor
+ endCursor
+ }
+ edges {
+ node {
+ #{all_graphql_fields_for('board_lists'.classify)}
+ }
+ }
+ }
+ }
+ }
+ }
+ BOARDS
+ )
+ end
+
+ shared_examples 'group and project board lists query' do
+ let!(:board) { create(:board, resource_parent: board_parent) }
+
+ context 'when the user does not have access to the board' do
+ it 'returns nil' do
+ post_graphql(query, current_user: unauth_user)
+
+ expect(graphql_data[board_parent_type]).to be_nil
+ end
+ end
+
+ context 'when user can read the board' do
+ before do
+ board_parent.add_reporter(user)
+ end
+
+ describe 'sorting and pagination' do
+ context 'when using default sorting' do
+ let!(:label_list) { create(:list, board: board, label: label, position: 10) }
+ let!(:label_list2) { create(:list, board: board, label: label2, position: 2) }
+ let!(:backlog_list) { create(:backlog_list, board: board) }
+ let(:closed_list) { board.lists.find_by(list_type: :closed) }
+
+ before do
+ post_graphql(query, current_user: user)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ context 'when ascending' do
+ let(:lists) { [backlog_list, label_list2, label_list, closed_list] }
+ let(:expected_list_gids) do
+ lists.map { |list| list.to_global_id.to_s }
+ end
+
+ it 'sorts lists' do
+ expect(grab_ids).to eq expected_list_gids
+ end
+
+ context 'when paginating' do
+ let(:params) { 'first: 2' }
+
+ it 'sorts boards' do
+ expect(grab_ids).to eq expected_list_gids.first(2)
+
+ cursored_query = query("after: \"#{end_cursor}\"")
+ post_graphql(cursored_query, current_user: user)
+
+ response_data = grab_list_data(response.body)
+
+ expect(grab_ids(response_data)).to eq expected_list_gids.drop(2).first(2)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
+ describe 'for a project' do
+ let(:board_parent) { project }
+ let(:label) { project_label }
+ let(: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 }
+
+ before do
+ allow(board_parent).to receive(:multiple_issue_boards_available?).and_return(false)
+ end
+
+ it_behaves_like 'group and project board lists query'
+ end
+
+ def grab_ids(data = lists_data)
+ data.map { |list| list.dig('node', 'id') }
+ end
+
+ def grab_list_data(response_body)
+ Gitlab::Json.parse(response_body)['data'][board_parent_type]['boards']['edges'][0]['node']['lists']['edges']
+ end
+end
diff --git a/spec/requests/api/graphql/current_user/todos_query_spec.rb b/spec/requests/api/graphql/current_user/todos_query_spec.rb
index 82deba0d92c..321e1062a96 100644
--- a/spec/requests/api/graphql/current_user/todos_query_spec.rb
+++ b/spec/requests/api/graphql/current_user/todos_query_spec.rb
@@ -9,6 +9,7 @@ describe 'Query current user todos' do
let_it_be(:commit_todo) { create(:on_commit_todo, user: current_user, project: create(:project, :repository)) }
let_it_be(:issue_todo) { create(:todo, user: current_user, target: create(:issue)) }
let_it_be(:merge_request_todo) { create(:todo, user: current_user, target: create(:merge_request)) }
+ let_it_be(:design_todo) { create(:todo, user: current_user, target: create(:design)) }
let(:fields) do
<<~QUERY
@@ -34,7 +35,8 @@ describe 'Query current user todos' do
is_expected.to include(
a_hash_including('id' => commit_todo.to_global_id.to_s),
a_hash_including('id' => issue_todo.to_global_id.to_s),
- a_hash_including('id' => merge_request_todo.to_global_id.to_s)
+ a_hash_including('id' => merge_request_todo.to_global_id.to_s),
+ a_hash_including('id' => design_todo.to_global_id.to_s)
)
end
@@ -42,7 +44,8 @@ describe 'Query current user todos' do
is_expected.to include(
a_hash_including('targetType' => 'COMMIT'),
a_hash_including('targetType' => 'ISSUE'),
- a_hash_including('targetType' => 'MERGEREQUEST')
+ a_hash_including('targetType' => 'MERGEREQUEST'),
+ a_hash_including('targetType' => 'DESIGN')
)
end
end
diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb
index cf409ea6c2d..266c98d6f08 100644
--- a/spec/requests/api/graphql/gitlab_schema_spec.rb
+++ b/spec/requests/api/graphql/gitlab_schema_spec.rb
@@ -190,7 +190,7 @@ describe 'GitlabSchema configurations' do
variables: {}.to_s,
complexity: 181,
depth: 13,
- duration: 7
+ duration_s: 7
}
expect_any_instance_of(Gitlab::Graphql::QueryAnalyzers::LoggerAnalyzer).to receive(:duration).and_return(7)
diff --git a/spec/requests/api/graphql/group/milestones_spec.rb b/spec/requests/api/graphql/group/milestones_spec.rb
index f8e3c0026f5..bad0024e7a3 100644
--- a/spec/requests/api/graphql/group/milestones_spec.rb
+++ b/spec/requests/api/graphql/group/milestones_spec.rb
@@ -7,7 +7,7 @@ describe 'Milestones through GroupQuery' do
let_it_be(:user) { create(:user) }
let_it_be(:now) { Time.now }
- let_it_be(:group) { create(:group, :private) }
+ let_it_be(:group) { create(:group) }
let_it_be(:milestone_1) { create(:milestone, group: group) }
let_it_be(:milestone_2) { create(:milestone, group: group, state: :closed, start_date: now, due_date: now + 1.day) }
let_it_be(:milestone_3) { create(:milestone, group: group, start_date: now, due_date: now + 2.days) }
@@ -17,10 +17,6 @@ describe 'Milestones through GroupQuery' do
let(:milestone_data) { graphql_data['group']['milestones']['edges'] }
describe 'Get list of milestones from a group' do
- before do
- group.add_developer(user)
- end
-
context 'when the request is correct' do
before do
fetch_milestones(user)
@@ -51,6 +47,48 @@ describe 'Milestones through GroupQuery' do
end
end
+ context 'when including milestones from decendants' do
+ let_it_be(:accessible_group) { create(:group, :private, parent: group) }
+ let_it_be(:accessible_project) { create(:project, group: accessible_group) }
+ let_it_be(:inaccessible_group) { create(:group, :private, parent: group) }
+ let_it_be(:inaccessible_project) { create(:project, :private, group: group) }
+ let_it_be(:submilestone_1) { create(:milestone, group: accessible_group) }
+ let_it_be(:submilestone_2) { create(:milestone, project: accessible_project) }
+ let_it_be(:submilestone_3) { create(:milestone, group: inaccessible_group) }
+ let_it_be(:submilestone_4) { create(:milestone, project: inaccessible_project) }
+
+ let(:args) { { include_descendants: true } }
+
+ before do
+ accessible_group.add_developer(user)
+ end
+
+ it 'returns milestones also from subgroups and subprojects visible to user' do
+ fetch_milestones(user, args)
+
+ expect_array_response(
+ milestone_1.to_global_id.to_s, milestone_2.to_global_id.to_s,
+ milestone_3.to_global_id.to_s, milestone_4.to_global_id.to_s,
+ submilestone_1.to_global_id.to_s, submilestone_2.to_global_id.to_s
+ )
+ end
+
+ context 'when group_milestone_descendants is disabled' do
+ before do
+ stub_feature_flags(group_milestone_descendants: false)
+ end
+
+ it 'ignores descendant milestones' do
+ fetch_milestones(user, args)
+
+ expect_array_response(
+ milestone_1.to_global_id.to_s, milestone_2.to_global_id.to_s,
+ milestone_3.to_global_id.to_s, milestone_4.to_global_id.to_s
+ )
+ end
+ end
+ end
+
def fetch_milestones(user = nil, args = {})
post_graphql(milestones_query(args), current_user: user)
end
diff --git a/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb b/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb
index f5a5f0a9ec2..cb35411b7a5 100644
--- a/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb
+++ b/spec/requests/api/graphql/metrics/dashboard/annotations_spec.rb
@@ -21,6 +21,7 @@ describe 'Getting Metrics Dashboard Annotations' do
create(:metrics_dashboard_annotation, environment: environment, starting_at: to.advance(minutes: 5), dashboard_path: path)
end
+ let(:args) { "from: \"#{from}\", to: \"#{to}\"" }
let(:fields) do
<<~QUERY
#{all_graphql_fields_for('MetricsDashboardAnnotation'.classify)}
@@ -47,63 +48,40 @@ describe 'Getting Metrics Dashboard Annotations' do
)
end
- context 'feature flag metrics_dashboard_annotations' do
- let(:args) { "from: \"#{from}\", to: \"#{to}\"" }
+ before do
+ project.add_developer(current_user)
+ post_graphql(query, current_user: current_user)
+ end
- before do
- project.add_developer(current_user)
- end
+ it_behaves_like 'a working graphql query'
- context 'is off' do
- before do
- stub_feature_flags(metrics_dashboard_annotations: false)
- post_graphql(query, current_user: current_user)
- end
+ it 'returns annotations' do
+ annotations = graphql_data.dig('project', 'environments', 'nodes')[0].dig('metricsDashboard', 'annotations', 'nodes')
- it 'returns empty nodes array' do
- annotations = graphql_data.dig('project', 'environments', 'nodes')[0].dig('metricsDashboard', 'annotations', 'nodes')
+ expect(annotations).to match_array [{
+ "description" => annotation.description,
+ "id" => annotation.to_global_id.to_s,
+ "panelId" => annotation.panel_xid,
+ "startingAt" => annotation.starting_at.iso8601,
+ "endingAt" => nil
+ }]
+ end
- expect(annotations).to be_empty
- end
- end
+ context 'arguments' do
+ context 'from is missing' do
+ let(:args) { "to: \"#{from}\"" }
- context 'is on' do
- before do
- stub_feature_flags(metrics_dashboard_annotations: true)
+ it 'returns error' do
post_graphql(query, current_user: current_user)
- end
- it_behaves_like 'a working graphql query'
-
- it 'returns annotations' do
- annotations = graphql_data.dig('project', 'environments', 'nodes')[0].dig('metricsDashboard', 'annotations', 'nodes')
-
- expect(annotations).to match_array [{
- "description" => annotation.description,
- "id" => annotation.to_global_id.to_s,
- "panelId" => annotation.panel_xid,
- "startingAt" => annotation.starting_at.to_s,
- "endingAt" => nil
- }]
+ expect(graphql_errors[0]).to include("message" => "Field 'annotations' is missing required arguments: from")
end
+ end
- context 'arguments' do
- context 'from is missing' do
- let(:args) { "to: \"#{from}\"" }
-
- it 'returns error' do
- post_graphql(query, current_user: current_user)
-
- expect(graphql_errors[0]).to include("message" => "Field 'annotations' is missing required arguments: from")
- end
- end
-
- context 'to is missing' do
- let(:args) { "from: \"#{from}\"" }
+ context 'to is missing' do
+ let(:args) { "from: \"#{from}\"" }
- it_behaves_like 'a working graphql query'
- end
- end
+ it_behaves_like 'a working graphql query'
end
end
end
diff --git a/spec/requests/api/graphql/mutations/alert_management/alerts/update_alert_status_spec.rb b/spec/requests/api/graphql/mutations/alert_management/alerts/update_alert_status_spec.rb
new file mode 100644
index 00000000000..fe50468134c
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/alert_management/alerts/update_alert_status_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Setting the status of an alert' do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let(:alert) { create(:alert_management_alert, project: project) }
+ let(:input) { { status: 'ACKNOWLEDGED' } }
+
+ let(:mutation) do
+ variables = {
+ project_path: project.full_path,
+ iid: alert.iid.to_s
+ }
+ graphql_mutation(:update_alert_status, variables.merge(input),
+ <<~QL
+ clientMutationId
+ errors
+ alert {
+ iid
+ status
+ }
+ QL
+ )
+ end
+
+ let(:mutation_response) { graphql_mutation_response(:update_alert_status) }
+
+ before do
+ project.add_developer(user)
+ end
+
+ it 'updates the status of the alert' do
+ post_graphql_mutation(mutation, current_user: user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['alert']['status']).to eq(input[:status])
+ 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 3fdeccc84f9..83dec7dd3e2 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
@@ -23,7 +23,7 @@ describe 'Adding an AwardEmoji' do
end
shared_examples 'a mutation that does not create an AwardEmoji' do
- it do
+ specify do
expect do
post_graphql_mutation(mutation, current_user: current_user)
end.not_to change { AwardEmoji.count }
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 c78f0c7ca27..a2997db6cae 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/remove_spec.rb
@@ -24,7 +24,7 @@ describe 'Removing an AwardEmoji' do
end
shared_examples 'a mutation that does not destroy an AwardEmoji' do
- it do
+ specify do
expect do
post_graphql_mutation(mutation, current_user: current_user)
end.not_to change { AwardEmoji.count }
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 bc796b34db4..e1180c85c6b 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
@@ -23,7 +23,7 @@ describe 'Toggling an AwardEmoji' do
end
shared_examples 'a mutation that does not create or destroy an AwardEmoji' do
- it do
+ specify do
expect do
post_graphql_mutation(mutation, current_user: current_user)
end.not_to change { AwardEmoji.count }
diff --git a/spec/requests/api/graphql/mutations/branches/create_spec.rb b/spec/requests/api/graphql/mutations/branches/create_spec.rb
new file mode 100644
index 00000000000..b3c378ec2bc
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/branches/create_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Creation of a new branch' do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:project) { create(:project, :public, :empty_repo) }
+ let(:input) { { project_path: project.full_path, name: new_branch, ref: ref } }
+ let(:new_branch) { 'new_branch' }
+ let(:ref) { 'master' }
+
+ let(:mutation) { graphql_mutation(:create_branch, input) }
+ 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']
+ end
+
+ context 'when user has permissions to create a branch' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it 'creates a new branch' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(mutation_response['branch']).to include(
+ 'name' => new_branch,
+ 'commit' => a_hash_including('id')
+ )
+ end
+
+ context 'when ref is not correct' do
+ let(:new_branch) { 'another_branch' }
+ let(:ref) { 'unknown' }
+
+ it_behaves_like 'a mutation that returns errors in the response',
+ errors: ['Invalid reference name: unknown']
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/design_management/delete_spec.rb b/spec/requests/api/graphql/mutations/design_management/delete_spec.rb
new file mode 100644
index 00000000000..10376305b3e
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/design_management/delete_spec.rb
@@ -0,0 +1,127 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+describe "deleting designs" do
+ include GraphqlHelpers
+ include DesignManagementTestHelpers
+
+ let(:developer) { create(:user) }
+ let(:current_user) { developer }
+ let(:issue) { create(:issue) }
+ let(:project) { issue.project }
+ let(:designs) { create_designs }
+ let(:variables) { {} }
+
+ let(:mutation) do
+ input = {
+ project_path: project.full_path,
+ iid: issue.iid,
+ filenames: designs.map(&:filename)
+ }.merge(variables)
+ graphql_mutation(:design_management_delete, input)
+ end
+
+ let(:mutation_response) { graphql_mutation_response(:design_management_delete) }
+
+ def mutate!
+ post_graphql_mutation(mutation, current_user: current_user)
+ end
+
+ before do
+ enable_design_management
+
+ project.add_developer(developer)
+ end
+
+ shared_examples 'a failed request' do
+ let(:the_error) { be_present }
+
+ it 'reports an error' do
+ mutate!
+
+ expect(graphql_errors).to include(a_hash_including('message' => the_error))
+ end
+ end
+
+ context 'the designs list is empty' do
+ it_behaves_like 'a failed request' do
+ let(:designs) { [] }
+ let(:the_error) { a_string_matching %r/was provided invalid value/ }
+ end
+ end
+
+ context 'the designs list contains filenames we cannot find' do
+ it_behaves_like 'a failed request' do
+ let(:designs) { %w/foo bar baz/.map { |fn| OpenStruct.new(filename: fn) } }
+ let(:the_error) { a_string_matching %r/filenames were not found/ }
+ end
+ end
+
+ context 'the current user does not have developer access' do
+ it_behaves_like 'a failed request' do
+ let(:current_user) { create(:user) }
+ let(:the_error) { a_string_matching %r/you don't have permission/ }
+ end
+ end
+
+ context "when the issue does not exist" do
+ it_behaves_like 'a failed request' do
+ let(:variables) { { iid: "1234567890" } }
+ let(:the_error) { a_string_matching %r/does not exist/ }
+ end
+ end
+
+ context "when saving the designs raises an error" do
+ let(:designs) { create_designs(1) }
+
+ it "responds with errors" do
+ expect_next_instance_of(::DesignManagement::DeleteDesignsService) do |service|
+ expect(service)
+ .to receive(:execute)
+ .and_return({ status: :error, message: "Something went wrong" })
+ end
+
+ mutate!
+
+ expect(mutation_response).to include('errors' => include(eq "Something went wrong"))
+ end
+ end
+
+ context 'one of the designs is already deleted' do
+ let(:designs) do
+ create_designs(2).push(create(:design, :with_file, deleted: true, issue: issue))
+ end
+
+ it 'reports an error' do
+ mutate!
+
+ expect(graphql_errors).to be_present
+ end
+ end
+
+ context 'when the user names designs to delete' do
+ before do
+ create_designs(1)
+ end
+
+ let!(:designs) { create_designs(2) }
+
+ it 'deletes the designs' do
+ expect { mutate! }
+ .to change { issue.reset.designs.current.count }.from(3).to(1)
+ end
+
+ it 'has no errors' do
+ mutate!
+
+ expect(mutation_response).to include('errors' => be_empty)
+ end
+ end
+
+ private
+
+ def create_designs(how_many = 2)
+ create_list(:design, how_many, :with_file, issue: issue)
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/design_management/upload_spec.rb b/spec/requests/api/graphql/mutations/design_management/upload_spec.rb
new file mode 100644
index 00000000000..22adc064406
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/design_management/upload_spec.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+require "spec_helper"
+
+describe "uploading designs" do
+ include GraphqlHelpers
+ include DesignManagementTestHelpers
+ include WorkhorseHelpers
+
+ let(:current_user) { create(:user) }
+ let(:issue) { create(:issue) }
+ let(:project) { issue.project }
+ let(:files) { [fixture_file_upload("spec/fixtures/dk.png")] }
+ let(:variables) { {} }
+
+ let(:mutation) do
+ input = {
+ project_path: project.full_path,
+ iid: issue.iid,
+ files: files
+ }.merge(variables)
+ graphql_mutation(:design_management_upload, input)
+ end
+
+ let(:mutation_response) { graphql_mutation_response(:design_management_upload) }
+
+ before do
+ enable_design_management
+
+ project.add_developer(current_user)
+ end
+
+ it "returns an error if the user is not allowed to upload designs" do
+ post_graphql_mutation(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)
+
+ 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(
+ a_hash_including("filename" => "dk.png")
+ )
+ )
+ end
+
+ it "can respond with skipped designs" do
+ 2.times do
+ post_graphql_mutation(mutation, current_user: current_user)
+ files.each(&:rewind)
+ end
+
+ expect(mutation_response).to include(
+ "skippedDesigns" => a_collection_containing_exactly(
+ a_hash_including("filename" => "dk.png")
+ )
+ )
+ end
+
+ context "when the issue does not exist" do
+ let(:variables) { { iid: "123" } }
+
+ it "returns an error" do
+ post_graphql_mutation(mutation, current_user: create(:user))
+
+ expect(graphql_errors).not_to be_empty
+ end
+ end
+
+ context "when saving the designs raises an error" do
+ it "responds with errors" do
+ expect_next_instance_of(::DesignManagement::SaveDesignsService) do |service|
+ expect(service).to receive(:execute).and_return({ status: :error, message: "Something went wrong" })
+ end
+
+ post_graphql_mutation(mutation, current_user: current_user)
+ expect(mutation_response["errors"].first).to eq("Something went wrong")
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/jira_import/start_spec.rb b/spec/requests/api/graphql/mutations/jira_import/start_spec.rb
index 014da5d1e1a..84110098400 100644
--- a/spec/requests/api/graphql/mutations/jira_import/start_spec.rb
+++ b/spec/requests/api/graphql/mutations/jira_import/start_spec.rb
@@ -3,6 +3,7 @@
require 'spec_helper'
describe 'Starting a Jira Import' do
+ include JiraServiceHelper
include GraphqlHelpers
let_it_be(:user) { create(:user) }
@@ -104,6 +105,8 @@ describe 'Starting a Jira Import' do
before do
project.reload
+
+ stub_jira_service_test
end
context 'when issues feature are disabled' do
@@ -118,7 +121,7 @@ describe 'Starting a Jira Import' do
it_behaves_like 'a mutation that returns errors in the response', errors: ['Unable to find Jira project to import data from.']
end
- context 'when jira import successfully scheduled' do
+ context 'when Jira import successfully scheduled' do
it 'schedules a Jira import' do
post_graphql_mutation(mutation, current_user: current_user)
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
new file mode 100644
index 00000000000..8568dc8ffc0
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/metrics/dashboard/annotations/create_spec.rb
@@ -0,0 +1,231 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Mutations::Metrics::Dashboard::Annotations::Create do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:project) { create(:project, :private, :repository) }
+ let_it_be(:environment) { create(:environment, project: project) }
+ let_it_be(:cluster) { create(:cluster, projects: [project]) }
+ let(:dashboard_path) { 'config/prometheus/common_metrics.yml' }
+ let(:starting_at) { Time.current.iso8601 }
+ let(:ending_at) { 1.hour.from_now.iso8601 }
+ let(:description) { 'test description' }
+
+ def mutation_response
+ graphql_mutation_response(:create_annotation)
+ end
+
+ specify { expect(described_class).to require_graphql_authorizations(:create_metrics_dashboard_annotation) }
+
+ context 'when annotation source is environment' do
+ let(:mutation) do
+ variables = {
+ environment_id: GitlabSchema.id_from_object(environment).to_s,
+ starting_at: starting_at,
+ ending_at: ending_at,
+ dashboard_path: dashboard_path,
+ description: description
+ }
+
+ graphql_mutation(:create_annotation, variables)
+ end
+
+ context 'when the user does not have permission' do
+ before do
+ project.add_reporter(current_user)
+ end
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
+
+ it 'does not create the annotation' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.not_to change { Metrics::Dashboard::Annotation.count }
+ end
+ end
+
+ context 'when the user has permission' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it 'creates the annotation' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { Metrics::Dashboard::Annotation.count }.by(1)
+ end
+
+ it 'returns the created annotation' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ annotation = Metrics::Dashboard::Annotation.first
+ annotation_id = GitlabSchema.id_from_object(annotation).to_s
+
+ expect(mutation_response['annotation']['description']).to match(description)
+ expect(mutation_response['annotation']['startingAt'].to_time).to match(starting_at.to_time)
+ expect(mutation_response['annotation']['endingAt'].to_time).to match(ending_at.to_time)
+ expect(mutation_response['annotation']['id']).to match(annotation_id)
+ expect(annotation.environment_id).to eq(environment.id)
+ end
+
+ context 'when environment_id is missing' do
+ let(:mutation) do
+ variables = {
+ environment_id: nil,
+ starting_at: starting_at,
+ ending_at: ending_at,
+ dashboard_path: dashboard_path,
+ description: description
+ }
+
+ graphql_mutation(:create_annotation, variables)
+ end
+
+ it_behaves_like 'a mutation that returns top-level errors', errors: [described_class::ANNOTATION_SOURCE_ARGUMENT_ERROR]
+ end
+
+ context 'when environment_id is invalid' do
+ let(:mutation) do
+ variables = {
+ environment_id: 'invalid_id',
+ starting_at: starting_at,
+ ending_at: ending_at,
+ dashboard_path: dashboard_path,
+ description: description
+ }
+
+ 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.']
+ end
+ end
+ end
+
+ context 'when annotation source is cluster' do
+ let(:mutation) do
+ variables = {
+ cluster_id: GitlabSchema.id_from_object(cluster).to_s,
+ starting_at: starting_at,
+ ending_at: ending_at,
+ dashboard_path: dashboard_path,
+ description: description
+ }
+
+ graphql_mutation(:create_annotation, variables)
+ end
+
+ context 'with permission' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it 'creates the annotation' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { Metrics::Dashboard::Annotation.count }.by(1)
+ end
+
+ it 'returns the created annotation' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ annotation = Metrics::Dashboard::Annotation.first
+ annotation_id = GitlabSchema.id_from_object(annotation).to_s
+
+ expect(mutation_response['annotation']['description']).to match(description)
+ expect(mutation_response['annotation']['startingAt'].to_time).to match(starting_at.to_time)
+ expect(mutation_response['annotation']['endingAt'].to_time).to match(ending_at.to_time)
+ expect(mutation_response['annotation']['id']).to match(annotation_id)
+ expect(annotation.cluster_id).to eq(cluster.id)
+ end
+
+ context 'when cluster_id is missing' do
+ let(:mutation) do
+ variables = {
+ cluster_id: nil,
+ starting_at: starting_at,
+ ending_at: ending_at,
+ dashboard_path: dashboard_path,
+ description: description
+ }
+
+ graphql_mutation(:create_annotation, variables)
+ end
+
+ it_behaves_like 'a mutation that returns top-level errors', errors: [described_class::ANNOTATION_SOURCE_ARGUMENT_ERROR]
+ end
+ end
+
+ context 'without permission' do
+ before do
+ project.add_guest(current_user)
+ end
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: [Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR]
+
+ it 'does not create the annotation' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.not_to change { Metrics::Dashboard::Annotation.count }
+ end
+ end
+
+ context 'when cluster_id is invalid' do
+ let(:mutation) do
+ variables = {
+ cluster_id: 'invalid_id',
+ starting_at: starting_at,
+ ending_at: ending_at,
+ dashboard_path: dashboard_path,
+ description: description
+ }
+
+ 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.']
+ end
+ end
+
+ context 'when both environment_id and cluster_id are provided' do
+ let(:mutation) do
+ variables = {
+ environment_id: GitlabSchema.id_from_object(environment).to_s,
+ cluster_id: GitlabSchema.id_from_object(cluster).to_s,
+ starting_at: starting_at,
+ ending_at: ending_at,
+ dashboard_path: dashboard_path,
+ description: description
+ }
+
+ graphql_mutation(:create_annotation, variables)
+ end
+
+ it_behaves_like 'a mutation that returns top-level errors', errors: [described_class::ANNOTATION_SOURCE_ARGUMENT_ERROR]
+ end
+
+ context 'when a non-cluster or environment id is provided' do
+ let(:mutation) do
+ variables = {
+ environment_id: GitlabSchema.id_from_object(project).to_s,
+ starting_at: starting_at,
+ ending_at: ending_at,
+ dashboard_path: dashboard_path,
+ description: description
+ }
+
+ graphql_mutation(:create_annotation, variables)
+ end
+
+ before do
+ project.add_developer(current_user)
+ end
+
+ it_behaves_like 'a mutation that returns top-level errors', errors: [described_class::INVALID_ANNOTATION_SOURCE_ERROR]
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/snippets/create_spec.rb b/spec/requests/api/graphql/mutations/snippets/create_spec.rb
index cef7fc5cbe3..e1e5fe22887 100644
--- a/spec/requests/api/graphql/mutations/snippets/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/create_spec.rb
@@ -13,6 +13,7 @@ describe 'Creating a Snippet' do
let(:file_name) { 'Initial file_name' }
let(:visibility_level) { 'public' }
let(:project_path) { nil }
+ let(:uploaded_files) { nil }
let(:mutation) do
variables = {
@@ -21,7 +22,8 @@ describe 'Creating a Snippet' do
visibility_level: visibility_level,
file_name: file_name,
title: title,
- project_path: project_path
+ project_path: project_path,
+ uploaded_files: uploaded_files
}
graphql_mutation(:create_snippet, variables)
@@ -31,6 +33,8 @@ describe 'Creating a Snippet' do
graphql_mutation_response(:create_snippet)
end
+ subject { post_graphql_mutation(mutation, current_user: current_user) }
+
context 'when the user does not have permission' do
let(:current_user) { nil }
@@ -39,7 +43,7 @@ describe 'Creating a Snippet' do
it 'does not create the Snippet' do
expect do
- post_graphql_mutation(mutation, current_user: current_user)
+ subject
end.not_to change { Snippet.count }
end
@@ -48,7 +52,7 @@ describe 'Creating a Snippet' do
it 'does not create the snippet when the user is not authorized' do
expect do
- post_graphql_mutation(mutation, current_user: current_user)
+ subject
end.not_to change { Snippet.count }
end
end
@@ -60,12 +64,12 @@ describe 'Creating a Snippet' do
context 'with PersonalSnippet' do
it 'creates the Snippet' do
expect do
- post_graphql_mutation(mutation, current_user: current_user)
+ subject
end.to change { Snippet.count }.by(1)
end
it 'returns the created Snippet' do
- post_graphql_mutation(mutation, current_user: current_user)
+ subject
expect(mutation_response['snippet']['blob']['richData']).to be_nil
expect(mutation_response['snippet']['blob']['plainData']).to match(content)
@@ -86,12 +90,12 @@ describe 'Creating a Snippet' do
it 'creates the Snippet' do
expect do
- post_graphql_mutation(mutation, current_user: current_user)
+ subject
end.to change { Snippet.count }.by(1)
end
it 'returns the created Snippet' do
- post_graphql_mutation(mutation, current_user: current_user)
+ subject
expect(mutation_response['snippet']['blob']['richData']).to be_nil
expect(mutation_response['snippet']['blob']['plainData']).to match(content)
@@ -106,7 +110,7 @@ describe 'Creating a Snippet' do
let(:project_path) { 'foobar' }
it 'returns an an error' do
- post_graphql_mutation(mutation, current_user: current_user)
+ subject
errors = json_response['errors']
expect(errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)
@@ -117,7 +121,7 @@ describe 'Creating a Snippet' do
it 'returns an an error' do
project.project_feature.update_attribute(:snippets_access_level, ProjectFeature::DISABLED)
- post_graphql_mutation(mutation, current_user: current_user)
+ subject
errors = json_response['errors']
expect(errors.first['message']).to eq(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)
@@ -132,15 +136,41 @@ describe 'Creating a Snippet' do
it 'does not create the Snippet' do
expect do
- post_graphql_mutation(mutation, current_user: current_user)
+ subject
end.not_to change { Snippet.count }
end
it 'does not return Snippet' do
- post_graphql_mutation(mutation, current_user: current_user)
+ subject
expect(mutation_response['snippet']).to be_nil
end
end
+
+ context 'when there uploaded files' do
+ shared_examples 'expected files argument' do |file_value, expected_value|
+ let(:uploaded_files) { file_value }
+
+ it do
+ expect(::Snippets::CreateService).to receive(:new).with(nil, user, hash_including(files: expected_value))
+
+ subject
+ end
+ end
+
+ it_behaves_like 'expected files argument', nil, nil
+ it_behaves_like 'expected files argument', %w(foo bar), %w(foo bar)
+ it_behaves_like 'expected files argument', 'foo', %w(foo)
+
+ context 'when files has an invalid value' do
+ let(:uploaded_files) { [1] }
+
+ it 'returns an error' do
+ subject
+
+ expect(json_response['errors']).to be
+ end
+ end
+ end
end
end
diff --git a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
index 351d2db8973..cb9aeea74b2 100644
--- a/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/destroy_spec.rb
@@ -6,9 +6,10 @@ describe 'Destroying a Snippet' do
include GraphqlHelpers
let(:current_user) { snippet.author }
+ let(:snippet_gid) { snippet.to_global_id.to_s }
let(:mutation) do
variables = {
- id: snippet.to_global_id.to_s
+ id: snippet_gid
}
graphql_mutation(:destroy_snippet, variables)
@@ -49,9 +50,11 @@ describe 'Destroying a Snippet' do
end
describe 'PersonalSnippet' do
- it_behaves_like 'graphql delete actions' do
- let_it_be(:snippet) { create(:personal_snippet) }
- end
+ let_it_be(:snippet) { create(:personal_snippet) }
+
+ it_behaves_like 'graphql delete actions'
+
+ it_behaves_like 'when the snippet is not found'
end
describe 'ProjectSnippet' do
@@ -85,5 +88,7 @@ describe 'Destroying a Snippet' do
end
end
end
+
+ it_behaves_like 'when the snippet is not found'
end
end
diff --git a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb
index 05e3f7e6806..6d4dce3f6f1 100644
--- a/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/mark_as_spam_spec.rb
@@ -10,9 +10,11 @@ describe 'Mark snippet as spam', :do_not_mock_admin_mode do
let_it_be(:snippet) { create(:personal_snippet) }
let_it_be(:user_agent_detail) { create(:user_agent_detail, subject: snippet) }
let(:current_user) { snippet.author }
+
+ let(:snippet_gid) { snippet.to_global_id.to_s }
let(:mutation) do
variables = {
- id: snippet.to_global_id.to_s
+ id: snippet_gid
}
graphql_mutation(:mark_as_spam_snippet, variables)
@@ -23,13 +25,15 @@ describe 'Mark snippet as spam', :do_not_mock_admin_mode do
end
shared_examples 'does not mark the snippet as spam' do
- it do
+ specify do
expect do
post_graphql_mutation(mutation, current_user: current_user)
end.not_to change { snippet.reload.user_agent_detail.submitted }
end
end
+ it_behaves_like 'when the snippet is not found'
+
context 'when the user does not have permission' do
let(:current_user) { other_user }
diff --git a/spec/requests/api/graphql/mutations/snippets/update_spec.rb b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
index 1035e3346e1..968ea5aed52 100644
--- a/spec/requests/api/graphql/mutations/snippets/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
@@ -15,9 +15,10 @@ describe 'Updating a Snippet' do
let(:updated_file_name) { 'Updated file_name' }
let(:current_user) { snippet.author }
+ let(:snippet_gid) { GitlabSchema.id_from_object(snippet).to_s }
let(:mutation) do
variables = {
- id: GitlabSchema.id_from_object(snippet).to_s,
+ id: snippet_gid,
content: updated_content,
description: updated_description,
visibility_level: 'public',
@@ -90,16 +91,18 @@ describe 'Updating a Snippet' do
end
describe 'PersonalSnippet' do
- it_behaves_like 'graphql update actions' do
- let(:snippet) do
- create(:personal_snippet,
- :private,
- file_name: original_file_name,
- title: original_title,
- content: original_content,
- description: original_description)
- end
+ let(:snippet) do
+ create(:personal_snippet,
+ :private,
+ file_name: original_file_name,
+ title: original_title,
+ content: original_content,
+ description: original_description)
end
+
+ it_behaves_like 'graphql update actions'
+
+ it_behaves_like 'when the snippet is not found'
end
describe 'ProjectSnippet' do
@@ -142,5 +145,7 @@ describe 'Updating a Snippet' do
end
end
end
+
+ it_behaves_like 'when the snippet is not found'
end
end
diff --git a/spec/requests/api/graphql/project/alert_management/alert_status_counts_spec.rb b/spec/requests/api/graphql/project/alert_management/alert_status_counts_spec.rb
new file mode 100644
index 00000000000..ffd328429ef
--- /dev/null
+++ b/spec/requests/api/graphql/project/alert_management/alert_status_counts_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe 'getting Alert Management Alert counts by status' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:alert_1) { create(:alert_management_alert, :resolved, project: project) }
+ let_it_be(:alert_2) { create(:alert_management_alert, project: project) }
+ let_it_be(:other_project_alert) { create(:alert_management_alert) }
+ let(:params) { {} }
+
+ let(:fields) do
+ <<~QUERY
+ #{all_graphql_fields_for('AlertManagementAlertStatusCountsType'.classify)}
+ QUERY
+ end
+
+ let(:query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field('alertManagementAlertStatusCounts', params, fields)
+ )
+ end
+
+ context 'with alert data' do
+ let(:alert_counts) { graphql_data.dig('project', 'alertManagementAlertStatusCounts') }
+
+ context 'without project permissions' do
+ let(:user) { create(:user) }
+
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it_behaves_like 'a working graphql query'
+ it { expect(alert_counts).to be nil }
+ end
+
+ context 'with project permissions' do
+ before do
+ project.add_developer(current_user)
+ post_graphql(query, current_user: current_user)
+ end
+
+ it_behaves_like 'a working graphql query'
+ it 'returns the correct counts for each status' do
+ expect(alert_counts).to eq(
+ 'open' => 1,
+ 'all' => 2,
+ 'triggered' => 1,
+ 'acknowledged' => 0,
+ 'resolved' => 1,
+ 'ignored' => 0
+ )
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/alert_management/alerts_spec.rb b/spec/requests/api/graphql/project/alert_management/alerts_spec.rb
new file mode 100644
index 00000000000..c226e659364
--- /dev/null
+++ b/spec/requests/api/graphql/project/alert_management/alerts_spec.rb
@@ -0,0 +1,139 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe 'getting Alert Management Alerts' do
+ include GraphqlHelpers
+
+ let_it_be(:payload) { { 'custom' => { 'alert' => 'payload' } } }
+ let_it_be(:project) { create(:project, :repository) }
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:resolved_alert) { create(:alert_management_alert, :all_fields, :resolved, project: project, issue: nil, severity: :low) }
+ let_it_be(:triggered_alert) { create(:alert_management_alert, :all_fields, project: project, severity: :critical, payload: payload) }
+ let_it_be(:other_project_alert) { create(:alert_management_alert, :all_fields) }
+ let(:params) { {} }
+
+ let(:fields) do
+ <<~QUERY
+ nodes {
+ #{all_graphql_fields_for('AlertManagementAlert'.classify)}
+ }
+ QUERY
+ end
+
+ let(:query) do
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => project.full_path },
+ query_graphql_field('alertManagementAlerts', params, fields)
+ )
+ end
+
+ context 'with alert data' do
+ let(:alerts) { graphql_data.dig('project', 'alertManagementAlerts', 'nodes') }
+
+ context 'without project permissions' do
+ let(:user) { create(:user) }
+
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it { expect(alerts).to be nil }
+ end
+
+ context 'with project permissions' do
+ before do
+ project.add_developer(current_user)
+ post_graphql(query, current_user: current_user)
+ end
+
+ let(:first_alert) { alerts.first }
+ let(:second_alert) { alerts.second }
+
+ it_behaves_like 'a working graphql query'
+
+ it { expect(alerts.size).to eq(2) }
+
+ it 'returns the correct properties of the alerts' do
+ expect(first_alert).to include(
+ 'iid' => triggered_alert.iid.to_s,
+ 'issueIid' => triggered_alert.issue_iid.to_s,
+ 'title' => triggered_alert.title,
+ 'description' => triggered_alert.description,
+ 'severity' => triggered_alert.severity.upcase,
+ 'status' => 'TRIGGERED',
+ 'monitoringTool' => triggered_alert.monitoring_tool,
+ 'service' => triggered_alert.service,
+ 'hosts' => triggered_alert.hosts,
+ 'eventCount' => triggered_alert.events,
+ 'startedAt' => triggered_alert.started_at.strftime('%Y-%m-%dT%H:%M:%SZ'),
+ 'endedAt' => nil,
+ 'details' => { 'custom.alert' => 'payload' },
+ 'createdAt' => triggered_alert.created_at.strftime('%Y-%m-%dT%H:%M:%SZ'),
+ 'updatedAt' => triggered_alert.updated_at.strftime('%Y-%m-%dT%H:%M:%SZ')
+ )
+
+ expect(second_alert).to include(
+ 'iid' => resolved_alert.iid.to_s,
+ 'issueIid' => nil,
+ 'status' => 'RESOLVED',
+ 'endedAt' => resolved_alert.ended_at.strftime('%Y-%m-%dT%H:%M:%SZ')
+ )
+ end
+
+ context 'with iid given' do
+ let(:params) { { iid: resolved_alert.iid.to_s } }
+
+ it_behaves_like 'a working graphql query'
+
+ it { expect(alerts.size).to eq(1) }
+ it { expect(first_alert['iid']).to eq(resolved_alert.iid.to_s) }
+ end
+
+ context 'with statuses given' do
+ let(:params) { 'statuses: [TRIGGERED, ACKNOWLEDGED]' }
+
+ it_behaves_like 'a working graphql query'
+
+ it { expect(alerts.size).to eq(1) }
+ it { expect(first_alert['iid']).to eq(triggered_alert.iid.to_s) }
+ end
+
+ context 'sorting data given' do
+ let(:params) { 'sort: SEVERITY_DESC' }
+ let(:iids) { alerts.map { |a| a['iid'] } }
+
+ it_behaves_like 'a working graphql query'
+
+ it 'sorts in the correct order' do
+ expect(iids).to eq [resolved_alert.iid.to_s, triggered_alert.iid.to_s]
+ end
+
+ context 'ascending order' do
+ let(:params) { 'sort: SEVERITY_ASC' }
+
+ it 'sorts in the correct order' do
+ expect(iids).to eq [triggered_alert.iid.to_s, resolved_alert.iid.to_s]
+ end
+ end
+ end
+
+ context 'searching' do
+ let(:params) { { search: resolved_alert.title } }
+
+ it_behaves_like 'a working graphql query'
+
+ it { expect(alerts.size).to eq(1) }
+ it { expect(first_alert['iid']).to eq(resolved_alert.iid.to_s) }
+
+ context 'unknown criteria' do
+ let(:params) { { search: 'something random' } }
+
+ it { expect(alerts.size).to eq(0) }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/grafana_integration_spec.rb b/spec/requests/api/graphql/project/grafana_integration_spec.rb
index e7155934b3a..c9bc6c1a68e 100644
--- a/spec/requests/api/graphql/project/grafana_integration_spec.rb
+++ b/spec/requests/api/graphql/project/grafana_integration_spec.rb
@@ -35,7 +35,7 @@ describe 'Getting Grafana Integration' do
it_behaves_like 'a working graphql query'
- it { expect(integration_data).to be nil }
+ specify { expect(integration_data).to be nil }
end
context 'with project admin permissions' do
@@ -45,16 +45,16 @@ describe 'Getting Grafana Integration' do
it_behaves_like 'a working graphql query'
- it { expect(integration_data['token']).to eql grafana_integration.masked_token }
- it { expect(integration_data['grafanaUrl']).to eql grafana_integration.grafana_url }
+ specify { expect(integration_data['token']).to eql grafana_integration.masked_token }
+ specify { expect(integration_data['grafanaUrl']).to eql grafana_integration.grafana_url }
- it do
+ specify do
expect(
integration_data['createdAt']
).to eql grafana_integration.created_at.strftime('%Y-%m-%dT%H:%M:%SZ')
end
- it do
+ specify do
expect(
integration_data['updatedAt']
).to eql grafana_integration.updated_at.strftime('%Y-%m-%dT%H:%M:%SZ')
diff --git a/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb b/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb
new file mode 100644
index 00000000000..04f445b4318
--- /dev/null
+++ b/spec/requests/api/graphql/project/issue/design_collection/version_spec.rb
@@ -0,0 +1,216 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Query.project(fullPath).issue(iid).designCollection.version(sha)' do
+ include GraphqlHelpers
+ include DesignManagementTestHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:developer) { create(:user) }
+ let_it_be(:stranger) { create(:user) }
+ let_it_be(:old_version) do
+ create(:design_version, issue: issue,
+ created_designs: create_list(:design, 3, issue: issue))
+ end
+ let_it_be(:version) do
+ create(:design_version, issue: issue,
+ modified_designs: old_version.designs,
+ created_designs: create_list(:design, 2, issue: issue))
+ end
+
+ let(:current_user) { developer }
+
+ def query(vq = version_fields)
+ graphql_query_for(:project, { fullPath: project.full_path },
+ query_graphql_field(:issue, { iid: issue.iid.to_s },
+ query_graphql_field(:design_collection, nil,
+ query_graphql_field(:version, { sha: version.sha }, vq))))
+ end
+
+ let(:post_query) { post_graphql(query, current_user: current_user) }
+ let(:path_prefix) { %w[project issue designCollection version] }
+
+ let(:data) { graphql_data.dig(*path) }
+
+ before do
+ enable_design_management
+ project.add_developer(developer)
+ end
+
+ describe 'scalar fields' do
+ let(:path) { path_prefix }
+ let(:version_fields) { query_graphql_field(:sha) }
+
+ before do
+ post_query
+ end
+
+ { id: ->(x) { x.to_global_id.to_s }, sha: ->(x) { x.sha } }.each do |field, value|
+ describe ".#{field}" do
+ let(:version_fields) { query_graphql_field(field) }
+
+ it "retrieves the #{field}" do
+ expect(data).to match(a_hash_including(field.to_s => value[version]))
+ end
+ end
+ end
+ end
+
+ describe 'design_at_version' do
+ let(:path) { path_prefix + %w[designAtVersion] }
+ let(:design) { issue.designs.visible_at_version(version).to_a.sample }
+ let(:design_at_version) { build(:design_at_version, design: design, version: version) }
+
+ let(:version_fields) do
+ query_graphql_field(:design_at_version, dav_params, 'id filename')
+ end
+
+ shared_examples :finds_dav do
+ it 'finds all the designs as of the given version' do
+ post_query
+
+ expect(data).to match(
+ a_hash_including(
+ 'id' => global_id_of(design_at_version),
+ 'filename' => design.filename
+ ))
+ end
+
+ context 'when the current_user is not authorized' do
+ let(:current_user) { stranger }
+
+ it 'returns nil' do
+ post_query
+
+ expect(data).to be_nil
+ end
+ end
+ end
+
+ context 'by ID' do
+ let(:dav_params) { { id: global_id_of(design_at_version) } }
+
+ include_examples :finds_dav
+ end
+
+ context 'by filename' do
+ let(:dav_params) { { filename: design.filename } }
+
+ include_examples :finds_dav
+ end
+
+ context 'by design_id' do
+ let(:dav_params) { { design_id: global_id_of(design) } }
+
+ include_examples :finds_dav
+ end
+ end
+
+ describe 'designs_at_version' do
+ let(:path) { path_prefix + %w[designsAtVersion edges] }
+ let(:version_fields) do
+ query_graphql_field(:designs_at_version, dav_params, 'edges { node { id filename } }')
+ end
+
+ let(:dav_params) { nil }
+
+ let(:results) do
+ issue.designs.visible_at_version(version).map do |d|
+ dav = build(:design_at_version, design: d, version: version)
+ { 'id' => global_id_of(dav), 'filename' => d.filename }
+ end
+ end
+
+ it 'finds all the designs as of the given version' do
+ post_query
+
+ expect(data.pluck('node')).to match_array(results)
+ end
+
+ describe 'filtering' do
+ let(:designs) { issue.designs.sample(3) }
+ let(:filenames) { designs.map(&:filename) }
+ let(:ids) do
+ designs.map { |d| global_id_of(build(:design_at_version, design: d, version: version)) }
+ end
+
+ before do
+ post_query
+ end
+
+ describe 'by filename' do
+ let(:dav_params) { { filenames: filenames } }
+
+ it 'finds the designs by filename' do
+ expect(data.map { |e| e.dig('node', 'id') }).to match_array(ids)
+ end
+ end
+
+ describe 'by design-id' do
+ let(:dav_params) { { ids: designs.map { |d| global_id_of(d) } } }
+
+ it 'finds the designs by id' do
+ expect(data.map { |e| e.dig('node', 'filename') }).to match_array(filenames)
+ end
+ end
+ end
+
+ describe 'pagination' do
+ let(:end_cursor) { graphql_data_at(*path_prefix, :designs_at_version, :page_info, :end_cursor) }
+
+ let(:ids) do
+ ::DesignManagement::Design.visible_at_version(version).order(:id).map do |d|
+ global_id_of(build(:design_at_version, design: d, version: version))
+ end
+ end
+
+ let(:version_fields) do
+ query_graphql_field(:designs_at_version, { first: 2 }, fields)
+ end
+
+ let(:cursored_query) do
+ frag = query_graphql_field(:designs_at_version, { after: end_cursor }, fields)
+ query(frag)
+ end
+
+ let(:fields) { ['pageInfo { endCursor }', 'edges { node { id } }'] }
+
+ def response_values(data = graphql_data)
+ data.dig(*path).map { |e| e.dig('node', 'id') }
+ end
+
+ it 'sorts designs for reliable pagination' do
+ post_graphql(query, current_user: current_user)
+
+ expect(response_values).to match_array(ids.take(2))
+
+ post_graphql(cursored_query, current_user: current_user)
+
+ new_data = Gitlab::Json.parse(response.body).fetch('data')
+
+ expect(response_values(new_data)).to match_array(ids.drop(2))
+ end
+ end
+ end
+
+ describe 'designs' do
+ let(:path) { path_prefix + %w[designs edges] }
+ let(:version_fields) do
+ query_graphql_field(:designs, nil, 'edges { node { id filename } }')
+ end
+
+ let(:results) do
+ version.designs.map do |design|
+ { 'id' => global_id_of(design), 'filename' => design.filename }
+ end
+ end
+
+ it 'finds all the designs as of the given version' do
+ post_query
+
+ expect(data.pluck('node')).to match_array(results)
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb b/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb
new file mode 100644
index 00000000000..18787bf925d
--- /dev/null
+++ b/spec/requests/api/graphql/project/issue/design_collection/versions_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Getting versions related to an issue' do
+ include GraphqlHelpers
+ include DesignManagementTestHelpers
+
+ let_it_be(:issue) { create(:issue) }
+
+ let_it_be(:version_a) do
+ create(:design_version, issue: issue)
+ end
+ let_it_be(:version_b) do
+ create(:design_version, issue: issue)
+ end
+ let_it_be(:version_c) do
+ create(:design_version, issue: issue)
+ end
+ let_it_be(:version_d) do
+ create(:design_version, issue: issue)
+ end
+
+ let_it_be(:owner) { issue.project.owner }
+
+ def version_query(params = version_params)
+ query_graphql_field(:versions, params, version_query_fields)
+ end
+
+ let(:version_params) { nil }
+
+ let(:version_query_fields) { ['edges { node { sha } }'] }
+
+ let(:project) { issue.project }
+ let(:current_user) { owner }
+
+ let(:query) { make_query }
+
+ def make_query(vq = version_query)
+ graphql_query_for(:project, { fullPath: project.full_path },
+ query_graphql_field(:issue, { iid: issue.iid.to_s },
+ query_graphql_field(:design_collection, {}, vq)))
+ end
+
+ let(:design_collection) do
+ graphql_data_at(:project, :issue, :design_collection)
+ end
+
+ def response_values(data = graphql_data, key = 'sha')
+ path = %w[project issue designCollection versions edges]
+ data.dig(*path).map { |e| e.dig('node', key) }
+ end
+
+ before do
+ enable_design_management
+ end
+
+ it 'returns the design filename' do
+ post_graphql(query, current_user: current_user)
+
+ expect(response_values).to match_array([version_a, version_b, version_c, version_d].map(&:sha))
+ end
+
+ describe 'filter by sha' do
+ let(:sha) { version_b.sha }
+
+ let(:version_params) { { earlier_or_equal_to_sha: sha } }
+
+ it 'finds only those versions at or before the given cut-off' do
+ post_graphql(query, current_user: current_user)
+
+ expect(response_values).to contain_exactly(version_a.sha, version_b.sha)
+ end
+ end
+
+ describe 'filter by id' do
+ let(:id) { global_id_of(version_c) }
+
+ let(:version_params) { { earlier_or_equal_to_id: id } }
+
+ it 'finds only those versions at or before the given cut-off' do
+ post_graphql(query, current_user: current_user)
+
+ expect(response_values).to contain_exactly(version_a.sha, version_b.sha, version_c.sha)
+ end
+ end
+
+ describe 'pagination' do
+ let(:end_cursor) { design_collection.dig('versions', 'pageInfo', 'endCursor') }
+
+ let(:ids) { issue.design_collection.versions.ordered.map(&:sha) }
+
+ let(:query) { make_query(version_query(first: 2)) }
+
+ let(:cursored_query) do
+ make_query(version_query(after: end_cursor))
+ end
+
+ let(:version_query_fields) { ['pageInfo { endCursor }', 'edges { node { sha } }'] }
+
+ it 'sorts designs for reliable pagination' do
+ post_graphql(query, current_user: current_user)
+
+ expect(response_values).to match_array(ids.take(2))
+
+ post_graphql(cursored_query, current_user: current_user)
+
+ new_data = Gitlab::Json.parse(response.body).fetch('data')
+
+ expect(response_values(new_data)).to match_array(ids.drop(2))
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/issue/designs/designs_spec.rb b/spec/requests/api/graphql/project/issue/designs/designs_spec.rb
new file mode 100644
index 00000000000..b6fd0d91bda
--- /dev/null
+++ b/spec/requests/api/graphql/project/issue/designs/designs_spec.rb
@@ -0,0 +1,388 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Getting designs related to an issue' do
+ include GraphqlHelpers
+ include DesignManagementTestHelpers
+
+ let_it_be(:design) { create(:design, :with_smaller_image_versions, versions_count: 1) }
+ let_it_be(:current_user) { design.project.owner }
+ let(:design_query) do
+ <<~NODE
+ designs {
+ edges {
+ node {
+ id
+ filename
+ fullPath
+ event
+ image
+ imageV432x230
+ }
+ }
+ }
+ NODE
+ end
+ let(:issue) { design.issue }
+ let(:project) { issue.project }
+ let(:query) { make_query }
+ let(:design_collection) do
+ graphql_data_at(:project, :issue, :design_collection)
+ end
+ let(:design_response) do
+ design_collection.dig('designs', 'edges').first['node']
+ end
+
+ def make_query(dq = design_query)
+ designs_field = query_graphql_field(:design_collection, {}, dq)
+ issue_field = query_graphql_field(:issue, { iid: issue.iid.to_s }, designs_field)
+
+ graphql_query_for(:project, { fullPath: project.full_path }, issue_field)
+ end
+
+ def design_image_url(design, ref: nil, size: nil)
+ Gitlab::UrlBuilder.build(design, ref: ref, size: size)
+ end
+
+ context 'when the feature is available' do
+ before do
+ enable_design_management
+ end
+
+ it 'returns the design properties correctly' do
+ version_sha = design.versions.first.sha
+
+ post_graphql(query, current_user: current_user)
+
+ expect(design_response).to eq(
+ 'id' => design.to_global_id.to_s,
+ 'event' => 'CREATION',
+ 'fullPath' => design.full_path,
+ 'filename' => design.filename,
+ 'image' => design_image_url(design, ref: version_sha),
+ 'imageV432x230' => design_image_url(design, ref: version_sha, size: :v432x230)
+ )
+ end
+
+ context 'when the v432x230-sized design image has not been processed' do
+ before do
+ allow_next_instance_of(DesignManagement::DesignV432x230Uploader) do |uploader|
+ allow(uploader).to receive(:file).and_return(nil)
+ end
+ end
+
+ it 'returns nil for the v432x230-sized design image' do
+ post_graphql(query, current_user: current_user)
+
+ expect(design_response['imageV432x230']).to be_nil
+ end
+ end
+
+ describe 'pagination' do
+ before do
+ create_list(:design, 5, :with_file, issue: issue)
+ project.add_developer(current_user)
+ post_graphql(query, current_user: current_user)
+ end
+
+ let(:issue) { create(:issue) }
+
+ let(:end_cursor) { design_collection.dig('designs', 'pageInfo', 'endCursor') }
+
+ let(:ids) { issue.designs.order(:id).map { |d| global_id_of(d) } }
+
+ let(:query) { make_query(designs_fragment(first: 2)) }
+
+ let(:design_query_fields) { 'pageInfo { endCursor } edges { node { id } }' }
+
+ let(:cursored_query) do
+ make_query(designs_fragment(after: end_cursor))
+ end
+
+ def designs_fragment(params)
+ query_graphql_field(:designs, params, design_query_fields)
+ end
+
+ def response_ids(data = graphql_data)
+ path = %w[project issue designCollection designs edges]
+ data.dig(*path).map { |e| e.dig('node', 'id') }
+ end
+
+ it 'sorts designs for reliable pagination' do
+ expect(response_ids).to match_array(ids.take(2))
+
+ post_graphql(cursored_query, current_user: current_user)
+
+ new_data = Gitlab::Json.parse(response.body).fetch('data')
+
+ expect(response_ids(new_data)).to match_array(ids.drop(2))
+ end
+ end
+
+ context 'with versions' do
+ let_it_be(:version) { design.versions.take }
+ let(:design_query) do
+ <<~NODE
+ designs {
+ edges {
+ node {
+ filename
+ versions {
+ edges {
+ node {
+ id
+ sha
+ }
+ }
+ }
+ }
+ }
+ }
+ NODE
+ end
+
+ it 'includes the version id' do
+ post_graphql(query, current_user: current_user)
+
+ version_id = design_response['versions']['edges'].first['node']['id']
+
+ expect(version_id).to eq(version.to_global_id.to_s)
+ end
+
+ it 'includes the version sha' do
+ post_graphql(query, current_user: current_user)
+
+ version_sha = design_response['versions']['edges'].first['node']['sha']
+
+ expect(version_sha).to eq(version.sha)
+ end
+ end
+
+ describe 'viewing a design board at a particular version' do
+ let_it_be(:issue) { design.issue }
+ let_it_be(:second_design, reload: true) { create(:design, :with_smaller_image_versions, issue: issue, versions_count: 1) }
+ let_it_be(:deleted_design) { create(:design, :with_versions, issue: issue, deleted: true, versions_count: 1) }
+ let(:all_versions) { issue.design_versions.ordered.reverse }
+ let(:design_query) do
+ <<~NODE
+ designs(atVersion: "#{version.to_global_id}") {
+ edges {
+ node {
+ id
+ image
+ imageV432x230
+ event
+ versions {
+ edges {
+ node {
+ id
+ }
+ }
+ }
+ }
+ }
+ }
+ NODE
+ end
+ let(:design_response) do
+ design_collection['designs']['edges']
+ end
+
+ def global_id(object)
+ object.to_global_id.to_s
+ end
+
+ # Filters just design nodes from the larger `design_response`
+ def design_nodes
+ design_response.map do |response|
+ response['node']
+ end
+ end
+
+ # Filters just version nodes from the larger `design_response`
+ def version_nodes
+ design_response.map do |response|
+ response.dig('node', 'versions', 'edges')
+ end
+ end
+
+ context 'viewing the original version, when one design was created' do
+ let(:version) { all_versions.first }
+
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it 'only returns the first design' do
+ expect(design_nodes).to contain_exactly(
+ a_hash_including('id' => global_id(design))
+ )
+ end
+
+ it 'returns the correct full-sized design image' do
+ expect(design_nodes).to contain_exactly(
+ a_hash_including('image' => design_image_url(design, ref: version.sha))
+ )
+ end
+
+ it 'returns the correct v432x230-sized design image' do
+ expect(design_nodes).to contain_exactly(
+ a_hash_including('imageV432x230' => design_image_url(design, ref: version.sha, size: :v432x230))
+ )
+ end
+
+ it 'returns the correct event for the design in this version' do
+ expect(design_nodes).to contain_exactly(
+ a_hash_including('event' => 'CREATION')
+ )
+ end
+
+ it 'only returns one version record for the design (the original version)' do
+ expect(version_nodes).to eq([
+ [{ 'node' => { 'id' => global_id(version) } }]
+ ])
+ end
+ end
+
+ context 'viewing the second version, when one design was created' do
+ let(:version) { all_versions.second }
+
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it 'only returns the first two designs' do
+ expect(design_nodes).to contain_exactly(
+ a_hash_including('id' => global_id(design)),
+ a_hash_including('id' => global_id(second_design))
+ )
+ end
+
+ it 'returns the correct full-sized design images' do
+ expect(design_nodes).to contain_exactly(
+ a_hash_including('image' => design_image_url(design, ref: version.sha)),
+ a_hash_including('image' => design_image_url(second_design, ref: version.sha))
+ )
+ end
+
+ it 'returns the correct v432x230-sized design images' do
+ expect(design_nodes).to contain_exactly(
+ a_hash_including('imageV432x230' => design_image_url(design, ref: version.sha, size: :v432x230)),
+ a_hash_including('imageV432x230' => design_image_url(second_design, ref: version.sha, size: :v432x230))
+ )
+ end
+
+ it 'returns the correct events for the designs in this version' do
+ expect(design_nodes).to contain_exactly(
+ a_hash_including('event' => 'NONE'),
+ a_hash_including('event' => 'CREATION')
+ )
+ end
+
+ it 'returns the correct versions records for both designs' do
+ expect(version_nodes).to eq([
+ [{ 'node' => { 'id' => global_id(design.versions.first) } }],
+ [{ 'node' => { 'id' => global_id(second_design.versions.first) } }]
+ ])
+ end
+ end
+
+ context 'viewing the last version, when one design was deleted and one was updated' do
+ let(:version) { all_versions.last }
+ let!(:second_design_update) do
+ create(:design_action, :with_image_v432x230, design: second_design, version: version, event: 'modification')
+ end
+
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it 'does not include the deleted design' do
+ # The design does exist in the version
+ expect(version.designs).to include(deleted_design)
+
+ # But the GraphQL API does not include it in these results
+ expect(design_nodes).to contain_exactly(
+ a_hash_including('id' => global_id(design)),
+ a_hash_including('id' => global_id(second_design))
+ )
+ end
+
+ it 'returns the correct full-sized design images' do
+ expect(design_nodes).to contain_exactly(
+ a_hash_including('image' => design_image_url(design, ref: version.sha)),
+ a_hash_including('image' => design_image_url(second_design, ref: version.sha))
+ )
+ end
+
+ it 'returns the correct v432x230-sized design images' do
+ expect(design_nodes).to contain_exactly(
+ a_hash_including('imageV432x230' => design_image_url(design, ref: version.sha, size: :v432x230)),
+ a_hash_including('imageV432x230' => design_image_url(second_design, ref: version.sha, size: :v432x230))
+ )
+ end
+
+ it 'returns the correct events for the designs in this version' do
+ expect(design_nodes).to contain_exactly(
+ a_hash_including('event' => 'NONE'),
+ a_hash_including('event' => 'MODIFICATION')
+ )
+ end
+
+ it 'returns all versions records for the designs' do
+ expect(version_nodes).to eq([
+ [
+ { 'node' => { 'id' => global_id(design.versions.first) } }
+ ],
+ [
+ { 'node' => { 'id' => global_id(second_design.versions.second) } },
+ { 'node' => { 'id' => global_id(second_design.versions.first) } }
+ ]
+ ])
+ end
+ end
+ end
+
+ describe 'a design with note annotations' do
+ let_it_be(:note) { create(:diff_note_on_design, noteable: design) }
+
+ let(:design_query) do
+ <<~NODE
+ designs {
+ edges {
+ node {
+ notesCount
+ notes {
+ edges {
+ node {
+ id
+ }
+ }
+ }
+ }
+ }
+ }
+ NODE
+ end
+
+ let(:design_response) do
+ design_collection['designs']['edges'].first['node']
+ end
+
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+
+ it 'returns the notes for the design' do
+ expect(design_response.dig('notes', 'edges')).to eq(
+ ['node' => { 'id' => note.to_global_id.to_s }]
+ )
+ end
+
+ it 'returns a note_count for the design' do
+ expect(design_response['notesCount']).to eq(1)
+ 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
new file mode 100644
index 00000000000..0207bb9123a
--- /dev/null
+++ b/spec/requests/api/graphql/project/issue/designs/notes_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Getting designs related to an issue' do
+ include GraphqlHelpers
+ include DesignManagementTestHelpers
+
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:design) { create(:design, :with_file, versions_count: 1, issue: issue) }
+ let_it_be(:current_user) { project.owner }
+ let_it_be(:note) { create(:diff_note_on_design, noteable: design, project: project) }
+
+ before do
+ enable_design_management
+
+ note
+ end
+
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_graphql(query, current_user: current_user)
+ end
+ end
+
+ it 'is not too deep for anonymous users' do
+ note_fields = <<~FIELDS
+ id
+ author { name }
+ FIELDS
+
+ post_graphql(query(note_fields), current_user: nil)
+
+ designs_data = graphql_data['project']['issue']['designs']['designs']
+ design_data = designs_data['edges'].first['node']
+ note_data = design_data['notes']['edges'].first['node']
+
+ expect(note_data['id']).to eq(note.to_global_id.to_s)
+ end
+
+ def query(note_fields = all_graphql_fields_for(Note))
+ design_node = <<~NODE
+ designs {
+ edges {
+ node {
+ notes {
+ edges {
+ node {
+ #{note_fields}
+ }
+ }
+ }
+ }
+ }
+ }
+ NODE
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => design.project.full_path },
+ query_graphql_field(
+ 'issue',
+ { iid: design.issue.iid.to_s },
+ query_graphql_field(
+ 'designs', {}, design_node
+ )
+ )
+ )
+ end
+end
diff --git a/spec/requests/api/graphql/project/issue_spec.rb b/spec/requests/api/graphql/project/issue_spec.rb
new file mode 100644
index 00000000000..92d2f9d0d31
--- /dev/null
+++ b/spec/requests/api/graphql/project/issue_spec.rb
@@ -0,0 +1,189 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Query.project(fullPath).issue(iid)' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:issue_b) { create(:issue, project: project) }
+ let_it_be(:developer) { create(:user) }
+ let(:current_user) { developer }
+
+ let_it_be(:project_params) { { 'fullPath' => project.full_path } }
+ let_it_be(:issue_params) { { 'iid' => issue.iid.to_s } }
+ let_it_be(:issue_fields) { 'title' }
+
+ let(:query) do
+ graphql_query_for('project', project_params, project_fields)
+ end
+
+ let(:project_fields) do
+ query_graphql_field(:issue, issue_params, issue_fields)
+ end
+
+ shared_examples 'being able to fetch a design-like object by ID' do
+ let(:design) { design_a }
+ let(:path) { %w[project issue designCollection] + [GraphqlHelpers.fieldnamerize(object_field_name)] }
+
+ let(:design_fields) do
+ [
+ query_graphql_field(:filename),
+ query_graphql_field(:project, nil, query_graphql_field(:id))
+ ]
+ end
+
+ let(:design_collection_fields) do
+ query_graphql_field(object_field_name, object_params, object_fields)
+ end
+
+ let(:object_fields) { design_fields }
+
+ context 'the ID is passed' do
+ let(:object_params) { { id: global_id_of(object) } }
+ let(:result_fields) { {} }
+
+ let(:expected_fields) do
+ result_fields.merge({ 'filename' => design.filename, 'project' => id_hash(project) })
+ end
+
+ it 'retrieves the object' do
+ post_query
+
+ data = graphql_data.dig(*path)
+
+ expect(data).to match(a_hash_including(expected_fields))
+ end
+
+ context 'the user is unauthorized' do
+ let(:current_user) { create(:user) }
+
+ it_behaves_like 'a failure to find anything'
+ end
+ end
+
+ context 'without parameters' do
+ let(:object_params) { nil }
+
+ it 'raises an error' do
+ post_query
+
+ expect(graphql_errors).to include(no_argument_error)
+ end
+ end
+
+ context 'attempting to retrieve an object from a different issue' do
+ let(:object_params) { { id: global_id_of(object_on_other_issue) } }
+
+ it_behaves_like 'a failure to find anything'
+ end
+ end
+
+ before do
+ project.add_developer(developer)
+ end
+
+ let(:post_query) { post_graphql(query, current_user: current_user) }
+
+ describe '.designCollection' do
+ include DesignManagementTestHelpers
+
+ let_it_be(:design_a) { create(:design, issue: issue) }
+ let_it_be(:version_a) { create(:design_version, issue: issue, created_designs: [design_a]) }
+
+ let(:issue_fields) do
+ query_graphql_field(:design_collection, dc_params, design_collection_fields)
+ end
+
+ let(:dc_params) { nil }
+ let(:design_collection_fields) { nil }
+
+ before do
+ enable_design_management
+ end
+
+ describe '.design' do
+ let(:object) { design }
+ let(:object_field_name) { :design }
+
+ let(:no_argument_error) do
+ custom_graphql_error(path, a_string_matching(%r/id or filename/))
+ end
+
+ let_it_be(:object_on_other_issue) { create(:design, issue: issue_b) }
+
+ it_behaves_like 'being able to fetch a design-like object by ID'
+
+ it_behaves_like 'being able to fetch a design-like object by ID' do
+ let(:object_params) { { filename: design.filename } }
+ end
+ end
+
+ describe '.version' do
+ let(:version) { version_a }
+ let(:path) { %w[project issue designCollection version] }
+
+ let(:design_collection_fields) do
+ query_graphql_field(:version, version_params, 'id sha')
+ end
+
+ context 'no parameters' do
+ let(:version_params) { nil }
+
+ it 'raises an error' do
+ post_query
+
+ expect(graphql_errors).to include(custom_graphql_error(path, a_string_matching(%r/id or sha/)))
+ end
+ end
+
+ shared_examples 'a successful query for a version' do
+ it 'finds the version' do
+ post_query
+
+ data = graphql_data.dig(*path)
+
+ expect(data).to match(
+ a_hash_including('id' => global_id_of(version),
+ 'sha' => version.sha)
+ )
+ end
+ end
+
+ context '(sha: STRING_TYPE)' do
+ let(:version_params) { { sha: version.sha } }
+
+ it_behaves_like 'a successful query for a version'
+ end
+
+ context '(id: ID_TYPE)' do
+ let(:version_params) { { id: global_id_of(version) } }
+
+ it_behaves_like 'a successful query for a version'
+ end
+ end
+
+ describe '.designAtVersion' do
+ it_behaves_like 'being able to fetch a design-like object by ID' do
+ let(:object) { build(:design_at_version, design: design, version: version) }
+ let(:object_field_name) { :design_at_version }
+
+ let(:version) { version_a }
+
+ let(:result_fields) { { 'version' => id_hash(version) } }
+ let(:object_fields) do
+ design_fields + [query_graphql_field(:version, nil, query_graphql_field(:id))]
+ end
+
+ let(:no_argument_error) { missing_required_argument(path, :id) }
+
+ let(:object_on_other_issue) { build(:design_at_version, issue: issue_b) }
+ end
+ end
+ end
+
+ def id_hash(object)
+ a_hash_including('id' => global_id_of(object))
+ end
+end
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
index 4ce7a3912a3..91fce3eed92 100644
--- a/spec/requests/api/graphql/project/issues_spec.rb
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -45,8 +45,8 @@ describe 'getting an issue list for a project' do
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
+ expect(issues_data[0]['node']['discussionLocked']).to eq(false)
+ expect(issues_data[1]['node']['discussionLocked']).to eq(true)
end
context 'when limiting the number of results' do
@@ -79,7 +79,7 @@ describe 'getting an issue list for a project' do
post_graphql(query)
- expect(issues_data).to eq []
+ expect(issues_data).to eq([])
end
end
@@ -118,131 +118,138 @@ describe 'getting an issue list for a project' do
end
describe 'sorting and pagination' do
- let(:start_cursor) { graphql_data['project']['issues']['pageInfo']['startCursor'] }
- let(:end_cursor) { graphql_data['project']['issues']['pageInfo']['endCursor'] }
+ let_it_be(:data_path) { [:project, :issues] }
- context 'when sorting by due date' do
- let(:sort_project) { create(:project, :public) }
-
- let!(:due_issue1) { create(:issue, project: sort_project, due_date: 3.days.from_now) }
- let!(:due_issue2) { create(:issue, project: sort_project, due_date: nil) }
- let!(:due_issue3) { create(:issue, project: sort_project, due_date: 2.days.ago) }
- let!(:due_issue4) { create(:issue, project: sort_project, due_date: nil) }
- let!(:due_issue5) { create(:issue, project: sort_project, due_date: 1.day.ago) }
-
- let(:params) { 'sort: DUE_DATE_ASC' }
-
- def query(issue_params = params)
- graphql_query_for(
- 'project',
- { 'fullPath' => sort_project.full_path },
- <<~ISSUES
- issues(#{issue_params}) {
- pageInfo {
- endCursor
- }
- edges {
- node {
- iid
- dueDate
- }
- }
- }
- ISSUES
- )
- end
+ def pagination_query(params, page_info)
+ graphql_query_for(
+ 'project',
+ { 'fullPath' => sort_project.full_path },
+ "issues(#{params}) { #{page_info} edges { node { iid dueDate } } }"
+ )
+ end
- before do
- post_graphql(query, current_user: current_user)
- end
+ def pagination_results_data(data)
+ data.map { |issue| issue.dig('node', 'iid').to_i }
+ end
- it_behaves_like 'a working graphql query'
+ context 'when sorting by due date' do
+ let_it_be(:sort_project) { create(:project, :public) }
+ 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) }
+ let_it_be(:due_issue3) { create(:issue, project: sort_project, due_date: 2.days.ago) }
+ let_it_be(:due_issue4) { create(:issue, project: sort_project, due_date: nil) }
+ let_it_be(:due_issue5) { create(:issue, project: sort_project, due_date: 1.day.ago) }
context 'when ascending' do
- it 'sorts issues' do
- expect(grab_iids).to eq [due_issue3.iid, due_issue5.iid, due_issue1.iid, due_issue4.iid, due_issue2.iid]
- end
-
- context 'when paginating' do
- let(:params) { 'sort: DUE_DATE_ASC, first: 2' }
-
- it 'sorts issues' do
- expect(grab_iids).to eq [due_issue3.iid, due_issue5.iid]
-
- cursored_query = query("sort: DUE_DATE_ASC, after: \"#{end_cursor}\"")
- post_graphql(cursored_query, current_user: current_user)
- response_data = JSON.parse(response.body)['data']['project']['issues']['edges']
-
- expect(grab_iids(response_data)).to eq [due_issue1.iid, due_issue4.iid, due_issue2.iid]
- end
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { 'DUE_DATE_ASC' }
+ let(:first_param) { 2 }
+ let(:expected_results) { [due_issue3.iid, due_issue5.iid, due_issue1.iid, due_issue4.iid, due_issue2.iid] }
end
end
context 'when descending' do
- let(:params) { 'sort: DUE_DATE_DESC' }
-
- it 'sorts issues' do
- expect(grab_iids).to eq [due_issue1.iid, due_issue5.iid, due_issue3.iid, due_issue4.iid, due_issue2.iid]
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { 'DUE_DATE_DESC' }
+ let(:first_param) { 2 }
+ let(:expected_results) { [due_issue1.iid, due_issue5.iid, due_issue3.iid, due_issue4.iid, due_issue2.iid] }
end
+ end
+ end
- context 'when paginating' do
- let(:params) { 'sort: DUE_DATE_DESC, first: 2' }
-
- it 'sorts issues' do
- expect(grab_iids).to eq [due_issue1.iid, due_issue5.iid]
-
- cursored_query = query("sort: DUE_DATE_DESC, after: \"#{end_cursor}\"")
- post_graphql(cursored_query, current_user: current_user)
- response_data = JSON.parse(response.body)['data']['project']['issues']['edges']
+ context 'when sorting by relative position' do
+ let_it_be(:sort_project) { create(:project, :public) }
+ let_it_be(:relative_issue1) { create(:issue, project: sort_project, relative_position: 2000) }
+ let_it_be(:relative_issue2) { create(:issue, project: sort_project, relative_position: nil) }
+ let_it_be(:relative_issue3) { create(:issue, project: sort_project, relative_position: 1000) }
+ let_it_be(:relative_issue4) { create(:issue, project: sort_project, relative_position: nil) }
+ let_it_be(:relative_issue5) { create(:issue, project: sort_project, relative_position: 500) }
- expect(grab_iids(response_data)).to eq [due_issue3.iid, due_issue4.iid, due_issue2.iid]
- end
+ context 'when ascending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { 'RELATIVE_POSITION_ASC' }
+ let(:first_param) { 2 }
+ let(:expected_results) { [relative_issue5.iid, relative_issue3.iid, relative_issue1.iid, relative_issue4.iid, relative_issue2.iid] }
end
end
end
- context 'when sorting by relative position' do
- let(:sort_project) { create(:project, :public) }
-
- let!(:relative_issue1) { create(:issue, project: sort_project, relative_position: 2000) }
- let!(:relative_issue2) { create(:issue, project: sort_project, relative_position: nil) }
- let!(:relative_issue3) { create(:issue, project: sort_project, relative_position: 1000) }
- let!(:relative_issue4) { create(:issue, project: sort_project, relative_position: nil) }
- let!(:relative_issue5) { create(:issue, project: sort_project, relative_position: 500) }
-
- let(:params) { 'sort: RELATIVE_POSITION_ASC' }
-
- def query(issue_params = params)
- graphql_query_for(
- 'project',
- { 'fullPath' => sort_project.full_path },
- "issues(#{issue_params}) { pageInfo { endCursor} edges { node { iid dueDate } } }"
- )
+ context 'when sorting by priority' do
+ let_it_be(:sort_project) { create(:project, :public) }
+ let_it_be(:early_milestone) { create(:milestone, project: sort_project, due_date: 10.days.from_now) }
+ let_it_be(:late_milestone) { create(:milestone, project: sort_project, due_date: 30.days.from_now) }
+ let_it_be(:priority_label1) { create(:label, project: sort_project, priority: 1) }
+ let_it_be(:priority_label2) { create(:label, project: sort_project, priority: 5) }
+ let_it_be(:priority_issue1) { create(:issue, project: sort_project, labels: [priority_label1], milestone: late_milestone) }
+ let_it_be(:priority_issue2) { create(:issue, project: sort_project, labels: [priority_label2]) }
+ let_it_be(:priority_issue3) { create(:issue, project: sort_project, milestone: early_milestone) }
+ let_it_be(:priority_issue4) { create(:issue, project: sort_project) }
+
+ context 'when ascending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { 'PRIORITY_ASC' }
+ let(:first_param) { 2 }
+ let(:expected_results) { [priority_issue3.iid, priority_issue1.iid, priority_issue2.iid, priority_issue4.iid] }
+ end
end
- before do
- post_graphql(query, current_user: current_user)
+ context 'when descending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { 'PRIORITY_DESC' }
+ let(:first_param) { 2 }
+ let(:expected_results) { [priority_issue1.iid, priority_issue3.iid, priority_issue2.iid, priority_issue4.iid] }
+ end
end
+ end
- it_behaves_like 'a working graphql query'
+ context 'when sorting by label priority' do
+ let_it_be(:sort_project) { create(:project, :public) }
+ let_it_be(:label1) { create(:label, project: sort_project, priority: 1) }
+ let_it_be(:label2) { create(:label, project: sort_project, priority: 5) }
+ let_it_be(:label3) { create(:label, project: sort_project, priority: 10) }
+ let_it_be(:label_issue1) { create(:issue, project: sort_project, labels: [label1]) }
+ let_it_be(:label_issue2) { create(:issue, project: sort_project, labels: [label2]) }
+ let_it_be(:label_issue3) { create(:issue, project: sort_project, labels: [label1, label3]) }
+ let_it_be(:label_issue4) { create(:issue, project: sort_project) }
context 'when ascending' do
- it 'sorts issues' do
- expect(grab_iids).to eq [relative_issue5.iid, relative_issue3.iid, relative_issue1.iid, relative_issue4.iid, relative_issue2.iid]
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { 'LABEL_PRIORITY_ASC' }
+ let(:first_param) { 2 }
+ let(:expected_results) { [label_issue3.iid, label_issue1.iid, label_issue2.iid, label_issue4.iid] }
end
+ end
- context 'when paginating' do
- let(:params) { 'sort: RELATIVE_POSITION_ASC, first: 2' }
+ context 'when descending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { 'LABEL_PRIORITY_DESC' }
+ let(:first_param) { 2 }
+ let(:expected_results) { [label_issue2.iid, label_issue3.iid, label_issue1.iid, label_issue4.iid] }
+ end
+ end
+ end
- it 'sorts issues' do
- expect(grab_iids).to eq [relative_issue5.iid, relative_issue3.iid]
+ context 'when sorting by milestone due date' do
+ let_it_be(:sort_project) { create(:project, :public) }
+ let_it_be(:early_milestone) { create(:milestone, project: sort_project, due_date: 10.days.from_now) }
+ let_it_be(:late_milestone) { create(:milestone, project: sort_project, due_date: 30.days.from_now) }
+ let_it_be(:milestone_issue1) { create(:issue, project: sort_project) }
+ let_it_be(:milestone_issue2) { create(:issue, project: sort_project, milestone: early_milestone) }
+ let_it_be(:milestone_issue3) { create(:issue, project: sort_project, milestone: late_milestone) }
- cursored_query = query("sort: RELATIVE_POSITION_ASC, after: \"#{end_cursor}\"")
- post_graphql(cursored_query, current_user: current_user)
- response_data = JSON.parse(response.body)['data']['project']['issues']['edges']
+ context 'when ascending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { 'MILESTONE_DUE_ASC' }
+ let(:first_param) { 2 }
+ let(:expected_results) { [milestone_issue2.iid, milestone_issue3.iid, milestone_issue1.iid] }
+ end
+ end
- expect(grab_iids(response_data)).to eq [relative_issue1.iid, relative_issue4.iid, relative_issue2.iid]
- end
+ context 'when descending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { 'MILESTONE_DUE_DESC' }
+ let(:first_param) { 2 }
+ let(:expected_results) { [milestone_issue3.iid, milestone_issue2.iid, milestone_issue1.iid] }
end
end
end
diff --git a/spec/requests/api/graphql/project/jira_import_spec.rb b/spec/requests/api/graphql/project/jira_import_spec.rb
index 43e1bb13342..e063068eb1a 100644
--- a/spec/requests/api/graphql/project/jira_import_spec.rb
+++ b/spec/requests/api/graphql/project/jira_import_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'query jira import data' do
+describe 'query Jira import data' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
@@ -18,6 +18,7 @@ describe 'query jira import data' do
jiraImports {
nodes {
jiraProjectKey
+ createdAt
scheduledAt
scheduledBy {
username
diff --git a/spec/requests/api/graphql/query_spec.rb b/spec/requests/api/graphql/query_spec.rb
new file mode 100644
index 00000000000..26b4c6eafd7
--- /dev/null
+++ b/spec/requests/api/graphql/query_spec.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Query' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:developer) { create(:user) }
+ let(:current_user) { developer }
+
+ describe '.designManagement' do
+ include DesignManagementTestHelpers
+
+ let_it_be(:version) { create(:design_version, issue: issue) }
+ let_it_be(:design) { version.designs.first }
+ let(:query_result) { graphql_data.dig(*path) }
+ let(:query) { graphql_query_for(:design_management, nil, dm_fields) }
+
+ before do
+ enable_design_management
+ project.add_developer(developer)
+ post_graphql(query, current_user: current_user)
+ end
+
+ shared_examples 'a query that needs authorization' do
+ context 'the current user is not able to read designs' do
+ let(:current_user) { create(:user) }
+
+ it 'does not retrieve the record' do
+ expect(query_result).to be_nil
+ end
+
+ it 'raises an error' do
+ expect(graphql_errors).to include(
+ a_hash_including('message' => a_string_matching(%r{you don't have permission}))
+ )
+ end
+ end
+ end
+
+ describe '.version' do
+ let(:path) { %w[designManagement version] }
+
+ let(:dm_fields) do
+ query_graphql_field(:version, { 'id' => global_id_of(version) }, 'id sha')
+ end
+
+ it_behaves_like 'a working graphql query'
+ it_behaves_like 'a query that needs authorization'
+
+ context 'the current user is able to read designs' do
+ it 'fetches the expected data' do
+ expect(query_result).to eq('id' => global_id_of(version), 'sha' => version.sha)
+ end
+ end
+ end
+
+ describe '.designAtVersion' do
+ let_it_be(:design_at_version) do
+ ::DesignManagement::DesignAtVersion.new(design: design, version: version)
+ end
+
+ let(:path) { %w[designManagement designAtVersion] }
+
+ let(:dm_fields) do
+ query_graphql_field(:design_at_version, { 'id' => global_id_of(design_at_version) }, <<~FIELDS)
+ id
+ filename
+ version { id sha }
+ design { id }
+ issue { title iid }
+ project { id fullPath }
+ FIELDS
+ end
+
+ it_behaves_like 'a working graphql query'
+ it_behaves_like 'a query that needs authorization'
+
+ context 'the current user is able to read designs' do
+ it 'fetches the expected data, including the correct associations' do
+ expect(query_result).to eq(
+ 'id' => global_id_of(design_at_version),
+ 'filename' => design_at_version.design.filename,
+ 'version' => { 'id' => global_id_of(version), 'sha' => version.sha },
+ 'design' => { 'id' => global_id_of(design) },
+ 'issue' => { 'title' => issue.title, 'iid' => issue.iid.to_s },
+ 'project' => { 'id' => global_id_of(project), 'fullPath' => project.full_path }
+ )
+ end
+ end
+ end
+ end
+end