summaryrefslogtreecommitdiff
path: root/spec/graphql
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-10-21 07:08:36 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-10-21 07:08:36 +0000
commit48aff82709769b098321c738f3444b9bdaa694c6 (patch)
treee00c7c43e2d9b603a5a6af576b1685e400410dee /spec/graphql
parent879f5329ee916a948223f8f43d77fba4da6cd028 (diff)
downloadgitlab-ce-48aff82709769b098321c738f3444b9bdaa694c6.tar.gz
Add latest changes from gitlab-org/gitlab@13-5-stable-eev13.5.0-rc42
Diffstat (limited to 'spec/graphql')
-rw-r--r--spec/graphql/features/feature_flag_spec.rb4
-rw-r--r--spec/graphql/mutations/design_management/move_spec.rb2
-rw-r--r--spec/graphql/mutations/discussions/toggle_resolve_spec.rb4
-rw-r--r--spec/graphql/mutations/issues/create_spec.rb146
-rw-r--r--spec/graphql/mutations/issues/move_spec.rb41
-rw-r--r--spec/graphql/mutations/issues/update_spec.rb17
-rw-r--r--spec/graphql/mutations/todos/mark_done_spec.rb3
-rw-r--r--spec/graphql/mutations/todos/restore_many_spec.rb33
-rw-r--r--spec/graphql/mutations/todos/restore_spec.rb11
-rw-r--r--spec/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver_spec.rb35
-rw-r--r--spec/graphql/resolvers/board_lists_resolver_spec.rb8
-rw-r--r--spec/graphql/resolvers/board_resolver_spec.rb72
-rw-r--r--spec/graphql/resolvers/ci/runner_platforms_resolver_spec.rb19
-rw-r--r--spec/graphql/resolvers/concerns/looks_ahead_spec.rb14
-rw-r--r--spec/graphql/resolvers/group_milestones_resolver_spec.rb23
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb33
-rw-r--r--spec/graphql/resolvers/project_milestones_resolver_spec.rb61
-rw-r--r--spec/graphql/resolvers/projects_resolver_spec.rb65
-rw-r--r--spec/graphql/resolvers/snippets/blobs_resolver_spec.rb50
-rw-r--r--spec/graphql/resolvers/terraform/states_resolver_spec.rb33
-rw-r--r--spec/graphql/types/alert_management/alert_type_spec.rb1
-rw-r--r--spec/graphql/types/alert_management/status_enum_spec.rb8
-rw-r--r--spec/graphql/types/base_field_spec.rb4
-rw-r--r--spec/graphql/types/ci/detailed_status_type_spec.rb2
-rw-r--r--spec/graphql/types/ci/group_type_spec.rb1
-rw-r--r--spec/graphql/types/ci/job_type_spec.rb2
-rw-r--r--spec/graphql/types/ci/runner_architecture_type_spec.rb16
-rw-r--r--spec/graphql/types/ci/runner_platform_type_spec.rb17
-rw-r--r--spec/graphql/types/ci/stage_type_spec.rb1
-rw-r--r--spec/graphql/types/ci/status_action_type_spec.rb19
-rw-r--r--spec/graphql/types/design_management/design_collection_copy_state_enum_spec.rb11
-rw-r--r--spec/graphql/types/design_management/design_collection_type_spec.rb2
-rw-r--r--spec/graphql/types/environment_type_spec.rb15
-rw-r--r--spec/graphql/types/global_id_type_spec.rb26
-rw-r--r--spec/graphql/types/group_type_spec.rb1
-rw-r--r--spec/graphql/types/issue_sort_enum_spec.rb2
-rw-r--r--spec/graphql/types/merge_request_type_spec.rb39
-rw-r--r--spec/graphql/types/package_type_enum_spec.rb2
-rw-r--r--spec/graphql/types/project_type_spec.rb9
-rw-r--r--spec/graphql/types/query_type_spec.rb11
-rw-r--r--spec/graphql/types/range_input_type_spec.rb43
-rw-r--r--spec/graphql/types/root_storage_statistics_type_spec.rb3
-rw-r--r--spec/graphql/types/snippet_type_spec.rb54
-rw-r--r--spec/graphql/types/terraform/state_type_spec.rb21
-rw-r--r--spec/graphql/types/timeframe_type_spec.rb38
45 files changed, 926 insertions, 96 deletions
diff --git a/spec/graphql/features/feature_flag_spec.rb b/spec/graphql/features/feature_flag_spec.rb
index b484663d675..9ebc6e595a6 100644
--- a/spec/graphql/features/feature_flag_spec.rb
+++ b/spec/graphql/features/feature_flag_spec.rb
@@ -12,6 +12,10 @@ RSpec.describe 'Graphql Field feature flags' do
let(:query_string) { '{ item { name } }' }
let(:result) { execute_query(query_type)['data'] }
+ before do
+ skip_feature_flags_yaml_validation
+ end
+
subject { result }
describe 'Feature flagged field' do
diff --git a/spec/graphql/mutations/design_management/move_spec.rb b/spec/graphql/mutations/design_management/move_spec.rb
index 7519347d07c..d17483e69b3 100644
--- a/spec/graphql/mutations/design_management/move_spec.rb
+++ b/spec/graphql/mutations/design_management/move_spec.rb
@@ -29,7 +29,7 @@ RSpec.describe Mutations::DesignManagement::Move do
next_design: next_design&.to_global_id
}.compact
- mutation.resolve(args)
+ mutation.resolve(**args)
end
shared_examples "resource not available" do
diff --git a/spec/graphql/mutations/discussions/toggle_resolve_spec.rb b/spec/graphql/mutations/discussions/toggle_resolve_spec.rb
index d779a2227c1..2e5d41a8f1e 100644
--- a/spec/graphql/mutations/discussions/toggle_resolve_spec.rb
+++ b/spec/graphql/mutations/discussions/toggle_resolve_spec.rb
@@ -50,8 +50,8 @@ RSpec.describe Mutations::Discussions::ToggleResolve do
it 'raises an error' do
expect { subject }.to raise_error(
- Gitlab::Graphql::Errors::ArgumentError,
- "#{discussion.to_global_id} is not a valid ID for Discussion."
+ GraphQL::CoercionError,
+ "\"#{discussion.to_global_id}\" does not represent an instance of Discussion"
)
end
end
diff --git a/spec/graphql/mutations/issues/create_spec.rb b/spec/graphql/mutations/issues/create_spec.rb
new file mode 100644
index 00000000000..57658f6b358
--- /dev/null
+++ b/spec/graphql/mutations/issues/create_spec.rb
@@ -0,0 +1,146 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Issues::Create do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:assignee1) { create(:user) }
+ let_it_be(:assignee2) { create(:user) }
+ let_it_be(:project_label1) { create(:label, project: project) }
+ let_it_be(:project_label2) { create(:label, project: project) }
+ let_it_be(:milestone) { create(:milestone, project: project) }
+ let_it_be(:new_label1) { FFaker::Lorem.word }
+ let_it_be(:new_label2) { new_label1 + 'Extra' }
+
+ let(:expected_attributes) do
+ {
+ title: 'new title',
+ description: 'new description',
+ confidential: true,
+ due_date: Date.tomorrow,
+ discussion_locked: true
+ }
+ end
+
+ let(:mutation_params) do
+ {
+ project_path: project.full_path,
+ milestone_id: milestone.to_global_id,
+ labels: [project_label1.title, project_label2.title, new_label1, new_label2],
+ assignee_ids: [assignee1.to_global_id, assignee2.to_global_id]
+ }.merge(expected_attributes)
+ end
+
+ let(:special_params) do
+ {
+ iid: non_existing_record_id,
+ created_at: 2.days.ago
+ }
+ end
+
+ let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
+ let(:mutated_issue) { subject[:issue] }
+
+ specify { expect(described_class).to require_graphql_authorizations(:create_issue) }
+
+ describe '#resolve' do
+ before do
+ stub_licensed_features(multiple_issue_assignees: false, issue_weights: false)
+ project.add_guest(assignee1)
+ project.add_guest(assignee2)
+ end
+
+ subject { mutation.resolve(mutation_params) }
+
+ context 'when the user does not have permission to create an issue' do
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'when the user can create an issue' do
+ context 'when creating an issue a developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'creates issue with correct values' do
+ expect(mutated_issue).to have_attributes(expected_attributes)
+ expect(mutated_issue.milestone_id).to eq(milestone.id)
+ expect(mutated_issue.labels.pluck(:title)).to eq([project_label1.title, project_label2.title, new_label1, new_label2])
+ expect(mutated_issue.assignees.pluck(:id)).to eq([assignee1.id])
+ end
+
+ context 'when passing in label_ids' do
+ before do
+ mutation_params.delete(:labels)
+ mutation_params.merge!(label_ids: [project_label1.to_global_id, project_label2.to_global_id])
+ end
+
+ it 'creates issue with correct values' do
+ expect(mutated_issue.labels.pluck(:title)).to eq([project_label1.title, project_label2.title])
+ end
+ end
+
+ context 'when trying to create issue with restricted params' do
+ before do
+ mutation_params.merge!(special_params)
+ end
+
+ it 'ignores the special params' do
+ expect(mutated_issue).not_to be_like_time(special_params[:created_at])
+ expect(mutated_issue.iid).not_to eq(special_params[:iid])
+ end
+ end
+ end
+
+ context 'when creating an issue as owner' do
+ let_it_be(:user) { project.owner }
+
+ before do
+ mutation_params.merge!(special_params)
+ end
+
+ it 'sets the special params' do
+ expect(mutated_issue.created_at).to be_like_time(special_params[:created_at])
+ expect(mutated_issue.iid).to eq(special_params[:iid])
+ end
+ end
+ end
+ end
+
+ describe "#ready?" do
+ context 'when passing in both labels and label_ids' do
+ before do
+ mutation_params.merge!(label_ids: [project_label1.to_global_id, project_label2.to_global_id])
+ end
+
+ it 'raises exception when mutually exclusive params are given' do
+ expect { mutation.ready?(mutation_params) }
+ .to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/)
+ end
+ end
+
+ context 'when passing only `discussion_to_resolve` param' do
+ before do
+ mutation_params.merge!(discussion_to_resolve: 'abc')
+ end
+
+ it 'raises exception when mutually exclusive params are given' do
+ expect { mutation.ready?(mutation_params) }
+ .to raise_error(Gitlab::Graphql::Errors::ArgumentError, /to resolve a discussion please also provide `merge_request_to_resolve_discussions_of` parameter/)
+ end
+ end
+
+ context 'when passing only `merge_request_to_resolve_discussions_of` param' do
+ before do
+ mutation_params.merge!(merge_request_to_resolve_discussions_of: 'abc')
+ end
+
+ it 'raises exception when mutually exclusive params are given' do
+ expect { mutation.ready?(mutation_params) }.not_to raise_error
+ end
+ end
+ end
+end
diff --git a/spec/graphql/mutations/issues/move_spec.rb b/spec/graphql/mutations/issues/move_spec.rb
new file mode 100644
index 00000000000..c8e9c556a3f
--- /dev/null
+++ b/spec/graphql/mutations/issues/move_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Issues::Move do
+ let_it_be(:issue) { create(:issue) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:target_project) { create(:project) }
+
+ subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
+
+ describe '#resolve' do
+ subject(:resolve) { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, target_project_path: target_project.full_path) }
+
+ it 'raises an error if the resource is not accessible to the user' do
+ expect { resolve }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+
+ context 'when user does not have permissions' do
+ before do
+ issue.project.add_developer(user)
+ end
+
+ it 'returns error message' do
+ expect(resolve[:issue]).to eq(nil)
+ expect(resolve[:errors].first).to eq('Cannot move issue due to insufficient permissions!')
+ end
+ end
+
+ context 'when user has sufficient permissions' do
+ before do
+ issue.project.add_developer(user)
+ target_project.add_developer(user)
+ end
+
+ it 'moves issue' do
+ expect(resolve[:issue].project).to eq(target_project)
+ end
+ end
+ end
+end
diff --git a/spec/graphql/mutations/issues/update_spec.rb b/spec/graphql/mutations/issues/update_spec.rb
index 15c15afd9b7..f9f4bdeb6fa 100644
--- a/spec/graphql/mutations/issues/update_spec.rb
+++ b/spec/graphql/mutations/issues/update_spec.rb
@@ -70,6 +70,23 @@ RSpec.describe Mutations::Issues::Update do
end
end
+ context 'when changing state' do
+ let_it_be_with_refind(:issue) { create(:issue, project: project, state: :opened) }
+
+ it 'closes issue' do
+ mutation_params[:state_event] = 'close'
+
+ expect { subject }.to change { issue.reload.state }.from('opened').to('closed')
+ end
+
+ it 'reopens issue' do
+ issue.close
+ mutation_params[:state_event] = 'reopen'
+
+ expect { subject }.to change { issue.reload.state }.from('closed').to('opened')
+ end
+ end
+
context 'when changing labels' do
let_it_be(:label_1) { create(:label, project: project) }
let_it_be(:label_2) { create(:label, project: project) }
diff --git a/spec/graphql/mutations/todos/mark_done_spec.rb b/spec/graphql/mutations/todos/mark_done_spec.rb
index 51ad3e1a5d7..b5f2ff5d044 100644
--- a/spec/graphql/mutations/todos/mark_done_spec.rb
+++ b/spec/graphql/mutations/todos/mark_done_spec.rb
@@ -52,7 +52,8 @@ RSpec.describe Mutations::Todos::MarkDone do
end
it 'ignores invalid GIDs' do
- expect { mutation.resolve(id: 'invalid_gid') }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
+ expect { mutation.resolve(id: author.to_global_id.to_s) }
+ .to raise_error(::GraphQL::CoercionError)
expect(todo1.reload.state).to eq('pending')
expect(todo2.reload.state).to eq('done')
diff --git a/spec/graphql/mutations/todos/restore_many_spec.rb b/spec/graphql/mutations/todos/restore_many_spec.rb
index b3b3e057745..59995e33f2d 100644
--- a/spec/graphql/mutations/todos/restore_many_spec.rb
+++ b/spec/graphql/mutations/todos/restore_many_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Mutations::Todos::RestoreMany do
+ include GraphqlHelpers
+
let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) }
@@ -44,8 +46,9 @@ RSpec.describe Mutations::Todos::RestoreMany do
expect_states_were_not_changed
end
- it 'ignores invalid GIDs' do
- expect { mutation.resolve(ids: ['invalid_gid']) }.to raise_error(URI::BadURIError)
+ it 'raises an error with invalid or non-Todo GIDs' do
+ expect { mutation.resolve(ids: [author.to_global_id.to_s]) }
+ .to raise_error(GraphQL::CoercionError)
expect_states_were_not_changed
end
@@ -78,38 +81,12 @@ RSpec.describe Mutations::Todos::RestoreMany do
it 'fails if too many todos are requested for update' do
expect { restore_mutation([todo1] * 51) }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
end
-
- it 'does not update todos from another app' do
- todo4 = create(:todo)
- todo4_gid = ::URI::GID.parse("gid://otherapp/Todo/#{todo4.id}")
-
- result = mutation.resolve(ids: [todo4_gid.to_s])
-
- expect(result[:updated_ids]).to be_empty
-
- expect_states_were_not_changed
- end
-
- it 'does not update todos from another model' do
- todo4 = create(:todo)
- todo4_gid = ::URI::GID.parse("gid://#{GlobalID.app}/Project/#{todo4.id}")
-
- result = mutation.resolve(ids: [todo4_gid.to_s])
-
- expect(result[:updated_ids]).to be_empty
-
- expect_states_were_not_changed
- end
end
def restore_mutation(todos)
mutation.resolve(ids: todos.map { |todo| global_id_of(todo) } )
end
- def global_id_of(todo)
- todo.to_global_id.to_s
- end
-
def expect_states_were_not_changed
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('pending')
diff --git a/spec/graphql/mutations/todos/restore_spec.rb b/spec/graphql/mutations/todos/restore_spec.rb
index 9043d7a44a8..22fb1bba7a8 100644
--- a/spec/graphql/mutations/todos/restore_spec.rb
+++ b/spec/graphql/mutations/todos/restore_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Mutations::Todos::Restore do
+ include GraphqlHelpers
+
let_it_be(:current_user) { create(:user) }
let_it_be(:author) { create(:user) }
let_it_be(:other_user) { create(:user) }
@@ -49,8 +51,9 @@ RSpec.describe Mutations::Todos::Restore do
expect(other_user_todo.reload.state).to eq('done')
end
- it 'ignores invalid GIDs' do
- expect { mutation.resolve(id: 'invalid_gid') }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
+ it 'raises error for invalid GID' do
+ expect { mutation.resolve(id: author.to_global_id.to_s) }
+ .to raise_error(::GraphQL::CoercionError)
expect(todo1.reload.state).to eq('done')
expect(todo2.reload.state).to eq('pending')
@@ -61,8 +64,4 @@ RSpec.describe Mutations::Todos::Restore do
def restore_mutation(todo)
mutation.resolve(id: global_id_of(todo))
end
-
- def global_id_of(todo)
- todo.to_global_id.to_s
- end
end
diff --git a/spec/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver_spec.rb b/spec/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver_spec.rb
index 76854be2daa..c5637d43382 100644
--- a/spec/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver_spec.rb
+++ b/spec/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver_spec.rb
@@ -5,9 +5,11 @@ require 'spec_helper'
RSpec.describe Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsResolver do
include GraphqlHelpers
+ let_it_be(:admin_user) { create(:user, :admin) }
+ let(:current_user) { admin_user }
+
describe '#resolve' do
let_it_be(:user) { create(:user) }
- let_it_be(:admin_user) { create(:user, :admin) }
let_it_be(:project_measurement_new) { create(:instance_statistics_measurement, :project_count, recorded_at: 2.days.ago) }
let_it_be(:project_measurement_old) { create(:instance_statistics_measurement, :project_count, recorded_at: 10.days.ago) }
@@ -39,6 +41,37 @@ RSpec.describe Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsReso
end
end
end
+
+ context 'when requesting pipeline counts by pipeline status' do
+ let_it_be(:pipelines_succeeded_measurement) { create(:instance_statistics_measurement, :pipelines_succeeded_count, recorded_at: 2.days.ago) }
+ let_it_be(:pipelines_skipped_measurement) { create(:instance_statistics_measurement, :pipelines_skipped_count, recorded_at: 2.days.ago) }
+
+ subject { resolve_measurements({ identifier: identifier }, { current_user: current_user }) }
+
+ context 'filter for pipelines_succeeded' do
+ let(:identifier) { 'pipelines_succeeded' }
+
+ it { is_expected.to eq([pipelines_succeeded_measurement]) }
+ end
+
+ context 'filter for pipelines_skipped' do
+ let(:identifier) { 'pipelines_skipped' }
+
+ it { is_expected.to eq([pipelines_skipped_measurement]) }
+ end
+
+ context 'filter for pipelines_failed' do
+ let(:identifier) { 'pipelines_failed' }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'filter for pipelines_canceled' do
+ let(:identifier) { 'pipelines_canceled' }
+
+ it { is_expected.to be_empty }
+ end
+ end
end
def resolve_measurements(args = {}, context = {})
diff --git a/spec/graphql/resolvers/board_lists_resolver_spec.rb b/spec/graphql/resolvers/board_lists_resolver_spec.rb
index fb6a5ccb781..c1d8041a1e0 100644
--- a/spec/graphql/resolvers/board_lists_resolver_spec.rb
+++ b/spec/graphql/resolvers/board_lists_resolver_spec.rb
@@ -101,6 +101,12 @@ RSpec.describe Resolvers::BoardListsResolver do
end
def resolve_board_lists(args: {}, current_user: user)
- resolve(described_class, obj: board, args: args, ctx: { current_user: current_user })
+ context = GraphQL::Query::Context.new(
+ query: OpenStruct.new(schema: nil),
+ values: { current_user: current_user },
+ object: nil
+ )
+
+ resolve(described_class, obj: board, args: args, ctx: context )
end
end
diff --git a/spec/graphql/resolvers/board_resolver_spec.rb b/spec/graphql/resolvers/board_resolver_spec.rb
new file mode 100644
index 00000000000..c70c696005f
--- /dev/null
+++ b/spec/graphql/resolvers/board_resolver_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::BoardResolver do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+
+ let(:dummy_gid) { 'gid://gitlab/Board/1' }
+
+ shared_examples_for 'group and project boards resolver' do
+ it 'does not create a default board' do
+ expect(resolve_board(id: dummy_gid)).to eq nil
+ end
+
+ it 'calls Boards::ListService' do
+ expect_next_instance_of(Boards::ListService) do |service|
+ expect(service).to receive(:execute).and_return([])
+ end
+
+ resolve_board(id: dummy_gid)
+ end
+
+ it 'requires an ID' do
+ expect do
+ resolve(described_class, obj: board_parent, args: {}, ctx: { current_user: user })
+ end.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
+ end
+
+ context 'when querying for a single board' do
+ let(:board1) { create(:board, name: 'One', resource_parent: board_parent) }
+
+ it 'returns specified board' do
+ expect(resolve_board(id: global_id_of(board1))).to eq board1
+ end
+
+ it 'returns nil if board not found' do
+ outside_parent = create(board_parent.class.underscore.to_sym) # rubocop:disable Rails/SaveBang
+ outside_board = create(:board, name: 'outside board', resource_parent: outside_parent)
+
+ expect(resolve_board(id: global_id_of(outside_board))).to eq nil
+ end
+ end
+ end
+
+ describe '#resolve' do
+ context 'when there is no parent' do
+ let(:board_parent) { nil }
+
+ it 'returns nil if parent is nil' do
+ expect(resolve_board(id: dummy_gid)).to eq(nil)
+ end
+ end
+
+ context 'when project boards' do
+ let(:board_parent) { create(:project, :public, creator_id: user.id, namespace: user.namespace ) }
+
+ it_behaves_like 'group and project boards resolver'
+ end
+
+ context 'when group boards' do
+ let(:board_parent) { create(:group) }
+
+ it_behaves_like 'group and project boards resolver'
+ end
+ end
+
+ def resolve_board(id:)
+ resolve(described_class, obj: board_parent, args: { id: id }, ctx: { current_user: user })
+ end
+end
diff --git a/spec/graphql/resolvers/ci/runner_platforms_resolver_spec.rb b/spec/graphql/resolvers/ci/runner_platforms_resolver_spec.rb
new file mode 100644
index 00000000000..1eb6f363d5b
--- /dev/null
+++ b/spec/graphql/resolvers/ci/runner_platforms_resolver_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::Ci::RunnerPlatformsResolver do
+ include GraphqlHelpers
+
+ describe '#resolve' do
+ subject(:resolve_subject) { resolve(described_class) }
+
+ it 'returns all possible runner platforms' do
+ expect(resolve_subject).to include(
+ hash_including(name: :linux), hash_including(name: :osx),
+ hash_including(name: :windows), hash_including(name: :docker),
+ hash_including(name: :kubernetes)
+ )
+ end
+ end
+end
diff --git a/spec/graphql/resolvers/concerns/looks_ahead_spec.rb b/spec/graphql/resolvers/concerns/looks_ahead_spec.rb
index f13823085b8..ebea9e5522b 100644
--- a/spec/graphql/resolvers/concerns/looks_ahead_spec.rb
+++ b/spec/graphql/resolvers/concerns/looks_ahead_spec.rb
@@ -117,20 +117,6 @@ RSpec.describe LooksAhead do
query.result
end
- context 'the feature flag is off' do
- before do
- stub_feature_flags(described_class::FEATURE_FLAG => false)
- end
-
- it_behaves_like 'a working query on the test schema'
-
- it 'does not preload labels on issues' do
- expect(the_user.issues).not_to receive(:preload).with(:labels)
-
- query.result
- end
- end
-
it 'issues fewer queries than the naive approach' do
the_user.reload # ensure no attributes are loaded before we begin
naive = <<-GQL
diff --git a/spec/graphql/resolvers/group_milestones_resolver_spec.rb b/spec/graphql/resolvers/group_milestones_resolver_spec.rb
index 05d0ec38192..d8ff8e9c1f2 100644
--- a/spec/graphql/resolvers/group_milestones_resolver_spec.rb
+++ b/spec/graphql/resolvers/group_milestones_resolver_spec.rb
@@ -15,6 +15,12 @@ RSpec.describe Resolvers::GroupMilestonesResolver do
let_it_be(:now) { Time.now }
let_it_be(:group) { create(:group, :private) }
+ def args(**arguments)
+ satisfy("contain only #{arguments.inspect}") do |passed|
+ expect(passed.compact).to match(arguments)
+ end
+ end
+
before_all do
group.add_developer(current_user)
end
@@ -30,7 +36,7 @@ RSpec.describe Resolvers::GroupMilestonesResolver do
context 'without parameters' do
it 'calls MilestonesFinder to retrieve all milestones' do
expect(MilestonesFinder).to receive(:new)
- .with(ids: nil, group_ids: group.id, state: 'all', start_date: nil, end_date: nil)
+ .with(args(group_ids: group.id, state: 'all'))
.and_call_original
resolve_group_milestones
@@ -43,11 +49,22 @@ RSpec.describe Resolvers::GroupMilestonesResolver do
end_date = start_date + 1.hour
expect(MilestonesFinder).to receive(:new)
- .with(ids: nil, group_ids: group.id, state: 'closed', start_date: start_date, end_date: end_date)
+ .with(args(group_ids: group.id, state: 'closed', start_date: start_date, end_date: end_date))
.and_call_original
resolve_group_milestones(start_date: start_date, end_date: end_date, state: 'closed')
end
+
+ it 'understands the timeframe argument' do
+ start_date = now
+ end_date = start_date + 1.hour
+
+ expect(MilestonesFinder).to receive(:new)
+ .with(args(group_ids: group.id, state: 'closed', start_date: start_date, end_date: end_date))
+ .and_call_original
+
+ resolve_group_milestones(timeframe: { start: start_date, end: end_date }, state: 'closed')
+ end
end
context 'by ids' do
@@ -55,7 +72,7 @@ RSpec.describe Resolvers::GroupMilestonesResolver do
milestone = create(:milestone, group: group)
expect(MilestonesFinder).to receive(:new)
- .with(ids: [milestone.id.to_s], group_ids: group.id, state: 'all', start_date: nil, end_date: nil)
+ .with(args(ids: [milestone.id.to_s], group_ids: group.id, state: 'all'))
.and_call_original
resolve_group_milestones(ids: [milestone.to_global_id])
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
index db5d009f0e7..3a6507f906c 100644
--- a/spec/graphql/resolvers/issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -46,6 +46,13 @@ RSpec.describe Resolvers::IssuesResolver do
expect(resolve_issues(assignee_username: assignee.username)).to contain_exactly(issue2)
end
+ it 'filters by two assignees' do
+ assignee2 = create(:user)
+ issue2.update!(assignees: [assignee, assignee2])
+
+ expect(resolve_issues(assignee_id: [assignee.id, assignee2.id])).to contain_exactly(issue2)
+ end
+
it 'filters by assignee_id' do
expect(resolve_issues(assignee_id: assignee.id)).to contain_exactly(issue2)
end
@@ -58,6 +65,10 @@ RSpec.describe Resolvers::IssuesResolver do
expect(resolve_issues(assignee_id: IssuableFinder::Params::FILTER_NONE)).to contain_exactly(issue1)
end
+ it 'filters by author' do
+ expect(resolve_issues(author_username: issue1.author.username)).to contain_exactly(issue1, issue2)
+ end
+
it 'filters by labels' do
expect(resolve_issues(label_name: [label1.title])).to contain_exactly(issue1, issue2)
expect(resolve_issues(label_name: [label1.title, label2.title])).to contain_exactly(issue2)
@@ -219,6 +230,21 @@ RSpec.describe Resolvers::IssuesResolver do
expect(resolve_issues(sort: :milestone_due_desc).items).to eq([milestone_issue3, milestone_issue2, milestone_issue1])
end
end
+
+ context 'when sorting by severity' do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:issue_high_severity) { create_issue_with_severity(project, severity: :high) }
+ let_it_be(:issue_low_severity) { create_issue_with_severity(project, severity: :low) }
+ let_it_be(:issue_no_severity) { create(:incident, project: project) }
+
+ it 'sorts issues ascending' do
+ expect(resolve_issues(sort: :severity_asc)).to eq([issue_no_severity, issue_low_severity, issue_high_severity])
+ end
+
+ it 'sorts issues descending' do
+ expect(resolve_issues(sort: :severity_desc)).to eq([issue_high_severity, issue_low_severity, issue_no_severity])
+ end
+ end
end
it 'returns issues user can see' do
@@ -304,6 +330,13 @@ RSpec.describe Resolvers::IssuesResolver do
expect(field.to_graphql.complexity.call({}, { labelName: 'foo' }, 1)).to eq 8
end
+ def create_issue_with_severity(project, severity:)
+ issue = create(:incident, project: project)
+ create(:issuable_severity, issue: issue, severity: severity)
+
+ issue
+ end
+
def resolve_issues(args = {}, context = { current_user: current_user })
resolve(described_class, obj: project, args: args, ctx: context)
end
diff --git a/spec/graphql/resolvers/project_milestones_resolver_spec.rb b/spec/graphql/resolvers/project_milestones_resolver_spec.rb
index e0b250cfe7c..b641a54393e 100644
--- a/spec/graphql/resolvers/project_milestones_resolver_spec.rb
+++ b/spec/graphql/resolvers/project_milestones_resolver_spec.rb
@@ -13,13 +13,19 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do
project.add_developer(current_user)
end
+ def args(**arguments)
+ satisfy("contain only #{arguments.inspect}") do |passed|
+ expect(passed.compact).to match(arguments)
+ end
+ end
+
def resolve_project_milestones(args = {}, context = { current_user: current_user })
resolve(described_class, obj: project, args: args, ctx: context)
end
it 'calls MilestonesFinder to retrieve all milestones' do
expect(MilestonesFinder).to receive(:new)
- .with(ids: nil, project_ids: project.id, state: 'all', start_date: nil, end_date: nil)
+ .with(args(project_ids: project.id, state: 'all'))
.and_call_original
resolve_project_milestones
@@ -36,7 +42,7 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do
it 'calls MilestonesFinder with correct parameters' do
expect(MilestonesFinder).to receive(:new)
- .with(ids: nil, project_ids: project.id, group_ids: contain_exactly(group, parent_group), state: 'all', start_date: nil, end_date: nil)
+ .with(args(project_ids: project.id, group_ids: contain_exactly(group, parent_group), state: 'all'))
.and_call_original
resolve_project_milestones(include_ancestors: true)
@@ -48,7 +54,7 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do
milestone = create(:milestone, project: project)
expect(MilestonesFinder).to receive(:new)
- .with(ids: [milestone.id.to_s], project_ids: project.id, state: 'all', start_date: nil, end_date: nil)
+ .with(args(ids: [milestone.id.to_s], project_ids: project.id, state: 'all'))
.and_call_original
resolve_project_milestones(ids: [milestone.to_global_id])
@@ -58,7 +64,7 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do
context 'by state' do
it 'calls MilestonesFinder with correct parameters' do
expect(MilestonesFinder).to receive(:new)
- .with(ids: nil, project_ids: project.id, state: 'closed', start_date: nil, end_date: nil)
+ .with(args(project_ids: project.id, state: 'closed'))
.and_call_original
resolve_project_milestones(state: 'closed')
@@ -72,7 +78,7 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do
end_date = Time.now + 5.days
expect(MilestonesFinder).to receive(:new)
- .with(ids: nil, project_ids: project.id, state: 'all', start_date: start_date, end_date: end_date)
+ .with(args(project_ids: project.id, state: 'all', start_date: start_date, end_date: end_date))
.and_call_original
resolve_project_milestones(start_date: start_date, end_date: end_date)
@@ -102,6 +108,51 @@ RSpec.describe Resolvers::ProjectMilestonesResolver do
end.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /Both startDate and endDate/)
end
end
+
+ context 'when passing a timeframe' do
+ it 'calls MilestonesFinder with correct parameters' do
+ start_date = Time.now
+ end_date = Time.now + 5.days
+
+ expect(MilestonesFinder).to receive(:new)
+ .with(args(project_ids: project.id, state: 'all', start_date: start_date, end_date: end_date))
+ .and_call_original
+
+ resolve_project_milestones(timeframe: { start: start_date, end: end_date })
+ end
+ end
+ end
+
+ context 'when title is present' do
+ it 'calls MilestonesFinder with correct parameters' do
+ expect(MilestonesFinder).to receive(:new)
+ .with(args(title: '13.5', state: 'all', project_ids: project.id))
+ .and_call_original
+
+ resolve_project_milestones(title: '13.5')
+ end
+ end
+
+ context 'when search_title is present' do
+ it 'calls MilestonesFinder with correct parameters' do
+ expect(MilestonesFinder).to receive(:new)
+ .with(args(search_title: '13', state: 'all', project_ids: project.id))
+ .and_call_original
+
+ resolve_project_milestones(search_title: '13')
+ end
+ end
+
+ context 'when containing date is present' do
+ it 'calls MilestonesFinder with correct parameters' do
+ t = Time.now
+
+ expect(MilestonesFinder).to receive(:new)
+ .with(args(containing_date: t, state: 'all', project_ids: project.id))
+ .and_call_original
+
+ resolve_project_milestones(containing_date: t)
+ end
end
context 'when user cannot read milestones' do
diff --git a/spec/graphql/resolvers/projects_resolver_spec.rb b/spec/graphql/resolvers/projects_resolver_spec.rb
index d22ffeed740..83a26062957 100644
--- a/spec/graphql/resolvers/projects_resolver_spec.rb
+++ b/spec/graphql/resolvers/projects_resolver_spec.rb
@@ -8,10 +8,14 @@ RSpec.describe Resolvers::ProjectsResolver do
describe '#resolve' do
subject { resolve(described_class, obj: nil, args: filters, ctx: { current_user: current_user }) }
+ let_it_be(:group) { create(:group, name: 'public-group') }
+ let_it_be(:private_group) { create(:group, name: 'private-group') }
let_it_be(:project) { create(:project, :public) }
let_it_be(:other_project) { create(:project, :public) }
+ let_it_be(:group_project) { create(:project, :public, group: group) }
let_it_be(:private_project) { create(:project, :private) }
let_it_be(:other_private_project) { create(:project, :private) }
+ let_it_be(:private_group_project) { create(:project, :private, group: private_group) }
let_it_be(:user) { create(:user) }
@@ -20,6 +24,11 @@ RSpec.describe Resolvers::ProjectsResolver do
before_all do
project.add_developer(user)
private_project.add_developer(user)
+ private_group.add_developer(user)
+ end
+
+ before do
+ stub_feature_flags(project_finder_similarity_sort: false)
end
context 'when user is not logged in' do
@@ -27,7 +36,7 @@ RSpec.describe Resolvers::ProjectsResolver do
context 'when no filters are applied' do
it 'returns all public projects' do
- is_expected.to contain_exactly(project, other_project)
+ is_expected.to contain_exactly(project, other_project, group_project)
end
context 'when search filter is provided' do
@@ -45,6 +54,22 @@ RSpec.describe Resolvers::ProjectsResolver do
is_expected.to be_empty
end
end
+
+ context 'when searchNamespaces filter is provided' do
+ let(:filters) { { search: 'group', search_namespaces: true } }
+
+ it 'returns projects in a matching namespace' do
+ is_expected.to contain_exactly(group_project)
+ end
+ end
+
+ context 'when searchNamespaces filter false' do
+ let(:filters) { { search: 'group', search_namespaces: false } }
+
+ it 'returns ignores namespace matches' do
+ is_expected.to be_empty
+ end
+ end
end
end
@@ -53,7 +78,7 @@ RSpec.describe Resolvers::ProjectsResolver do
context 'when no filters are applied' do
it 'returns all visible projects for the user' do
- is_expected.to contain_exactly(project, other_project, private_project)
+ is_expected.to contain_exactly(project, other_project, group_project, private_project, private_group_project)
end
context 'when search filter is provided' do
@@ -68,7 +93,23 @@ RSpec.describe Resolvers::ProjectsResolver do
let(:filters) { { membership: true } }
it 'returns projects that user is member of' do
- is_expected.to contain_exactly(project, private_project)
+ is_expected.to contain_exactly(project, private_project, private_group_project)
+ end
+ end
+
+ context 'when searchNamespaces filter is provided' do
+ let(:filters) { { search: 'group', search_namespaces: true } }
+
+ it 'returns projects from matching group' do
+ is_expected.to contain_exactly(group_project, private_group_project)
+ end
+ end
+
+ context 'when searchNamespaces filter false' do
+ let(:filters) { { search: 'group', search_namespaces: false } }
+
+ it 'returns ignores namespace matches' do
+ is_expected.to be_empty
end
end
@@ -79,6 +120,24 @@ RSpec.describe Resolvers::ProjectsResolver do
is_expected.to contain_exactly(project)
end
end
+
+ context 'when sort is similarity' do
+ let_it_be(:named_project1) { create(:project, :public, name: 'projAB', path: 'projAB') }
+ let_it_be(:named_project2) { create(:project, :public, name: 'projABC', path: 'projABC') }
+ let_it_be(:named_project3) { create(:project, :public, name: 'projA', path: 'projA') }
+
+ let(:filters) { { search: 'projA', sort: 'similarity' } }
+
+ it 'returns projects in order of similarity to search' do
+ stub_feature_flags(project_finder_similarity_sort: true)
+
+ is_expected.to eq([named_project3, named_project1, named_project2])
+ end
+
+ it 'returns projects not in order of similarity to search if flag is off' do
+ is_expected.not_to eq([named_project3, named_project1, named_project2])
+ end
+ end
end
end
end
diff --git a/spec/graphql/resolvers/snippets/blobs_resolver_spec.rb b/spec/graphql/resolvers/snippets/blobs_resolver_spec.rb
new file mode 100644
index 00000000000..fdbd87c32be
--- /dev/null
+++ b/spec/graphql/resolvers/snippets/blobs_resolver_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::Snippets::BlobsResolver do
+ include GraphqlHelpers
+
+ describe '#resolve' do
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:snippet) { create(:personal_snippet, :private, :repository, author: current_user) }
+
+ context 'when user is not authorized' do
+ let(:other_user) { create(:user) }
+
+ it 'raises an error' do
+ expect do
+ resolve_blobs(snippet, user: other_user)
+ end.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'when using no filter' do
+ it 'returns all snippet blobs' do
+ expect(resolve_blobs(snippet).map(&:path)).to contain_exactly(*snippet.list_files)
+ end
+ end
+
+ context 'when using filters' do
+ context 'when paths is a single string' do
+ it 'returns an array of files' do
+ path = 'CHANGELOG'
+
+ expect(resolve_blobs(snippet, args: { paths: path }).first.path).to eq(path)
+ end
+ end
+
+ context 'when paths is an array of string' do
+ it 'returns an array of files' do
+ paths = ['CHANGELOG', 'README.md']
+
+ expect(resolve_blobs(snippet, args: { paths: paths }).map(&:path)).to contain_exactly(*paths)
+ end
+ end
+ end
+ end
+
+ def resolve_blobs(snippet, user: current_user, args: {})
+ resolve(described_class, args: args, ctx: { current_user: user }, obj: snippet)
+ end
+end
diff --git a/spec/graphql/resolvers/terraform/states_resolver_spec.rb b/spec/graphql/resolvers/terraform/states_resolver_spec.rb
new file mode 100644
index 00000000000..64b515528cd
--- /dev/null
+++ b/spec/graphql/resolvers/terraform/states_resolver_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::Terraform::StatesResolver do
+ include GraphqlHelpers
+
+ it { expect(described_class.type).to eq(Types::Terraform::StateType) }
+ it { expect(described_class.null).to be_truthy }
+
+ describe '#resolve' do
+ let_it_be(:project) { create(:project) }
+
+ let_it_be(:production_state) { create(:terraform_state, project: project) }
+ let_it_be(:staging_state) { create(:terraform_state, project: project) }
+ let_it_be(:other_state) { create(:terraform_state) }
+
+ let(:ctx) { Hash(current_user: user) }
+ let(:user) { create(:user, developer_projects: [project]) }
+
+ subject { resolve(described_class, obj: project, ctx: ctx) }
+
+ it 'returns states associated with the agent' do
+ expect(subject).to contain_exactly(production_state, staging_state)
+ end
+
+ context 'user does not have permission' do
+ let(:user) { create(:user) }
+
+ it { is_expected.to be_empty }
+ end
+ end
+end
diff --git a/spec/graphql/types/alert_management/alert_type_spec.rb b/spec/graphql/types/alert_management/alert_type_spec.rb
index e14c189d4b6..82b48a20708 100644
--- a/spec/graphql/types/alert_management/alert_type_spec.rb
+++ b/spec/graphql/types/alert_management/alert_type_spec.rb
@@ -32,6 +32,7 @@ RSpec.describe GitlabSchema.types['AlertManagementAlert'] do
todos
details_url
prometheus_alert
+ environment
]
expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/alert_management/status_enum_spec.rb b/spec/graphql/types/alert_management/status_enum_spec.rb
index ac7a8eb53f6..1252efabe4c 100644
--- a/spec/graphql/types/alert_management/status_enum_spec.rb
+++ b/spec/graphql/types/alert_management/status_enum_spec.rb
@@ -9,10 +9,10 @@ RSpec.describe GitlabSchema.types['AlertManagementStatus'] do
using RSpec::Parameterized::TableSyntax
where(:status_name, :status_value) do
- 'TRIGGERED' | 0
- 'ACKNOWLEDGED' | 1
- 'RESOLVED' | 2
- 'IGNORED' | 3
+ 'TRIGGERED' | :triggered
+ 'ACKNOWLEDGED' | :acknowledged
+ 'RESOLVED' | :resolved
+ 'IGNORED' | :ignored
end
with_them do
diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb
index bcfbd7f2480..d61ea6aa6e9 100644
--- a/spec/graphql/types/base_field_spec.rb
+++ b/spec/graphql/types/base_field_spec.rb
@@ -126,6 +126,10 @@ RSpec.describe Types::BaseField do
let(:field) { described_class.new(name: 'test', type: GraphQL::STRING_TYPE, feature_flag: flag, null: false) }
let(:context) { {} }
+ before do
+ skip_feature_flags_yaml_validation
+ end
+
it 'returns false if the feature is not enabled' do
stub_feature_flags(flag => false)
diff --git a/spec/graphql/types/ci/detailed_status_type_spec.rb b/spec/graphql/types/ci/detailed_status_type_spec.rb
index 67199848df0..ddb3a1450df 100644
--- a/spec/graphql/types/ci/detailed_status_type_spec.rb
+++ b/spec/graphql/types/ci/detailed_status_type_spec.rb
@@ -8,6 +8,6 @@ RSpec.describe Types::Ci::DetailedStatusType do
it "has all fields" do
expect(described_class).to have_graphql_fields(:group, :icon, :favicon,
:details_path, :has_details,
- :label, :text, :tooltip)
+ :label, :text, :tooltip, :action)
end
end
diff --git a/spec/graphql/types/ci/group_type_spec.rb b/spec/graphql/types/ci/group_type_spec.rb
index 8d547b19af3..d7ce5602612 100644
--- a/spec/graphql/types/ci/group_type_spec.rb
+++ b/spec/graphql/types/ci/group_type_spec.rb
@@ -10,6 +10,7 @@ RSpec.describe Types::Ci::GroupType do
name
size
jobs
+ detailedStatus
]
expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/ci/job_type_spec.rb b/spec/graphql/types/ci/job_type_spec.rb
index faf3a95cf25..3a54ed2efed 100644
--- a/spec/graphql/types/ci/job_type_spec.rb
+++ b/spec/graphql/types/ci/job_type_spec.rb
@@ -9,6 +9,8 @@ RSpec.describe Types::Ci::JobType do
expected_fields = %i[
name
needs
+ detailedStatus
+ scheduledAt
]
expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/ci/runner_architecture_type_spec.rb b/spec/graphql/types/ci/runner_architecture_type_spec.rb
new file mode 100644
index 00000000000..527adef8cf9
--- /dev/null
+++ b/spec/graphql/types/ci/runner_architecture_type_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::Ci::RunnerArchitectureType do
+ specify { expect(described_class.graphql_name).to eq('RunnerArchitecture') }
+
+ it 'exposes the expected fields' do
+ expected_fields = %i[
+ name
+ download_location
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/ci/runner_platform_type_spec.rb b/spec/graphql/types/ci/runner_platform_type_spec.rb
new file mode 100644
index 00000000000..66b83f607d0
--- /dev/null
+++ b/spec/graphql/types/ci/runner_platform_type_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::Ci::RunnerPlatformType do
+ specify { expect(described_class.graphql_name).to eq('RunnerPlatform') }
+
+ it 'exposes the expected fields' do
+ expected_fields = %i[
+ name
+ human_readable_name
+ architectures
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/ci/stage_type_spec.rb b/spec/graphql/types/ci/stage_type_spec.rb
index 0c352ed27aa..9a8d4fa96a3 100644
--- a/spec/graphql/types/ci/stage_type_spec.rb
+++ b/spec/graphql/types/ci/stage_type_spec.rb
@@ -9,6 +9,7 @@ RSpec.describe Types::Ci::StageType do
expected_fields = %i[
name
groups
+ detailedStatus
]
expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/ci/status_action_type_spec.rb b/spec/graphql/types/ci/status_action_type_spec.rb
new file mode 100644
index 00000000000..8a99068e44f
--- /dev/null
+++ b/spec/graphql/types/ci/status_action_type_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::Ci::StatusActionType do
+ specify { expect(described_class.graphql_name).to eq('StatusAction') }
+
+ it 'exposes the expected fields' do
+ expected_fields = %i[
+ buttonTitle
+ icon
+ path
+ method
+ title
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+end
diff --git a/spec/graphql/types/design_management/design_collection_copy_state_enum_spec.rb b/spec/graphql/types/design_management/design_collection_copy_state_enum_spec.rb
new file mode 100644
index 00000000000..f536d91aeda
--- /dev/null
+++ b/spec/graphql/types/design_management/design_collection_copy_state_enum_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['DesignCollectionCopyState'] do
+ it { expect(described_class.graphql_name).to eq('DesignCollectionCopyState') }
+
+ it 'exposes the correct event states' do
+ expect(described_class.values.keys).to match_array(%w(READY IN_PROGRESS ERROR))
+ end
+end
diff --git a/spec/graphql/types/design_management/design_collection_type_spec.rb b/spec/graphql/types/design_management/design_collection_type_spec.rb
index 6b1d3a87c2d..83208705249 100644
--- a/spec/graphql/types/design_management/design_collection_type_spec.rb
+++ b/spec/graphql/types/design_management/design_collection_type_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe GitlabSchema.types['DesignCollection'] do
it { expect(described_class).to require_graphql_authorizations(:read_design) }
it 'has the expected fields' do
- expected_fields = %i[project issue designs versions version designAtVersion design]
+ expected_fields = %i[project issue designs versions version designAtVersion design copyState]
expect(described_class).to have_graphql_fields(*expected_fields)
end
diff --git a/spec/graphql/types/environment_type_spec.rb b/spec/graphql/types/environment_type_spec.rb
index abeeeba543f..2220f847e4e 100644
--- a/spec/graphql/types/environment_type_spec.rb
+++ b/spec/graphql/types/environment_type_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['Environment'] do
it 'has the expected fields' do
expected_fields = %w[
- name id state metrics_dashboard latest_opened_most_severe_alert
+ name id state metrics_dashboard latest_opened_most_severe_alert path
]
expect(described_class).to have_graphql_fields(*expected_fields)
@@ -28,6 +28,7 @@ RSpec.describe GitlabSchema.types['Environment'] do
project(fullPath: "#{project.full_path}") {
environment(name: "#{environment.name}") {
name
+ path
state
}
}
@@ -43,6 +44,18 @@ RSpec.describe GitlabSchema.types['Environment'] do
expect(subject['data']['project']['environment']['name']).to eq(environment.name)
end
+ it 'returns the path when the feature is enabled' do
+ expect(subject['data']['project']['environment']['path']).to eq(
+ Gitlab::Routing.url_helpers.project_environment_path(project, environment)
+ )
+ end
+
+ it 'does not return the path when the feature is disabled' do
+ stub_feature_flags(expose_environment_path_in_alert_details: false)
+
+ expect(subject['data']['project']['environment']['path']).to be_nil
+ end
+
context 'when query alert data for the environment' do
let_it_be(:query) do
%(
diff --git a/spec/graphql/types/global_id_type_spec.rb b/spec/graphql/types/global_id_type_spec.rb
index 2a7b26f66b0..7589b0e285e 100644
--- a/spec/graphql/types/global_id_type_spec.rb
+++ b/spec/graphql/types/global_id_type_spec.rb
@@ -99,8 +99,6 @@ RSpec.describe Types::GlobalIDType do
end
describe 'compatibility' do
- # Simplified schema to test compatibility
-
def query(doc, vars)
GraphQL::Query.new(schema, document: doc, context: {}, variables: vars)
end
@@ -112,6 +110,7 @@ RSpec.describe Types::GlobalIDType do
all_types = [::GraphQL::ID_TYPE, ::Types::GlobalIDType, ::Types::GlobalIDType[::Project]]
shared_examples 'a working query' do
+ # Simplified schema to test compatibility
let!(:schema) do
# capture values so they can be closed over
arg_type = argument_type
@@ -135,10 +134,21 @@ RSpec.describe Types::GlobalIDType do
argument :id, arg_type, required: true
end
+ # This is needed so that all types are always registered as input types
+ field :echo, String, null: true do
+ argument :id, ::GraphQL::ID_TYPE, required: false
+ argument :gid, ::Types::GlobalIDType, required: false
+ argument :pid, ::Types::GlobalIDType[::Project], required: false
+ end
+
def project_by_id(id:)
gid = ::Types::GlobalIDType[::Project].coerce_isolated_input(id)
gid.model_class.find(gid.model_id)
end
+
+ def echo(id: nil, gid: nil, pid: nil)
+ "id: #{id}, gid: #{gid}, pid: #{pid}"
+ end
end)
end
end
@@ -152,7 +162,7 @@ RSpec.describe Types::GlobalIDType do
end
end
- context 'when the argument is declared as ID' do
+ context 'when the client declares the argument as ID the actual argument can be any type' do
let(:document) do
<<-GRAPHQL
query($projectId: ID!){
@@ -163,16 +173,16 @@ RSpec.describe Types::GlobalIDType do
GRAPHQL
end
- let(:argument_type) { ::GraphQL::ID_TYPE }
-
- where(:result_type) { all_types }
+ where(:result_type, :argument_type) do
+ all_types.flat_map { |arg_type| all_types.zip([arg_type].cycle) }
+ end
with_them do
it_behaves_like 'a working query'
end
end
- context 'when the argument is declared as GlobalID' do
+ context 'when the client passes the argument as GlobalID' do
let(:document) do
<<-GRAPHQL
query($projectId: GlobalID!) {
@@ -192,7 +202,7 @@ RSpec.describe Types::GlobalIDType do
end
end
- context 'when the argument is declared as ProjectID' do
+ context 'when the client passes the argument as ProjectID' do
let(:document) do
<<-GRAPHQL
query($projectId: ProjectID!) {
diff --git a/spec/graphql/types/group_type_spec.rb b/spec/graphql/types/group_type_spec.rb
index bf7ddebaf5b..7d14ef87551 100644
--- a/spec/graphql/types/group_type_spec.rb
+++ b/spec/graphql/types/group_type_spec.rb
@@ -17,6 +17,7 @@ RSpec.describe GitlabSchema.types['Group'] do
subgroup_creation_level require_two_factor_authentication
two_factor_grace_period auto_devops_enabled emails_disabled
mentions_disabled parent boards milestones group_members
+ merge_requests
]
expect(described_class).to include_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/issue_sort_enum_spec.rb b/spec/graphql/types/issue_sort_enum_spec.rb
index 9313d3aee84..4433709d193 100644
--- a/spec/graphql/types/issue_sort_enum_spec.rb
+++ b/spec/graphql/types/issue_sort_enum_spec.rb
@@ -9,7 +9,7 @@ RSpec.describe GitlabSchema.types['IssueSort'] do
it 'exposes all the existing issue sort values' do
expect(described_class.values.keys).to include(
- *%w[DUE_DATE_ASC DUE_DATE_DESC RELATIVE_POSITION_ASC]
+ *%w[DUE_DATE_ASC DUE_DATE_DESC RELATIVE_POSITION_ASC SEVERITY_ASC SEVERITY_DESC]
)
end
end
diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb
index 46aebbdabeb..9d901655b7b 100644
--- a/spec/graphql/types/merge_request_type_spec.rb
+++ b/spec/graphql/types/merge_request_type_spec.rb
@@ -27,16 +27,51 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do
upvotes downvotes head_pipeline pipelines task_completion_status
milestone assignees participants subscribed labels discussion_locked time_estimate
total_time_spent reference author merged_at commit_count current_user_todos
- conflicts auto_merge_enabled
+ conflicts auto_merge_enabled approved_by
]
if Gitlab.ee?
expected_fields << 'approved'
expected_fields << 'approvals_left'
expected_fields << 'approvals_required'
- expected_fields << 'approved_by'
end
expect(described_class).to have_graphql_fields(*expected_fields)
end
+
+ describe '#diff_stats_summary' do
+ subject { GitlabSchema.execute(query, context: { current_user: current_user }).as_json }
+
+ let(:current_user) { create :admin }
+ let(:query) do
+ %(
+ {
+ project(fullPath: "#{project.full_path}") {
+ mergeRequests {
+ nodes {
+ diffStatsSummary {
+ additions, deletions
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ let(:project) { create(:project, :public) }
+ let(:merge_request) { create(:merge_request, target_project: project, source_project: project) }
+
+ let(:response) { subject.dig('data', 'project', 'mergeRequests', 'nodes').first['diffStatsSummary'] }
+
+ context 'when MR metrics has additions and deletions' do
+ before do
+ merge_request.metrics.update!(added_lines: 5, removed_lines: 8)
+ end
+
+ it 'pulls out data from metrics object' do
+ expect(response).to match('additions' => 5, 'deletions' => 8)
+ end
+ end
+ end
end
diff --git a/spec/graphql/types/package_type_enum_spec.rb b/spec/graphql/types/package_type_enum_spec.rb
index 80a20a68bc2..407d5786f65 100644
--- a/spec/graphql/types/package_type_enum_spec.rb
+++ b/spec/graphql/types/package_type_enum_spec.rb
@@ -4,6 +4,6 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['PackageTypeEnum'] do
it 'exposes all package types' do
- expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC])
+ expect(described_class.values.keys).to contain_exactly(*%w[MAVEN NPM CONAN NUGET PYPI COMPOSER GENERIC GOLANG DEBIAN])
end
end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index 44a89bfa35e..8aa9e1138cc 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe GitlabSchema.types['Project'] do
environment boards jira_import_status jira_imports services releases release
alert_management_alerts alert_management_alert alert_management_alert_status_counts
container_expiration_policy service_desk_enabled service_desk_address
- issue_status_counts
+ issue_status_counts terraform_states
]
expect(described_class).to include_graphql_fields(*expected_fields)
@@ -154,5 +154,12 @@ RSpec.describe GitlabSchema.types['Project'] do
it { is_expected.to have_graphql_type(Types::ContainerExpirationPolicyType) }
end
+ describe 'terraform states field' do
+ subject { described_class.fields['terraformStates'] }
+
+ it { is_expected.to have_graphql_type(Types::Terraform::StateType.connection_type) }
+ it { is_expected.to have_graphql_resolver(Resolvers::Terraform::StatesResolver) }
+ end
+
it_behaves_like 'a GraphQL type with labels'
end
diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb
index 11f780a4f3f..1d9ca8323f8 100644
--- a/spec/graphql/types/query_type_spec.rb
+++ b/spec/graphql/types/query_type_spec.rb
@@ -22,6 +22,7 @@ RSpec.describe GitlabSchema.types['Query'] do
users
issue
instance_statistics_measurements
+ runner_platforms
]
expect(described_class).to have_graphql_fields(*expected_fields).at_least
@@ -67,8 +68,16 @@ RSpec.describe GitlabSchema.types['Query'] do
describe 'instance_statistics_measurements field' do
subject { described_class.fields['instanceStatisticsMeasurements'] }
- it 'returns issue' do
+ it 'returns instance statistics measurements' do
is_expected.to have_graphql_type(Types::Admin::Analytics::InstanceStatistics::MeasurementType.connection_type)
end
end
+
+ describe 'runner_platforms field' do
+ subject { described_class.fields['runnerPlatforms'] }
+
+ it 'returns runner platforms' do
+ is_expected.to have_graphql_type(Types::Ci::RunnerPlatformType.connection_type)
+ end
+ end
end
diff --git a/spec/graphql/types/range_input_type_spec.rb b/spec/graphql/types/range_input_type_spec.rb
new file mode 100644
index 00000000000..aa6fd72cf13
--- /dev/null
+++ b/spec/graphql/types/range_input_type_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::Types::RangeInputType do
+ let(:of_integer) { ::GraphQL::INT_TYPE }
+
+ context 'parameterized on Integer' do
+ let(:type) { described_class[of_integer] }
+
+ it 'accepts start and end' do
+ input = { start: 1, end: 10 }
+ output = { start: 1, end: 10 }
+
+ expect(type.coerce_isolated_input(input)).to eq(output)
+ end
+
+ it 'rejects inverted ranges' do
+ input = { start: 10, end: 1 }
+
+ expect { type.coerce_isolated_input(input) }.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
+ end
+ end
+
+ it 'follows expected subtyping relationships for instances' do
+ context = GraphQL::Query::Context.new(
+ query: OpenStruct.new(schema: nil),
+ values: {},
+ object: nil
+ )
+ instance = described_class[of_integer].new(context: context, defaults_used: [], ruby_kwargs: {})
+
+ expect(instance).to be_a_kind_of(described_class)
+ expect(instance).to be_a_kind_of(described_class[of_integer])
+ expect(instance).not_to be_a_kind_of(described_class[GraphQL::ID_TYPE])
+ end
+
+ it 'follows expected subtyping relationships for classes' do
+ expect(described_class[of_integer]).to be < described_class
+ expect(described_class[of_integer]).not_to be < described_class[GraphQL::ID_TYPE]
+ expect(described_class[of_integer]).not_to be < described_class[of_integer, false]
+ end
+end
diff --git a/spec/graphql/types/root_storage_statistics_type_spec.rb b/spec/graphql/types/root_storage_statistics_type_spec.rb
index f01c55cbccb..79d474f13ad 100644
--- a/spec/graphql/types/root_storage_statistics_type_spec.rb
+++ b/spec/graphql/types/root_storage_statistics_type_spec.rb
@@ -7,7 +7,8 @@ RSpec.describe GitlabSchema.types['RootStorageStatistics'] do
it 'has all the required fields' do
expect(described_class).to have_graphql_fields(:storage_size, :repository_size, :lfs_objects_size,
- :build_artifacts_size, :packages_size, :wiki_size, :snippets_size)
+ :build_artifacts_size, :packages_size, :wiki_size, :snippets_size,
+ :pipeline_artifacts_size)
end
specify { expect(described_class).to require_graphql_authorizations(:read_statistics) }
diff --git a/spec/graphql/types/snippet_type_spec.rb b/spec/graphql/types/snippet_type_spec.rb
index 86af69f1294..e73665a1b1d 100644
--- a/spec/graphql/types/snippet_type_spec.rb
+++ b/spec/graphql/types/snippet_type_spec.rb
@@ -16,6 +16,15 @@ RSpec.describe GitlabSchema.types['Snippet'] do
expect(described_class).to have_graphql_fields(*expected_fields)
end
+ describe 'blobs field' do
+ subject { described_class.fields['blobs'] }
+
+ it 'returns blobs' do
+ is_expected.to have_graphql_type(Types::Snippets::BlobType.connection_type)
+ is_expected.to have_graphql_resolver(Resolvers::Snippets::BlobsResolver)
+ end
+ end
+
context 'when restricted visibility level is set to public' do
let_it_be(:snippet) { create(:personal_snippet, :repository, :public, author: user) }
@@ -115,7 +124,7 @@ RSpec.describe GitlabSchema.types['Snippet'] do
end
describe '#blob' do
- let(:query_blob) { subject.dig('data', 'snippets', 'edges')[0]['node']['blob'] }
+ let(:query_blob) { subject.dig('data', 'snippets', 'nodes')[0]['blob'] }
subject { GitlabSchema.execute(snippet_query_for(field: 'blob'), context: { current_user: user }).as_json }
@@ -142,9 +151,26 @@ RSpec.describe GitlabSchema.types['Snippet'] do
describe '#blobs' do
let_it_be(:snippet) { create(:personal_snippet, :public, author: user) }
- let(:query_blobs) { subject.dig('data', 'snippets', 'edges')[0]['node']['blobs'] }
+ let(:query_blobs) { subject.dig('data', 'snippets', 'nodes')[0].dig('blobs', 'nodes') }
+ let(:paths) { [] }
+ let(:query) do
+ %(
+ {
+ snippets {
+ nodes {
+ blobs(paths: #{paths}) {
+ nodes {
+ name
+ path
+ }
+ }
+ }
+ }
+ }
+ )
+ end
- subject { GitlabSchema.execute(snippet_query_for(field: 'blobs'), context: { current_user: user }).as_json }
+ subject { GitlabSchema.execute(query, context: { current_user: user }).as_json }
shared_examples 'an array' do
it 'returns an array of snippet blobs' do
@@ -174,6 +200,18 @@ RSpec.describe GitlabSchema.types['Snippet'] do
expect(resulting_blobs_names).to match_array(blobs.map(&:name))
end
+
+ context 'when specific path is set' do
+ let(:paths) { ['CHANGELOG'] }
+
+ it_behaves_like 'an array'
+
+ it 'returns specific files' do
+ resulting_blobs_names = query_blobs.map { |b| b['name'] }
+
+ expect(resulting_blobs_names).to match(paths)
+ end
+ end
end
end
@@ -181,12 +219,10 @@ RSpec.describe GitlabSchema.types['Snippet'] do
%(
{
snippets {
- edges {
- node {
- #{field} {
- name
- path
- }
+ nodes {
+ #{field} {
+ name
+ path
}
}
}
diff --git a/spec/graphql/types/terraform/state_type_spec.rb b/spec/graphql/types/terraform/state_type_spec.rb
new file mode 100644
index 00000000000..51508208046
--- /dev/null
+++ b/spec/graphql/types/terraform/state_type_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['TerraformState'] do
+ it { expect(described_class.graphql_name).to eq('TerraformState') }
+ it { expect(described_class).to require_graphql_authorizations(:read_terraform_state) }
+
+ describe 'fields' do
+ let(:fields) { %i[id name locked_by_user locked_at created_at updated_at] }
+
+ it { expect(described_class).to have_graphql_fields(fields) }
+
+ it { expect(described_class.fields['id'].type).to be_non_null }
+ it { expect(described_class.fields['name'].type).to be_non_null }
+ it { expect(described_class.fields['lockedByUser'].type).not_to be_non_null }
+ it { expect(described_class.fields['lockedAt'].type).not_to be_non_null }
+ it { expect(described_class.fields['createdAt'].type).to be_non_null }
+ it { expect(described_class.fields['updatedAt'].type).to be_non_null }
+ end
+end
diff --git a/spec/graphql/types/timeframe_type_spec.rb b/spec/graphql/types/timeframe_type_spec.rb
new file mode 100644
index 00000000000..dfde3242897
--- /dev/null
+++ b/spec/graphql/types/timeframe_type_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['Timeframe'] do
+ let(:input) { { start: "2018-06-04", end: "2020-10-06" } }
+ let(:output) { { start: Date.parse(input[:start]), end: Date.parse(input[:end]) } }
+
+ it 'coerces ISO-dates into Time objects' do
+ expect(described_class.coerce_isolated_input(input)).to eq(output)
+ end
+
+ it 'rejects invalid input' do
+ input[:start] = 'foo'
+
+ expect { described_class.coerce_isolated_input(input) }
+ .to raise_error(GraphQL::CoercionError)
+ end
+
+ it 'accepts times as input' do
+ with_time = input.merge(start: '2018-06-04T13:48:14Z')
+
+ expect(described_class.coerce_isolated_input(with_time)).to eq(output)
+ end
+
+ it 'requires both ends of the range' do
+ types = described_class.arguments.slice('start', 'end').values.map(&:type)
+
+ expect(types).to all(be_non_null)
+ end
+
+ it 'rejects invalid range' do
+ input.merge!(start: input[:end], end: input[:start])
+
+ expect { described_class.coerce_isolated_input(input) }
+ .to raise_error(::Gitlab::Graphql::Errors::ArgumentError, 'start must be before end')
+ end
+end