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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/graphql')
-rw-r--r--spec/graphql/features/authorization_spec.rb37
-rw-r--r--spec/graphql/features/feature_flag_spec.rb3
-rw-r--r--spec/graphql/gitlab_schema_spec.rb25
-rw-r--r--spec/graphql/mutations/boards/update_spec.rb57
-rw-r--r--spec/graphql/mutations/concerns/mutations/can_mutate_spammable_spec.rb2
-rw-r--r--spec/graphql/mutations/custom_emoji/create_spec.rb27
-rw-r--r--spec/graphql/mutations/merge_requests/accept_spec.rb171
-rw-r--r--spec/graphql/mutations/release_asset_links/create_spec.rb105
-rw-r--r--spec/graphql/mutations/user_callouts/create_spec.rb42
-rw-r--r--spec/graphql/resolvers/admin/analytics/usage_trends/measurements_resolver_spec.rb (renamed from spec/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver_spec.rb)10
-rw-r--r--spec/graphql/resolvers/alert_management/http_integrations_resolver_spec.rb51
-rw-r--r--spec/graphql/resolvers/board_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/boards_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/branch_commit_resolver_spec.rb4
-rw-r--r--spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb3
-rw-r--r--spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb6
-rw-r--r--spec/graphql/resolvers/group_labels_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/group_packages_resolver_spec.rb18
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb8
-rw-r--r--spec/graphql/resolvers/labels_resolver_spec.rb36
-rw-r--r--spec/graphql/resolvers/merge_requests_resolver_spec.rb2
-rw-r--r--spec/graphql/resolvers/namespace_projects_resolver_spec.rb57
-rw-r--r--spec/graphql/resolvers/project_packages_resolver_spec.rb (renamed from spec/graphql/resolvers/packages_resolver_spec.rb)2
-rw-r--r--spec/graphql/resolvers/project_pipeline_resolver_spec.rb37
-rw-r--r--spec/graphql/resolvers/release_milestones_resolver_spec.rb2
-rw-r--r--spec/graphql/types/access_level_enum_spec.rb2
-rw-r--r--spec/graphql/types/admin/analytics/usage_trends/measurement_identifier_enum_spec.rb (renamed from spec/graphql/types/admin/analytics/instance_statistics/measurement_identifier_enum_spec.rb)2
-rw-r--r--spec/graphql/types/admin/analytics/usage_trends/measurement_type_spec.rb (renamed from spec/graphql/types/admin/analytics/instance_statistics/measurement_type_spec.rb)12
-rw-r--r--spec/graphql/types/alert_management/alert_type_spec.rb3
-rw-r--r--spec/graphql/types/base_argument_spec.rb17
-rw-r--r--spec/graphql/types/board_type_spec.rb2
-rw-r--r--spec/graphql/types/ci/job_type_spec.rb2
-rw-r--r--spec/graphql/types/ci/pipeline_type_spec.rb4
-rw-r--r--spec/graphql/types/global_id_type_spec.rb23
-rw-r--r--spec/graphql/types/group_type_spec.rb1
-rw-r--r--spec/graphql/types/label_type_spec.rb11
-rw-r--r--spec/graphql/types/merge_request_type_spec.rb31
-rw-r--r--spec/graphql/types/query_type_spec.rb10
-rw-r--r--spec/graphql/types/snippet_type_spec.rb10
-rw-r--r--spec/graphql/types/snippets/blob_type_spec.rb47
-rw-r--r--spec/graphql/types/user_callout_feature_name_enum_spec.rb11
-rw-r--r--spec/graphql/types/user_callout_type_spec.rb11
-rw-r--r--spec/graphql/types/user_type_spec.rb9
43 files changed, 794 insertions, 129 deletions
diff --git a/spec/graphql/features/authorization_spec.rb b/spec/graphql/features/authorization_spec.rb
index ec67ed16fe9..33b11e1ca09 100644
--- a/spec/graphql/features/authorization_spec.rb
+++ b/spec/graphql/features/authorization_spec.rb
@@ -2,17 +2,22 @@
require 'spec_helper'
-RSpec.describe 'Gitlab::Graphql::Authorization' do
+RSpec.describe 'Gitlab::Graphql::Authorize' do
include GraphqlHelpers
+ include Graphql::ResolverFactories
let_it_be(:user) { create(:user) }
let(:permission_single) { :foo }
let(:permission_collection) { [:foo, :bar] }
let(:test_object) { double(name: 'My name') }
let(:query_string) { '{ item { name } }' }
- let(:result) { execute_query(query_type)['data'] }
+ let(:result) do
+ schema = empty_schema
+ schema.use(Gitlab::Graphql::Authorize)
+ execute_query(query_type, schema: schema)
+ end
- subject { result['item'] }
+ subject { result.dig('data', 'item') }
shared_examples 'authorization with a single permission' do
it 'returns the protected field when user has permission' do
@@ -55,7 +60,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
describe 'with a single permission' do
let(:query_type) do
query_factory do |query|
- query.field :item, type, null: true, resolver: simple_resolver(test_object), authorize: permission_single
+ query.field :item, type, null: true, resolver: new_resolver(test_object), authorize: permission_single
end
end
@@ -66,7 +71,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
let(:query_type) do
permissions = permission_collection
query_factory do |qt|
- qt.field :item, type, null: true, resolver: simple_resolver(test_object) do
+ qt.field :item, type, null: true, resolver: new_resolver(test_object) do
authorize permissions
end
end
@@ -79,7 +84,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
describe 'Field authorizations when field is a built in type' do
let(:query_type) do
query_factory do |query|
- query.field :item, type, null: true, resolver: simple_resolver(test_object)
+ query.field :item, type, null: true, resolver: new_resolver(test_object)
end
end
@@ -132,7 +137,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
describe 'Type authorizations' do
let(:query_type) do
query_factory do |query|
- query.field :item, type, null: true, resolver: simple_resolver(test_object)
+ query.field :item, type, null: true, resolver: new_resolver(test_object)
end
end
@@ -169,7 +174,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
let(:query_type) do
query_factory do |query|
- query.field :item, type, null: true, resolver: simple_resolver(test_object), authorize: permission_2
+ query.field :item, type, null: true, resolver: new_resolver(test_object), authorize: permission_2
end
end
@@ -188,11 +193,11 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
let(:query_type) do
query_factory do |query|
- query.field :item, type.connection_type, null: true, resolver: simple_resolver([test_object, second_test_object])
+ query.field :item, type.connection_type, null: true, resolver: new_resolver([test_object, second_test_object])
end
end
- subject { result.dig('item', 'edges') }
+ subject { result.dig('data', 'item', 'edges') }
it 'returns only the elements visible to the user' do
permit(permission_single)
@@ -208,7 +213,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
describe 'limiting connections with multiple objects' do
let(:query_type) do
query_factory do |query|
- query.field :item, type.connection_type, null: true, resolver: simple_resolver([test_object, second_test_object])
+ query.field :item, type.connection_type, null: true, resolver: new_resolver([test_object, second_test_object])
end
end
@@ -232,11 +237,11 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
let(:query_type) do
query_factory do |query|
- query.field :item, [type], null: true, resolver: simple_resolver([test_object])
+ query.field :item, [type], null: true, resolver: new_resolver([test_object])
end
end
- subject { result['item'].first }
+ subject { result.dig('data', 'item', 0) }
include_examples 'authorization with a single permission'
end
@@ -260,13 +265,13 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
type_factory do |type|
type.graphql_name 'FakeProjectType'
type.field :test_issues, issue_type.connection_type, null: false,
- resolver: simple_resolver(Issue.where(project: [visible_project, other_project]).order(id: :asc))
+ resolver: new_resolver(Issue.where(project: [visible_project, other_project]).order(id: :asc))
end
end
let(:query_type) do
query_factory do |query|
- query.field :test_project, project_type, null: false, resolver: simple_resolver(visible_project)
+ query.field :test_project, project_type, null: false, resolver: new_resolver(visible_project)
end
end
@@ -281,7 +286,7 @@ RSpec.describe 'Gitlab::Graphql::Authorization' do
end
it 'renders the issues the user has access to' do
- issue_edges = result['testProject']['testIssues']['edges']
+ issue_edges = result.dig('data', 'testProject', 'testIssues', 'edges')
issue_ids = issue_edges.map { |issue_edge| issue_edge['node']&.fetch('id') }
expect(issue_edges.size).to eq(visible_issues.size)
diff --git a/spec/graphql/features/feature_flag_spec.rb b/spec/graphql/features/feature_flag_spec.rb
index 77810f78257..30238cf9cb3 100644
--- a/spec/graphql/features/feature_flag_spec.rb
+++ b/spec/graphql/features/feature_flag_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'Graphql Field feature flags' do
include GraphqlHelpers
+ include Graphql::ResolverFactories
let_it_be(:user) { create(:user) }
@@ -23,7 +24,7 @@ RSpec.describe 'Graphql Field feature flags' do
let(:query_type) do
query_factory do |query|
- query.field :item, type, null: true, feature_flag: feature_flag, resolver: simple_resolver(test_object)
+ query.field :item, type, null: true, feature_flag: feature_flag, resolver: new_resolver(test_object)
end
end
diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb
index 4db12643069..cb2bb25b098 100644
--- a/spec/graphql/gitlab_schema_spec.rb
+++ b/spec/graphql/gitlab_schema_spec.rb
@@ -18,14 +18,6 @@ RSpec.describe GitlabSchema do
expect(field_instrumenters).to include(instance_of(::Gitlab::Graphql::Authorize::Instrumentation))
end
- it 'enables using presenters' do
- expect(field_instrumenters).to include(instance_of(::Gitlab::Graphql::Present::Instrumentation))
- end
-
- it 'enables using gitaly call checker' do
- expect(field_instrumenters).to include(instance_of(::Gitlab::Graphql::CallsGitaly::Instrumentation))
- end
-
it 'has the base mutation' do
expect(described_class.mutation).to eq(::Types::MutationType)
end
@@ -47,7 +39,7 @@ RSpec.describe GitlabSchema do
end
describe '.execute' do
- context 'for different types of users' do
+ context 'with different types of users' do
context 'when no context' do
it 'returns DEFAULT_MAX_COMPLEXITY' do
expect(GraphQL::Schema)
@@ -78,13 +70,15 @@ RSpec.describe GitlabSchema do
context 'when a logged in user' do
it 'returns AUTHENTICATED_COMPLEXITY' do
- expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::AUTHENTICATED_COMPLEXITY))
+ expect(GraphQL::Schema).to receive(:execute)
+ .with('query', hash_including(max_complexity: GitlabSchema::AUTHENTICATED_COMPLEXITY))
described_class.execute('query', context: { current_user: user })
end
it 'returns AUTHENTICATED_MAX_DEPTH' do
- expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_depth: GitlabSchema::AUTHENTICATED_MAX_DEPTH))
+ expect(GraphQL::Schema).to receive(:execute)
+ .with('query', hash_including(max_depth: GitlabSchema::AUTHENTICATED_MAX_DEPTH))
described_class.execute('query', context: { current_user: user })
end
@@ -94,7 +88,8 @@ RSpec.describe GitlabSchema do
it 'returns ADMIN_COMPLEXITY' do
user = build :user, :admin
- expect(GraphQL::Schema).to receive(:execute).with('query', hash_including(max_complexity: GitlabSchema::ADMIN_COMPLEXITY))
+ expect(GraphQL::Schema).to receive(:execute)
+ .with('query', hash_including(max_complexity: GitlabSchema::ADMIN_COMPLEXITY))
described_class.execute('query', context: { current_user: user })
end
@@ -130,7 +125,7 @@ RSpec.describe GitlabSchema do
end
describe '.object_from_id' do
- context 'for subclasses of `ApplicationRecord`' do
+ context 'with subclasses of `ApplicationRecord`' do
let_it_be(:user) { create(:user) }
it 'returns the correct record' do
@@ -162,7 +157,7 @@ RSpec.describe GitlabSchema do
end
end
- context 'for classes that are not ActiveRecord subclasses and have implemented .lazy_find' do
+ context 'with classes that are not ActiveRecord subclasses and have implemented .lazy_find' do
it 'returns the correct record' do
note = create(:discussion_note_on_merge_request)
@@ -182,7 +177,7 @@ RSpec.describe GitlabSchema do
end
end
- context 'for other classes' do
+ context 'with other classes' do
# We cannot use an anonymous class here as `GlobalID` expects `.name` not
# to return `nil`
before do
diff --git a/spec/graphql/mutations/boards/update_spec.rb b/spec/graphql/mutations/boards/update_spec.rb
new file mode 100644
index 00000000000..da3dfeecd4d
--- /dev/null
+++ b/spec/graphql/mutations/boards/update_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Boards::Update do
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:board) { create(:board, project: project) }
+
+ let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
+ let(:mutated_board) { subject[:board] }
+
+ let(:mutation_params) do
+ {
+ id: board.to_global_id,
+ hide_backlog_list: true,
+ hide_closed_list: false
+ }
+ end
+
+ subject { mutation.resolve(**mutation_params) }
+
+ specify { expect(described_class).to require_graphql_authorizations(:admin_issue_board) }
+
+ describe '#resolve' do
+ context 'when the user cannot admin the board' do
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context 'with invalid params' do
+ it 'raises an error' do
+ mutation_params[:id] = project.to_global_id
+
+ expect { subject }.to raise_error(::GraphQL::CoercionError)
+ end
+ end
+
+ context 'when user can update board' do
+ before do
+ board.resource_parent.add_reporter(user)
+ end
+
+ it 'updates board with correct values' do
+ expected_attributes = {
+ hide_backlog_list: true,
+ hide_closed_list: false
+ }
+
+ subject
+
+ expect(board.reload).to have_attributes(expected_attributes)
+ end
+ end
+ end
+end
diff --git a/spec/graphql/mutations/concerns/mutations/can_mutate_spammable_spec.rb b/spec/graphql/mutations/concerns/mutations/can_mutate_spammable_spec.rb
index ee8db7a1f31..8d1fce406fa 100644
--- a/spec/graphql/mutations/concerns/mutations/can_mutate_spammable_spec.rb
+++ b/spec/graphql/mutations/concerns/mutations/can_mutate_spammable_spec.rb
@@ -30,7 +30,7 @@ RSpec.describe Mutations::CanMutateSpammable do
end
it 'merges in spam action fields from spammable' do
- result = subject.send(:with_spam_action_fields, spammable) do
+ result = subject.send(:with_spam_action_response_fields, spammable) do
{ other_field: true }
end
expect(result)
diff --git a/spec/graphql/mutations/custom_emoji/create_spec.rb b/spec/graphql/mutations/custom_emoji/create_spec.rb
new file mode 100644
index 00000000000..118c5d67188
--- /dev/null
+++ b/spec/graphql/mutations/custom_emoji/create_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::CustomEmoji::Create do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:user) { create(:user) }
+ let(:args) { { group_path: group.full_path, name: 'tanuki', url: 'https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png' } }
+
+ before do
+ group.add_developer(user)
+ end
+
+ describe '#resolve' do
+ subject(:resolve) { described_class.new(object: nil, context: { current_user: user }, field: nil).resolve(**args) }
+
+ it 'creates the custom emoji' do
+ expect { resolve }.to change(CustomEmoji, :count).by(1)
+ end
+
+ it 'sets the creator to be the user who added the emoji' do
+ resolve
+
+ expect(CustomEmoji.last.creator).to eq(user)
+ end
+ end
+end
diff --git a/spec/graphql/mutations/merge_requests/accept_spec.rb b/spec/graphql/mutations/merge_requests/accept_spec.rb
new file mode 100644
index 00000000000..db75c64a447
--- /dev/null
+++ b/spec/graphql/mutations/merge_requests/accept_spec.rb
@@ -0,0 +1,171 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::MergeRequests::Accept do
+ include AfterNextHelpers
+
+ let_it_be(:user) { create(:user) }
+ let(:project) { create(:project, :public, :repository) }
+
+ subject(:mutation) { described_class.new(context: context, object: nil, field: nil) }
+
+ let_it_be(:context) do
+ GraphQL::Query::Context.new(
+ query: OpenStruct.new(schema: GitlabSchema),
+ values: { current_user: user },
+ object: nil
+ )
+ end
+
+ before do
+ project.repository.expire_all_method_caches
+ end
+
+ describe '#resolve' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ def common_args(merge_request)
+ {
+ project_path: project.full_path,
+ iid: merge_request.iid.to_s,
+ sha: merge_request.diff_head_sha,
+ squash: false # default value
+ }
+ end
+
+ it 'merges the merge request' do
+ merge_request = create(:merge_request, source_project: project)
+
+ result = mutation.resolve(**common_args(merge_request))
+
+ expect(result).to include(errors: be_empty, merge_request: be_merged)
+ end
+
+ it 'rejects the mutation if the SHA is a mismatch' do
+ merge_request = create(:merge_request, source_project: project)
+ args = common_args(merge_request).merge(sha: 'not a good sha')
+
+ result = mutation.resolve(**args)
+
+ expect(result).not_to include(merge_request: be_merged)
+ expect(result).to include(errors: [described_class::SHA_MISMATCH])
+ end
+
+ it 'respects the merge commit message' do
+ merge_request = create(:merge_request, source_project: project)
+ args = common_args(merge_request).merge(commit_message: 'my super custom message')
+
+ result = mutation.resolve(**args)
+
+ expect(result).to include(merge_request: be_merged)
+ expect(project.repository.commit(merge_request.target_branch)).to have_attributes(
+ message: args[:commit_message]
+ )
+ end
+
+ it 'respects the squash flag' do
+ merge_request = create(:merge_request, source_project: project)
+ args = common_args(merge_request).merge(squash: true)
+
+ result = mutation.resolve(**args)
+
+ expect(result).to include(merge_request: be_merged)
+ expect(result[:merge_request].squash_commit_sha).to be_present
+ end
+
+ it 'respects the squash_commit_message argument' do
+ merge_request = create(:merge_request, source_project: project)
+ args = common_args(merge_request).merge(squash: true, squash_commit_message: 'squish')
+
+ result = mutation.resolve(**args)
+ sha = result[:merge_request].squash_commit_sha
+
+ expect(result).to include(merge_request: be_merged)
+ expect(project.repository.commit(sha)).to have_attributes(message: "squish\n")
+ end
+
+ it 'respects the should_remove_source_branch argument when true' do
+ b = project.repository.add_branch(user, generate(:branch), 'master')
+ merge_request = create(:merge_request, source_branch: b.name, source_project: project)
+ args = common_args(merge_request).merge(should_remove_source_branch: true)
+
+ expect(::MergeRequests::DeleteSourceBranchWorker).to receive(:perform_async)
+
+ result = mutation.resolve(**args)
+
+ expect(result).to include(merge_request: be_merged)
+ end
+
+ it 'respects the should_remove_source_branch argument when false' do
+ b = project.repository.add_branch(user, generate(:branch), 'master')
+ merge_request = create(:merge_request, source_branch: b.name, source_project: project)
+ args = common_args(merge_request).merge(should_remove_source_branch: false)
+
+ expect(::MergeRequests::DeleteSourceBranchWorker).not_to receive(:perform_async)
+
+ result = mutation.resolve(**args)
+
+ expect(result).to include(merge_request: be_merged)
+ end
+
+ it 'rejects unmergeable MRs' do
+ merge_request = create(:merge_request, :closed, source_project: project)
+ args = common_args(merge_request)
+
+ result = mutation.resolve(**args)
+
+ expect(result).not_to include(merge_request: be_merged)
+ expect(result).to include(errors: [described_class::NOT_MERGEABLE])
+ end
+
+ it 'rejects merges when we cannot validate the hooks' do
+ merge_request = create(:merge_request, source_project: project)
+ args = common_args(merge_request)
+ expect_next(::MergeRequests::MergeService)
+ .to receive(:hooks_validation_pass?).with(merge_request).and_return(false)
+
+ result = mutation.resolve(**args)
+
+ expect(result).not_to include(merge_request: be_merged)
+ expect(result).to include(errors: [described_class::HOOKS_VALIDATION_ERROR])
+ end
+
+ it 'rejects merges when the merge service returns an error' do
+ merge_request = create(:merge_request, source_project: project)
+ args = common_args(merge_request)
+ expect_next(::MergeRequests::MergeService)
+ .to receive(:execute).with(merge_request).and_return(:failed)
+
+ result = mutation.resolve(**args)
+
+ expect(result).not_to include(merge_request: be_merged)
+ expect(result).to include(errors: [described_class::MERGE_FAILED])
+ end
+
+ it 'rejects merges when the merge service raises merge error' do
+ merge_request = create(:merge_request, source_project: project)
+ args = common_args(merge_request)
+ expect_next(::MergeRequests::MergeService)
+ .to receive(:execute).and_raise(::MergeRequests::MergeBaseService::MergeError, 'boom')
+
+ result = mutation.resolve(**args)
+
+ expect(result).not_to include(merge_request: be_merged)
+ expect(result).to include(errors: ['boom'])
+ end
+
+ it "can use the MERGE_WHEN_PIPELINE_SUCCEEDS strategy" do
+ enum = ::Types::MergeStrategyEnum.values['MERGE_WHEN_PIPELINE_SUCCEEDS']
+ merge_request = create(:merge_request, :with_head_pipeline, source_project: project)
+ args = common_args(merge_request).merge(auto_merge_strategy: enum.value)
+
+ result = mutation.resolve(**args)
+
+ expect(result).not_to include(merge_request: be_merged)
+ expect(result).to include(errors: be_empty, merge_request: be_auto_merge_enabled)
+ end
+ end
+end
diff --git a/spec/graphql/mutations/release_asset_links/create_spec.rb b/spec/graphql/mutations/release_asset_links/create_spec.rb
new file mode 100644
index 00000000000..089bc3d3276
--- /dev/null
+++ b/spec/graphql/mutations/release_asset_links/create_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::ReleaseAssetLinks::Create do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project, :private, :repository) }
+ let_it_be(:release) { create(:release, project: project, tag: 'v13.10') }
+ let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } }
+ let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
+
+ let(:current_user) { developer }
+ let(:context) { { current_user: current_user } }
+ let(:project_path) { project.full_path }
+ let(:tag) { release.tag }
+ let(:name) { 'awesome-app.dmg' }
+ let(:url) { 'https://example.com/download/awesome-app.dmg' }
+ let(:filepath) { '/binaries/awesome-app.dmg' }
+
+ let(:args) do
+ {
+ project_path: project_path,
+ tag_name: tag,
+ name: name,
+ direct_asset_path: filepath,
+ url: url
+ }
+ end
+
+ let(:last_release_link) { release.links.last }
+
+ describe '#resolve' do
+ subject do
+ resolve(described_class, obj: project, args: args, ctx: context)
+ end
+
+ context 'when the user has access and no validation errors occur' do
+ it 'creates a new release asset link', :aggregate_failures do
+ expect(subject).to eq({
+ link: release.reload.links.first,
+ errors: []
+ })
+
+ expect(release.links.length).to be(1)
+
+ expect(last_release_link.name).to eq(name)
+ expect(last_release_link.url).to eq(url)
+ expect(last_release_link.filepath).to eq(filepath)
+ end
+ end
+
+ context "when the user doesn't have access to the project" do
+ let(:current_user) { reporter }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context "when the project doesn't exist" do
+ let(:project_path) { 'project/that/does/not/exist' }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ end
+ end
+
+ context "when a validation errors occur" do
+ shared_examples 'returns errors-as-data' do |expected_messages|
+ it { expect(subject[:errors]).to eq(expected_messages) }
+ end
+
+ context "when the release doesn't exist" do
+ let(:tag) { "nonexistent-tag" }
+
+ it_behaves_like 'returns errors-as-data', ['Release with tag "nonexistent-tag" was not found']
+ end
+
+ context 'when the URL is badly formatted' do
+ let(:url) { 'badly-formatted-url' }
+
+ it_behaves_like 'returns errors-as-data', ["Url is blocked: Only allowed schemes are http, https, ftp"]
+ end
+
+ context 'when the name is not provided' do
+ let(:name) { '' }
+
+ it_behaves_like 'returns errors-as-data', ["Name can't be blank"]
+ end
+
+ context 'when the link already exists' do
+ let!(:existing_release_link) do
+ create(:release_link, release: release, name: name, url: url, filepath: filepath)
+ end
+
+ it_behaves_like 'returns errors-as-data', [
+ "Url has already been taken",
+ "Name has already been taken",
+ "Filepath has already been taken"
+ ]
+ end
+ end
+ end
+end
diff --git a/spec/graphql/mutations/user_callouts/create_spec.rb b/spec/graphql/mutations/user_callouts/create_spec.rb
new file mode 100644
index 00000000000..93f227d8b82
--- /dev/null
+++ b/spec/graphql/mutations/user_callouts/create_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::UserCallouts::Create do
+ let(:current_user) { create(:user) }
+ let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) }
+
+ describe '#resolve' do
+ subject(:resolve) { mutation.resolve(feature_name: feature_name) }
+
+ context 'when feature name is not supported' do
+ let(:feature_name) { 'not_supported' }
+
+ it 'does not create a user callout' do
+ expect { resolve }.not_to change(UserCallout, :count).from(0)
+ end
+
+ it 'returns error about feature name not being supported' do
+ expect(resolve[:errors]).to include("Feature name is not included in the list")
+ end
+ end
+
+ context 'when feature name is supported' do
+ let(:feature_name) { UserCallout.feature_names.each_key.first.to_s }
+
+ it 'creates a user callout' do
+ expect { resolve }.to change(UserCallout, :count).from(0).to(1)
+ end
+
+ it 'sets dismissed_at for the user callout' do
+ freeze_time do
+ expect(resolve[:user_callout].dismissed_at).to eq(Time.current)
+ end
+ end
+
+ it 'has no errors' do
+ expect(resolve[:errors]).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver_spec.rb b/spec/graphql/resolvers/admin/analytics/usage_trends/measurements_resolver_spec.rb
index 578d679ade4..269a1fb1758 100644
--- a/spec/graphql/resolvers/admin/analytics/instance_statistics/measurements_resolver_spec.rb
+++ b/spec/graphql/resolvers/admin/analytics/usage_trends/measurements_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsResolver do
+RSpec.describe Resolvers::Admin::Analytics::UsageTrends::MeasurementsResolver do
include GraphqlHelpers
let_it_be(:admin_user) { create(:user, :admin) }
@@ -11,8 +11,8 @@ RSpec.describe Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsReso
describe '#resolve' do
let_it_be(:user) { create(:user) }
- 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) }
+ let_it_be(:project_measurement_new) { create(:usage_trends_measurement, :project_count, recorded_at: 2.days.ago) }
+ let_it_be(:project_measurement_old) { create(:usage_trends_measurement, :project_count, recorded_at: 10.days.ago) }
let(:arguments) { { identifier: 'projects' } }
@@ -63,8 +63,8 @@ RSpec.describe Resolvers::Admin::Analytics::InstanceStatistics::MeasurementsReso
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) }
+ let_it_be(:pipelines_succeeded_measurement) { create(:usage_trends_measurement, :pipelines_succeeded_count, recorded_at: 2.days.ago) }
+ let_it_be(:pipelines_skipped_measurement) { create(:usage_trends_measurement, :pipelines_skipped_count, recorded_at: 2.days.ago) }
subject { resolve_measurements({ identifier: identifier }, { current_user: current_user }) }
diff --git a/spec/graphql/resolvers/alert_management/http_integrations_resolver_spec.rb b/spec/graphql/resolvers/alert_management/http_integrations_resolver_spec.rb
new file mode 100644
index 00000000000..2cd61dd7bcf
--- /dev/null
+++ b/spec/graphql/resolvers/alert_management/http_integrations_resolver_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::AlertManagement::HttpIntegrationsResolver do
+ include GraphqlHelpers
+
+ let_it_be(:guest) { create(:user) }
+ let_it_be(:developer) { create(:user) }
+ let_it_be(:maintainer) { create(:user) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:prometheus_integration) { create(:prometheus_service, project: project) }
+ let_it_be(:active_http_integration) { create(:alert_management_http_integration, project: project) }
+ let_it_be(:inactive_http_integration) { create(:alert_management_http_integration, :inactive, project: project) }
+ let_it_be(:other_proj_integration) { create(:alert_management_http_integration) }
+
+ subject { sync(resolve_http_integrations) }
+
+ before do
+ project.add_developer(developer)
+ project.add_maintainer(maintainer)
+ end
+
+ specify do
+ expect(described_class).to have_nullable_graphql_type(Types::AlertManagement::HttpIntegrationType.connection_type)
+ end
+
+ context 'user does not have permission' do
+ let(:current_user) { guest }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'user has developer permission' do
+ let(:current_user) { developer }
+
+ it { is_expected.to be_empty }
+ end
+
+ context 'user has maintainer permission' do
+ let(:current_user) { maintainer }
+
+ it { is_expected.to contain_exactly(active_http_integration) }
+ end
+
+ private
+
+ def resolve_http_integrations(args = {}, context = { current_user: current_user })
+ resolve(described_class, obj: project, ctx: context)
+ end
+end
diff --git a/spec/graphql/resolvers/board_resolver_spec.rb b/spec/graphql/resolvers/board_resolver_spec.rb
index c70c696005f..e9c51a536ee 100644
--- a/spec/graphql/resolvers/board_resolver_spec.rb
+++ b/spec/graphql/resolvers/board_resolver_spec.rb
@@ -14,8 +14,8 @@ RSpec.describe Resolvers::BoardResolver do
expect(resolve_board(id: dummy_gid)).to eq nil
end
- it 'calls Boards::ListService' do
- expect_next_instance_of(Boards::ListService) do |service|
+ it 'calls Boards::BoardsFinder' do
+ expect_next_instance_of(Boards::BoardsFinder) do |service|
expect(service).to receive(:execute).and_return([])
end
diff --git a/spec/graphql/resolvers/boards_resolver_spec.rb b/spec/graphql/resolvers/boards_resolver_spec.rb
index f121e8a4083..cb3bcb002ec 100644
--- a/spec/graphql/resolvers/boards_resolver_spec.rb
+++ b/spec/graphql/resolvers/boards_resolver_spec.rb
@@ -12,8 +12,8 @@ RSpec.describe Resolvers::BoardsResolver do
expect(resolve_boards).to eq []
end
- it 'calls Boards::ListService' do
- expect_next_instance_of(Boards::ListService) do |service|
+ it 'calls Boards::BoardsFinder' do
+ expect_next_instance_of(Boards::BoardsFinder) do |service|
expect(service).to receive(:execute)
end
diff --git a/spec/graphql/resolvers/branch_commit_resolver_spec.rb b/spec/graphql/resolvers/branch_commit_resolver_spec.rb
index 78d4959c3f9..9d085fd19a6 100644
--- a/spec/graphql/resolvers/branch_commit_resolver_spec.rb
+++ b/spec/graphql/resolvers/branch_commit_resolver_spec.rb
@@ -15,6 +15,10 @@ RSpec.describe Resolvers::BranchCommitResolver do
is_expected.to eq(repository.commits('master', limit: 1).last)
end
+ it 'sets project container' do
+ expect(commit.container).to eq(repository.project)
+ end
+
context 'when branch does not exist' do
let(:branch) { nil }
diff --git a/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb b/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb
index 5370f7a7433..e9e7fff6e6e 100644
--- a/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb
+++ b/spec/graphql/resolvers/concerns/caching_array_resolver_spec.rb
@@ -9,7 +9,6 @@ RSpec.describe ::CachingArrayResolver do
let_it_be(:admins) { create_list(:user, 4, admin: true) }
let(:query_context) { { current_user: admins.first } }
let(:max_page_size) { 10 }
- let(:field) { double('Field', max_page_size: max_page_size) }
let(:schema) do
Class.new(GitlabSchema) do
default_max_page_size 3
@@ -210,6 +209,6 @@ RSpec.describe ::CachingArrayResolver do
args = { is_admin: admin }
opts = resolver.field_options
allow(resolver).to receive(:field_options).and_return(opts.merge(max_page_size: max_page_size))
- resolve(resolver, args: args, ctx: query_context, schema: schema, field: field)
+ resolve(resolver, args: args, ctx: query_context, schema: schema)
end
end
diff --git a/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb
index 170a602fb0d..68badb8e333 100644
--- a/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb
+++ b/spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe Resolvers::ErrorTracking::SentryErrorsResolver do
end
describe '#resolve' do
- context 'insufficient user permission' do
+ context 'with insufficient user permission' do
let(:user) { create(:user) }
it 'returns nil' do
@@ -29,7 +29,7 @@ RSpec.describe Resolvers::ErrorTracking::SentryErrorsResolver do
end
end
- context 'user with permission' do
+ context 'with sufficient permission' do
before do
project.add_developer(current_user)
@@ -93,7 +93,7 @@ RSpec.describe Resolvers::ErrorTracking::SentryErrorsResolver do
end
it 'returns an externally paginated array' do
- expect(resolve_errors).to be_a Gitlab::Graphql::ExternallyPaginatedArray
+ expect(resolve_errors).to be_a Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection
end
end
end
diff --git a/spec/graphql/resolvers/group_labels_resolver_spec.rb b/spec/graphql/resolvers/group_labels_resolver_spec.rb
index ed94f12502a..3f4ad8760c0 100644
--- a/spec/graphql/resolvers/group_labels_resolver_spec.rb
+++ b/spec/graphql/resolvers/group_labels_resolver_spec.rb
@@ -42,7 +42,7 @@ RSpec.describe Resolvers::GroupLabelsResolver do
context 'without parent' do
it 'returns no labels' do
- expect(resolve_labels(nil)).to eq(Label.none)
+ expect(resolve_labels(nil)).to be_empty
end
end
diff --git a/spec/graphql/resolvers/group_packages_resolver_spec.rb b/spec/graphql/resolvers/group_packages_resolver_spec.rb
new file mode 100644
index 00000000000..59438b8d5ad
--- /dev/null
+++ b/spec/graphql/resolvers/group_packages_resolver_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Resolvers::GroupPackagesResolver do
+ include GraphqlHelpers
+
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group, :public) }
+ let_it_be(:project) { create(:project, :public, group: group) }
+ let_it_be(:package) { create(:package, project: project) }
+
+ describe '#resolve' do
+ subject(:packages) { resolve(described_class, ctx: { current_user: user }, obj: group) }
+
+ it { is_expected.to contain_exactly(package) }
+ end
+end
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
index 8980f4aa19d..6e802bf7d25 100644
--- a/spec/graphql/resolvers/issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -264,7 +264,7 @@ RSpec.describe Resolvers::IssuesResolver do
end
it 'finds a specific issue with iid', :request_store do
- result = batch_sync(max_queries: 4) { resolve_issues(iid: issue1.iid) }
+ result = batch_sync(max_queries: 4) { resolve_issues(iid: issue1.iid).to_a }
expect(result).to contain_exactly(issue1)
end
@@ -281,7 +281,7 @@ RSpec.describe Resolvers::IssuesResolver do
it 'finds a specific issue with iids', :request_store do
result = batch_sync(max_queries: 4) do
- resolve_issues(iids: [issue1.iid])
+ resolve_issues(iids: [issue1.iid]).to_a
end
expect(result).to contain_exactly(issue1)
@@ -290,7 +290,7 @@ RSpec.describe Resolvers::IssuesResolver do
it 'finds multiple issues with iids' do
create(:issue, project: project, author: current_user)
- expect(batch_sync { resolve_issues(iids: [issue1.iid, issue2.iid]) })
+ expect(batch_sync { resolve_issues(iids: [issue1.iid, issue2.iid]).to_a })
.to contain_exactly(issue1, issue2)
end
@@ -302,7 +302,7 @@ RSpec.describe Resolvers::IssuesResolver do
create(:issue, project: another_project, iid: iid)
end
- expect(batch_sync { resolve_issues(iids: iids) }).to contain_exactly(issue1, issue2)
+ expect(batch_sync { resolve_issues(iids: iids).to_a }).to contain_exactly(issue1, issue2)
end
end
end
diff --git a/spec/graphql/resolvers/labels_resolver_spec.rb b/spec/graphql/resolvers/labels_resolver_spec.rb
index 3d027a6c8d5..be6229553d7 100644
--- a/spec/graphql/resolvers/labels_resolver_spec.rb
+++ b/spec/graphql/resolvers/labels_resolver_spec.rb
@@ -42,50 +42,36 @@ RSpec.describe Resolvers::LabelsResolver do
context 'without parent' do
it 'returns no labels' do
- expect(resolve_labels(nil)).to eq(Label.none)
+ expect(resolve_labels(nil)).to be_empty
end
end
- context 'at project level' do
+ context 'with a parent project' do
before_all do
group.add_developer(current_user)
end
- # because :include_ancestor_groups, :include_descendant_groups, :only_group_labels default to false
- # the `nil` value would be equivalent to passing in `false` so just check for `nil` option
- where(:include_ancestor_groups, :include_descendant_groups, :only_group_labels, :search_term, :test) do
- nil | nil | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2) }
- nil | nil | true | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2) }
- nil | true | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) }
- nil | true | true | nil | -> { expect(subject).to contain_exactly(label1, label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) }
- true | nil | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2) }
- true | nil | true | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2) }
- true | true | nil | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) }
- true | true | true | nil | -> { expect(subject).to contain_exactly(label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2, sub_subgroup_label1, sub_subgroup_label2) }
-
- nil | nil | nil | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2) }
- nil | nil | true | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2) }
- nil | true | nil | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2, sub_subgroup_label2) }
- nil | true | true | 'new' | -> { expect(subject).to contain_exactly(label2, subgroup_label2, sub_subgroup_label2) }
- true | nil | nil | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2) }
- true | nil | true | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2) }
- true | true | nil | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2, sub_subgroup_label2) }
- true | true | true | 'new' | -> { expect(subject).to contain_exactly(label2, group_label2, subgroup_label2, sub_subgroup_label2) }
+ # the expected result is wrapped in a lambda to get around the phase restrictions of RSpec::Parameterized
+ where(:include_ancestor_groups, :search_term, :expected_labels) do
+ nil | nil | -> { [label1, label2, subgroup_label1, subgroup_label2] }
+ false | nil | -> { [label1, label2, subgroup_label1, subgroup_label2] }
+ true | nil | -> { [label1, label2, group_label1, group_label2, subgroup_label1, subgroup_label2] }
+ nil | 'new' | -> { [label2, subgroup_label2] }
+ false | 'new' | -> { [label2, subgroup_label2] }
+ true | 'new' | -> { [label2, group_label2, subgroup_label2] }
end
with_them do
let(:params) do
{
include_ancestor_groups: include_ancestor_groups,
- include_descendant_groups: include_descendant_groups,
- only_group_labels: only_group_labels,
search_term: search_term
}
end
subject { resolve_labels(project, params) }
- it { self.instance_exec(&test) }
+ specify { expect(subject).to match_array(instance_exec(&expected_labels)) }
end
end
end
diff --git a/spec/graphql/resolvers/merge_requests_resolver_spec.rb b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
index c5c368fc88f..7dd968d90a8 100644
--- a/spec/graphql/resolvers/merge_requests_resolver_spec.rb
+++ b/spec/graphql/resolvers/merge_requests_resolver_spec.rb
@@ -69,7 +69,7 @@ RSpec.describe Resolvers::MergeRequestsResolver do
it 'batch-resolves by target project full path and IIDS', :request_store do
result = batch_sync(max_queries: queries_per_project) do
- resolve_mr(project, iids: [iid_1, iid_2])
+ resolve_mr(project, iids: [iid_1, iid_2]).to_a
end
expect(result).to contain_exactly(merge_request_1, merge_request_2)
diff --git a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
index 4ad8f99219f..147a02e1d79 100644
--- a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
+++ b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
@@ -6,6 +6,18 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
include GraphqlHelpers
let(:current_user) { create(:user) }
+ let(:include_subgroups) { true }
+ let(:sort) { nil }
+ let(:search) { nil }
+ let(:ids) { nil }
+ let(:args) do
+ {
+ include_subgroups: include_subgroups,
+ sort: sort,
+ search: search,
+ ids: ids
+ }
+ end
context "with a group" do
let(:group) { create(:group) }
@@ -27,7 +39,7 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
end
it 'finds all projects including the subgroups' do
- expect(resolve_projects(include_subgroups: true, sort: nil, search: nil)).to contain_exactly(project1, project2, nested_project)
+ expect(resolve_projects(args)).to contain_exactly(project1, project2, nested_project)
end
context 'with an user namespace' do
@@ -38,7 +50,7 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
end
it 'finds all projects including the subgroups' do
- expect(resolve_projects(include_subgroups: true, sort: nil, search: nil)).to contain_exactly(project1, project2)
+ expect(resolve_projects(args)).to contain_exactly(project1, project2)
end
end
end
@@ -48,6 +60,9 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
let(:project_2) { create(:project, name: 'Test Project', path: 'test-project', namespace: namespace) }
let(:project_3) { create(:project, name: 'Test', path: 'test', namespace: namespace) }
+ let(:sort) { :similarity }
+ let(:search) { 'test' }
+
before do
project_1.add_developer(current_user)
project_2.add_developer(current_user)
@@ -55,7 +70,7 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
end
it 'returns projects ordered by similarity to the search input' do
- projects = resolve_projects(include_subgroups: true, sort: :similarity, search: 'test')
+ projects = resolve_projects(args)
project_names = projects.map { |proj| proj['name'] }
expect(project_names.first).to eq('Test')
@@ -63,15 +78,17 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
end
it 'filters out result that do not match the search input' do
- projects = resolve_projects(include_subgroups: true, sort: :similarity, search: 'test')
+ projects = resolve_projects(args)
project_names = projects.map { |proj| proj['name'] }
expect(project_names).not_to include('Project')
end
context 'when `search` parameter is not given' do
+ let(:search) { nil }
+
it 'returns projects not ordered by similarity' do
- projects = resolve_projects(include_subgroups: true, sort: :similarity, search: nil)
+ projects = resolve_projects(args)
project_names = projects.map { |proj| proj['name'] }
expect(project_names.first).not_to eq('Test')
@@ -79,14 +96,40 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
end
context 'when only search term is given' do
+ let(:sort) { nil }
+ let(:search) { 'test' }
+
it 'filters out result that do not match the search input, but does not sort them' do
- projects = resolve_projects(include_subgroups: true, sort: :nil, search: 'test')
+ projects = resolve_projects(args)
project_names = projects.map { |proj| proj['name'] }
expect(project_names).to contain_exactly('Test', 'Test Project')
end
end
end
+
+ context 'ids filtering' do
+ subject(:projects) { resolve_projects(args) }
+
+ let(:include_subgroups) { false }
+ let(:project_3) { create(:project, name: 'Project', path: 'project', namespace: namespace) }
+
+ context 'when ids is provided' do
+ let(:ids) { [project_3.to_global_id.to_s] }
+
+ it 'returns matching project' do
+ expect(projects).to contain_exactly(project_3)
+ end
+ end
+
+ context 'when ids is nil' do
+ let(:ids) { nil }
+
+ it 'returns all projects' do
+ expect(projects).to contain_exactly(project1, project2, project_3)
+ end
+ end
+ end
end
context "when passing a non existent, batch loaded namespace" do
@@ -108,7 +151,7 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do
expect(field.to_graphql.complexity.call({}, { include_subgroups: true }, 1)).to eq 24
end
- def resolve_projects(args = { include_subgroups: false, sort: nil, search: nil }, context = { current_user: current_user })
+ def resolve_projects(args = { include_subgroups: false, sort: nil, search: nil, ids: nil }, context = { current_user: current_user })
resolve(described_class, obj: namespace, args: args, ctx: context)
end
end
diff --git a/spec/graphql/resolvers/packages_resolver_spec.rb b/spec/graphql/resolvers/project_packages_resolver_spec.rb
index bc0588daf7f..c8105ed2a38 100644
--- a/spec/graphql/resolvers/packages_resolver_spec.rb
+++ b/spec/graphql/resolvers/project_packages_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Resolvers::PackagesResolver do
+RSpec.describe Resolvers::ProjectPackagesResolver do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
diff --git a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb
index b852b349d4f..69127c4b061 100644
--- a/spec/graphql/resolvers/project_pipeline_resolver_spec.rb
+++ b/spec/graphql/resolvers/project_pipeline_resolver_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Resolvers::ProjectPipelineResolver do
include GraphqlHelpers
let_it_be(:project) { create(:project) }
- let_it_be(:pipeline) { create(:ci_pipeline, project: project, iid: '1234') }
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project, iid: '1234', sha: 'sha') }
let_it_be(:other_pipeline) { create(:ci_pipeline) }
let(:current_user) { create(:user) }
@@ -30,7 +30,15 @@ RSpec.describe Resolvers::ProjectPipelineResolver do
expect(result).to eq(pipeline)
end
- it 'keeps the queries under the threshold' do
+ it 'resolves pipeline for the passed sha' do
+ result = batch_sync do
+ resolve_pipeline(project, { sha: 'sha' })
+ end
+
+ expect(result).to eq(pipeline)
+ end
+
+ it 'keeps the queries under the threshold for iid' do
create(:ci_pipeline, project: project, iid: '1235')
control = ActiveRecord::QueryRecorder.new do
@@ -45,6 +53,21 @@ RSpec.describe Resolvers::ProjectPipelineResolver do
end.not_to exceed_query_limit(control)
end
+ it 'keeps the queries under the threshold for sha' do
+ create(:ci_pipeline, project: project, sha: 'sha2')
+
+ control = ActiveRecord::QueryRecorder.new do
+ batch_sync { resolve_pipeline(project, { sha: 'sha' }) }
+ end
+
+ expect do
+ batch_sync do
+ resolve_pipeline(project, { sha: 'sha' })
+ resolve_pipeline(project, { sha: 'sha2' })
+ end
+ end.not_to exceed_query_limit(control)
+ end
+
it 'does not resolve a pipeline outside the project' do
result = batch_sync do
resolve_pipeline(other_pipeline.project, { iid: '1234' })
@@ -53,8 +76,14 @@ RSpec.describe Resolvers::ProjectPipelineResolver do
expect(result).to be_nil
end
- it 'errors when no iid is passed' do
- expect { resolve_pipeline(project, {}) }.to raise_error(ArgumentError)
+ it 'errors when no iid or sha is passed' do
+ expect { resolve_pipeline(project, {}) }
+ .to raise_error(Gitlab::Graphql::Errors::ArgumentError)
+ end
+
+ it 'errors when both iid and sha are passed' do
+ expect { resolve_pipeline(project, { iid: '1234', sha: 'sha' }) }
+ .to raise_error(Gitlab::Graphql::Errors::ArgumentError)
end
context 'when the pipeline is a dangling pipeline' do
diff --git a/spec/graphql/resolvers/release_milestones_resolver_spec.rb b/spec/graphql/resolvers/release_milestones_resolver_spec.rb
index f05069998d0..a5a523859f9 100644
--- a/spec/graphql/resolvers/release_milestones_resolver_spec.rb
+++ b/spec/graphql/resolvers/release_milestones_resolver_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe Resolvers::ReleaseMilestonesResolver do
describe '#resolve' do
it "uses offset-pagination" do
- expect(resolved).to be_a(::Gitlab::Graphql::Pagination::OffsetPaginatedRelation)
+ expect(resolved).to be_a(::Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection)
end
it "includes the release's milestones in the returned OffsetActiveRecordRelationConnection" do
diff --git a/spec/graphql/types/access_level_enum_spec.rb b/spec/graphql/types/access_level_enum_spec.rb
index eeb10a50b7e..1b379c56ff9 100644
--- a/spec/graphql/types/access_level_enum_spec.rb
+++ b/spec/graphql/types/access_level_enum_spec.rb
@@ -6,6 +6,6 @@ RSpec.describe GitlabSchema.types['AccessLevelEnum'] do
specify { expect(described_class.graphql_name).to eq('AccessLevelEnum') }
it 'exposes all the existing access levels' do
- expect(described_class.values.keys).to match_array(%w[NO_ACCESS GUEST REPORTER DEVELOPER MAINTAINER OWNER])
+ expect(described_class.values.keys).to match_array(%w[NO_ACCESS MINIMAL_ACCESS GUEST REPORTER DEVELOPER MAINTAINER OWNER])
end
end
diff --git a/spec/graphql/types/admin/analytics/instance_statistics/measurement_identifier_enum_spec.rb b/spec/graphql/types/admin/analytics/usage_trends/measurement_identifier_enum_spec.rb
index 8a7408224a2..91851c11dc8 100644
--- a/spec/graphql/types/admin/analytics/instance_statistics/measurement_identifier_enum_spec.rb
+++ b/spec/graphql/types/admin/analytics/usage_trends/measurement_identifier_enum_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe GitlabSchema.types['MeasurementIdentifier'] do
it 'exposes all the existing identifier values' do
ee_only_identifiers = %w[billable_users]
- identifiers = Analytics::InstanceStatistics::Measurement.identifiers.keys.reject do |x|
+ identifiers = Analytics::UsageTrends::Measurement.identifiers.keys.reject do |x|
ee_only_identifiers.include?(x)
end.map(&:upcase)
diff --git a/spec/graphql/types/admin/analytics/instance_statistics/measurement_type_spec.rb b/spec/graphql/types/admin/analytics/usage_trends/measurement_type_spec.rb
index ffb1a0f30c9..c50092d7f0e 100644
--- a/spec/graphql/types/admin/analytics/instance_statistics/measurement_type_spec.rb
+++ b/spec/graphql/types/admin/analytics/usage_trends/measurement_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe GitlabSchema.types['InstanceStatisticsMeasurement'] do
+RSpec.describe GitlabSchema.types['UsageTrendsMeasurement'] do
subject { described_class }
it { is_expected.to have_graphql_field(:recorded_at) }
@@ -10,13 +10,13 @@ RSpec.describe GitlabSchema.types['InstanceStatisticsMeasurement'] do
it { is_expected.to have_graphql_field(:count) }
describe 'authorization' do
- let_it_be(:measurement) { create(:instance_statistics_measurement, :project_count) }
+ let_it_be(:measurement) { create(:usage_trends_measurement, :project_count) }
let(:user) { create(:user) }
let(:query) do
<<~GRAPHQL
- query instanceStatisticsMeasurements($identifier: MeasurementIdentifier!) {
- instanceStatisticsMeasurements(identifier: $identifier) {
+ query usageTrendsMeasurements($identifier: MeasurementIdentifier!) {
+ usageTrendsMeasurements(identifier: $identifier) {
nodes {
count
identifier
@@ -36,7 +36,7 @@ RSpec.describe GitlabSchema.types['InstanceStatisticsMeasurement'] do
context 'when the user is not admin' do
it 'returns no data' do
- expect(subject.dig('data', 'instanceStatisticsMeasurements')).to be_nil
+ expect(subject.dig('data', 'usageTrendsMeasurements')).to be_nil
end
end
@@ -48,7 +48,7 @@ RSpec.describe GitlabSchema.types['InstanceStatisticsMeasurement'] do
end
it 'returns data' do
- expect(subject.dig('data', 'instanceStatisticsMeasurements', 'nodes')).not_to be_empty
+ expect(subject.dig('data', 'usageTrendsMeasurements', 'nodes')).not_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 82b48a20708..9ff01418c9a 100644
--- a/spec/graphql/types/alert_management/alert_type_spec.rb
+++ b/spec/graphql/types/alert_management/alert_type_spec.rb
@@ -10,7 +10,8 @@ RSpec.describe GitlabSchema.types['AlertManagementAlert'] do
it 'exposes the expected fields' do
expected_fields = %i[
iid
- issue_iid
+ issueIid
+ issue
title
description
severity
diff --git a/spec/graphql/types/base_argument_spec.rb b/spec/graphql/types/base_argument_spec.rb
new file mode 100644
index 00000000000..61e0179ff21
--- /dev/null
+++ b/spec/graphql/types/base_argument_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::BaseArgument do
+ include_examples 'Gitlab-style deprecations' do
+ let_it_be(:field) do
+ Types::BaseField.new(name: 'field', type: String, null: true)
+ end
+
+ let(:base_args) { { name: 'test', type: String, required: false, owner: field } }
+
+ def subject(args = {})
+ described_class.new(**base_args.merge(args))
+ end
+ end
+end
diff --git a/spec/graphql/types/board_type_spec.rb b/spec/graphql/types/board_type_spec.rb
index 5ea87d5f473..dca3cfd8aaf 100644
--- a/spec/graphql/types/board_type_spec.rb
+++ b/spec/graphql/types/board_type_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['Board'] do
specify { expect(described_class.graphql_name).to eq('Board') }
- specify { expect(described_class).to require_graphql_authorizations(:read_board) }
+ specify { expect(described_class).to require_graphql_authorizations(:read_issue_board) }
it 'has specific fields' do
expected_fields = %w[id name web_url web_path]
diff --git a/spec/graphql/types/ci/job_type_spec.rb b/spec/graphql/types/ci/job_type_spec.rb
index e277916f5cb..25f626cea0f 100644
--- a/spec/graphql/types/ci/job_type_spec.rb
+++ b/spec/graphql/types/ci/job_type_spec.rb
@@ -14,6 +14,8 @@ RSpec.describe Types::Ci::JobType do
detailedStatus
scheduledAt
artifacts
+ finished_at
+ duration
]
expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/ci/pipeline_type_spec.rb b/spec/graphql/types/ci/pipeline_type_spec.rb
index 2a1e030480d..e0e84a1b635 100644
--- a/spec/graphql/types/ci/pipeline_type_spec.rb
+++ b/spec/graphql/types/ci/pipeline_type_spec.rb
@@ -12,11 +12,11 @@ RSpec.describe Types::Ci::PipelineType do
id iid sha before_sha status detailed_status config_source duration
coverage created_at updated_at started_at finished_at committed_at
stages user retryable cancelable jobs source_job downstream
- upstream path project active user_permissions warnings
+ upstream path project active user_permissions warnings commit_path
]
if Gitlab.ee?
- expected_fields << 'security_report_summary'
+ expected_fields += %w[security_report_summary security_report_findings]
end
expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/global_id_type_spec.rb b/spec/graphql/types/global_id_type_spec.rb
index cb129868f7e..8eb023ad2a3 100644
--- a/spec/graphql/types/global_id_type_spec.rb
+++ b/spec/graphql/types/global_id_type_spec.rb
@@ -5,7 +5,6 @@ require 'spec_helper'
RSpec.describe Types::GlobalIDType do
let_it_be(:project) { create(:project) }
let(:gid) { project.to_global_id }
- let(:foreign_gid) { GlobalID.new(::URI::GID.build(app: 'otherapp', model_name: 'Project', model_id: project.id, params: nil)) }
it 'is has the correct name' do
expect(described_class.to_graphql.name).to eq('GlobalID')
@@ -41,16 +40,18 @@ RSpec.describe Types::GlobalIDType do
it 'rejects invalid input' do
expect { described_class.coerce_isolated_input('not valid') }
- .to raise_error(GraphQL::CoercionError)
+ .to raise_error(GraphQL::CoercionError, /not a valid Global ID/)
end
it 'rejects nil' do
expect(described_class.coerce_isolated_input(nil)).to be_nil
end
- it 'rejects gids from different apps' do
- expect { described_class.coerce_isolated_input(foreign_gid) }
- .to raise_error(GraphQL::CoercionError)
+ it 'rejects GIDs from different apps' do
+ invalid_gid = GlobalID.new(::URI::GID.build(app: 'otherapp', model_name: 'Project', model_id: project.id, params: nil))
+
+ expect { described_class.coerce_isolated_input(invalid_gid) }
+ .to raise_error(GraphQL::CoercionError, /is not a Gitlab Global ID/)
end
end
@@ -79,14 +80,22 @@ RSpec.describe Types::GlobalIDType do
let(:gid) { build_stubbed(:user).to_global_id }
it 'raises errors when coercing results' do
- expect { type.coerce_isolated_result(gid) }.to raise_error(GraphQL::CoercionError)
+ expect { type.coerce_isolated_result(gid) }
+ .to raise_error(GraphQL::CoercionError, /Expected a Project ID/)
end
it 'will not coerce invalid input, even if its a valid GID' do
expect { type.coerce_isolated_input(gid.to_s) }
- .to raise_error(GraphQL::CoercionError)
+ .to raise_error(GraphQL::CoercionError, /does not represent an instance of Project/)
end
end
+
+ it 'handles GIDs for invalid resource names gracefully' do
+ invalid_gid = GlobalID.new(::URI::GID.build(app: GlobalID.app, model_name: 'invalid', model_id: 1, params: nil))
+
+ expect { type.coerce_isolated_input(invalid_gid) }
+ .to raise_error(GraphQL::CoercionError, /does not represent an instance of Project/)
+ end
end
describe 'a parameterized type with a namespace' do
diff --git a/spec/graphql/types/group_type_spec.rb b/spec/graphql/types/group_type_spec.rb
index bba702ba3e9..ef11e3d309c 100644
--- a/spec/graphql/types/group_type_spec.rb
+++ b/spec/graphql/types/group_type_spec.rb
@@ -18,6 +18,7 @@ RSpec.describe GitlabSchema.types['Group'] do
two_factor_grace_period auto_devops_enabled emails_disabled
mentions_disabled parent boards milestones group_members
merge_requests container_repositories container_repositories_count
+ packages
]
expect(described_class).to include_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/label_type_spec.rb b/spec/graphql/types/label_type_spec.rb
index 6a999a2e925..427b5d2dcef 100644
--- a/spec/graphql/types/label_type_spec.rb
+++ b/spec/graphql/types/label_type_spec.rb
@@ -3,7 +3,16 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['Label'] do
it 'has the correct fields' do
- expected_fields = [:id, :description, :description_html, :title, :color, :text_color]
+ expected_fields = [
+ :id,
+ :description,
+ :description_html,
+ :title,
+ :color,
+ :text_color,
+ :created_at,
+ :updated_at
+ ]
expect(described_class).to have_graphql_fields(*expected_fields)
end
diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb
index 63d288934e5..3314ea62324 100644
--- a/spec/graphql/types/merge_request_type_spec.rb
+++ b/spec/graphql/types/merge_request_type_spec.rb
@@ -23,7 +23,7 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do
merge_error allow_collaboration should_be_rebased rebase_commit_sha
rebase_in_progress default_merge_commit_message
merge_ongoing mergeable_discussions_state web_url
- source_branch_exists target_branch_exists
+ source_branch_exists target_branch_exists diverged_from_target_branch
upvotes downvotes head_pipeline pipelines task_completion_status
milestone assignees reviewers participants subscribed labels discussion_locked time_estimate
total_time_spent reference author merged_at commit_count current_user_todos
@@ -77,4 +77,33 @@ RSpec.describe GitlabSchema.types['MergeRequest'] do
end
end
end
+
+ describe '#diverged_from_target_branch' do
+ subject(:execute_query) { GitlabSchema.execute(query, context: { current_user: current_user }).as_json }
+
+ let!(:merge_request) { create(:merge_request, target_project: project, source_project: project) }
+ let(:project) { create(:project, :public) }
+ let(:current_user) { create :admin }
+ let(:query) do
+ %(
+ {
+ project(fullPath: "#{project.full_path}") {
+ mergeRequests {
+ nodes {
+ divergedFromTargetBranch
+ }
+ }
+ }
+ }
+ )
+ end
+
+ it 'delegates the diverged_from_target_branch? call to the merge request entity' do
+ expect_next_found_instance_of(MergeRequest) do |instance|
+ expect(instance).to receive(:diverged_from_target_branch?)
+ end
+
+ execute_query
+ end
+ end
end
diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb
index fea0a3bd37e..cb8e875dbf4 100644
--- a/spec/graphql/types/query_type_spec.rb
+++ b/spec/graphql/types/query_type_spec.rb
@@ -21,7 +21,7 @@ RSpec.describe GitlabSchema.types['Query'] do
user
users
issue
- instance_statistics_measurements
+ usage_trends_measurements
runner_platforms
]
@@ -65,11 +65,11 @@ RSpec.describe GitlabSchema.types['Query'] do
end
end
- describe 'instance_statistics_measurements field' do
- subject { described_class.fields['instanceStatisticsMeasurements'] }
+ describe 'usage_trends_measurements field' do
+ subject { described_class.fields['usageTrendsMeasurements'] }
- it 'returns instance statistics measurements' do
- is_expected.to have_graphql_type(Types::Admin::Analytics::InstanceStatistics::MeasurementType.connection_type)
+ it 'returns usage trends measurements' do
+ is_expected.to have_graphql_type(Types::Admin::Analytics::UsageTrends::MeasurementType.connection_type)
end
end
diff --git a/spec/graphql/types/snippet_type_spec.rb b/spec/graphql/types/snippet_type_spec.rb
index e73665a1b1d..4d827186a9b 100644
--- a/spec/graphql/types/snippet_type_spec.rb
+++ b/spec/graphql/types/snippet_type_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['Snippet'] do
+ include GraphqlHelpers
+
let_it_be(:user) { create(:user) }
it 'has the correct fields' do
@@ -25,6 +27,14 @@ RSpec.describe GitlabSchema.types['Snippet'] do
end
end
+ describe '#user_permissions' do
+ let_it_be(:snippet) { create(:personal_snippet, :repository, :public, author: user) }
+
+ it 'can resolve the snippet permissions' do
+ expect(resolve_field(:user_permissions, snippet)).to eq(snippet)
+ end
+ end
+
context 'when restricted visibility level is set to public' do
let_it_be(:snippet) { create(:personal_snippet, :repository, :public, author: user) }
diff --git a/spec/graphql/types/snippets/blob_type_spec.rb b/spec/graphql/types/snippets/blob_type_spec.rb
index bfac08f40d3..60c0db8e551 100644
--- a/spec/graphql/types/snippets/blob_type_spec.rb
+++ b/spec/graphql/types/snippets/blob_type_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe GitlabSchema.types['SnippetBlob'] do
+ include GraphqlHelpers
+
it 'has the correct fields' do
expected_fields = [:rich_data, :plain_data,
:raw_path, :size, :binary, :name, :path,
@@ -12,16 +14,37 @@ RSpec.describe GitlabSchema.types['SnippetBlob'] do
expect(described_class).to have_graphql_fields(*expected_fields)
end
- specify { expect(described_class.fields['richData'].type).not_to be_non_null }
- specify { expect(described_class.fields['plainData'].type).not_to be_non_null }
- specify { expect(described_class.fields['rawPath'].type).to be_non_null }
- specify { expect(described_class.fields['size'].type).to be_non_null }
- specify { expect(described_class.fields['binary'].type).to be_non_null }
- specify { expect(described_class.fields['name'].type).not_to be_non_null }
- specify { expect(described_class.fields['path'].type).not_to be_non_null }
- specify { expect(described_class.fields['simpleViewer'].type).to be_non_null }
- specify { expect(described_class.fields['richViewer'].type).not_to be_non_null }
- specify { expect(described_class.fields['mode'].type).not_to be_non_null }
- specify { expect(described_class.fields['externalStorage'].type).not_to be_non_null }
- specify { expect(described_class.fields['renderedAsText'].type).to be_non_null }
+ let_it_be(:nullity) do
+ {
+ 'richData' => be_nullable,
+ 'plainData' => be_nullable,
+ 'rawPath' => be_non_null,
+ 'size' => be_non_null,
+ 'binary' => be_non_null,
+ 'name' => be_nullable,
+ 'path' => be_nullable,
+ 'simpleViewer' => be_non_null,
+ 'richViewer' => be_nullable,
+ 'mode' => be_nullable,
+ 'externalStorage' => be_nullable,
+ 'renderedAsText' => be_non_null
+ }
+ end
+
+ let_it_be(:blob) { create(:snippet, :public, :repository).blobs.first }
+
+ shared_examples 'a field from the snippet blob presenter' do |field|
+ it "resolves using the presenter", :request_store do
+ presented = SnippetBlobPresenter.new(blob)
+
+ expect(resolve_field(field, blob)).to eq(presented.try(field.method_sym))
+ end
+ end
+
+ described_class.fields.each_value do |field|
+ describe field.graphql_name do
+ it_behaves_like 'a field from the snippet blob presenter', field
+ specify { expect(field.type).to match(nullity.fetch(field.graphql_name)) }
+ end
+ end
end
diff --git a/spec/graphql/types/user_callout_feature_name_enum_spec.rb b/spec/graphql/types/user_callout_feature_name_enum_spec.rb
new file mode 100644
index 00000000000..28755e1301b
--- /dev/null
+++ b/spec/graphql/types/user_callout_feature_name_enum_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['UserCalloutFeatureNameEnum'] do
+ specify { expect(described_class.graphql_name).to eq('UserCalloutFeatureNameEnum') }
+
+ it 'exposes all the existing user callout feature names' do
+ expect(described_class.values.keys).to match_array(::UserCallout.feature_names.keys.map(&:upcase))
+ end
+end
diff --git a/spec/graphql/types/user_callout_type_spec.rb b/spec/graphql/types/user_callout_type_spec.rb
new file mode 100644
index 00000000000..b26b85a4e8b
--- /dev/null
+++ b/spec/graphql/types/user_callout_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['UserCallout'] do
+ specify { expect(described_class.graphql_name).to eq('UserCallout') }
+
+ it 'has expected fields' do
+ expect(described_class).to have_graphql_fields(:feature_name, :dismissed_at)
+ end
+end
diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb
index 5b3662383d8..d9e67ff348b 100644
--- a/spec/graphql/types/user_type_spec.rb
+++ b/spec/graphql/types/user_type_spec.rb
@@ -31,6 +31,7 @@ RSpec.describe GitlabSchema.types['User'] do
groupCount
projectMemberships
starredProjects
+ callouts
]
expect(described_class).to have_graphql_fields(*expected_fields)
@@ -44,4 +45,12 @@ RSpec.describe GitlabSchema.types['User'] do
is_expected.to have_graphql_resolver(Resolvers::Users::SnippetsResolver)
end
end
+
+ describe 'callouts field' do
+ subject { described_class.fields['callouts'] }
+
+ it 'returns user callouts' do
+ is_expected.to have_graphql_type(Types::UserCalloutType.connection_type)
+ end
+ end
end